Files
fusion/packages/engine/src/executor/task-executor-options.ts
gsxdsm 1cf86baa1c refactor: package code organization wave 18 (executor pure peels) (#3317)
## 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 -->
2026-08-09 15:46:09 -10:00

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;