## Summary
Wave 18 continues the package code-organization program after wave 17
domain folders (U4 Slice A from
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`).
### What changed
Peel **pure, behavior-preserving** helpers out of
`packages/engine/src/executor.ts` into domain modules under
`packages/engine/src/executor/`, with **stable re-exports** from
`executor.ts` so deep imports and `vi.mock("../executor.js")` keep
working.
| New module | Symbols |
|------------|---------|
| `executor/task-done-refusal.ts` | `evaluateTaskDoneRefusal`,
`determineRevisionResetStart`, skip-bypass refusal helper |
| `executor/workflow-feedback-paths.ts` |
`extractReferencedPathsFromWorkflowFeedback`,
`isAlwaysAllowedScopeLeakPath`, `workflowPathMatchesDeclaredScope` |
| `executor/workflow-step-verdict.ts` |
`FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE`, `parseWorkflowStepVerdict`
/ `parseWorkflowStepOutput`, step outcome types |
| `executor/await-input-parse.ts` | `parseAwaitInputSentinel`,
`parseAwaitInputQuestionToolCall` |
| `executor/no-commit-eligibility.ts` | `getNoCommitEligibilityReason`
(+ prompt heuristics) |
`executor.ts` live LOC ~**22817 → ~22427** (first pure-peel batch; more
peels needed to approach the 2k cap).
### Shims
- `old path` `executor.ts` public exports → `new path` `executor/*.ts` →
delete-when consumer deep-imports are re-pointed (not this PR)
### Test plan
- [x] `@fusion/engine` typecheck
- [x] Oracle: task-done refusal, skip-bypass, workflow malformed
verdict, scope-leak allowlist, executor-step-session, executor-prompt
- [x] `vitest --project=engine-core` (merge-gate curated suite)
- [ ] CI merge gate
**Stack:** wave17 (merged) → **this PR**
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Improved recognition of workflow outcomes from structured and
conversational responses.
* Added support for extracting questions from await-input responses and
tool calls.
* Improved workflow feedback handling for referenced files and declared
scope patterns.
* Added clearer guidance for task execution, approvals, verification,
and available tools.
* **Bug Fixes**
* Prevented completion when required review approvals are missing or
revisions remain pending.
* Improved handling of workflows that legitimately require no code
changes.
* Added clearer refusal messages and more reliable revision restarts.
* Sanitized repository paths in Git remediation instructions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
162 lines
8.6 KiB
TypeScript
162 lines
8.6 KiB
TypeScript
/**
|
|
* FNXC:CodeOrganization 2026-08-03-21:00:
|
|
* TaskExecutorOptions / CliAgentRuntime / ActiveExecutorSessionState peeled from
|
|
* executor.ts preamble (U4) so the facade file keeps options types out of line.
|
|
*
|
|
* FNXC:WorkflowExecution 2026-07-19-01:30:
|
|
* U5d (R9) — the `graphCompletionInterceptors` Map is DELETED. It was shared per-task
|
|
* mutable state used to signal "this execute() call is a graph implementation phase":
|
|
* the graph set an entry, re-entered execute(), and execute() read the Map at ~12 sites
|
|
* to decide whether to stop at the implementation-complete boundary, skip outer routing,
|
|
* suppress `fn_review_step`, and mark review gates graph-owned. Signalling through a
|
|
* shared Map made the graph/legacy split invisible at the call site and left stale
|
|
* entries to clean up on abort. It is replaced by an EXPLICIT optional
|
|
* `graphCompletion` callback: presence of the callback IS the "graph-owned implementation
|
|
* phase" signal, and invoking it hands the captured modifiedFiles back to the graph runner.
|
|
*
|
|
* FNXC:WorkflowExecution 2026-07-19-02:10:
|
|
* U5e (R9) — the RE-ENTRY is now gone too. `executeCore`'s implementation body was lifted
|
|
* into `runImplementation()`, which the graph seam calls DIRECTLY; `executeCore` is routing
|
|
* only and `execute()` no longer carries a completion parameter. There is no longer any path
|
|
* by which the graph runner calls back into `execute()`.
|
|
*/
|
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
import type { MissionStore, AsyncMissionStore, Slice, Task, CliSessionStore } from "@fusion/core";
|
|
import type { AgentSemaphore } from "../concurrency/concurrency.js";
|
|
import type { WorktreePool } from "../worktree/worktree-pool.js";
|
|
import type { UsageLimitPauser } from "../errors/usage-limit-detector.js";
|
|
import type { CredentialInstanceRotator } from "../credential-instance-rotation.js";
|
|
import type { StuckTaskDetector } from "../healing/stuck-task-detector.js";
|
|
import type { AgentReflectionService } from "../agents/agent-reflection.js";
|
|
import type { PluginRunner } from "../plugins/plugin-runner.js";
|
|
import type { AutoRecoveryDispatcher } from "../healing/auto-recovery.js";
|
|
import type { GenerateFeatureVideoOptions } from "../review-artifacts/feature-video.js";
|
|
import type { CliSessionManager } from "../cli-agent/session-manager.js";
|
|
import type { TelemetryHub } from "../cli-agent/telemetry-hub.js";
|
|
import type { CliAdapterRegistry } from "../cli-agent/adapter.js";
|
|
|
|
export interface TaskExecutorOptions {
|
|
/*
|
|
* FNXC:PlanReviewLease 2026-07-26-21:07:
|
|
* Resolves this engine's cluster node id for review-gate lease attribution. A GETTER, not a
|
|
* value: the runtime resolves the id asynchronously during start(), which can complete after
|
|
* the executor is constructed, so a snapshot taken at construction would be permanently
|
|
* undefined. Read at runner-construction time instead.
|
|
*/
|
|
getLocalNodeId?: () => string | undefined;
|
|
semaphore?: AgentSemaphore;
|
|
/** Worktree pool for recycling idle worktrees across tasks. */
|
|
pool?: WorktreePool;
|
|
/**
|
|
* FNXC:ProviderRateLimitIsolation 2026-07-21-18:00:
|
|
* Parks only tasks routed through the provider whose API limit was detected.
|
|
*/
|
|
usageLimitPauser?: UsageLimitPauser;
|
|
/** Runtime-owned credential rotation inventory/cooldown coordinator. */
|
|
credentialRotator?: CredentialInstanceRotator;
|
|
/** Stuck task detector — monitors agent sessions for stagnation and triggers recovery. */
|
|
stuckTaskDetector?: StuckTaskDetector;
|
|
/** AgentStore for tracking spawned child agents. If not provided, spawning is disabled. */
|
|
agentStore?: import("@fusion/core").AgentStore;
|
|
/** Reflection service used to generate self-reflection insights for agents. */
|
|
reflectionService?: AgentReflectionService;
|
|
/** Plugin runner for invoking plugin hooks and providing plugin tools. */
|
|
pluginRunner?: PluginRunner;
|
|
/** MessageStore for sending messages to other agents. When provided, executor agents gain fn_send_message capability. */
|
|
messageStore?: import("@fusion/core").MessageStore;
|
|
missionStore?: MissionStore | AsyncMissionStore;
|
|
secretsStore?: Pick<import("@fusion/core").SecretsStore, "listEnvExportable">;
|
|
onSliceComplete?: (slice: Slice) => void;
|
|
onStart?: (task: Task, worktreePath: string) => void;
|
|
onComplete?: (task: Task) => void;
|
|
onError?: (task: Task, error: Error) => void;
|
|
/** Testable, best-effort completion-deliverable seam; production uses generateFeatureVideo. */
|
|
reviewArtifactGenerator?: (options: GenerateFeatureVideoOptions) => Promise<import("../review-artifacts/feature-video.js").FeatureVideoResult>;
|
|
onAgentText?: (taskId: string, delta: string) => void;
|
|
/**
|
|
* FNXC:StuckDetector 2026-07-22-19:25:
|
|
* Optional third arg is the primary-arg summary from AgentLogger so downstream
|
|
* telemetry (and any external onAgentTool subscribers) keep the same fingerprint contract
|
|
* the stuck detector uses — do not drop `detail` at the executor boundary.
|
|
*/
|
|
onAgentTool?: (taskId: string, toolName: string, detail?: string) => void;
|
|
/*
|
|
FNXC:PlannerOversight 2026-07-13-23:05:
|
|
Session-advisor live delta path — AgentLogger invokes this after durable
|
|
log flushes. Fail-soft; must not throw.
|
|
*/
|
|
onExecutorLogFlushed?: (
|
|
taskId: string,
|
|
entries: Array<{ type?: string; text?: string; detail?: string; agent?: string }>,
|
|
) => void;
|
|
autoRecoveryDispatcher?: AutoRecoveryDispatcher;
|
|
/** PR-entity node deps (U3): assembled `PrNodeDeps` (store + injected GitHub
|
|
* callbacks) for the `pr-create`/`pr-respond`/`pr-merge` workflow nodes. The
|
|
* runtime binds the store and threads the CLI-injected ops. Absent → the pr-*
|
|
* node kinds fail closed. */
|
|
prNodes?: import("../merge/pr-nodes.js").PrNodeDeps;
|
|
/**
|
|
* CLI Agent Executor runtime (U7). When present, workflow nodes with
|
|
* `config.executor === "cli-agent"` drive an engine-owned CLI session via the
|
|
* task-session orchestration. Absent → cli-agent nodes report a clear config
|
|
* error (the runtime was not wired). Bundled so a single option threads the
|
|
* PTY manager + telemetry hub + adapter registry + hook endpoint together.
|
|
*/
|
|
cliAgentRuntime?: CliAgentRuntime;
|
|
}
|
|
|
|
/** Bundled CLI Agent Executor runtime dependencies (U7). */
|
|
export interface CliAgentRuntime {
|
|
/** Engine-owned PTY session manager (U2). */
|
|
manager: CliSessionManager;
|
|
/** In-process telemetry hub (U3) — owns per-session tokens + state machines. */
|
|
hub: TelemetryHub;
|
|
/** Adapter registry (U2) — resolves adapter id → adapter. */
|
|
registry: CliAdapterRegistry;
|
|
/** Durable session store (U1) — for re-entry / follow-up session lookups. */
|
|
store: CliSessionStore;
|
|
/** Project this runtime drives (the executor is per-project; `cli_sessions` needs it). */
|
|
projectId: string;
|
|
/**
|
|
* Absolute URL of the dashboard hook ingestion endpoint the hook scripts POST
|
|
* to (e.g. `http://127.0.0.1:4040/api/cli-agent/hooks`).
|
|
*/
|
|
hookEndpointUrl: string;
|
|
/** Optional override for the hook scratch-dir root (tests). */
|
|
hookDirRoot?: string;
|
|
}
|
|
|
|
export interface ActiveExecutorSessionState {
|
|
session: AgentSession;
|
|
seenSteeringIds: Set<string>;
|
|
lastResolvedModelProvider?: string;
|
|
lastResolvedModelId?: string;
|
|
lastTaskModelProvider?: string | null;
|
|
lastTaskModelId?: string | null;
|
|
lastAssignedAgentId?: string | null;
|
|
lastEffectiveColumnAgentId?: string | null;
|
|
}
|
|
|
|
/*
|
|
FNXC:WorkflowExecution 2026-07-19-01:30:
|
|
U5d (R9): explicit replacement for the deleted `graphCompletionInterceptors` Map. When this
|
|
callback is present the run IS a graph-owned implementation phase: execution stops at the
|
|
implementation-complete boundary (no workflow steps, no legacy in-review handoff),
|
|
`fn_review_step` is not injected, review gates are marked graph-owned, and the captured
|
|
modifiedFiles are handed back through the callback. Absent callback == the legacy path.
|
|
|
|
FNXC:WorkflowExecution 2026-07-19-02:10:
|
|
U5e (R9): this is now a parameter of `runImplementation()`, NOT of `execute()`. The graph
|
|
calls the runner directly, so the callback no longer travels through routing.
|
|
|
|
FNXC:CodeOrganization 2026-08-04-02:35:
|
|
Remaining U5e work: the callback should become MANDATORY and collapse into an ordinary
|
|
return value. It is still optional only because `executeWorkflowGraph` keeps one
|
|
legacy fallback (executor.ts, the workflow-selection-api-unavailable branch) that minimal
|
|
TEST stores reach; production stores always expose a workflow-selection reader and are
|
|
always graph-owned. Deleting that fallback makes every `runImplementation` call
|
|
graph-owned, at which point this type disappears in favor of a returned outcome. See
|
|
docs/plans/2026-07-19-002-u5e-remaining-deletions-handoff.md.
|
|
*/
|
|
export type GraphCompletionCallback = (info: { modifiedFiles: string[] }) => void;
|