@ordewell/core 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +5 -2
  2. package/dist/{IFileSystem-BnLh-_SK.d.mts → IFileSystem-D96mT-bm.d.ts} +1 -1
  3. package/dist/{IFileSystem-DofiRcCo.d.ts → IFileSystem-DXJCRw5x.d.mts} +1 -1
  4. package/dist/IRunner-CGCihlGQ.d.ts +1165 -0
  5. package/dist/IRunner-XVUddYVX.d.mts +1165 -0
  6. package/dist/{ModeResolver-DkMYf4uS.d.ts → ModeResolver-DE-o2lRF.d.mts} +2 -2
  7. package/dist/{ModeResolver-DZzJSdIU.d.mts → ModeResolver-EWlpmVAy.d.ts} +2 -2
  8. package/dist/Task-1Q19y7sZ.d.mts +1122 -0
  9. package/dist/Task-1Q19y7sZ.d.ts +1122 -0
  10. package/dist/{chunk-ZV5LDNCO.mjs → chunk-2M4X3VB7.mjs} +18 -6
  11. package/dist/chunk-2M4X3VB7.mjs.map +1 -0
  12. package/dist/{chunk-Y36NIASW.mjs → chunk-BLLIBCCS.mjs} +153 -264
  13. package/dist/chunk-BLLIBCCS.mjs.map +1 -0
  14. package/dist/chunk-H5O74L77.mjs +185 -0
  15. package/dist/chunk-H5O74L77.mjs.map +1 -0
  16. package/dist/{chunk-LGQNXPBX.mjs → chunk-UUU46PVQ.mjs} +72 -192
  17. package/dist/chunk-UUU46PVQ.mjs.map +1 -0
  18. package/dist/index.d.mts +1119 -930
  19. package/dist/index.d.ts +1119 -930
  20. package/dist/index.js +8361 -8849
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +8197 -8614
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/order-labels.d.mts +1 -4
  25. package/dist/order-labels.d.ts +1 -4
  26. package/dist/{parsing-ddZXZFP4.d.ts → parsing-CZdp2p_n.d.ts} +5 -3
  27. package/dist/{parsing-B6ctfF80.d.mts → parsing-GgUJoYKs.d.mts} +5 -3
  28. package/dist/parsing.d.mts +3 -6
  29. package/dist/parsing.d.ts +3 -6
  30. package/dist/parsing.js +25 -4
  31. package/dist/parsing.js.map +1 -1
  32. package/dist/parsing.mjs +3 -2
  33. package/dist/{plan-utils-CKgNtDwQ.d.mts → plan-utils-BMayhGBj.d.mts} +89 -14
  34. package/dist/{plan-utils--kQRzSE-.d.ts → plan-utils-Cv48fe7r.d.ts} +89 -14
  35. package/dist/plan-utils.d.mts +4 -4
  36. package/dist/plan-utils.d.ts +4 -4
  37. package/dist/plan-utils.js +128 -65
  38. package/dist/plan-utils.js.map +1 -1
  39. package/dist/plan-utils.mjs +10 -2
  40. package/dist/testing.d.mts +12 -24
  41. package/dist/testing.d.ts +12 -24
  42. package/dist/testing.js +29 -29
  43. package/dist/testing.js.map +1 -1
  44. package/dist/testing.mjs +21 -27
  45. package/dist/testing.mjs.map +1 -1
  46. package/package.json +1 -1
  47. package/skills/tdd/SKILL.md +19 -0
  48. package/dist/Task-BnLxkVQF.d.mts +0 -2594
  49. package/dist/Task-BnLxkVQF.d.ts +0 -2594
  50. package/dist/chunk-LGQNXPBX.mjs.map +0 -1
  51. package/dist/chunk-Y36NIASW.mjs.map +0 -1
  52. package/dist/chunk-ZV5LDNCO.mjs.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,16 +1,18 @@
1
- import { a$ as RunnerId, ax as PlanStatus, ab as LegacyPlanState, L as IApproval, k as ApprovalDecision, p as ApprovalRequest, j as ApprovalAnswer, O as ITerminalRunner, M as IConfig, h as AiProvider, aM as ProviderModelLists, m as ApprovalMode, _ as IsolationMergeResult, W as IsolationLandedTask, Q as IWorktreeIsolation, a7 as IsolationTaskStatus, au as PlanIsolation, b4 as RunnerTransport, aU as RepoGroupLayout, P as ITerminalSession, y as DiscoveredModel, bh as Task, f as AgentProcessDeps, c as AgentAdapter, ar as OrdewellMcpServer, aW as ResearchProgress, aV as ResearchLogEntry, w as ConversationMessage, aA as PlannerToolHandler, bE as UsageRecord, bt as TaskSnapshot, r as AwaitingReason, T as IsolationHandoff, a3 as IsolationRun, a4 as IsolationTaskRecord, bj as TaskIsolation, a8 as IsolationView, $ as IsolationOutcome, aT as RepairEvidence, bC as TreeSnapshot, bL as Verdict, b2 as RunnerRegistry, aR as QueuedTaskMessage, aQ as QueuedMessage, b as ActiveTaskSession, aq as OrchestratorOption, bm as TaskModeAgentAdapter, g as AgentStartOptions, e as AgentEvent, aZ as ResearchToolType, aB as PlannerUsage, n as ApprovalPolicy, aw as PlanState, bi as TaskCompleteArgs, t as CheckpointAnswer, b0 as RunnerInvocation, A as AbstractRunner, a as AbstractTerminalSession, b5 as RunnerVersionProbe, ba as StructuredSessionCapability, bu as TaskStartOptions, bw as TaskTokenScope, bb as StructuredTurnEnd, b9 as StructuredEvent, b3 as RunnerSpawnOptions, N as IPluginStore, b1 as RunnerPluginManifest, a_ as ResolveContext, bs as TaskRunnerFlags } from './Task-BnLxkVQF.js';
2
- export { d as AgentAdapterFactory, i as AllProviderModels, l as ApprovalKind, o as ApprovalPolicyOptions, q as ApprovalSource, B as BlockingPrompt, C as CMD_EXE_MAX_COMMAND_LINE, s as CatalogModel, u as CheckpointArgs, v as CommandLineTooLongError, D as DENY_ALL, x as DiscoveredMode, z as DiscoveryCommand, E as EditPlanArgs, F as EmbeddedNewlineError, G as ExecutableNotFoundError, H as FetchAllProviderModelsOptions, I as HeadlessRunner, J as HeadlessRunnerDeps, K as HeadlessSession, R as IntegrationDisposal, S as IsolationAvailability, U as IsolationHandoffRepo, V as IsolationInactiveReason, X as IsolationLanding, Y as IsolationMergeBlock, Z as IsolationMergeBlockReason, a0 as IsolationPruneResult, a1 as IsolationRemoval, a2 as IsolationRepo, a5 as IsolationTaskRef, a6 as IsolationTaskRepo, a9 as LaunchDeps, aa as LaunchPlan, ac as LineBuffer, ad as ListModelsArgs, ae as ListRunnersArgs, af as McpClientConfig, ag as McpCredential, ah as McpTool, ai as McpToolContext, aj as McpToolReply, ak as Message, al as ModelCatalog, am as ModelShortcut, an as ORCHESTRATOR_SHORTCUTS, ao as ORDEWELL_MCP_PATH, ap as ORDEWELL_MCP_SERVER_NAME, as as OwnerOnlyFile, at as PLANNER_TOOLS, av as PlanModificationWarnings, ay as PlannerStartOptions, az as PlannerTokenScope, aC as PluginCloneFn, aD as PluginEntry, aE as PluginFeatures, aF as PluginMode, aG as PluginModelDiscovery, aH as PluginRunnerDef, aI as PreparedLaunch, aJ as PreparedTask, aK as PreservedWork, aL as ProviderCredentialSource, aN as ProviderModelsResult, aO as PtySize, aP as PtyWrapOptions, aS as RefusedRemoval, aX as ResearchStep, aY as ResearchStepOutcome, b6 as StreamEvent, b7 as StreamStepEvent, b8 as StreamThinkingEvent, bc as SubagentLogEntry, bd as SubagentOutcome, be as SubmitPlanArgs, bf as TASK_COMPLETE_TOOL, bg as TASK_TOOLS, bk as TaskIsolationState, bl as TaskMode, bn as TaskModeUnsupportedError, bo as TaskModelAssignment, bp as TaskOutputArgs, bq as TaskOutputSummary, br as TaskQueryArgs, bv as TaskStatus, bx as TaskToolHandler, by as TaskTransport, bz as TaskType, bA as TaskWithParent, bB as ThinkingBlock, bD as UsageLine, bF as UsageTotals, bG as UserPromptEntry, bH as UserStep, bI as ValidationCheck, bJ as ValidationContext, bK as ValidationResult, bM as VerificationCheck, bN as WINDOWS_MAX_COMMAND_LINE, bO as addPlannerUsage, bP as addTaskToPlan, bQ as addUsage, bR as approvalScopes, bS as buildShellInvocation, bT as collectProviderCredentials, bU as createEmptyPlan, bV as createTask, bW as emptyWarnings, bX as enabledRunners, bY as fetchAllProviderModels, bZ as flattenTasks, b_ as flattenTasksWithParents, b$ as inheritedOps, c0 as isAwaitingReason, c1 as isExecutableResolved, c2 as isGranted, c3 as isMeasured, c4 as isReservedRunnerName, c5 as isRunnerApproval, c6 as isRunnerTransport, c7 as isStructuredSession, c8 as keepExecutionState, c9 as knownModelId, ca as mcpClientConfig, cb as migrateLegacyPlan, cc as migratePlanState, cd as migrateTask, ce as opsFlag, cf as ownerOnlyConfigFile, cg as partedPromptUsage, ch as planDirectLaunch, ci as planShellLaunch, cj as plannerContextFill, ck as posixShellQuote, cl as removeTaskFromPlan, cm as renumberTasks, cn as resolveModelShortcut, co as resolveProvider, cp as sharedMcpServer, cq as stripAnsi, cr as toApprovalDecision, cs as toOrchestratorOptions, ct as updateTaskInPlan, cu as usageLine, cv as validateModifiedPlan, cw as warningsText, cx as windowsCommandLine, cy as wrapWithPty } from './Task-BnLxkVQF.js';
3
- import { z as TaskOp, M as MergeGateView, r as SessionBroadcaster, A as ApplyTaskOpsResult, s as SessionMessage, t as SessionNotice, x as TaskLogEvent } from './plan-utils--kQRzSE-.js';
4
- export { a as ApprovalBlock, b as ApprovalStatus, C as CHECKPOINT_TRUNCATE_LENGTH, c as ConversationInput, d as ConversationView, D as DiffRow, e as DiffRowKind, f as DiffStat, g as DisplayBlock, E as EMPTY_CONVERSATION, h as EMPTY_HOLD, i as EMPTY_TASK_LOG, G as GatedConversation, L as LocalEntry, j as MessageBlock, k as MessageRole, N as NOT_STARTED, l as NO_TURN, m as NewTaskFields, O as OutputPreview, P as PlanBlock, n as PlanMarkerStatus, o as PromptHold, S as SerializedPlan, p as SerializedTask, q as SerializedTaskStatus, u as SubagentBlock, v as SubagentChild, w as SubagentStatus, T as TakenPrompt, y as TaskLogView, B as TaskRef, F as TaskRowAction, H as TaskRowContext, I as TaskRowIsolation, J as TaskRowKind, K as TaskRowPlacement, Q as TaskRowSubject, R as TaskRowView, U as TaskStatusKind, V as ThinkingDisplayBlock, W as ToolBlock, X as ToolHeadline, Y as ToolStatus, Z as TurnGate, _ as UsageBlock, $ as aheadOfDraft, a0 as applyTaskOps, a1 as approvalLabel, a2 as awaitingLabel, a3 as canMergeTasks, a4 as canSetDependencies, a5 as canSplitTask, a6 as capConflictFiles, a7 as classifyOutcome, a8 as coalesceTaskLog, a9 as dependencyCandidates, aa as dependentsNotice, ab as dependentsOf, ac as diffRows, ad as diffStat, ae as diffSummary, af as digestTaskLog, ag as drainNext, ah as executionSummary, ai as followTurn, aj as fromTranscript, ak as hasHiddenDetail, al as holdPrompt, am as isTaskRunning, an as markAction, ao as markRequestFor, ap as namedRepos, aq as newTaskFields, ar as opsEditable, as as outputLines, at as outputPreview, au as parseTaskOpsJson, av as pastGateConfirmation, aw as reduceConversation, ax as reduceTaskLog, ay as replayTaskLog, az as runnerToolSubject, aA as serializePlan, aB as serializeTask, aC as serializeTaskStatus, aD as stopTurn, aE as summarizeToolCall, aF as taskRowActions, aG as taskRowView, aH as taskStartedNotice, aI as taskStatusKind, aJ as textHasTaskOps, aK as titledRefs, aL as toTaskLogEvent, aM as toolHeadline, aN as trimToolOutput, aO as truncateCheckpointSummary, aP as unsendAll, aQ as unsendLatest } from './plan-utils--kQRzSE-.js';
5
- import { I as IFileSystem, R as ReadFileOpts, T as ToolOutcome, b as GrepOptions, a as GlobOptions, F as FindSymbolOptions } from './IFileSystem-DofiRcCo.js';
6
- export { G as GREP_DEFAULT_HEAD_LIMIT, c as GrepOutputMode, S as SEARCH_EXCLUSIONS } from './IFileSystem-DofiRcCo.js';
7
- import { R as RunnerModeInfo } from './ModeResolver-DkMYf4uS.js';
8
- export { M as ManifestLookup, a as autonomyLevelLabel, b as buildModeGuide, f as filteredBuildModes, p as parseAutonomyLevel, r as resolveDefaultMode, c as resolveTaskMode, d as runnerModesFrom } from './ModeResolver-DkMYf4uS.js';
1
+ import { W as RunnerId, E as PlanStatus, L as LegacyPlanState, n as IsolationMergeResult, j as IsolationLandedTask, I as IWorktreeIsolation, w as IsolationTaskStatus, P as PlanIsolation, a3 as SkillsService, _ as SkillInfo, N as RepoGroupLayout, c as DiscoveredModel, a9 as Task, S as ResearchProgress, O as ResearchLogEntry, C as ConversationMessage, ao as UsageRecord, ah as TaskSnapshot, a as AwaitingReason, g as IsolationHandoff, s as IsolationRun, t as IsolationTaskRecord, aa as TaskIsolation, x as IsolationView, o as IsolationOutcome, K as RepairEvidence, am as TreeSnapshot, av as Verdict, ag as TaskSkillSnapshot, $ as SkillLoad, Q as QueuedMessage, A as ActiveTaskSession, V as ResearchToolType, G as PlannerUsage, z as PlanState } from './Task-1Q19y7sZ.js';
2
+ export { B as BUILTIN_SKILL_NAMES, D as DEFAULT_RUNNERS, b as DiscoveredMode, d as IntegrationDisposal, e as InvalidSkill, f as IsolationAvailability, h as IsolationHandoffRepo, i as IsolationInactiveReason, k as IsolationLanding, l as IsolationMergeBlock, m as IsolationMergeBlockReason, p as IsolationPruneResult, q as IsolationRemoval, r as IsolationRepo, u as IsolationTaskRef, v as IsolationTaskRepo, M as Message, y as PlanModificationWarnings, F as PlannerTaskView, H as PreparedTask, J as PreservedWork, R as RefusedRemoval, T as ResearchStep, U as ResearchStepOutcome, X as ShadowedSkill, Y as SkillAppliesTo, Z as SkillCatalog, a0 as SkillLoadNotice, a1 as SkillMetadata, a2 as SkillSource, a4 as StreamEvent, a5 as StreamStepEvent, a6 as StreamThinkingEvent, a7 as SubagentLogEntry, a8 as SubagentOutcome, ab as TaskIsolationState, ac as TaskMode, ad as TaskModelAssignment, ae as TaskOutputSummary, af as TaskSkillNotice, ai as TaskStatus, aj as TaskType, ak as TaskWithParent, al as ThinkingBlock, an as UsageLine, ap as UsageTotals, aq as UserPromptEntry, ar as UserStep, as as ValidationCheck, at as ValidationContext, au as ValidationResult, aw as VerificationCheck, ax as addPlannerUsage, ay as addTaskToPlan, az as addUsage, aA as createEmptyPlan, aB as createSkillsService, aC as createTask, aD as emptyWarnings, aE as flattenTasks, aF as flattenTasksWithParents, aG as inheritedOps, aH as inheritedSkills, aI as isAwaitingReason, aJ as isMeasured, aK as isSkillName, aL as isUserMessage, aM as keepExecutionState, aN as migrateLegacyPlan, aO as migrateLoadedTasks, aP as migratePlanState, aQ as migrateTask, aR as opsFlag, aS as partedPromptUsage, aT as plannerContextFill, aU as plannerTaskView, aV as removeTaskFromPlan, aW as renumberTasks, aX as skillNames, aY as updateTaskInPlan, aZ as usageLine, a_ as validateModifiedPlan, a$ as warningsText } from './Task-1Q19y7sZ.js';
3
+ import { R as TaskOp, M as MergeGateView, j as EditCatalog, u as SessionBroadcaster, A as ApplyTaskOpsResult, w as SessionNotice, v as SessionMessage, K as TaskLogEvent, H as SurfacePlan, I as SurfacePlanState } from './plan-utils-Cv48fe7r.js';
4
+ export { a as ApprovalBlock, b as ApprovalStatus, C as CHECKPOINT_TRUNCATE_LENGTH, c as ConversationInput, d as ConversationView, D as DiffRow, e as DiffRowKind, f as DiffStat, g as DisplayBlock, E as EMPTY_CONVERSATION, h as EMPTY_HOLD, i as EMPTY_TASK_LOG, G as GatedConversation, L as LocalEntry, k as MessageBlock, l as MessageRole, N as NOT_STARTED, m as NO_TURN, n as NewTaskFields, O as OutputPreview, P as PlanBlock, o as PlanMarkerStatus, p as PromptHold, S as SerializedConversationMessage, q as SerializedPlan, r as SerializedQueuedMessage, s as SerializedTask, t as SerializedTaskStatus, x as SkillLoadBlock, y as SkillToken, z as SubagentBlock, B as SubagentChild, F as SubagentStatus, J as SurfaceTask, T as TakenPrompt, Q as TaskLogView, U as TaskRef, V as TaskRowAction, W as TaskRowContext, X as TaskRowIsolation, Y as TaskRowKind, Z as TaskRowPlacement, _ as TaskRowSubject, $ as TaskRowView, a0 as TaskStatusKind, a1 as ThinkingDisplayBlock, a2 as ToolBlock, a3 as ToolHeadline, a4 as ToolStatus, a5 as TurnGate, a6 as UsageBlock, a7 as aheadOfDraft, a8 as applyTaskOps, a9 as approvalLabel, aa as awaitingLabel, ab as canMergeTasks, ac as canSetDependencies, ad as canSplitTask, ae as capConflictFiles, af as classifyOutcome, ag as coalesceTaskLog, ah as dependencyCandidates, ai as dependentsNotice, aj as dependentsOf, ak as diffRows, al as diffStat, am as diffSummary, an as digestTaskLog, ao as drainNext, ap as executionSummary, aq as followTurn, ar as fromTranscript, as as hasHiddenDetail, at as holdPrompt, au as isTaskRunning, av as loadedSkillTokens, aw as markAction, ax as markRequestFor, ay as namedRepos, az as newTaskFields, aA as opsEditable, aB as outputLines, aC as outputPreview, aD as parseTaskOpsJson, aE as pastGateConfirmation, aF as reduceConversation, aG as reduceTaskLog, aH as replayTaskLog, aI as runnerToolSubject, aJ as serializePlan, aK as serializeTask, aL as serializeTaskStatus, aM as skillTokens, aN as stopTurn, aO as summarizeToolCall, aP as surfacePlan, aQ as surfacePlanState, aR as taskLogForSurface, aS as taskRowActions, aT as taskRowView, aU as taskStartedNotice, aV as taskStatusKind, aW as textHasTaskOps, aX as titledRefs, aY as toTaskLogEvent, aZ as toolHeadline, a_ as trimToolOutput, a$ as truncateCheckpointSummary, b0 as unsendAll, b1 as unsendLatest } from './plan-utils-Cv48fe7r.js';
5
+ import { I as IFileSystem, R as ReadFileOpts, T as ToolOutcome, b as GrepOptions, a as GlobOptions, F as FindSymbolOptions } from './IFileSystem-D96mT-bm.js';
6
+ export { G as GREP_DEFAULT_HEAD_LIMIT, c as GrepOutputMode, S as SEARCH_EXCLUSIONS } from './IFileSystem-D96mT-bm.js';
7
+ import { I as IApproval, m as ApprovalRequest, h as ApprovalDecision, g as ApprovalAnswer, s as IRunner, r as IConfig, e as AiProvider, W as ProviderModelLists, j as ApprovalMode, t as IRunnerSession, c as AgentProcessDeps, A as AgentAdapter, P as OrdewellMcpServer, U as PlannerToolHandler, a2 as RunnerRegistry, Y as QueuedTaskMessage, N as OrchestratorOption, aa as TaskModeAgentAdapter, d as AgentStartOptions, b as AgentEvent, k as ApprovalPolicy, a9 as TaskCompleteArgs, o as CheckpointAnswer, a5 as StructuredTurnEnd, a4 as StructuredEvent, af as TaskStartOptions, ag as TaskTokenScope, $ as RunnerManifest, ae as TaskRunnerFlags } from './IRunner-CGCihlGQ.js';
8
+ export { a as AgentAdapterFactory, f as AllProviderModels, i as ApprovalKind, l as ApprovalPolicyOptions, n as ApprovalSource, C as CatalogModel, p as CheckpointArgs, D as DENY_ALL, q as DiscoveryCommand, E as EditPlanArgs, F as FetchAllProviderModelsOptions, L as LineBuffer, u as ListModelsArgs, v as ListRunnersArgs, w as LoadSkillArgs, M as McpClientConfig, x as McpCredential, y as McpTool, z as McpToolContext, B as McpToolReply, G as ModelCatalog, H as ModelShortcut, O as ORCHESTRATOR_SHORTCUTS, J as ORDEWELL_MCP_PATH, K as ORDEWELL_MCP_SERVER_NAME, Q as OwnerOnlyFile, R as PLANNER_TOOLS, S as PlannerStartOptions, T as PlannerTokenScope, V as ProviderCredentialSource, X as ProviderModelsResult, Z as RunnerEntry, _ as RunnerFeatures, a0 as RunnerMode, a1 as RunnerModelDiscovery, a3 as SpawnFn, a6 as SubmitPlanArgs, a7 as TASK_COMPLETE_TOOL, a8 as TASK_TOOLS, ab as TaskModeUnsupportedError, ac as TaskOutputArgs, ad as TaskQueryArgs, ah as TaskToolHandler, ai as approvalScopes, aj as collectProviderCredentials, ak as enabledRunners, al as fetchAllProviderModels, am as isGranted, an as isRunnerApproval, ao as knownModelId, ap as mcpClientConfig, aq as ownerOnlyConfigFile, ar as resolveModelShortcut, as as resolveProvider, at as sharedMcpServer, au as toApprovalDecision, av as toOrchestratorOptions } from './IRunner-CGCihlGQ.js';
9
+ import { R as RunnerModeInfo } from './ModeResolver-EWlpmVAy.js';
10
+ export { M as ManifestLookup, a as autonomyLevelLabel, b as buildModeGuide, f as filteredBuildModes, p as parseAutonomyLevel, r as resolveDefaultMode, c as resolveTaskMode, d as runnerModesFrom } from './ModeResolver-EWlpmVAy.js';
9
11
  import { ChildProcess } from 'child_process';
10
- import { b as PlanParseError } from './parsing-ddZXZFP4.js';
11
- export { P as PLAN_ENVELOPE_KEY, a as PartialPlanTask, c as PlanTaskError, d as PlanTasksResult, T as TASK_OPS_ENVELOPE_KEY, e as TASK_QUERY_ENVELOPE_KEY, f as checkDepsResolve, g as checkImmutableLog, h as checkInProgress, i as checkNoCycles, j as checkUniqueIds, k as escapeControlCharsInStrings, l as extractJsonObject, m as extractObjectWithBalance, n as extractObjectsWithKey, o as looksLikePlanAttempt, p as opensWithJsonObject, q as parsePartialPlan, r as parsePlanJson, s as stripModelNoise, t as stripTrailingCommas, v as validatePlanModification, u as validatePlanTasks } from './parsing-ddZXZFP4.js';
12
+ import { b as PlanParseError } from './parsing-CZdp2p_n.js';
13
+ export { P as PLAN_ENVELOPE_KEY, a as PartialPlanTask, c as PlanTaskError, d as PlanTasksResult, T as TASK_OPS_ENVELOPE_KEY, e as TASK_QUERY_ENVELOPE_KEY, f as checkDepsResolve, g as checkImmutableLog, h as checkInProgress, i as checkNoCycles, j as checkUniqueIds, k as escapeControlCharsInStrings, l as extractJsonObject, m as extractObjectWithBalance, n as extractObjectsWithKey, o as looksLikePlanAttempt, p as opensWithJsonObject, q as parsePartialPlan, r as parsePlanJson, s as requestedSkills, t as stripModelNoise, u as stripTrailingCommas, v as validatePlanModification, w as validatePlanTasks } from './parsing-CZdp2p_n.js';
14
+ import { EventEmitter } from 'events';
12
15
  export { resolveOrderLabel, taskOrderLabel, taskRef, titledTaskRef } from './order-labels.js';
13
- import 'events';
14
16
  import 'zod/v4';
15
17
 
16
18
  interface SessionMeta {
@@ -410,7 +412,7 @@ declare class RunnerApprovals {
410
412
  /** The open request ids of each live structured session. */
411
413
  private readonly open;
412
414
  constructor(approvals: PendingApprovals);
413
- wrap(runner: ITerminalRunner): ITerminalRunner;
415
+ wrap(runner: IRunner): IRunner;
414
416
  /** How many of a task's runner requests wait for an answer — what "waiting for approval" is derived from. */
415
417
  waiting(taskId: string): number;
416
418
  private watch;
@@ -650,6 +652,7 @@ type GitExecFn = (file: string, args: string[], opts: {
650
652
  stdout: string;
651
653
  stderr: string;
652
654
  }>;
655
+
653
656
  interface WorktreeIsolationDeps {
654
657
  config: Pick<IConfig, 'worktreeIsolation' | 'worktreeSetupCommand' | 'workspaceRepos' | 'worktreeLinks'>;
655
658
  /** Test seam: every git invocation goes through this. */
@@ -699,86 +702,31 @@ declare class ConsoleLogger implements ILogger {
699
702
  }
700
703
  declare const defaultLogger: ILogger;
701
704
 
702
- interface UserSettings {
703
- tdd: {
704
- enabled: boolean;
705
- };
706
- verification: {
707
- enabled: boolean;
708
- };
709
- modelAllowlist?: Record<string, string[]>;
710
- /** Last model (and its thinking effort) the user chose for each planner backend, keyed by AiProvider id. */
711
- plannerModels?: Record<string, {
712
- model: string;
713
- effort?: string;
714
- }>;
715
- /**
716
- * Runners the user picked for planning. Absent — not empty — is what falls
717
- * back to the environment's defaults; `[]` is a deliberate "none of them".
718
- */
719
- enabledRunners?: string[];
720
- /**
721
- * How tasks' runners are driven (ADR-0018). A run copies it onto its plan
722
- * when it starts, so a change applies from the next run.
723
- */
724
- runnerTransport: RunnerTransport;
725
- }
726
705
  /**
727
- * Where the user's toggles live. `ORDEWELL_SETTINGS_PATH` overrides it so several
728
- * Ordewell processes on one machine can hold *different* settings at the same
729
- * time. Without it the file is a single shared mutable global: the benchmark
730
- * harness runs parallel lanes that each pin the mode toggles, and because
731
- * `getAll()` re-reads whenever the mtime moves, a lane needing a toggle
732
- * `true` would silently plan with `false` the moment another lane pinned its
733
- * own — turning an A/B of that toggle into a comparison of one condition
734
- * against itself.
706
+ * Task skills end to end (ADR-0024): a skill reaches a runner only by being
707
+ * attached to its task in the plan, and Ordewell puts its body in the task's
708
+ * prompt — so it works the same on every runner, and nothing is written to a
709
+ * harness's own skills directory.
735
710
  */
736
- declare function getSettingsPath(): string;
737
- declare class SettingsService {
738
- private filePath;
739
- private cache;
740
- private cachedMtimeMs;
711
+ /** Where a task's skill names are looked up: global first, then the workspace folders it reads. */
712
+ interface SkillLookup extends Pick<SkillsService, 'findSkill' | 'searchedDirs'> {
741
713
  /**
742
- * Whether the transport on disk (or just set) is the user's own choice.
743
- * Writing the default back out would turn it into one, and a later change of
744
- * default would then never reach this user.
714
+ * Set when tasks get worktrees: of `skills`, the workspace ones git will not
715
+ * carry into them, each SKILL.md path mapped to the folder to commit,
716
+ * relative to the workspace root.
745
717
  */
746
- private transportChosen;
747
- constructor(filePath?: string);
748
- getAll(): UserSettings;
749
- private fileMtimeMs;
750
- getTdd(): boolean;
751
- setTdd(enabled: boolean): void;
752
- getVerification(): boolean;
753
- setVerification(enabled: boolean): void;
754
- getRunnerTransport(): RunnerTransport;
755
- setRunnerTransport(transport: RunnerTransport): void;
756
- getModelAllowlist(runner: string): string[] | undefined;
757
- setModelAllowlist(runner: string, ids: string[] | undefined): void;
758
- getPlannerModel(provider: string): {
759
- model: string;
760
- effort?: string;
761
- } | undefined;
762
- setPlannerModel(provider: string, entry: {
763
- model: string;
764
- effort?: string;
765
- } | undefined): void;
766
- getEnabledRunners(): string[] | undefined;
767
- setEnabledRunners(ids: string[]): void;
768
- private load;
769
- private persist;
770
- }
771
-
772
- /** How the same toggle is named once a host has read it off disk. */
773
- interface PlannerRuntimeToggles {
774
- tddEnabled: boolean;
775
- verificationEnabled: boolean;
718
+ uncommitted?(skills: readonly SkillInfo[]): Promise<Map<string, string>>;
776
719
  }
777
720
  /**
778
- * The planner-facing mode set for one operation. Replaces the boolean tail that
779
- * every planner signature used to carry positionally — where a thirteenth
780
- * parameter was the only place left to put a new toggle.
721
+ * Where a workspace's skills are read, known without asking git: the repos
722
+ * of its group in layout order (`['.']` for one repository), and whether
723
+ * tasks will get worktrees, which only a committed skill reaches.
781
724
  */
725
+ interface SkillLayout {
726
+ repos: readonly string[];
727
+ worktrees: boolean;
728
+ }
729
+
782
730
  /**
783
731
  * Where the run's tasks will work: `false` in the shared workspace root;
784
732
  * otherwise each in its own worktree of every repo of the group (ADR-0013,
@@ -787,47 +735,24 @@ interface PlannerRuntimeToggles {
787
735
  type IsolatedExecution = false | RepoGroupLayout;
788
736
  interface PlannerModes {
789
737
  autonomousDefault: boolean;
790
- verification: boolean;
791
738
  /** Not a toggle: a fact about the run. */
792
739
  isolatedExecution: IsolatedExecution;
740
+ /**
741
+ * Where the run's tasks will look their skills up (ADR-0024), also a fact
742
+ * about the run. A plan attaching a planner skill is refused against it,
743
+ * and a coding agent's submit_plan call is refused in its turn rather than
744
+ * accepted and then failed. Absent: the plan is not checked.
745
+ */
746
+ taskSkills?: SkillLookup;
793
747
  }
794
748
 
795
- /** Context of one task attempt, enough to locate its transcript. */
796
- interface TranscriptQuery {
797
- runner: string;
798
- /** The task's working directory (the agent's cwd at spawn). */
799
- cwd: string;
800
- /** When the agent process started, ISO — used to reject older sessions. */
801
- startedAt?: string;
802
- /**
803
- * The task's completion marker UUID. The prompt carries it, so a transcript
804
- * that does not contain it belongs to some other task — parallel attempts in
805
- * one cwd are otherwise indistinguishable by directory and recency.
806
- */
807
- marker: string;
808
- }
809
- /** Reads the agent's own session transcript for a task's final answer. */
810
- interface TranscriptReader {
811
- /** Last assistant-authored prose, clamped to `maxChars` from the end, or null. */
812
- finalAssistantText(query: TranscriptQuery, maxChars?: number): Promise<string | null>;
813
- }
814
- /** The part of a task attempt its output is looked up by. */
815
- interface TaskOutputAttempt {
816
- readonly taskId: string;
817
- readonly runner: string;
818
- /** Null when the attempt ended before its working directory was settled. */
819
- readonly cwd: string | null;
820
- readonly startedAt: string;
821
- /** The completion marker UUID the attempt's prompt carried. */
822
- readonly completionMarker: string;
823
- }
824
749
  interface LiveTailOptions {
825
750
  maxLines: number;
826
751
  /** A previous {@link LiveTail.nextOffset}: only output after it is rendered. */
827
752
  sinceOffset?: number;
828
753
  }
829
754
  interface LiveTail {
830
- /** Clean terminal render, not raw ANSI. */
755
+ /** Plain text, escapes and control characters removed. */
831
756
  text: string;
832
757
  /** Total output received so far; pass it back as `sinceOffset` to read only what follows. */
833
758
  nextOffset: number;
@@ -839,13 +764,13 @@ interface LiveTail {
839
764
  */
840
765
  interface TaskOutputSource {
841
766
  /** Start capturing a session; replaces any earlier capture of the task. */
842
- attach(taskId: string, session: ITerminalSession): void;
767
+ attach(taskId: string, session: IRunnerSession): void;
843
768
  /** Stop capturing the task's session; what was captured stays readable. */
844
769
  detach(taskId: string): void;
845
770
  /** Drop every capture (a new plan reuses task ids). */
846
771
  reset(): void;
847
- /** The attempt's final answer: its transcript first, the clean terminal render otherwise. */
848
- finalText(attempt: TaskOutputAttempt, doneToken: string): Promise<string>;
772
+ /** The task's final answer: the summary its runner reported through `task_complete`, else what its last turn said. */
773
+ finalText(taskId: string): string;
849
774
  /** Recent output of the task's latest capture, or null if it never had one. */
850
775
  liveTail(taskId: string, opts: LiveTailOptions): LiveTail | null;
851
776
  }
@@ -865,7 +790,7 @@ interface TaskOutputSource {
865
790
  * lives in PlanRepair alongside the plan and task-ops envelopes.
866
791
  */
867
792
  /** The long fields a query may ask for. Everything else is already in the plan block. */
868
- declare const TASK_QUERY_FIELDS: readonly ["description", "prompt", "userSteps", "verdict", "outputSummary", "userStoriesCovered", "output"];
793
+ declare const TASK_QUERY_FIELDS: readonly ["description", "prompt", "userSteps", "verdict", "outputSummary", "userStoriesCovered", "skills", "output"];
869
794
  type TaskQueryField = typeof TASK_QUERY_FIELDS[number];
870
795
  /** Lines a `output` read returns when the query sets no count. */
871
796
  declare const OUTPUT_LINES_DEFAULT = 80;
@@ -936,7 +861,7 @@ interface CliAgentAiServiceDeps extends Partial<AgentProcessDeps> {
936
861
  createAdapter?: (runner: string, deps: AgentProcessDeps) => AgentAdapter | null;
937
862
  /** Workspace root the agent explores. Defaults to the host process's cwd. */
938
863
  workspaceRoot?: () => string;
939
- /** Where a conversation's planner tools are served (ADR-0022). Absent: every planner gets the envelopes. */
864
+ /** Where a conversation's planner tools are served (ADR-0022). Absent: no conversation can start. */
940
865
  mcpServer?: OrdewellMcpServer;
941
866
  }
942
867
  /**
@@ -946,8 +871,8 @@ interface CliAgentAiServiceDeps extends Partial<AgentProcessDeps> {
946
871
  * `GeminiService`. It deliberately does **not** extend {@link BaseAiService}:
947
872
  * that class's body is Ordewell executing research tools on a model's behalf,
948
873
  * which is precisely the part a coding agent replaces. What it reuses instead
949
- * is everything above the transport — `settleReply` (reply classification
950
- * and the bounded corrective retries), `parsePlanJson`, the `ResearchProgress` events the
874
+ * is everything above the transport — `settleReply` (the empty-reply nudge
875
+ * and the turn's settling), the plan validator, the `ResearchProgress` events the
951
876
  * four surfaces already render. That is why this backend reaches VS Code, the
952
877
  * web UI, the CLI and the TUI without any of them learning a coding agent is
953
878
  * on the other end.
@@ -969,8 +894,6 @@ declare class CliAgentAiService implements IAiService {
969
894
  private readonly mcpServer;
970
895
  /** The planner token the running process was spawned with; revoked with the process (ADR-0022, A3). */
971
896
  private plannerToken;
972
- /** Whether the running planner was spawned with its tools and the CLI reported them connected. */
973
- private toolsAttached;
974
897
  private activeAbort;
975
898
  /**
976
899
  * Every subagent this conversation has reported starting, and finishing.
@@ -989,7 +912,7 @@ declare class CliAgentAiService implements IAiService {
989
912
  * on the old model. No conversation yet is vacuously "current".
990
913
  */
991
914
  conversationMatchesConfig(): boolean;
992
- /** Whether this conversation's planner submits through the Ordewell MCP server rather than the envelopes. */
915
+ /** Always: a conversation whose planner cannot reach the Ordewell MCP server fails to start rather than falling back to the envelopes. */
993
916
  plannerToolsAttached(): boolean;
994
917
  /** The agent's own session id, a resumption hint only — Ordewell's transcript is authoritative (T4). */
995
918
  nativeSessionId(): string | null;
@@ -1018,7 +941,8 @@ declare class CliAgentAiService implements IAiService {
1018
941
  * mode from here without changing this signature.
1019
942
  */
1020
943
  private startAdapter;
1021
- /** Spawn with the Ordewell server injected. False, with nothing left running, when it did not connect. */
944
+ private newAdapter;
945
+ /** One spawn with the Ordewell server injected: the adapter once the runner reports it connected, otherwise why not, with nothing left running. */
1022
946
  private startWithTools;
1023
947
  /** Kill the planner process, and with it the token it was spawned with. */
1024
948
  private disposeAdapter;
@@ -1030,12 +954,18 @@ declare class CliAgentAiService implements IAiService {
1030
954
  */
1031
955
  private ensureAdapter;
1032
956
  private plannerModel;
957
+ /**
958
+ * A plan from one-shot agent sessions: only what a submit_plan call
959
+ * accepted, never the reply text, so a plan the tool refused cannot come
960
+ * back in as the JSON the agent echoed. A session that ends without an
961
+ * accepted call is followed by one corrective session, then fails.
962
+ */
963
+ private submittedPlan;
1033
964
  /**
1034
965
  * A single agent session that answers one prompt and exits. Used by every
1035
- * non-conversational entry point; the plan is parsed from the reply text by
1036
- * the same extractor the conversational path uses. Its envelope streams to
1037
- * the plan display, as a vendor planner's one-shot does, and the prose
1038
- * around it is not streamed at all, since no turn is open to show it in.
966
+ * non-conversational entry point. A plan-shaped envelope in its text
967
+ * streams to the plan display, as a vendor planner's one-shot does, and the
968
+ * prose around it is not streamed at all, since no turn is open to show it in.
1039
969
  */
1040
970
  private oneShot;
1041
971
  researchAndPlan(userDescription: string, runners: RunnerId[], modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, fs: IFileSystem, onProgress: (progress: ResearchProgress) => void, _fetcher?: IWebFetcher, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes, signal?: AbortSignal): Promise<{
@@ -1073,7 +1003,6 @@ interface ConversationRequest {
1073
1003
  fetcher?: IWebFetcher;
1074
1004
  runnerModes?: Record<RunnerId, RunnerModeInfo[]>;
1075
1005
  autonomousDefault?: boolean;
1076
- verificationEnabled?: boolean;
1077
1006
  /**
1078
1007
  * The planner model's context window when the catalog knows it. Threaded here
1079
1008
  * so a usage record can carry it (and the UI can show context fill); absent
@@ -1096,6 +1025,12 @@ interface ConversationRequest {
1096
1025
  */
1097
1026
  initialMessage?: string;
1098
1027
  plannerTools?: PlannerToolsOffer;
1028
+ /**
1029
+ * The workspace's skill catalog. Every planner is shown the task skills it
1030
+ * may attach; only one with tools is shown the planner skills, which load
1031
+ * through `load_skill` alone (ADR-0024).
1032
+ */
1033
+ skills?: readonly SkillInfo[];
1099
1034
  }
1100
1035
  /**
1101
1036
  * One planner turn's outcome. The planner talks to the user (`message`),
@@ -1106,10 +1041,13 @@ interface ConversationRequest {
1106
1041
  * atomically and answers queries out of its own state; the AI service only
1107
1042
  * parses them.
1108
1043
  */
1109
- type ConversationTurn = {
1044
+ type ConversationTurn =
1045
+ /** `aborted`: the call was stopped, and `text` is only what it had by then. */
1046
+ {
1110
1047
  kind: 'message';
1111
1048
  text: string;
1112
1049
  researchLog: ResearchLogEntry[];
1050
+ aborted?: boolean;
1113
1051
  } | {
1114
1052
  kind: 'plan';
1115
1053
  tasks: Task[];
@@ -1191,6 +1129,11 @@ interface IAiService {
1191
1129
  * and cheap in every implementation, so callers never gate it.
1192
1130
  */
1193
1131
  reset(): void;
1132
+ /**
1133
+ * A one-shot plan from a prompt the caller built, so built for this
1134
+ * planner's transport: one with {@link plannerToolsAttached} is asked for a
1135
+ * submit_plan call, never JSON in its reply.
1136
+ */
1194
1137
  sendPlanningPrompt(prompt: string, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean): Promise<Task[]>;
1195
1138
  }
1196
1139
  /**
@@ -1463,16 +1406,8 @@ declare class PlanStore {
1463
1406
  unblockDependents(id: string): void;
1464
1407
  setTaskVerdict(id: string, verdict: Task['verdict']): void;
1465
1408
  setTaskOutputSummary(id: string, summary: Task['outputSummary']): void;
1466
- setTaskTransport(id: string, transport: Task['transport']): void;
1467
- getPlanVisualization(): {
1468
- tasks: {
1469
- id: string;
1470
- title: string;
1471
- dependencies: string[];
1472
- parallelGroups: number[][];
1473
- }[];
1474
- parallelGroups: number[][];
1475
- };
1409
+ setTaskRunnerSessionId(id: string, runnerSessionId: string | undefined): void;
1410
+ setTaskAttemptSkills(id: string, skills: Task['attemptSkills']): void;
1476
1411
  /**
1477
1412
  * Widen the plan's runner set. Retargeting a task onto a runner the plan has
1478
1413
  * not used before has to land here as well as on the plan state, or
@@ -1493,23 +1428,12 @@ type IsolationNoticeLevel = 'info' | 'warn' | 'error';
1493
1428
  interface IsolationRunListener {
1494
1429
  /** The run record changed and should be persisted with the plan. */
1495
1430
  changed(): void;
1496
- /**
1497
- * A run opened — the one moment shared by Execute, a manual task run and
1498
- * a force start, so whatever a run pins for its whole length is read here.
1499
- * Whether it isolates is decided later, at its first change task.
1500
- */
1501
- opened(): void;
1502
1431
  /** A run did not start: `repos` of the group have tracked changes. */
1503
1432
  blocked(repos: string[]): void;
1504
1433
  /** An isolated run closed and handed its integration branches over. */
1505
1434
  handoff(handoff: IsolationHandoff): void;
1506
1435
  /** Already said through the notifications; also for a surface that shows no toasts. */
1507
1436
  notice(level: IsolationNoticeLevel, message: string): void;
1508
- /**
1509
- * These tasks' worktrees are about to be removed. An agent left running in
1510
- * one would sit in a deleted directory, so it has to go first.
1511
- */
1512
- releasing(taskIds: string[]): void;
1513
1437
  }
1514
1438
  interface IsolationRunControllerDeps {
1515
1439
  isolation: IWorktreeIsolation;
@@ -1552,6 +1476,8 @@ declare class IsolationRunController {
1552
1476
  /** Resolver task id → the conflicted task it resolves; see {@link linkResolver}. */
1553
1477
  private resolvers;
1554
1478
  private opening;
1479
+ /** Bumped by an interrupt, so an activation in flight across it does not open the run it ended. */
1480
+ private activations;
1555
1481
  /** The start a dirty tree turned away, handed back once the user chooses how to go on. */
1556
1482
  private blockedStart;
1557
1483
  /** The dirty repos behind {@link blockedStart}, for the stash notice. */
@@ -1604,6 +1530,15 @@ declare class IsolationRunController {
1604
1530
  * the safe rule then.
1605
1531
  */
1606
1532
  plannerLayout(): Promise<IsolatedExecution>;
1533
+ /**
1534
+ * Where the workspace's skills are read, and whether tasks get worktrees.
1535
+ * Read synchronously inside a planner turn or a chip edit, so it comes from
1536
+ * the files, not git or the planner's last layout: a restored session is
1537
+ * right from its first edit. The run's repos while it isolates; otherwise
1538
+ * the group the workspace holds, in worktrees unless isolation is off or
1539
+ * this run chose to share the root.
1540
+ */
1541
+ skillLayout(): SkillLayout;
1607
1542
  /**
1608
1543
  * Go on with the start a dirty tree turned away. `stash` puts the user's
1609
1544
  * tracked changes on the git stash first, so the run isolates; `shared` runs
@@ -1746,6 +1681,10 @@ declare class IsolationRunController {
1746
1681
  * whole. One that cannot be continued for another reason — it ran from a
1747
1682
  * different workspace path — keeps its integration branch in each repo that
1748
1683
  * has not merged it: only the user gives landed work up.
1684
+ *
1685
+ * False when `interrupted` came while git was minting: a newer activation
1686
+ * may be minting its own run, so this one is never installed over it, and
1687
+ * is discarded rather than left as branches no record names.
1749
1688
  */
1750
1689
  private mint;
1751
1690
  private reportCopies;
@@ -1897,7 +1836,7 @@ interface OrchestratorObserver {
1897
1836
  }): void;
1898
1837
  onTick?(): void;
1899
1838
  onExecutionComplete?(): void;
1900
- /** Queued user messages are ready to be processed by the planner. */
1839
+ /** The run is parked behind queued user messages, with nothing live, until they are drained. */
1901
1840
  onQueueReady?(): void;
1902
1841
  onReviewNeeded?(data: {
1903
1842
  tasks: Task[];
@@ -1931,8 +1870,8 @@ interface OrchestratorObserver {
1931
1870
  }): void;
1932
1871
  }
1933
1872
  /**
1934
- * A message or interrupt a task cannot take: it is not running, it runs on the
1935
- * terminal transport, or it is waiting on something a message does not answer.
1873
+ * A message or interrupt a task cannot take: it is not running, or it is
1874
+ * waiting on something a message does not answer.
1936
1875
  * The request is wrong, not the orchestrator, so a surface says why.
1937
1876
  */
1938
1877
  declare class TaskControlError extends Error {
@@ -1948,17 +1887,18 @@ interface TaskAttemptSnapshot {
1948
1887
  runner: string;
1949
1888
  cwd: string | null;
1950
1889
  startedAt: string;
1890
+ skills: readonly TaskSkillSnapshot[];
1951
1891
  }
1952
1892
  /**
1953
1893
  * The wiring a {@link TaskOrchestrator} runs on, every collaborator resolved.
1954
- * {@link createTaskOrchestrator} is the only production caller; tests build one
1955
- * through that factory too. The constructor makes nothing itself, so there is
1956
- * exactly one place defaults live.
1894
+ * {@link TaskOrchestrator.compose} is the only production caller; tests build
1895
+ * one through it too. The constructor makes nothing itself, so there is exactly
1896
+ * one place defaults live.
1957
1897
  */
1958
1898
  interface TaskOrchestratorDeps {
1959
1899
  config: IConfig;
1960
1900
  notifications: INotification;
1961
- terminalRunner: ITerminalRunner;
1901
+ runner: IRunner;
1962
1902
  store: PlanStore;
1963
1903
  output: TaskOutputSource;
1964
1904
  /** The plan's isolation run and the open run's lifecycle (ADR-0013). */
@@ -1970,30 +1910,27 @@ interface TaskOrchestratorDeps {
1970
1910
  workspaceRoot: () => string;
1971
1911
  /** The workspace's own variables for a task's cwd (ADR-0016). */
1972
1912
  workspaceEnv: (cwd: string) => Promise<WorkspaceEnv>;
1973
- /** Read live at each spawn, so a toggle takes effect on the next task. */
1974
- tddEnabled: () => boolean;
1975
- /** Read once as a run opens, never per spawn (ADR-0018, S1). */
1976
- runnerTransport: () => RunnerTransport;
1913
+ /** The skill catalog over workspace roots, as {@link workspaceSkillRoots} orders them — read at each spawn. */
1914
+ skillsAt: (roots: readonly string[]) => SkillLookup;
1977
1915
  /** What the task's last saved attempt did, or null when it left no log (ADR-0020). */
1978
1916
  previousAttemptFromLog: (taskId: string) => string | null;
1979
1917
  }
1980
1918
  /**
1981
- * What a caller supplies to {@link createTaskOrchestrator}; every optional one
1982
- * gets its default there. This is what a host or test chooses, not the resolved
1983
- * wiring the orchestrator itself runs on.
1919
+ * What a caller supplies to {@link TaskOrchestrator.compose}; every optional
1920
+ * one gets its default there. This is what a host or test chooses, not the
1921
+ * resolved wiring the orchestrator itself runs on.
1984
1922
  */
1985
1923
  interface TaskOrchestratorOptions {
1986
1924
  config: IConfig;
1987
1925
  notifications: INotification;
1988
- terminalRunner: ITerminalRunner;
1926
+ runner: IRunner;
1989
1927
  store?: PlanStore;
1990
1928
  output?: TaskOutputSource;
1991
1929
  isolation?: IWorktreeIsolation;
1992
1930
  registry?: RunnerRegistry | null;
1993
1931
  workspaceRoot?: () => string;
1994
1932
  workspaceEnv?: (cwd: string) => Promise<WorkspaceEnv>;
1995
- tddEnabled?: () => boolean;
1996
- runnerTransport?: () => RunnerTransport;
1933
+ skillsAt?: (roots: readonly string[]) => SkillLookup;
1997
1934
  previousAttemptFromLog?: (taskId: string) => string | null;
1998
1935
  }
1999
1936
  /**
@@ -2002,14 +1939,14 @@ interface TaskOrchestratorOptions {
2002
1939
  * All task-shaped state — the plan tree, the flat index, the completed set
2003
1940
  * — lives in {@link PlanStore}, injected at construction. The orchestrator
2004
1941
  * calls `store.markCompleted(id)` / `store.markFailed(id)` instead of mutating
2005
- * task state directly. A task completes only after the runner emits its
2006
- * per-task completion marker; process exit without that evidence is a visible
2007
- * failure and does not unblock dependent work.
1942
+ * task state directly. A task completes only after the runner reports it done
1943
+ * through `task_complete` on its attempt's token; process exit without that
1944
+ * evidence is a visible failure and does not unblock dependent work.
2008
1945
  */
2009
1946
  declare class TaskOrchestrator {
2010
1947
  private config;
2011
1948
  private notifications;
2012
- private terminalRunner;
1949
+ private runner;
2013
1950
  private store;
2014
1951
  private output;
2015
1952
  private attempts;
@@ -2020,8 +1957,6 @@ declare class TaskOrchestrator {
2020
1957
  private reviewApproved;
2021
1958
  private retryCounts;
2022
1959
  private spawnCounts;
2023
- /** The terminal a verdict left open, by task; see {@link LingeringRunners}. */
2024
- private lingering;
2025
1960
  /**
2026
1961
  * A full-plan run a failure paused. Retrying the failed task is the explicit
2027
1962
  * resume the pause waits for — without this a retry only reset the task to
@@ -2060,20 +1995,13 @@ declare class TaskOrchestrator {
2060
1995
  private registry;
2061
1996
  private workspaceRootFn;
2062
1997
  private observers;
2063
- private tddEnabled;
2064
- private readRunnerTransport;
1998
+ private skillsAt;
2065
1999
  private previousAttemptFromLog;
2066
- /**
2067
- * The setting as the latest run copied it when it opened. Every spawn of
2068
- * that run uses this, so flipping the setting mid-run changes the next run
2069
- * only — the plan, not a live setting, says what runs (ADR-0001).
2070
- */
2071
- private planTransport;
2072
2000
  constructor(deps: TaskOrchestratorDeps);
2073
2001
  /**
2074
2002
  * The composition root: resolves every optional collaborator and wires the
2075
2003
  * isolation listener that the constructor cannot, because it needs the
2076
- * instance's own (private) emit and lingering seams. Nothing else in the
2004
+ * instance's own (private) emit seam. Nothing else in the
2077
2005
  * codebase constructs an orchestrator, so this is the one place defaults
2078
2006
  * live — a host or test supplies what it cares about and gets the rest.
2079
2007
  */
@@ -2082,7 +2010,7 @@ declare class TaskOrchestrator {
2082
2010
  getIdleSince(taskId: string): string | null;
2083
2011
  /**
2084
2012
  * What an ops retry is told about the attempt before. The saved log is read
2085
- * whether or not the session was reloaded, so both behave alike; the terminal
2013
+ * whether or not the session was reloaded, so both behave alike; the output
2086
2014
  * buffer is the fallback for a runner that has no log. A reload clears the
2087
2015
  * attempt count along with the buffer, so a retry made since is what says
2088
2016
  * there was an attempt to report on.
@@ -2119,13 +2047,13 @@ declare class TaskOrchestrator {
2119
2047
  forceSendQueuedTaskMessage(taskId: string, id: string): boolean;
2120
2048
  /** Take back a message still queued behind a turn; false once it was delivered. */
2121
2049
  removeQueuedTaskMessage(taskId: string, id: string): boolean;
2122
- /** Stop a structured task's running turn; it then waits for input like any turn that ends without its marker. */
2050
+ /** Stop a structured task's running turn; it then waits for input like any turn that ends without a `task_complete` call. */
2123
2051
  interruptTask(taskId: string): Promise<void>;
2124
2052
  /** What is waiting for a structured task's turn to end; empty for any other task. */
2125
2053
  getQueuedTaskMessages(taskId: string): QueuedTaskMessage[];
2126
- private structuredSession;
2054
+ private runnerSession;
2127
2055
  /**
2128
- * A structured turn that ended without the done marker (ADR-0018, W1). No
2056
+ * A structured turn that ended without a `task_complete` call (ADR-0018, W1). No
2129
2057
  * verdict is guessed: the task waits for input, unless a checkpoint in the
2130
2058
  * same turn already has it waiting, or a queued message went straight out —
2131
2059
  * the session never passes through idle then, so nothing flickers.
@@ -2151,7 +2079,6 @@ declare class TaskOrchestrator {
2151
2079
  /** Task id → session id of every attempt whose runner is up; a spawn in flight has no session yet. */
2152
2080
  get activeSessionMap(): Map<string, string>;
2153
2081
  getAttempt(taskId: string): TaskAttemptSnapshot | undefined;
2154
- getAttemptSession(taskId: string): ITerminalSession | undefined;
2155
2082
  get queuedCount(): number;
2156
2083
  /**
2157
2084
  * "Merge all"; a settled run that merged everything is cleared up and
@@ -2172,18 +2099,12 @@ declare class TaskOrchestrator {
2172
2099
  * the task has started, and for every other task.
2173
2100
  */
2174
2101
  getMergeGate(taskId: string): string[];
2175
- private watchBlockingPrompts;
2176
- /** The transport the plan's latest run copied from the setting; null before its first run. */
2177
- get runnerTransport(): RunnerTransport | null;
2178
- /** Take over a saved plan's copied transport. The next run copies the setting afresh. */
2179
- adoptRunnerTransport(transport: RunnerTransport | null): void;
2180
- queueMessage(text: string): void;
2102
+ queueMessage(text: string, skills?: readonly SkillLoad[]): void;
2181
2103
  getQueuedMessages(): QueuedMessage[];
2182
2104
  /** Take one unsent message back out of the queue; false when it was never there (or already drained). */
2183
2105
  removeQueuedMessage(id: string): boolean;
2184
2106
  setQueuedMessages(messages: QueuedMessage[]): void;
2185
2107
  clearQueuedMessages(): void;
2186
- processNextQueuedMessage(): QueuedMessage | null;
2187
2108
  loadPlan(tasks: readonly Task[], planRunners?: RunnerId[]): void;
2188
2109
  /**
2189
2110
  * Adopt an edited plan without letting go of the run in progress. Everything
@@ -2224,7 +2145,16 @@ declare class TaskOrchestrator {
2224
2145
  * run up, so neither may happen under a task the plan still shows at work.
2225
2146
  */
2226
2147
  private get settled();
2227
- onUserTaskComplete(taskId: string): Promise<void>;
2148
+ /**
2149
+ * The boundary a verdict crosses: nothing awaits the verifier's listener, so
2150
+ * a throw from reading the output, landing, releasing a worktree or the tick
2151
+ * after would be an unhandled rejection — and an attempt left integrating is
2152
+ * live work forever. One that never settled fails (a repair waits on the
2153
+ * user, as its conflict did); either way the run halts and says why.
2154
+ */
2155
+ private deliverVerdict;
2156
+ /** Work nothing awaits: a rejection is said, not left unhandled. */
2157
+ private unawaited;
2228
2158
  private onVerdict;
2229
2159
  private afterVerdict;
2230
2160
  /**
@@ -2281,7 +2211,6 @@ declare class TaskOrchestrator {
2281
2211
  */
2282
2212
  releaseTask(taskId: string): Promise<void>;
2283
2213
  markTaskComplete(taskId: string): Promise<void>;
2284
- markAiTaskComplete(taskId: string): Promise<void>;
2285
2214
  /**
2286
2215
  * Undo a completion: return the task to "not executed" — pending, verdict and
2287
2216
  * summary dropped, archive entry removed. It is put on hold like a cancel, so
@@ -2328,16 +2257,9 @@ declare class TaskOrchestrator {
2328
2257
  isAllComplete(): boolean;
2329
2258
  isAnyFailed(): boolean;
2330
2259
  approveReview(): Promise<void>;
2331
- getPlanVisualization(): {
2332
- tasks: {
2333
- id: string;
2334
- title: string;
2335
- dependencies: string[];
2336
- parallelGroups: number[][];
2337
- }[];
2338
- parallelGroups: number[][];
2339
- };
2340
2260
  tick(): Promise<void>;
2261
+ /** Whether the scheduler may start `id` now: it passes every readiness gate and a slot is free. */
2262
+ private stillReady;
2341
2263
  /**
2342
2264
  * A run paused at merge gates says so, as plainly as a task that waits on
2343
2265
  * the user: which tasks wait, and that Merge all lets them go on (ADR-0020).
@@ -2351,13 +2273,14 @@ declare class TaskOrchestrator {
2351
2273
  * unwound (both leave `startTask` with nothing further to announce).
2352
2274
  */
2353
2275
  private spawnAttempt;
2276
+ /** For a skill the main checkout has, the folder a worktree only gets once it is committed. */
2277
+ private uncommittedSkillFolder;
2354
2278
  /**
2355
- * Say on the task how this attempt is driven, when its plan asked for the
2356
- * structured transport; a terminal plan's tasks record nothing. A structured
2357
- * request that came back a terminal session is a fallback, and says why —
2358
- * a host without a router included — never a silent downgrade.
2279
+ * The roots whose `.ordewell/skills/` an attempt at `cwd` reads: in a repo
2280
+ * group, `cwd` holds each repo — a worktree of it when isolated, the
2281
+ * workspace's own checkout when not — and no folder of its own (ADR-0014).
2359
2282
  */
2360
- private recordTransport;
2283
+ private skillRoots;
2361
2284
  /**
2362
2285
  * A continue whose runner never announced the session it was told to
2363
2286
  * resume. Nothing was continued, and nothing fresh was started in its place.
@@ -2383,24 +2306,17 @@ declare class TaskOrchestrator {
2383
2306
  * it receives for an attempt that is no longer current).
2384
2307
  *
2385
2308
  * A user interruption also bumps the verifier's generation *before* stopping
2386
- * the runner: some runners (e.g. tmux) fire onExit synchronously from
2309
+ * the runner: a runner may fire onExit synchronously from
2387
2310
  * stop(), and if that exit reaches VerdictEngine under the still-valid
2388
2311
  * generation it delivers a verdict that marks the task 'completed' for one
2389
- * tick — long enough for the scheduler to start a dependent task. A verdict
2390
- * leaves a terminal runner up so its screen stays readable (a structured one
2391
- * ends), and stop/load reset the whole verifier themselves.
2312
+ * tick — long enough for the scheduler to start a dependent task. Stop and
2313
+ * load reset the whole verifier themselves.
2392
2314
  */
2393
2315
  private endAttempt;
2394
2316
  /** Kept on the task once the attempt ends, so a continue can resume it after a reload (ADR-0018, K1). */
2395
2317
  private saveNativeSession;
2396
2318
  private endAllAttempts;
2397
2319
  }
2398
- /**
2399
- * Build a {@link TaskOrchestrator}, filling in every collaborator a caller did
2400
- * not inject. The one entry point for hosts, bench harnesses and tests alike;
2401
- * {@link TaskOrchestrator} itself constructs nothing.
2402
- */
2403
- declare function createTaskOrchestrator(options: TaskOrchestratorOptions): TaskOrchestrator;
2404
2320
 
2405
2321
  /**
2406
2322
  * Everything needed to produce a plan from a goal. Carries the planning context
@@ -2417,9 +2333,13 @@ interface PlanRequest {
2417
2333
  autonomousDefault?: boolean;
2418
2334
  signal?: AbortSignal;
2419
2335
  perRunnerAllowlist?: Partial<Record<RunnerId, string[]>>;
2420
- /** The mode toggles this run honours. `modesFor('one-shot', …)` decides which apply. */
2421
2336
  modes?: PlannerModes;
2422
2337
  }
2338
+ /** A generated plan, and what its skill check warned about. */
2339
+ interface GeneratedPlan extends LegacyPlanState {
2340
+ /** Names not found yet, or no skill could have; the plan still lands. Absent means none, or not checked. */
2341
+ skillWarnings?: string[];
2342
+ }
2423
2343
  interface ModifyPlanRequest {
2424
2344
  existingPlan: LegacyPlanState;
2425
2345
  userRequest: string;
@@ -2444,10 +2364,14 @@ interface ModifyDuringExecutionRequest {
2444
2364
  perRunnerAllowlist?: Partial<Record<RunnerId, string[]>>;
2445
2365
  /** Where the run in force puts each task (ADR-0013, ADR-0014). */
2446
2366
  isolatedExecution?: IsolatedExecution;
2367
+ /** The workspace's skill catalog under its repo group's layout, which the rewritten tasks' skills are checked against. */
2368
+ skills: SkillLookup;
2447
2369
  }
2448
2370
  interface ModifyDuringExecutionResult {
2449
2371
  pendingTasks: Task[];
2450
2372
  message: string;
2373
+ /** Skill names not found yet; the edit still lands, as a submitted plan's does. Absent means none. */
2374
+ skillWarnings?: string[];
2451
2375
  }
2452
2376
  /**
2453
2377
  * Owns one-shot plan generation and plan modification — the "planning is a
@@ -2466,8 +2390,12 @@ declare class Planner {
2466
2390
  private readonly resolveAiService;
2467
2391
  constructor(config: IConfig, aiService: IAiService | (() => IAiService));
2468
2392
  private get aiService();
2469
- /** One-shot plan generation for non-conversational surfaces. Never asks questions. */
2470
- generate(req: PlanRequest): Promise<LegacyPlanState>;
2393
+ /**
2394
+ * One-shot plan generation for non-conversational surfaces. Never asks
2395
+ * questions. A plan attaching a planner skill to a task is refused, not
2396
+ * returned: there is no conversation to send it back through.
2397
+ */
2398
+ generate(req: PlanRequest): Promise<GeneratedPlan>;
2471
2399
  modify(req: ModifyPlanRequest): Promise<{
2472
2400
  tasks: Task[];
2473
2401
  }>;
@@ -2563,9 +2491,7 @@ declare class RunnerInstallation {
2563
2491
  * typing a real goal is the failure this exists to prevent.
2564
2492
  *
2565
2493
  * "Usable" is deliberately shallow: the binary answers `--version`, and the
2566
- * runner has a connector to plan over. Plugin runners have none yet: a
2567
- * manifest-level planner capability, so a plugin can declare itself
2568
- * plannable, is a follow-up. Whether the user's subscription is
2494
+ * runner has a connector to plan over. Whether the user's subscription is
2569
2495
  * live cannot be known without spending a turn on it, so an expired login
2570
2496
  * surfaces where it actually bites — as the agent's own error text on the
2571
2497
  * first turn.
@@ -2641,6 +2567,37 @@ declare function isCliProvider(id: AiProvider): boolean;
2641
2567
  declare function runnerForProvider(id: AiProvider): string | null;
2642
2568
  /** The harness planner that wraps a given runner, or null when none does. */
2643
2569
  declare function providerForRunner(runner: string): AiProvider | null;
2570
+ /** One runner's planner usability, as `RunnerInstallation.plannerUsability` reports it. */
2571
+ interface PlannerUsability {
2572
+ usable: boolean;
2573
+ reason?: string;
2574
+ }
2575
+ /**
2576
+ * One entry of the planner backend list (ADR-0009). `id` is the `AiProvider`
2577
+ * either way; `runner` is set only for a harness planner, whose model catalog
2578
+ * comes from that runner, and `reason` is the second line a surface renders —
2579
+ * the block detail for a harness planner, the API-key variable for a vendor.
2580
+ */
2581
+ interface PlannerBackendEntry {
2582
+ id: AiProvider;
2583
+ label: string;
2584
+ kind: 'harness' | 'vendor';
2585
+ runner?: string;
2586
+ usable: boolean;
2587
+ reason?: string;
2588
+ }
2589
+ /** The detail a usable harness planner is always shown with. */
2590
+ declare const HARNESS_PLANNER_REASON = "coding agent \u00B7 no API key needed";
2591
+ /**
2592
+ * The planner backends every surface offers, in order: the harness planners
2593
+ * first, always listed so a missing CLI is greyed with its reason rather than
2594
+ * hidden, then the configured vendors in `PROVIDER_PRIORITY` order. Pure: the
2595
+ * caller supplies what it knows of each runner (VS Code its `plannerUsability`
2596
+ * results, the TUI its installed set) and maps the entries to its own item
2597
+ * type, keeping surface-specific affordances — e.g. the CLI's empty-vendor
2598
+ * placeholder — to itself.
2599
+ */
2600
+ declare function plannerBackendEntries(usability: Readonly<Record<string, PlannerUsability>>, configured: readonly AiProvider[]): PlannerBackendEntry[];
2644
2601
  /**
2645
2602
  * The single definition of which providers count as "configured" — one API
2646
2603
  * key each, or (for `openai_compatible`) an explicit base URL. Every surface
@@ -2719,7 +2676,7 @@ interface EnvAdmission {
2719
2676
  */
2720
2677
  declare function admitSettingsEnv(env: Record<string, unknown>): EnvAdmission;
2721
2678
 
2722
- /** Whether a runner's tasks can run on the structured transport (ADR-0018, S3). */
2679
+ /** Whether Ordewell can run a runner's tasks (ADR-0018, S3). */
2723
2680
  declare function supportsTaskMode(runner: string): boolean;
2724
2681
  /** The adapter that drives one task. Throws {@link TaskModeUnsupportedError} for a runner without a connector. */
2725
2682
  declare function createTaskAdapter(runner: string, deps: AgentProcessDeps): TaskModeAgentAdapter;
@@ -2740,8 +2697,8 @@ interface SpawnSpec {
2740
2697
  *
2741
2698
  * Subclasses own their agent's protocol: what to spawn, how to phrase a user
2742
2699
  * turn, and how to read one protocol line. Everything below — line framing,
2743
- * turn lifecycle, abort, stderr capture, premature-exit detection — is the
2744
- * same for all of them.
2700
+ * turn lifecycle, abort — is the same for all of them, and the process itself
2701
+ * is a {@link RunnerProcess}.
2745
2702
  */
2746
2703
  declare abstract class StdioAgentAdapter implements AgentAdapter {
2747
2704
  protected deps: AgentProcessDeps;
@@ -2749,8 +2706,7 @@ declare abstract class StdioAgentAdapter implements AgentAdapter {
2749
2706
  protected process: ChildProcess | null;
2750
2707
  protected sessionId: string | null;
2751
2708
  private readonly stdout;
2752
- private stderrTail;
2753
- private exited;
2709
+ private readonly runner;
2754
2710
  /** Set for the duration of a turn. Outside one, events are buffered rather than dropped. */
2755
2711
  private turnEmit;
2756
2712
  /** Set for the duration of a turn. Pinged on every raw line, whether or not `handleLine` turns it into an event. */
@@ -2772,11 +2728,6 @@ declare abstract class StdioAgentAdapter implements AgentAdapter {
2772
2728
  /** A task's session, which wants what the agent says on its own after a turn closed; a planner registers none and drops it. */
2773
2729
  private outOfTurnListener;
2774
2730
  private disposed;
2775
- /** Resolves when the process ends, so a handshake can lose the race instead of waiting out its timeout. */
2776
- protected processEnded: Promise<void>;
2777
- /** The environment the agent was spawned under, for any side process a handshake needs. */
2778
- protected spawnEnv: NodeJS.ProcessEnv;
2779
- private markEnded;
2780
2731
  /** What this process is to the user — a planner or a task's runner — for the words a failure is reported in. */
2781
2732
  protected role: 'planner' | 'task';
2782
2733
  constructor(deps: AgentProcessDeps);
@@ -2792,6 +2743,17 @@ declare abstract class StdioAgentAdapter implements AgentAdapter {
2792
2743
  /** Protocol handshake, if the agent needs one before it accepts a turn. */
2793
2744
  protected handshake(_opts: AgentStartOptions): Promise<void>;
2794
2745
  start(opts: AgentStartOptions): Promise<void>;
2746
+ /**
2747
+ * Every protocol line goes to `handleLine`, turn or no turn: an agent's
2748
+ * handshake and its session-id announcement both arrive before the first
2749
+ * message is sent, and dropping them left the handshake waiting on a reply
2750
+ * that had already come and gone.
2751
+ */
2752
+ private readProtocol;
2753
+ /** Resolves when the process ends, so a handshake can lose the race instead of waiting out its timeout. */
2754
+ protected get processEnded(): Promise<void>;
2755
+ /** The environment the agent was spawned under, for any side process a handshake needs. */
2756
+ protected get spawnEnv(): NodeJS.ProcessEnv;
2795
2757
  send(message: string, onEvent: (event: AgentEvent) => void, signal?: AbortSignal, onActivity?: () => void): Promise<void>;
2796
2758
  onOutOfTurn(listener: (event: AgentEvent) => void): void;
2797
2759
  nativeSessionId(): string | null;
@@ -2839,7 +2801,7 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
2839
2801
  * A task's turn whose `result` arrived while background work was still open.
2840
2802
  * The CLI reports that result when the model stops talking, then opens a turn
2841
2803
  * of its own when the work finishes; a turn ended at the first result would
2842
- * lose everything said after it, the completion marker included.
2804
+ * lose everything done after it, the `task_complete` call included.
2843
2805
  */
2844
2806
  private resultHeld;
2845
2807
  private followOnTimer;
@@ -2859,8 +2821,7 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
2859
2821
  /**
2860
2822
  * A task's run: the manifest decides what its mode and effort mean
2861
2823
  * (ADR-0001), and this adds only the protocol around them. No tool list and
2862
- * no system prompt — the task's prompt is its first turn, as on the terminal
2863
- * transport. `--permission-prompt-tool stdio` routes the questions the mode
2824
+ * no system prompt — the task's prompt is its first turn. `--permission-prompt-tool stdio` routes the questions the mode
2864
2825
  * leaves open to the control channel, where the adapter must answer them;
2865
2826
  * without it `-p` refuses them silently and nothing can ever surface one.
2866
2827
  */
@@ -2868,7 +2829,6 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
2868
2829
  /** The file holding the token lives exactly as long as the process that reads it. */
2869
2830
  private ordewellServerArgs;
2870
2831
  private removeMcpConfig;
2871
- start(opts: AgentStartOptions): Promise<void>;
2872
2832
  /** Asks the CLI itself, which reports `pending` until its connection attempt settles. */
2873
2833
  mcpAttached(): Promise<boolean>;
2874
2834
  /**
@@ -2974,16 +2934,12 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
2974
2934
  private threadId;
2975
2935
  /** The model Codex opened the thread with — usage records name it. */
2976
2936
  private threadModel;
2977
- private nextRequestId;
2978
- private settleHandshake;
2979
- private handshakeError;
2937
+ private readonly rpc;
2980
2938
  private startOpts;
2981
2939
  /** Whether this turn has already emitted prose — see the `agentMessage` case. */
2982
2940
  private turnHasText;
2983
2941
  /** The turn in flight. `turn/interrupt` and `turn/steer` cannot be sent before it has an id. */
2984
2942
  private turn;
2985
- /** The JSON-RPC id of the `turn/start` that opened {@link turn}. */
2986
- private turnRequestId;
2987
2943
  /** The last turn this adapter ended — so its second end signal, landing late, ends nothing. */
2988
2944
  private endedTurnId;
2989
2945
  /** An interrupt was asked for during this turn, so however it ends, it was cut short. */
@@ -2994,8 +2950,6 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
2994
2950
  * that belongs to no turn of the agent's any more (ADR-0023, F4).
2995
2951
  */
2996
2952
  private readonly interruptedTurnIds;
2997
- /** An interrupt asked for before Codex named the turn, sent once it does. */
2998
- private interruptOnStart;
2999
2953
  /** A task's approval requests still waiting for an answer, by the request id as a string. */
3000
2954
  private readonly openPermissions;
3001
2955
  /**
@@ -3005,10 +2959,6 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
3005
2959
  private readonly fileChangePaths;
3006
2960
  /** The row each announced file of an in-flight `fileChange` item is shown under, by the file's shown path. */
3007
2961
  private readonly fileChangeRows;
3008
- /** Requests this adapter sent and awaits an answer to, by JSON-RPC id. */
3009
- private readonly pendingRequests;
3010
- private resumeAttempted;
3011
- private resumeFallbackSent;
3012
2962
  private sandbox;
3013
2963
  /** The Ordewell server's startup state, as Codex last reported it for this thread. */
3014
2964
  private mcpStartup;
@@ -3019,6 +2969,7 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
3019
2969
  * one stream; the thread id is what tells them apart (see {@link subagentOf}).
3020
2970
  */
3021
2971
  private readonly subagents;
2972
+ constructor(deps: AgentProcessDeps);
3022
2973
  protected spawnSpec(opts: AgentStartOptions): SpawnSpec;
3023
2974
  /**
3024
2975
  * `initialize`, then `thread/start`, both before the first user message. A
@@ -3028,16 +2979,18 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
3028
2979
  * construction. A task's runs as its mode says (see {@link threadParams}).
3029
2980
  */
3030
2981
  protected handshake(opts: AgentStartOptions): Promise<void>;
2982
+ private handshakeIncomplete;
3031
2983
  /**
3032
2984
  * Open this session's thread. A resume id means the previous
3033
2985
  * process died mid-session: `thread/resume` puts the agent back in front of
3034
2986
  * the context it already paid to read. A planner's failed resume is not an
3035
- * error — the response handler falls back to a fresh thread, which is the
3036
- * same degradation `restoreChat` performs on every surface (T4). A task's
3037
- * is: Continue (K1) offers the session that did the work, and a fresh thread
3038
- * in its place would pretend it was resumed.
2987
+ * error — it falls back to a fresh thread, which is the same degradation
2988
+ * `restoreChat` performs on every surface (T4). A task's is: Continue (K1)
2989
+ * offers the session that did the work, and a fresh thread in its place
2990
+ * would pretend it was resumed.
3039
2991
  */
3040
- private startThread;
2992
+ private openThread;
2993
+ private requestThread;
3041
2994
  private threadParams;
3042
2995
  /**
3043
2996
  * Codex's own startup report for the server, which it sends once the thread
@@ -3048,10 +3001,12 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
3048
3001
  private effort;
3049
3002
  /**
3050
3003
  * `turn/interrupt` names the turn as well as the thread, so an interrupt
3051
- * asked for before Codex has named the turn waits for it. Codex acknowledges
3052
- * with an empty result, then ends the turn as `interrupted`.
3004
+ * asked for before Codex has named the turn waits for it, as a steer does.
3005
+ * Codex acknowledges with an empty result, then ends the turn as `interrupted`.
3053
3006
  */
3054
3007
  interrupt(timeoutMs: number): Promise<boolean>;
3008
+ /** Run `send` now if Codex has named the turn, and once it does if not. */
3009
+ private whenNamed;
3055
3010
  /**
3056
3011
  * `turn/steer` into the running turn (ADR-0023). Codex answers with the
3057
3012
  * turn's id at once — acceptance, not delivery — and shows the input to the
@@ -3070,6 +3025,8 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
3070
3025
  */
3071
3026
  answerPermission(id: string, decision: ApprovalDecision): boolean;
3072
3027
  protected turnPayload(message: string): string;
3028
+ /** `turn/start`'s answer. One for a turn that has since closed or been replaced names and closes nothing. */
3029
+ private turnAnswered;
3073
3030
  protected handleLine(line: string, emit: (event: AgentEvent) => void): void;
3074
3031
  private turnStarted;
3075
3032
  /**
@@ -3148,8 +3105,8 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
3148
3105
  * (ADR-0009, ADR-0018).
3149
3106
  *
3150
3107
  * The odd one out: `opencode serve` is a real server rather than a stdio
3151
- * protocol, so this adapter owns both halves of the boundary — it spawns the
3152
- * process through the same injected `spawn` every other adapter uses, then
3108
+ * protocol, so this adapter owns both halves of the boundary — it starts the
3109
+ * process as the same {@link RunnerProcess} every other adapter uses, then
3153
3110
  * talks to it through the injected `fetch`. Both are part of the one seam the
3154
3111
  * tests drive.
3155
3112
  *
@@ -3169,12 +3126,8 @@ declare class OpenCodeAdapter implements TaskModeAgentAdapter {
3169
3126
  /** Sent on every request to a task's server, which a per-task password secures. */
3170
3127
  private authorization;
3171
3128
  private sessionId;
3172
- private stderrTail;
3173
- private exited;
3174
- private exitCode;
3129
+ private readonly runner;
3175
3130
  private disposed;
3176
- private markEnded;
3177
- private readonly processEnded;
3178
3131
  private planner;
3179
3132
  private task;
3180
3133
  /** The last assistant message already settled — the baseline {@link recoverReply} measures a new reply against. */
@@ -3190,7 +3143,19 @@ declare class OpenCodeAdapter implements TaskModeAgentAdapter {
3190
3143
  /** The Ordewell server this process was configured with; null when none was given or its config could not be merged. */
3191
3144
  private ordewell;
3192
3145
  constructor(deps: AgentProcessDeps);
3146
+ private get processEnded();
3147
+ private get exited();
3193
3148
  start(opts: AgentStartOptions): Promise<void>;
3149
+ private launch;
3150
+ /**
3151
+ * Where `serve` listens, or null once it ends or gives up. Stdout only: that
3152
+ * is where `serve` prints its address, and stderr can carry another URL — a
3153
+ * warning about a provider it could not reach. Read a line at a time, and
3154
+ * only until the address is known.
3155
+ */
3156
+ private serverAddress;
3157
+ /** From the server's address to an open session — the 2.x API's, or a 1.x one created or resumed. */
3158
+ private connect;
3194
3159
  send(message: string, onEvent: (event: AgentEvent) => void, signal?: AbortSignal, onActivity?: () => void): Promise<void>;
3195
3160
  /**
3196
3161
  * One task turn. `prompt_async` only queues the work, so the turn is over
@@ -3219,8 +3184,8 @@ declare class OpenCodeAdapter implements TaskModeAgentAdapter {
3219
3184
  /**
3220
3185
  * Whatever of the turn's own messages the stream missed. Each part is
3221
3186
  * emitted once, so a turn the stream saw whole adds nothing here; one that
3222
- * lost its last text part still reaches the plain-text channel, where the
3223
- * done marker is looked for.
3187
+ * lost its last text part still reaches the plain-text channel, which a
3188
+ * summary falls back to.
3224
3189
  */
3225
3190
  private readBack;
3226
3191
  /**
@@ -3340,12 +3305,191 @@ declare function mapAgentTool(name: string): MappedTool;
3340
3305
  */
3341
3306
  declare function normalizeAgentArgs(tool: ResearchToolType, args: Record<string, unknown>): Record<string, unknown>;
3342
3307
 
3308
+ /** What a runner offers a task: the models discovered for it and the modes its manifest declares. */
3309
+ interface RunnerCatalog {
3310
+ models: DiscoveredModel[];
3311
+ modes: RunnerModeInfo[];
3312
+ /**
3313
+ * The mode a task lands on when it carries none this runner offers: the
3314
+ * manifest's `autonomous`/`safe`-tagged mode under the user's toggle
3315
+ * (ADR-0001). Absent, `modes[0]` stands in — which for Codex is its
3316
+ * workspace-write sandbox, not the full access an unattended run needs.
3317
+ */
3318
+ defaultMode?: string;
3319
+ }
3320
+ /**
3321
+ * The model, thinking effort and mode a runner offers for a task.
3322
+ *
3323
+ * A task's model, effort and mode are all scoped to its runner:
3324
+ * `claude-sonnet-4-5` on Codex, or `acceptEdits` on OpenCode, are not degraded
3325
+ * choices but unspawnable ones. So this is where any task acquires a runnable
3326
+ * assignment — a runner change (below) and a hand-added task derive it the same
3327
+ * way. Each field in `current` is preserved when the runner also offers it, and
3328
+ * otherwise snapped to that runner's preferred entry (discovery already sorts
3329
+ * models by the manifest's `preferredPatterns`; the mode is the catalog's
3330
+ * `defaultMode`).
3331
+ *
3332
+ * An empty catalog means discovery failed — not that the runner offers nothing. That field is left out of the
3333
+ * patch and the runner validates last, matching `coerceAssignments`.
3334
+ */
3335
+ declare function runnerAssignment(catalog: RunnerCatalog, current?: Pick<Task, 'assignedModel' | 'taskMode'>): Partial<Task>;
3336
+ /**
3337
+ * Move a task onto a different runner, carrying its model, thinking effort and
3338
+ * mode over to values that runner actually offers. A runner change is never a
3339
+ * single-field edit — it either brings the other three with it or leaves the
3340
+ * task unrunnable.
3341
+ *
3342
+ * Returns the patch to apply, or `{}` when there is nothing to change.
3343
+ */
3344
+ declare function retargetTaskRunner(task: Task, runner: RunnerId, catalog: RunnerCatalog): Partial<Task>;
3345
+
3346
+ type ModelsByRunner = Partial<Record<RunnerId, DiscoveredModel[]>>;
3347
+ interface SessionCatalogDeps {
3348
+ config: Pick<IConfig, 'enabledRunners' | 'autonomousMode'>;
3349
+ registry: Pick<RunnerRegistry, 'getManifest'>;
3350
+ modelResolver: Pick<ModelResolver, 'modelsForRunners' | 'getCachedRunnerModels'>;
3351
+ /** Read at each call: a toggle or allowlist edit made since the session was built counts (#69). */
3352
+ settings: () => {
3353
+ enabledRunners?: RunnerId[];
3354
+ modelAllowlist?: Record<string, string[]>;
3355
+ };
3356
+ /** The runners of the plan being edited; empty while the session has none. */
3357
+ planRunners: () => RunnerId[];
3358
+ }
3359
+ /**
3360
+ * What a session may assign: the enabled runners, their modes, the models
3361
+ * discovery found for them and the allowlist narrowing those. Every view of
3362
+ * it — the planner's per-turn block, its tools, a user's direct edit — reads
3363
+ * it here, so none can be shown or checked against something the others are not.
3364
+ *
3365
+ * The discovered models are remembered per runner and only ever merged into:
3366
+ * discovering one runner must not forget another's, since coercion later clamps
3367
+ * thinking efforts against whatever was last found.
3368
+ */
3369
+ declare class SessionCatalog {
3370
+ private readonly deps;
3371
+ private discovered;
3372
+ constructor(deps: SessionCatalogDeps);
3373
+ /** The runners enabled right now. */
3374
+ enabledRunners(): RunnerId[];
3375
+ /**
3376
+ * The allowlist in force right now. Unset means no restriction — falling
3377
+ * back to the one planning started under would keep a restriction the user
3378
+ * has since cleared.
3379
+ */
3380
+ allowlist(): Record<string, string[]>;
3381
+ /** The models the user allows on one runner; unset means no restriction. */
3382
+ allowlistFor(runner: RunnerId): string[] | undefined;
3383
+ /**
3384
+ * The discovered catalog as the resolver holds it now, per runner, with this
3385
+ * session's own discovery as the fallback where the resolver has nothing
3386
+ * cached. The resolver's cache outlives this session's snapshot in both
3387
+ * directions — an allowlist picker re-discovers into it mid-session — and a
3388
+ * model allowed after that must reach the planner with its real label and
3389
+ * variants, not as an id-only stub. Never triggers discovery itself.
3390
+ */
3391
+ models(): ModelsByRunner;
3392
+ /** What this session's own discovery found for a runner; empty when it never ran there. */
3393
+ known(runner: RunnerId): DiscoveredModel[];
3394
+ /** Discover the runners' models, spawning their CLIs where the resolver has none cached, and remember them. */
3395
+ discover(runners: RunnerId[]): Promise<ModelsByRunner>;
3396
+ /**
3397
+ * Keep what an admitted runner was found to offer, for the next coercion's
3398
+ * effort clamping. An empty list is discovery having found nothing, not a
3399
+ * runner offering nothing, so it never displaces what is known.
3400
+ */
3401
+ admit(runner: RunnerId, models: DiscoveredModel[]): void;
3402
+ /** Session boundaries are hard: nothing discovered for one goal informs the next. */
3403
+ reset(): void;
3404
+ modes(runners: RunnerId[]): Record<RunnerId, RunnerModeInfo[]>;
3405
+ /**
3406
+ * What a planner may assign right now: every enabled runner, its modes, and
3407
+ * its allowlisted models. Discovers a runner's models the first time it is
3408
+ * asked for.
3409
+ */
3410
+ live(): Promise<TaskQueryCatalog>;
3411
+ queryCatalog(runners: RunnerId[]): TaskQueryCatalog;
3412
+ /**
3413
+ * The catalog a model/task-mode edit is checked against — the same
3414
+ * discovered models and manifest modes the per-turn catalog block shows the
3415
+ * planner, so a refusal here can never name something invalid that the
3416
+ * planner was never told about. The catalog is not filtered by the
3417
+ * allowlist here — `checkModelAndModeValidity` narrows by allowlist itself,
3418
+ * the same way `coerceAssignments` does.
3419
+ */
3420
+ edit(runners?: RunnerId[]): EditCatalog;
3421
+ /** What a runner offers, as `runnerAssignment` needs it. Spawns the runner's CLI to list models. */
3422
+ runner(runner: RunnerId): Promise<RunnerCatalog>;
3423
+ /**
3424
+ * The one place planning discovers what it may draw from: the runners'
3425
+ * models and their modes. `filteredModels` is the allowlisted view a prompt
3426
+ * may show; `modelsByRunner` stays whole because coercion needs real labels
3427
+ * and variants.
3428
+ */
3429
+ planning(runners: RunnerId[]): Promise<{
3430
+ modelsByRunner: Partial<Record<string, DiscoveredModel[]>>;
3431
+ filteredModels: Partial<Record<string, DiscoveredModel[]>>;
3432
+ runnerModes: Record<string, RunnerModeInfo[]>;
3433
+ }>;
3434
+ }
3435
+
3343
3436
  /** The dialogue record a fork carries — what {@link PlannerConversation.clone} returns. */
3344
3437
  interface ForkedDialogue {
3345
3438
  conversationHistory: ConversationMessage[];
3346
3439
  researchLog: ResearchLogEntry[];
3347
3440
  }
3348
3441
 
3442
+ /** A user's message as sent, and the skills its `/name` tokens load. */
3443
+ interface SkillInvocation {
3444
+ text: string;
3445
+ skills: SkillLoad[];
3446
+ }
3447
+ /**
3448
+ * A path with the home directory written `~`, for a notice or listing. On
3449
+ * Windows the drive letter's case is not significant and the separators are
3450
+ * rendered `/`, so the same skill reads alike wherever the session reloads.
3451
+ */
3452
+ declare function abbreviateHome(file: string, home?: string, platform?: NodeJS.Platform): string;
3453
+ /**
3454
+ * Resolve the `/skill-name` tokens in a message to snapshots of the skills they
3455
+ * name. Pure and exported so surfaces can resolve without a full Session. The text itself is never rewritten.
3456
+ *
3457
+ * Every distinct skill named loads, once, in the order first named. A token
3458
+ * naming no skill is plain text, wherever it stands — a whole message of
3459
+ * `/unknown` included: embedded in a sentence it is as likely to be incidental
3460
+ * text (a path, an example command) as a typo'd invocation. So is one naming
3461
+ * a skill with `user-invocable: false`, which only the model may invoke.
3462
+ */
3463
+ declare function resolveSkillInvocation(text: string, skillsService: Pick<SkillsService, 'findSkill'>, home?: string): SkillInvocation;
3464
+ /**
3465
+ * What the planner receives for a user message: the bodies of the skills it
3466
+ * loaded, then the message as the user wrote it. The one composition for a
3467
+ * live send and for a replay of the transcript, so a resumed conversation
3468
+ * hands the planner what the live one did.
3469
+ */
3470
+ declare function plannerMessage(text: string, loads: readonly SkillLoad[]): string;
3471
+ /**
3472
+ * The transcript as the planner is to see it on a replay: each user message
3473
+ * carries the skills loaded after it, composed by {@link plannerMessage}, and
3474
+ * the load entries themselves are folded away. Sessions saved before skill
3475
+ * loads existed hold the expanded text in the message and replay unchanged.
3476
+ */
3477
+ declare function plannerTranscript(history: readonly ConversationMessage[]): ConversationMessage[];
3478
+
3479
+ /**
3480
+ * The planner turn (see CONTEXT.md): the one piece of planner work that holds
3481
+ * the conversation, from its claim to its settled outcome. Its signal is what a
3482
+ * stop aborts.
3483
+ */
3484
+ declare class PlannerTurn {
3485
+ private readonly controller;
3486
+ private dropped;
3487
+ get signal(): AbortSignal;
3488
+ /** The plan it was answering was dropped under it, so nothing it settles may land. */
3489
+ get abandoned(): boolean;
3490
+ stop(): void;
3491
+ abandon(): void;
3492
+ }
3349
3493
  /** A read made through a planner tool: refused once the turn's budget is gone, else answered, told to land when the budget asks it to. */
3350
3494
  type ToolRead<T> = {
3351
3495
  status: 'refused';
@@ -3386,7 +3530,7 @@ interface PlannerConversationHost {
3386
3530
  * conversation never reaches into execution state directly.
3387
3531
  */
3388
3532
  liveOutput: LiveOutputLookup;
3389
- hasLiveWork(): boolean;
3533
+ isExecuting(): boolean;
3390
3534
  /** The session's single mutation ritual: op → persist → notify (default: the plan). */
3391
3535
  mutate(op: () => boolean, notify?: () => void): LegacyPlanState | null;
3392
3536
  broadcast: SessionBroadcaster;
@@ -3394,13 +3538,19 @@ interface PlannerConversationHost {
3394
3538
  broadcastPlan(turnId?: string): void;
3395
3539
  /** Validate a batch against live state. Pure — nothing is applied. */
3396
3540
  validateOps(ops: TaskOp[]): ApplyTaskOpsResult;
3541
+ /** The workspace's skill catalog under its repo group's layout, which the skills a plan or edit attaches are checked against. */
3542
+ taskSkills(): SkillLookup;
3543
+ /** Tell the user something the transcript does not carry. */
3544
+ notice(level: SessionNotice['level'], message: string): void;
3397
3545
  /** Load planner-produced tasks: an edit keeps run state, a commit starts over. Returns how many landed. */
3398
3546
  adoptTasks(tasks: readonly Task[], how: 'edit' | 'commit'): number;
3399
3547
  capturePrd(text: string): void;
3400
3548
  /** Park a structural edit until the next batch boundary. Returns the queue length. */
3401
- queueEdit(userMessage: string): number;
3549
+ queueEdit(message: SkillInvocation): number;
3402
3550
  /** Wake a scheduler an applied edit may have unblocked. */
3403
3551
  afterEdit(): Promise<void>;
3552
+ /** The planner turn was stopped or abandoned: whatever it is waiting on (a parked approval) will never be answered. */
3553
+ turnAborted(): void;
3404
3554
  }
3405
3555
  /**
3406
3556
  * A conversation edit (rewind, fork) the conversation refused because the
@@ -3419,6 +3569,25 @@ declare class ConversationEditError extends Error {
3419
3569
  declare class ConversationBusyError extends ConversationEditError {
3420
3570
  constructor(operation: string);
3421
3571
  }
3572
+ /**
3573
+ * A planner turn that settled after the plan it was answering was dropped (a
3574
+ * new plan, another session adopted, the session closed), or after its call
3575
+ * was cut off by something other than its own stop. Nothing it produced was
3576
+ * committed: landing it would write one session's reply into another's
3577
+ * transcript.
3578
+ */
3579
+ declare class PlannerTurnDiscardedError extends Error {
3580
+ constructor(options?: ErrorOptions);
3581
+ }
3582
+ /**
3583
+ * The user stopped the turn and the backend reported that as a thrown error
3584
+ * rather than a settled reply. Typed so a surface can stay quiet for it without
3585
+ * reading the message: some SDKs' abort errors are plain `Error`s by name, and
3586
+ * a real failure can mention "aborted" too.
3587
+ */
3588
+ declare class PlannerTurnStoppedError extends Error {
3589
+ constructor(options?: ErrorOptions);
3590
+ }
3422
3591
  /** A user message a rewind can fork the conversation from just before. */
3423
3592
  interface RewindTarget {
3424
3593
  /** Position in the transcript — what {@link PlannerConversation.cloneBefore} takes. */
@@ -3449,8 +3618,6 @@ interface TranscriptSnapshot {
3449
3618
  }
3450
3619
  interface ReplyOptions {
3451
3620
  signal?: AbortSignal;
3452
- /** The user's words before skill expansion — what a mid-run queue replays later. */
3453
- verbatim?: string;
3454
3621
  }
3455
3622
  /**
3456
3623
  * The planner conversation (ADR-0002), end to end: the persisted dialogue
@@ -3474,15 +3641,35 @@ declare class PlannerConversation {
3474
3641
  private persisted;
3475
3642
  /** Saves an execution event made while a turn may be in flight; see {@link restore}. */
3476
3643
  private savedInBackground;
3477
- private turnsInFlight;
3478
- private compacting;
3644
+ private live;
3479
3645
  private openTurn;
3480
3646
  constructor(host: PlannerConversationHost);
3481
3647
  get transcript(): readonly ConversationMessage[];
3482
- /** Whether a user turn is between its transcript append and its settled outcome. */
3648
+ /** Whether a planner turn holds the conversation. */
3483
3649
  get isTurnInFlight(): boolean;
3650
+ /** Stop the planner turn in flight; it settles as stopped. False when none is. */
3651
+ stopTurn(): boolean;
3652
+ /**
3653
+ * Drop the planner turn in flight together with the plan it was answering:
3654
+ * it is stopped, whatever it settles is discarded, and the conversation is
3655
+ * free at once rather than when the abandoned turn finishes unwinding.
3656
+ */
3657
+ abandonTurn(): void;
3658
+ /**
3659
+ * Run planner work that is not a conversation turn — one-shot plan
3660
+ * generation — as the planner turn, so it is stopped, refused and superseded
3661
+ * like one. It starts a fresh plan, so it supersedes the turn in flight.
3662
+ */
3663
+ hold<T>(signal: AbortSignal | undefined, body: (turn: PlannerTurn) => Promise<T>): Promise<T>;
3664
+ /**
3665
+ * Take the conversation for a new planner turn. A caller's own signal stops
3666
+ * the turn too; the relay is detached on release because a caller may reuse
3667
+ * that signal, and a stale listener would stop the next turn with it.
3668
+ */
3669
+ private claim;
3484
3670
  /** The user turn being answered, for what the host raises during it — an approval the turn's research asks for. */
3485
3671
  get currentTurnId(): string | undefined;
3672
+ recordPlannerSkill(skill: SkillLoad): boolean;
3486
3673
  /**
3487
3674
  * Hand the open turn a plan (already validated) or a task edit. The turn
3488
3675
  * settles with it exactly as with the same plan or ops parsed out of its
@@ -3512,6 +3699,7 @@ declare class PlannerConversation {
3512
3699
  timestamp?: string;
3513
3700
  kind?: ConversationMessage['kind'];
3514
3701
  }): void;
3702
+ private appendEntries;
3515
3703
  /** Swap the whole transcript. The live context no longer matches it, so pair with {@link reset}. */
3516
3704
  replace(messages: readonly ConversationMessage[]): void;
3517
3705
  snapshot(): TranscriptSnapshot | null;
@@ -3583,20 +3771,30 @@ declare class PlannerConversation {
3583
3771
  * Call inside the host's mutation ritual.
3584
3772
  */
3585
3773
  recordQueuedEdits(messages: string[], taskCount: number): void;
3774
+ /**
3775
+ * What the skill check of a landed plan or edit warned about, kept in the
3776
+ * transcript so every surface shows it, on reload too, and not only the
3777
+ * planner that may have read it in a tool result. Call inside the host's
3778
+ * mutation ritual, so the broadcast plan carries it.
3779
+ */
3780
+ recordSkillWarnings(warnings: readonly string[], timestamp?: string): void;
3586
3781
  /**
3587
3782
  * Queued edits the between-batches drain could not apply. Recorded so the
3588
3783
  * transcript does not go on promising a change that never landed. Call
3589
3784
  * inside the host's mutation ritual.
3590
3785
  */
3591
3786
  recordQueuedEditsFailed(messages: string[], reason: string): void;
3592
- /** Open the conversation on a fresh plan: the goal is its first message. */
3593
- start(goal: string, opening: ConversationOpening, signal?: AbortSignal): Promise<LegacyPlanState>;
3787
+ /**
3788
+ * Open the conversation on the host's fresh plan: the goal is its first
3789
+ * message. `prepare` (discovery) runs inside the turn, so a stop reaches it.
3790
+ */
3791
+ start(goal: SkillInvocation, prepare: () => Promise<ConversationOpening>, signal?: AbortSignal): Promise<LegacyPlanState>;
3594
3792
  /**
3595
3793
  * Every later user message, from the transcript append to a persisted
3596
3794
  * outcome. A turn that throws before anything was persisted takes its own
3597
3795
  * writes back out, so session memory never drifts from disk and the UI.
3598
3796
  */
3599
- reply(message: string, options?: ReplyOptions): Promise<LegacyPlanState>;
3797
+ reply(message: string | SkillInvocation, options?: ReplyOptions): Promise<LegacyPlanState>;
3600
3798
  private replyTurn;
3601
3799
  /**
3602
3800
  * Whether a settled structural edit reaches work a runner is executing. Only
@@ -3607,7 +3805,28 @@ declare class PlannerConversation {
3607
3805
  */
3608
3806
  private editTouchesLiveWork;
3609
3807
  private assertIdle;
3610
- private inTurn;
3808
+ /**
3809
+ * Refuse to land a turn that no longer belongs here: it was abandoned, its
3810
+ * plan is no longer the host's, or its call was cut off by something other
3811
+ * than its own stop (`IAiService.reset` under a plan change) — what such a
3812
+ * call hands back is a fragment of an answer to a dialogue that is gone.
3813
+ */
3814
+ private assertCurrent;
3815
+ /**
3816
+ * {@link assertCurrent} on the far side of an await that precedes a plan
3817
+ * mutation: the skill check waits on git, and a stop, rewind or session
3818
+ * switch in that window drops the edit instead of landing it on whatever
3819
+ * plan is current by then. A stop counts here because nothing has landed
3820
+ * yet — the user asked for the turn to end before it changed the plan.
3821
+ */
3822
+ private assertStillCurrent;
3823
+ /**
3824
+ * A live conversation is only safe to continue in-place when it also
3825
+ * matches the model/effort configured right now — a harness planner's
3826
+ * running process was spawned with the old one baked in and cannot pick up
3827
+ * a switch (ADR-0009). Otherwise the turn resumes from the transcript.
3828
+ */
3829
+ private canContinueLive;
3611
3830
  /**
3612
3831
  * Bracket one user turn with its start and end, under an id minted here:
3613
3832
  * the turn is where the stream a surface draws begins and ends, and only the
@@ -3615,6 +3834,7 @@ declare class PlannerConversation {
3615
3834
  */
3616
3835
  private userTurn;
3617
3836
  private requirePlan;
3837
+ /** The message as typed, then one entry per skill it loaded, sharing its timestamp so a reload keeps them together. */
3618
3838
  private recordUser;
3619
3839
  private recordResearch;
3620
3840
  /**
@@ -3652,12 +3872,15 @@ declare class PlannerConversation {
3652
3872
  */
3653
3873
  private taskQueryAnswer;
3654
3874
  /**
3655
- * Drive a planner turn to a persisted, broadcast outcome. Task edits apply
3656
- * atomically; validation failures are fed back to the model for up to 2
3657
- * silent retries, then surfaced as a message with the plan untouched. The
3658
- * first turn and every later turn route through here — one path, not two.
3875
+ * Drive a planner turn, its reads already drained, to a persisted, broadcast
3876
+ * outcome. Task edits apply atomically; validation failures are fed back to
3877
+ * the model for up to 2 silent retries, then surfaced as a message with the
3878
+ * plan untouched. The first turn and every later turn route through here —
3879
+ * one path, not two.
3659
3880
  */
3660
3881
  private settle;
3882
+ /** A name not found at submit only warns: a task the attaching one depends on may create it. */
3883
+ private warnSkills;
3661
3884
  /** Validate + commit a task_ops turn atomically. Returns the errors on rejection (plan untouched). */
3662
3885
  private applyTaskOps;
3663
3886
  /** Commit a settled (non-task_ops) turn through the host's mutation ritual. */
@@ -3686,42 +3909,48 @@ declare class PlannerConversation {
3686
3909
  private currentPlanLines;
3687
3910
  }
3688
3911
 
3689
- declare const BUILTIN_SKILL_NAMES: readonly ["grilling", "to-spec", "improve-codebase-architecture"];
3690
- interface SkillMetadata {
3691
- name: string;
3692
- description: string;
3693
- disableModelInvocation?: boolean;
3912
+ interface UserSettings {
3913
+ modelAllowlist?: Record<string, string[]>;
3914
+ /** Last model (and its thinking effort) the user chose for each planner backend, keyed by AiProvider id. */
3915
+ plannerModels?: Record<string, {
3916
+ model: string;
3917
+ effort?: string;
3918
+ }>;
3919
+ /**
3920
+ * Runners the user picked for planning. Absent — not empty — is what falls
3921
+ * back to the environment's defaults; `[]` is a deliberate "none of them".
3922
+ */
3923
+ enabledRunners?: string[];
3924
+ }
3925
+ /**
3926
+ * The model allowlist, remembered planner models and enabled runners are shared
3927
+ * across processes by default. `ORDEWELL_SETTINGS_PATH` lets independent runs
3928
+ * use separate files: otherwise a write in one run would change another's
3929
+ * settings when `getAll()` detects the new mtime.
3930
+ */
3931
+ declare function getSettingsPath(): string;
3932
+ declare class SettingsService {
3933
+ private filePath;
3934
+ private cache;
3935
+ private cachedMtimeMs;
3936
+ constructor(filePath?: string);
3937
+ getAll(): UserSettings;
3938
+ private fileMtimeMs;
3939
+ getModelAllowlist(runner: string): string[] | undefined;
3940
+ setModelAllowlist(runner: string, ids: string[] | undefined): void;
3941
+ getPlannerModel(provider: string): {
3942
+ model: string;
3943
+ effort?: string;
3944
+ } | undefined;
3945
+ setPlannerModel(provider: string, entry: {
3946
+ model: string;
3947
+ effort?: string;
3948
+ } | undefined): void;
3949
+ getEnabledRunners(): string[] | undefined;
3950
+ setEnabledRunners(ids: string[]): void;
3951
+ private load;
3952
+ private persist;
3694
3953
  }
3695
- interface SkillInfo {
3696
- name: string;
3697
- description: string;
3698
- metadata: SkillMetadata;
3699
- /** Full content of SKILL.md with frontmatter stripped */
3700
- content: string;
3701
- /** Absolute path to the SKILL.md file */
3702
- path: string;
3703
- /** Whether this came from global (~/.ordewell/skills/) or local (.ordewell/skills/) */
3704
- source: 'global' | 'local';
3705
- }
3706
- declare class SkillsService {
3707
- private workspaceRoot?;
3708
- constructor(workspaceRoot?: string | undefined);
3709
- private localDir;
3710
- private globalDir;
3711
- private readSeedManifest;
3712
- private recordSeed;
3713
- /** A seed the user never edited: safe to replace with a newer built-in. */
3714
- private isUntouchedSeed;
3715
- private seed;
3716
- seedBuiltinSkill(name: string): boolean;
3717
- /** Removes a retired built-in's seed from the global dir, but only if it's untouched by the user. */
3718
- private isUnmodifiedRetiredSeed;
3719
- private pruneRetired;
3720
- findSkill(name: string): SkillInfo | undefined;
3721
- listSkills(): SkillInfo[];
3722
- getSkillContent(name: string): string | undefined;
3723
- }
3724
- declare function createSkillsService(workspaceRoot?: string): SkillsService;
3725
3954
 
3726
3955
  /** The `planner_usage` member of the session broadcast union. */
3727
3956
  type PlannerUsageMessage = Extract<SessionMessage, {
@@ -3766,8 +3995,12 @@ interface SessionEventRelayDeps {
3766
3995
  /** What a task's waiting checkpoint asks, whole. */
3767
3996
  checkpointQuestion?: (taskId: string) => string | undefined;
3768
3997
  }
3769
- /** Every orchestrator event the relay announces; `onTaskSettled` is persistence only, so it is the Session's. */
3770
- type RelayObserver = Required<Omit<OrchestratorObserver, 'onTaskSettled'>>;
3998
+ /**
3999
+ * Every orchestrator event the relay announces. `onTaskSettled` is persistence
4000
+ * only and `onQueueReady` is the Session's cue to drain its queue, so both are
4001
+ * the Session's alone.
4002
+ */
4003
+ type RelayObserver = Required<Omit<OrchestratorObserver, 'onTaskSettled' | 'onQueueReady'>>;
3771
4004
  /**
3772
4005
  * Turns orchestrator and planner events into {@link SessionMessage}
3773
4006
  * broadcasts — the one place a surface's view of a Session is produced. It
@@ -3860,31 +4093,11 @@ interface GeneratePlanOptions {
3860
4093
  type SessionPlanner = Pick<Planner, 'generate' | 'modifyDuringExecution'>;
3861
4094
  /** Runtime prefs read live — may toggle between operations. */
3862
4095
  interface SessionRuntimeSettings {
3863
- tddEnabled: boolean;
3864
- verificationEnabled?: boolean;
3865
4096
  modelAllowlist?: Record<string, string[]>;
3866
- /** Read once as a run starts, never per spawn (ADR-0018, S1). Absent means terminal. */
3867
- runnerTransport?: RunnerTransport;
3868
4097
  /** Absent means the user never chose, and `config.enabledRunners` (the host's defaults) decides. */
3869
4098
  enabledRunners?: RunnerId[];
3870
4099
  }
3871
4100
  declare function sessionRuntimeSettings(settings: UserSettings): SessionRuntimeSettings;
3872
- /**
3873
- * Resolve `/skill-name` invocations to their skill's markdown content. Pure
3874
- * and exported so surfaces and verification can drive substitution without a
3875
- * full Session.
3876
- *
3877
- * A message that is *only* `/skill-name` keeps the legacy whole-message
3878
- * behaviour: an unknown skill becomes a notice naming what IS available,
3879
- * instead of a bare slash token a runner would mis-resolve in its own skills
3880
- * directory. Anywhere else in a message, a matching token is spliced in place
3881
- * (surrounding text is untouched); a token that doesn't name a real skill is
3882
- * left as plain text rather than raising a notice, since embedded in a
3883
- * sentence it's as likely to be incidental text (a path, an example command)
3884
- * as a typo'd invocation. The same skill name repeated only expands its first
3885
- * occurrence — later repeats stay literal.
3886
- */
3887
- declare function resolveSkillInvocation(text: string, skillsService: Pick<SkillsService, 'findSkill' | 'listSkills'>): string;
3888
4101
  /** Where a fork landed: the new session's id, and what adopting it needs. */
3889
4102
  interface ConversationFork {
3890
4103
  sessionId: string;
@@ -3900,14 +4113,14 @@ type SaveSession = (plan: LegacyPlanState, goal: string, workspace: string, sess
3900
4113
  /**
3901
4114
  * Everything a delivery surface constructs to host a session. Structural config
3902
4115
  * (orchestratorModel, providerModelLists) is snapshotted inside `config` at
3903
- * construction and never re-read from the environment. Runtime settings (tdd,
3904
- * verification, enabled runners) are read live via the `settings` callback so a
4116
+ * construction and never re-read from the environment. Runtime settings
4117
+ * (enabled runners) are read live via the `settings` callback so a
3905
4118
  * toggle between operations takes effect.
3906
4119
  */
3907
4120
  interface SessionDeps {
3908
4121
  config: IConfig;
3909
4122
  notifications: INotification;
3910
- runner: ITerminalRunner;
4123
+ runner: IRunner;
3911
4124
  registry: RunnerRegistry;
3912
4125
  /** Resolves the workspace root for the orchestrator (lazy — VS Code can change it). */
3913
4126
  workspaceRoot: () => string;
@@ -3919,7 +4132,7 @@ interface SessionDeps {
3919
4132
  onNotice?: (notice: SessionNotice) => void;
3920
4133
  /** Shared across sessions — sole producer of model catalogs and routing lists. */
3921
4134
  modelResolver: ModelResolver;
3922
- /** Live runtime settings (tdd, verification, enabled runners). Read at each operation that needs them. */
4135
+ /** Live runtime settings (enabled runners). Read at each operation that needs them. */
3923
4136
  settings: () => SessionRuntimeSettings;
3924
4137
  /**
3925
4138
  * Host-assigned session id. When set, every persist writes under this id so
@@ -3940,11 +4153,7 @@ interface SessionDeps {
3940
4153
  * over the session's workspace root.
3941
4154
  */
3942
4155
  skillsService?: SkillsService;
3943
- /**
3944
- * Where a task's output and final answer are read. Defaults to the agents'
3945
- * own transcripts under the user's home; tests inject one that never
3946
- * touches the disk.
3947
- */
4156
+ /** Where a task's output and final answer are read. */
3948
4157
  taskOutput?: TaskOutputSource;
3949
4158
  /**
3950
4159
  * Git worktree isolation (ADR-0013). Defaults to git itself, gated by
@@ -3967,10 +4176,11 @@ declare function createSession(deps: SessionDeps): Session;
3967
4176
  /** What {@link createSession} hands a Session: every collaborator, already built and wired. */
3968
4177
  interface SessionParts {
3969
4178
  config: IConfig;
3970
- registry: RunnerRegistry;
4179
+ catalog: SessionCatalog;
3971
4180
  workspace: string;
3972
4181
  fsAdapter: IFileSystem;
3973
4182
  broadcast: SessionBroadcaster;
4183
+ notifications: INotification;
3974
4184
  onNotice?: (notice: SessionNotice) => void;
3975
4185
  modelResolver: ModelResolver;
3976
4186
  settings: () => SessionRuntimeSettings;
@@ -3986,7 +4196,7 @@ interface SessionParts {
3986
4196
  approvals: PendingApprovals;
3987
4197
  approvalPolicy: ApprovalPolicy;
3988
4198
  fetcher: IWebFetcher;
3989
- skillsService: Pick<SkillsService, 'findSkill' | 'listSkills'>;
4199
+ skillsService: Pick<SkillsService, 'findSkill' | 'listSkills' | 'searchedDirs' | 'forRoot'>;
3990
4200
  saveSession: SaveSession;
3991
4201
  /** The conversation's host is the Session itself, so only the Session can make it. */
3992
4202
  conversation: (host: PlannerConversationHost) => PlannerConversation;
@@ -4012,11 +4222,12 @@ declare class Session {
4012
4222
  private readonly runs;
4013
4223
  private readonly store;
4014
4224
  private readonly config;
4015
- private readonly registry;
4225
+ private readonly catalog;
4016
4226
  private plan;
4017
4227
  private goal;
4018
4228
  private workspace;
4019
4229
  private readonly broadcast;
4230
+ private readonly notifications;
4020
4231
  private readonly onNotice?;
4021
4232
  private readonly modelResolver;
4022
4233
  private readonly fsAdapter;
@@ -4025,8 +4236,12 @@ declare class Session {
4025
4236
  private readonly fetcher;
4026
4237
  private readonly settingsFn;
4027
4238
  private readonly save;
4028
- /** Last discovered model catalog — lets sync plan commits clamp thinking efforts to real variants. */
4029
- private modelsCache;
4239
+ /** The queue drain in flight, which a second call joins; see {@link processQueuedMessages}. */
4240
+ private queueDrain;
4241
+ /** The queued messages the planner has been given, no longer the user's to take back. */
4242
+ private drainingIds;
4243
+ /** User controls in flight; see {@link withSave}. */
4244
+ private controlsInFlight;
4030
4245
  private unsubObserver;
4031
4246
  private readonly hostSessionId?;
4032
4247
  private currentSessionId;
@@ -4059,7 +4274,7 @@ declare class Session {
4059
4274
  /**
4060
4275
  * The relay announces every orchestrator event; this adds the saves some of
4061
4276
  * them owe, each made before the announcement so no surface sees state the
4062
- * disk lacks.
4277
+ * disk lacks, and drains the queue the scheduler parked behind.
4063
4278
  */
4064
4279
  private observer;
4065
4280
  /**
@@ -4094,43 +4309,8 @@ declare class Session {
4094
4309
  * session.
4095
4310
  */
4096
4311
  reset(): void;
4097
- /**
4098
- * What a planner may assign right now: every enabled runner, its modes, and
4099
- * its allowlisted models — read as of this call, so a runner enabled or an
4100
- * allowlist edited since planning started is in it (#69). Discovers a
4101
- * runner's models the first time it is asked for.
4102
- */
4312
+ /** What a planner may assign right now; see {@link SessionCatalog.live}. */
4103
4313
  liveCatalog(): Promise<TaskQueryCatalog>;
4104
- private catalogOf;
4105
- private runnerModesFor;
4106
- /**
4107
- * The catalog a model/task-mode edit is checked against — the same
4108
- * discovered models and manifest modes the per-turn catalog block shows the
4109
- * planner, so a refusal here can never name something invalid that the
4110
- * planner was never told about. The catalog is not filtered by the
4111
- * allowlist here — {@link checkModelAndModeValidity} narrows by
4112
- * allowlist itself, the same way `coerceAssignments` does.
4113
- */
4114
- private editCatalog;
4115
- /** The runners enabled right now — a toggle made since the session was built counts (#69). */
4116
- private enabledRunners;
4117
- /**
4118
- * The allowlist in force right now. Unset means no restriction — falling
4119
- * back to the one planning started under would keep a restriction the user
4120
- * has since cleared.
4121
- */
4122
- private allowlist;
4123
- /**
4124
- * The discovered catalog as the resolver holds it now, per runner, with this
4125
- * session's own discovery as the fallback where the resolver has nothing
4126
- * cached. The resolver's cache outlives this session's snapshot in both
4127
- * directions — an allowlist picker re-discovers into it mid-session — and a
4128
- * model allowed after that must reach the planner with its real label and
4129
- * variants, not as an id-only stub. Never triggers discovery itself.
4130
- */
4131
- private models;
4132
- /** What a runner offers, as `runnerAssignment` needs it. Spawns the runner's CLI to list models. */
4133
- private catalogFor;
4134
4314
  get planState(): LegacyPlanState | null;
4135
4315
  /**
4136
4316
  * The live plan in the shape a saved session is read back as. The disk
@@ -4146,24 +4326,16 @@ declare class Session {
4146
4326
  * nothing executing, and reporting it as executing is what left the plan
4147
4327
  * unstartable after its last live task was cancelled.
4148
4328
  */
4149
- get isExecuting(): boolean;
4150
4329
  /** See {@link TaskOrchestrator.hasLiveWork} — a spawned runner, not merely an armed scheduler. */
4151
- get hasLiveWork(): boolean;
4330
+ get isExecuting(): boolean;
4152
4331
  get status(): 'approved' | 'running' | 'completed';
4332
+ /** Whether a planner turn — a reply, a compaction, plan generation — holds the conversation. */
4333
+ get isPlannerBusy(): boolean;
4153
4334
  /**
4154
- * Deny every parked approval as soon as a planning turn is aborted, for the
4155
- * same reason `beginFreshPlan` does it: a prompt raised by a turn nobody is
4156
- * waiting on has no one left to serve, and denying it unblocks the research
4157
- * loop instead of stranding it. Without this the request sat out its
4158
- * five-minute timeout and the loop then carried on as if nothing had
4159
- * happened — the abort was real, but invisible until long after the user
4160
- * pressed stop.
4161
- *
4162
- * The listener is returned as a disposer rather than left attached: callers
4163
- * own the signal and may reuse it, and a leaked listener would deny the
4164
- * *next* turn's prompts the moment that stale signal aborted.
4335
+ * Stop the planner turn in flight. It still settles — a backend hands back
4336
+ * what it had — and ends as stopped. False when no turn is in flight.
4165
4337
  */
4166
- private denyApprovalsOnAbort;
4338
+ abortPlannerTurn(): boolean;
4167
4339
  generatePlan(goal: string, runners: RunnerId[], options?: GeneratePlanOptions): Promise<LegacyPlanState>;
4168
4340
  /**
4169
4341
  * Kick off the planner conversation (ADR-0002): research + the first planner
@@ -4225,21 +4397,20 @@ declare class Session {
4225
4397
  /** Whether the planner conversation is live (started and not yet committed to a plan). */
4226
4398
  get isConversationActive(): boolean;
4227
4399
  /**
4228
- * Intercept a skill invocation (/skill-name) and substitute the skill's
4229
- * markdown content BEFORE the message reaches the planner. Prevents runners
4230
- * (Claude Code, OpenCode) from trying to resolve the skill in their own
4231
- * directory instead of .ordewell/skills/. An unknown skill is surfaced to
4232
- * the planner as a notice naming what IS available, rather than passing a
4233
- * bare /unknown through to be mis-resolved.
4400
+ * The skills a message's `/skill-name` tokens load, snapshotted here so the
4401
+ * planner gets Ordewell's skill rather than a runner (Claude Code, OpenCode)
4402
+ * resolving the token in its own skills directory. The message stays as
4403
+ * typed.
4234
4404
  */
4235
4405
  private resolveSkillInvocation;
4236
4406
  /**
4237
- * The one place planning discovers what it may draw from: the runners'
4238
- * models (cached for later effort clamping), their modes, and the settings
4239
- * in force. `filteredModels` is the allowlisted view a prompt may show;
4240
- * `modelsByRunner` stays whole because coercion needs real labels and variants.
4407
+ * The one catalog every planner path reads — its skill catalogs, `/name`,
4408
+ * and the check of what a plan or an edit attaches — over the folders the
4409
+ * workspace's tasks will read at spawn.
4241
4410
  */
4242
- private plannerCatalog;
4411
+ private workspaceSkills;
4412
+ /** The catalog over workspace `roots`; the workspace root alone is the session's own. */
4413
+ private skillsAt;
4243
4414
  private conversationOpening;
4244
4415
  /**
4245
4416
  * Load planner-produced tasks, coerced to the allowlist read live — it may
@@ -4276,7 +4447,19 @@ declare class Session {
4276
4447
  cancelTask(taskId: string): Promise<void>;
4277
4448
  markTaskComplete(taskId: string): Promise<void>;
4278
4449
  markTaskIncomplete(taskId: string): Promise<void>;
4450
+ /**
4451
+ * Hand the queued edits to the planner. The Session does this itself each
4452
+ * time the scheduler parks behind the queue, so no surface has to; a caller
4453
+ * only needs it to await the drain. The messages stay queued until the
4454
+ * planner's answer is applied, so the scheduler stays paused behind them
4455
+ * meanwhile; one drain runs at a time, and a call made during it joins it.
4456
+ * Whatever was queued while it ran is drained after, before the run goes on.
4457
+ */
4279
4458
  processQueuedMessages(): Promise<void>;
4459
+ private drainQueue;
4460
+ /** Said through both channels: a host shows one or the other, never both (the daemon's toasts are no-ops). */
4461
+ private notice;
4462
+ private applyQueuedEdits;
4280
4463
  /**
4281
4464
  * Every finished task as the log records it: this run's own entries, plus
4282
4465
  * the tasks earlier runs completed, which the log dropped when this run
@@ -4295,15 +4478,14 @@ declare class Session {
4295
4478
  sendTaskMessage(taskId: string, text: string): string;
4296
4479
  /**
4297
4480
  * Force send (ADR-0023, F1): interrupt the task's running turn and deliver
4298
- * this message next. Throws `TaskControlError` where a plain message would,
4299
- * and for a task on the terminal transport.
4481
+ * this message next. Throws `TaskControlError` where a plain message would.
4300
4482
  */
4301
4483
  forceSendTaskMessage(taskId: string, text: string): string;
4302
4484
  forceSendQueuedTaskMessage(taskId: string, id: string): boolean;
4303
4485
  removeQueuedTaskMessage(taskId: string, id: string): boolean;
4304
4486
  interruptTask(taskId: string): Promise<void>;
4305
4487
  getQueuedMessages(): QueuedMessage[];
4306
- /** Take back one unsent message; the plan's persisted queue follows so a reload cannot resurrect it. */
4488
+ /** Take back one unsent message — false once the planner has it; the plan's persisted queue follows so a reload cannot resurrect it. */
4307
4489
  removeQueuedMessage(id: string): boolean;
4308
4490
  setQueuedMessages(msgs: QueuedMessage[]): void;
4309
4491
  /** Replay a run a dirty tree blocked, after stashing the tracked changes. */
@@ -4343,6 +4525,21 @@ declare class Session {
4343
4525
  /** Add the task that merges a conflicted task's branch by hand; see {@link PlanEditor.addConflictResolver}. */
4344
4526
  resolveConflictAsTask(taskId: string): Promise<LegacyPlanState | null>;
4345
4527
  stopExecution(): void;
4528
+ /**
4529
+ * Run a user control and save what it changed before any surface hears of
4530
+ * it. Not a status hold, as {@link saved} is: these await spawns and git, so
4531
+ * a hold would stall every task's status for as long, and an
4532
+ * `execution_complete` the control triggers would overtake the status held
4533
+ * back — which a surface treats as the end of the stream. Instead each status
4534
+ * announced while one runs is saved first (see {@link observer}), as the
4535
+ * background save of an execution event: it may land mid planner turn. So
4536
+ * is the last one where the control is itself execution, not the user's
4537
+ * word on the plan (`background`).
4538
+ */
4539
+ private withSave;
4540
+ private saveForControl;
4541
+ /** A control that finishes synchronously: its status is held until it is saved. */
4542
+ private saved;
4346
4543
  /**
4347
4544
  * The one mutation seam: every structural plan mutation runs the same
4348
4545
  * ritual — store op → persist (which snapshots tasks from PlanStore) →
@@ -4399,9 +4596,261 @@ declare class PlanEditError extends Error {
4399
4596
  constructor(message: string);
4400
4597
  }
4401
4598
 
4599
+ /** What the user last picked for one planner backend. */
4600
+ interface PlannerModelChoice {
4601
+ model: string;
4602
+ effort?: string;
4603
+ }
4604
+ /**
4605
+ * A candidate model as this planner's catalog lists it. Deliberately plain
4606
+ * data: a harness planner's catalog is its runner's own model list and a
4607
+ * vendor's is a picker list (ADR-0009), and the rule below is the same for
4608
+ * both — so the caller normalizes, and this module never reaches for a
4609
+ * ModelResolver, a config or the network.
4610
+ */
4611
+ interface PlannerModelCandidate {
4612
+ id: string;
4613
+ variants?: {
4614
+ id: string;
4615
+ }[];
4616
+ }
4617
+ interface PlannerModelRecall {
4618
+ model: string;
4619
+ effort: string;
4620
+ source: 'remembered' | 'catalog-default' | 'none';
4621
+ }
4622
+ /** The slice of `SettingsService` this needs, so tests can pass a plain object. */
4623
+ interface PlannerModelStore {
4624
+ getPlannerModel(provider: string): PlannerModelChoice | undefined;
4625
+ setPlannerModel(provider: string, entry: PlannerModelChoice | undefined): void;
4626
+ }
4627
+ /**
4628
+ * Remembers, per planner backend, the model the user last chose for it.
4629
+ *
4630
+ * Switching the planner has to clear the model — an id is scoped to the agent
4631
+ * that listed it, so an OpenRouter slug handed to Claude Code points it at
4632
+ * something it cannot spawn. That clear is correct and stays; what was missing
4633
+ * is that switching *back* then forced a re-pick of a model the user had
4634
+ * already chosen. Both surfaces' clears and the restore now read one rule from
4635
+ * here rather than three copies drifting apart.
4636
+ */
4637
+ declare class PlannerModelMemory {
4638
+ private store;
4639
+ constructor(store: PlannerModelStore);
4640
+ /** Record what the user picked for `provider`; a blank model forgets it instead. */
4641
+ remember(provider: string, model: string, effort?: string): void;
4642
+ /**
4643
+ * What the model and effort should become when `provider` takes over the
4644
+ * planner, given the models it actually serves. The `source` is what the
4645
+ * three surfaces word their notice from — restored, defaulted, or nothing.
4646
+ */
4647
+ recall(provider: string, catalog: PlannerModelCandidate[]): PlannerModelRecall;
4648
+ }
4649
+
4650
+ /**
4651
+ * The daemon contract: every HTTP body the local daemon sends, in one place.
4652
+ *
4653
+ * The daemon's routes build their bodies against these types and the CLI's
4654
+ * `ApiClient` reads them back, so a field added on one side is a compile error
4655
+ * on the other. Before this there were three hand-written copies — the routes'
4656
+ * inline `c.json`, the client's per-method generics, and the TUI's slice of the
4657
+ * client — and they had drifted (the client declared a `headless` field the
4658
+ * daemon never sent, and typed `GET /sessions/:id`'s plan as the wrong shape).
4659
+ *
4660
+ * Types only. Core is the one package both the daemon and its clients already
4661
+ * depend on; importing from the daemon would pull its runtime into the CLI.
4662
+ */
4663
+ /**
4664
+ * Why a request was refused, as a stable code a client switches on instead of
4665
+ * reading the message. Absent on a refusal nobody needs to tell apart — a
4666
+ * missing field, say — where the message is all there is.
4667
+ */
4668
+ type DaemonErrorCode = 'session_not_found' | 'task_not_found' | 'no_plan' | 'already_executing' | 'workspace_not_found' | 'workspace_not_a_project' | 'conversation_busy'
4669
+ /** The user stopped the planner turn: the turn ended as asked, so a client stays quiet. */
4670
+ | 'planner_turn_stopped'
4671
+ /** The planner turn settled after its plan was dropped; nothing it produced was kept. */
4672
+ | 'planner_turn_discarded'
4673
+ /** The request asked for something the plan, task or conversation does not allow. */
4674
+ | 'refused' | 'checkpoint_not_waiting' | 'approval_not_outstanding';
4675
+ /** The body of every non-2xx answer a route gives. */
4676
+ interface ErrorBody {
4677
+ error: string;
4678
+ code?: DaemonErrorCode;
4679
+ /** Set with `workspace_not_a_project`: the directory the daemon refused, so a client can offer to initialize it. */
4680
+ workspace?: string;
4681
+ }
4682
+ interface OkResponse {
4683
+ ok: boolean;
4684
+ }
4685
+ /**
4686
+ * A plan as the planner routes return it: the session's plan state, not the
4687
+ * `SerializedPlan` a websocket `plan_generated` carries. The two differ —
4688
+ * this one holds raw tasks and the dialogue's bookkeeping — and a client that
4689
+ * reads either reads only the fields it needs. Skill bodies stay in the
4690
+ * session: loads and attempt snapshots arrive as notices.
4691
+ */
4692
+ interface PlanResponse {
4693
+ plan: SurfacePlan;
4694
+ }
4695
+ /** `POST /plans/:id/resolve-conflict`: `null` when the session had no conflict to hand a resolver task. */
4696
+ interface ResolveConflictResponse {
4697
+ plan: SurfacePlan | null;
4698
+ }
4699
+ /** What `GET /api/models` returns, and what the pool's provider discovery produces. */
4700
+ interface ModelsResponse {
4701
+ models: DiscoveredModel[];
4702
+ modelsByRunner: Record<string, DiscoveredModel[]>;
4703
+ /** Each runner's manifest modes, so a surface can offer a per-task mode picker. */
4704
+ modesByRunner: Record<string, RunnerModeInfo[]>;
4705
+ orchestratorModel: string;
4706
+ providers: AiProvider[];
4707
+ /** Full cross-provider catalog for the orchestrator (planner) model picker. */
4708
+ orchestratorModels: OrchestratorOption[];
4709
+ /** Per-provider catalog-fetch failures, keyed by provider id. */
4710
+ providerErrors: Record<string, string>;
4711
+ }
4712
+ /** One-shot planning answers in the phase-tagged shape a saved session is read back in. */
4713
+ interface GeneratePlanResponse extends Pick<ModelsResponse, 'models' | 'modelsByRunner'> {
4714
+ plan: SurfacePlanState;
4715
+ /** The host notes the plan's transcript records, a skill check's warnings among them, which the phase-tagged plan does not carry. */
4716
+ notes?: string[];
4717
+ }
4718
+ interface ExecuteResponse {
4719
+ status: 'running';
4720
+ }
4721
+ interface StopResponse {
4722
+ status: 'stopped';
4723
+ }
4724
+ interface CancelPlanningResponse {
4725
+ cancelled: boolean;
4726
+ }
4727
+ interface TaskMessageResponse {
4728
+ id: string;
4729
+ }
4730
+ interface ForceSendResponse {
4731
+ sent: boolean;
4732
+ }
4733
+ interface RemoveMessageResponse {
4734
+ removed: boolean;
4735
+ }
4736
+ interface MergeGateEntry {
4737
+ id: string;
4738
+ order: number;
4739
+ title: string;
4740
+ }
4741
+ interface MergeGateResponse {
4742
+ mergeGate: MergeGateEntry[];
4743
+ }
4744
+ interface IsolationDiffResponse {
4745
+ diff: string;
4746
+ }
4747
+ /** A merge that conflicts or is blocked is an outcome, not a refusal, so it answers 200 with the result. */
4748
+ type IsolationMergeResponse = IsolationMergeResult;
4749
+ /** A fork the daemon has already adopted. */
4750
+ interface ConversationForkResponse {
4751
+ sessionId: string;
4752
+ goal: string;
4753
+ plan: SurfacePlan;
4754
+ }
4755
+ /** A fork made by a rewind, with the full text of the message it was made just before. */
4756
+ interface ConversationRewindResponse extends ConversationForkResponse {
4757
+ rewoundMessage: string;
4758
+ }
4759
+ interface RewindTargetsResponse {
4760
+ targets: RewindTarget[];
4761
+ }
4762
+ interface ConversationCompactResponse extends ConversationCompaction, PlanResponse {
4763
+ }
4764
+ interface PrdResponse {
4765
+ prdMarkdown: string;
4766
+ }
4767
+ interface RunnerEntry {
4768
+ id: string;
4769
+ name: string;
4770
+ enabled: boolean;
4771
+ }
4772
+ interface RunnersResponse {
4773
+ runners: RunnerEntry[];
4774
+ orchestratorModel: string;
4775
+ }
4776
+ /** Sessions: a saved one comes off its file, a live one from the pool's store. */
4777
+ interface SessionResponse {
4778
+ meta: SessionMeta;
4779
+ plan: SurfacePlanState;
4780
+ }
4781
+ interface AdoptSessionResponse extends OkResponse {
4782
+ plan: SurfacePlan;
4783
+ goal: string;
4784
+ }
4785
+ interface TaskLogAttemptsResponse {
4786
+ attempts: number[];
4787
+ }
4788
+ interface TaskLogResponse {
4789
+ attempt: number;
4790
+ events: TaskLogEvent[];
4791
+ }
4792
+ type SessionListResponse = SessionMeta[];
4793
+ interface SettingsResponse {
4794
+ orchestratorModel: string;
4795
+ /** Who plans (ADR-0009) — a vendor id, or one of the three harness planners. */
4796
+ aiProvider: AiProvider;
4797
+ plannerThinkingEffort: string;
4798
+ maxParallel: number;
4799
+ modelAllowlist: UserSettings['modelAllowlist'];
4800
+ plannerModels: UserSettings['plannerModels'];
4801
+ }
4802
+ /** What `PATCH /api/settings` answers: the settings as they now stand, plus what the write itself decided. */
4803
+ interface SettingsUpdateResponse extends SettingsResponse {
4804
+ /**
4805
+ * Which model a planner switch landed on and why — remembered for that
4806
+ * backend, its catalog default, or none. Present only when the write changed
4807
+ * the planner; absent for every other write, where nothing was recalled.
4808
+ */
4809
+ switchRecall?: PlannerModelRecall;
4810
+ /** Env keys the daemon refused to apply, so a client does not persist what the daemon would not. */
4811
+ rejectedEnvKeys?: string[];
4812
+ }
4813
+ interface CommandDescriptor {
4814
+ name: string;
4815
+ description: string;
4816
+ }
4817
+ interface CommandsResponse {
4818
+ commands: CommandDescriptor[];
4819
+ }
4820
+ interface WorkspacesResponse {
4821
+ workspaces: string[];
4822
+ }
4823
+ interface ApprovalsResponse {
4824
+ pending: PendingApproval[];
4825
+ approvedScopes: string[];
4826
+ }
4827
+ interface ApprovalAnswerResponse extends OkResponse {
4828
+ granted: boolean;
4829
+ decision: ApprovalDecision['decision'];
4830
+ }
4831
+
4832
+ /**
4833
+ * Why a call on a session could not be made. Each is a class rather than a
4834
+ * message so the daemon can map it to a status and a stable code in one place,
4835
+ * and a client can switch on that code instead of reading the wording. Like
4836
+ * `PlanEditError`, none carries a status: core is transport-agnostic.
4837
+ */
4838
+ /** No session with that id is held — never planned in this process, or dropped by a restart. */
4839
+ declare class SessionNotFoundError extends Error {
4840
+ constructor();
4841
+ }
4842
+ /** The session holds no plan (or an empty one) for the operation to act on. */
4843
+ declare class NoPlanError extends Error {
4844
+ constructor(operation: 'execute' | 'review');
4845
+ }
4846
+ /** A run is already live, and starting another would reset the one in flight. */
4847
+ declare class AlreadyExecutingError extends Error {
4848
+ constructor();
4849
+ }
4850
+
4402
4851
  /**
4403
4852
  * Where Ordewell keeps machine-global data (settings, daemon tokens, caches,
4404
- * user plugins). A single home for everything not tied to a project directory,
4853
+ * credentials). A single home for everything not tied to a project directory,
4405
4854
  * so per-project state in `.ordewell/` and global state never collide.
4406
4855
  */
4407
4856
  declare function globalDataDir(): string;
@@ -4428,22 +4877,14 @@ type CheckpointWithdrawnListener = (taskId: string) => void;
4428
4877
  /** Fires on every idleSince transition (null→timestamp on silence, timestamp→null on resume/teardown). */
4429
4878
  type IdleListener = (taskId: string, idleSince: string | null) => void;
4430
4879
  declare class VerdictEngine {
4431
- private markerSeen;
4432
4880
  private pendingVerdicts;
4433
- private structuredSessions;
4434
- private markerTails;
4435
- private checkpointCarry;
4436
- private pausedSessions;
4881
+ private sessions;
4437
4882
  private listeners;
4438
4883
  private checkpointListeners;
4439
4884
  private withdrawnListeners;
4440
- /**
4441
- * The open `checkpoint` tool call per task (ADR-0022, V5): settling it is how
4442
- * an answer reaches a runner that asked through the tool, where the marker's
4443
- * answer is typed into the session instead.
4444
- */
4885
+ /** The open `checkpoint` tool call per task (ADR-0022, V5): settling it is how an answer reaches the runner. */
4445
4886
  private toolCheckpoints;
4446
- /** The question a task's checkpoint asks, whole, for as long as it waits — by either route. */
4887
+ /** The question a task's checkpoint asks, whole, for as long as it waits. */
4447
4888
  private checkpointQuestions;
4448
4889
  private idleListeners;
4449
4890
  /**
@@ -4485,32 +4926,22 @@ declare class VerdictEngine {
4485
4926
  private touchIdle;
4486
4927
  /** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
4487
4928
  private clearIdle;
4488
- /**
4489
- * Submit a synchronized resume token to the paused session. An interactive
4490
- * TUI only accepts the Enter keystroke (`\r`) — a `\n` types the token into
4491
- * its composer without sending it, leaving the agent paused until a human
4492
- * presses Enter. A line-oriented piped session has no composer; it reads a
4493
- * `\n`-terminated line, and the leading newline flushes a partial line.
4494
- */
4495
- private resumeToken;
4496
4929
  /** What a task's waiting checkpoint asks, untruncated; undefined when none waits. */
4497
4930
  getCheckpointQuestion(taskId: string): string | undefined;
4498
4931
  approveCheckpoint(taskId: string): void;
4499
4932
  rejectCheckpoint(taskId: string, reason: string): void;
4500
4933
  /**
4501
- * Attach to a spawned session: scan the output tail for the task's completion
4502
- * marker (delivering a verdict immediately while leaving interactive sessions
4503
- * open), scan for checkpoint markers, and on exit produce a failed verdict
4504
- * when the marker was never observed. A structured session's `task_complete`
4505
- * call is evidence too (ADR-0022, V2): whichever signal comes first decides.
4934
+ * Attach to a spawned session. The runner's `task_complete` call on the
4935
+ * attempt's token is the only completion evidence (ADR-0022, V2); a session
4936
+ * that exits before one gets a failed verdict.
4506
4937
  *
4507
4938
  * Returns the attempt's generation, what {@link signalComplete} is checked against.
4508
4939
  */
4509
- watch(task: Task, session: ITerminalSession): number;
4940
+ watch(task: Task, session: IRunnerSession): number;
4510
4941
  /**
4511
4942
  * The runner's own `task_complete` call (ADR-0022, V1/V3): settles the
4512
- * attempt exactly as the marker does, unless that attempt is no longer the
4513
- * task's current one or another signal already settled it.
4943
+ * attempt, unless that attempt is no longer the task's current one or an
4944
+ * earlier call already settled it.
4514
4945
  */
4515
4946
  signalComplete(taskId: string, generation: number, report: TaskCompleteArgs): void;
4516
4947
  /** The runner was told something after its evidence so far, so only what it reports from here counts. */
@@ -4518,9 +4949,8 @@ declare class VerdictEngine {
4518
4949
  private acceptVerdict;
4519
4950
  private publishVerdict;
4520
4951
  /**
4521
- * The runner's `checkpoint` call (ADR-0022, V5): raised through the same
4522
- * listeners as the marker, so the task waits on the user the same way, and
4523
- * settled by {@link approveCheckpoint} or {@link rejectCheckpoint}. It is
4952
+ * The runner's `checkpoint` call (ADR-0022, V5): the task waits on the
4953
+ * user until it is settled by {@link approveCheckpoint} or {@link rejectCheckpoint}. It is
4524
4954
  * withdrawn, never left hanging, once the attempt is over or the call goes.
4525
4955
  */
4526
4956
  raiseCheckpoint(taskId: string, generation: number, question: string, signal: AbortSignal): Promise<CheckpointAnswer>;
@@ -4529,64 +4959,33 @@ declare class VerdictEngine {
4529
4959
  private withdrawToolCheckpoint;
4530
4960
  private approvalOpened;
4531
4961
  private approvalClosed;
4532
- /** Scan only the new text plus the unmatched carry, so a long run stays linear. */
4533
- private scanCheckpoints;
4534
4962
  private bumpGeneration;
4535
4963
  private forget;
4536
4964
  /** Manual "Mark complete" override: a pass verdict that bypasses evidence. */
4537
4965
  markComplete(task: Task): Verdict;
4538
4966
  /** Clear verification state for a task (used on retry). */
4539
4967
  clear(task: Task): void;
4968
+ /** Drop a task's verification state and invalidate any callbacks still holding its generation. */
4969
+ private ceaseTracking;
4540
4970
  /** Drop all tracking state (used on stop / loadPlan). */
4541
4971
  reset(): void;
4542
- private decide;
4543
4972
  }
4544
4973
 
4545
- /**
4546
- * Owns one bounded raw buffer per task attempt, fed from the attempt's
4547
- * session, and reads final answers from the agent's transcript through an
4548
- * injected {@link TranscriptReader}.
4549
- */
4974
+ /** Owns one bounded output buffer per task attempt, fed from the attempt's session. */
4550
4975
  declare class BufferedTaskOutputSource implements TaskOutputSource {
4551
4976
  private captures;
4552
- private transcripts;
4553
4977
  private maxBufferChars;
4554
4978
  constructor(opts?: {
4555
- transcripts?: TranscriptReader;
4556
4979
  maxBufferChars?: number;
4557
4980
  });
4558
- attach(taskId: string, session: ITerminalSession): void;
4981
+ attach(taskId: string, session: IRunnerSession): void;
4559
4982
  detach(taskId: string): void;
4560
4983
  reset(): void;
4561
- finalText(attempt: TaskOutputAttempt, doneToken: string): Promise<string>;
4984
+ finalText(taskId: string): string;
4562
4985
  liveTail(taskId: string, opts: LiveTailOptions): LiveTail | null;
4563
4986
  private append;
4564
4987
  }
4565
4988
 
4566
- /**
4567
- * Read a task's final answer from the agent's own session transcript — the
4568
- * clean, structured record the agent writes about itself — instead of
4569
- * reconstructing what a TUI painted (issue #16, built on #14's render).
4570
- *
4571
- * These stores are private and undocumented and DO rotate (OpenCode migrated
4572
- * from files to SQLite mid-2026). Every reader is therefore defensive: any
4573
- * missing file, unparseable line, or shape drift returns null and the caller
4574
- * falls back to the terminal capture. Nothing here is a hard dependency.
4575
- *
4576
- * Directory and recency only narrow the candidates: with several attempts in
4577
- * one cwd the newest session is whichever task finished last. A transcript is
4578
- * accepted only when its content carries the task's completion marker.
4579
- */
4580
- declare class HomeTranscriptReader implements TranscriptReader {
4581
- private readonly home;
4582
- private readonly claudeConfigDirs;
4583
- constructor(opts?: {
4584
- homeDir?: string;
4585
- workspaceEnv?: (cwd: string) => Promise<Record<string, string>>;
4586
- });
4587
- finalAssistantText(query: TranscriptQuery, maxChars?: number): Promise<string | null>;
4588
- }
4589
-
4590
4989
  /**
4591
4990
  * The ids a runner may actually run: the user's allowlist with the ids that
4592
4991
  * provably belong to a *different* runner dropped. `undefined` means no
@@ -4596,7 +4995,7 @@ declare class HomeTranscriptReader implements TranscriptReader {
4596
4995
  * allowlisted for Claude Code does not limit it — it points it at something it
4597
4996
  * cannot spawn, and the plan only dies once a task is already running. But
4598
4997
  * "this runner didn't list it" is not enough to call an id wrong: discovery can
4599
- * be stale, and a plugin runner may have no list at all. What settles it is
4998
+ * be stale or return no list at all. What settles it is
4600
4999
  * whether *another* runner listed the id. So:
4601
5000
  *
4602
5001
  * - listed for this runner → keep;
@@ -4623,45 +5022,6 @@ declare function clampThinkingEffort(effort: string | undefined, variants: {
4623
5022
  }[]): string | undefined;
4624
5023
  declare function coerceAssignments(tasks: readonly Task[], perRunnerAllowlist: Partial<Record<RunnerId, string[]>>, allowedRunners?: RunnerId[], modelsByRunner?: Partial<Record<RunnerId, DiscoveredModel[]>>): Task[];
4625
5024
 
4626
- /** What a runner offers a task: the models discovered for it and the modes its manifest declares. */
4627
- interface RunnerCatalog {
4628
- models: DiscoveredModel[];
4629
- modes: RunnerModeInfo[];
4630
- /**
4631
- * The mode a task lands on when it carries none this runner offers: the
4632
- * manifest's `autonomous`/`safe`-tagged mode under the user's toggle
4633
- * (ADR-0001). Absent, `modes[0]` stands in — which for Codex is its
4634
- * workspace-write sandbox, not the full access an unattended run needs.
4635
- */
4636
- defaultMode?: string;
4637
- }
4638
- /**
4639
- * The model, thinking effort and mode a runner offers for a task.
4640
- *
4641
- * A task's model, effort and mode are all scoped to its runner:
4642
- * `claude-sonnet-4-5` on Codex, or `acceptEdits` on OpenCode, are not degraded
4643
- * choices but unspawnable ones. So this is where any task acquires a runnable
4644
- * assignment — a runner change (below) and a hand-added task derive it the same
4645
- * way. Each field in `current` is preserved when the runner also offers it, and
4646
- * otherwise snapped to that runner's preferred entry (discovery already sorts
4647
- * models by the manifest's `preferredPatterns`; the mode is the catalog's
4648
- * `defaultMode`).
4649
- *
4650
- * An empty catalog means discovery failed or the runner is a plugin we have no
4651
- * list for — not that the runner offers nothing. That field is left out of the
4652
- * patch and the runner validates last, matching `coerceAssignments`.
4653
- */
4654
- declare function runnerAssignment(catalog: RunnerCatalog, current?: Pick<Task, 'assignedModel' | 'taskMode'>): Partial<Task>;
4655
- /**
4656
- * Move a task onto a different runner, carrying its model, thinking effort and
4657
- * mode over to values that runner actually offers. A runner change is never a
4658
- * single-field edit — it either brings the other three with it or leaves the
4659
- * task unrunnable.
4660
- *
4661
- * Returns the patch to apply, or `{}` when there is nothing to change.
4662
- */
4663
- declare function retargetTaskRunner(task: Task, runner: RunnerId, catalog: RunnerCatalog): Partial<Task>;
4664
-
4665
5025
  declare function buildResearchToolsPrompt(): string;
4666
5026
  /**
4667
5027
  * System prompt for one read-only research subagent (issue #34). The digest
@@ -4672,26 +5032,30 @@ declare function buildResearchToolsPrompt(): string;
4672
5032
  declare function buildSubagentSystemPrompt(): string;
4673
5033
  /**
4674
5034
  * Facts about who plans and where tasks will run, as opposed to the user's
4675
- * mode toggles: they change what the prompt can promise, not what it asks for.
5035
+ * choices: they change what the prompt can promise, not what it asks for.
4676
5036
  */
4677
5037
  interface ConversationVariant {
4678
- /** Harness planner (ADR-0009): the agent owns its own tools and research budget. */
5038
+ /**
5039
+ * Harness planner (ADR-0009): the agent owns its own tools and research
5040
+ * budget, and reads the catalog and submits the plan through Ordewell's MCP
5041
+ * tools (ADR-0022). An API planner speaks the JSON envelopes instead.
5042
+ */
4679
5043
  harness?: boolean;
4680
- /** The planner reads the catalog and submits the plan through Ordewell's MCP tools (ADR-0022), not the JSON envelope. */
4681
- plannerTools?: boolean;
5044
+ /** The skill catalog: task skills it can attach on every path, planner skills it can load only with tools. */
5045
+ skills?: readonly SkillInfo[];
4682
5046
  /** Every AI task gets its own worktree (ADR-0013), so file overlap no longer forces an order; of every repo of a group (ADR-0014). */
4683
5047
  isolatedExecution?: IsolatedExecution;
4684
5048
  }
4685
- declare function buildConversationSystemPrompt(goal: string, context: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean, verificationEnabled?: boolean, variant?: ConversationVariant): string;
5049
+ declare function buildConversationSystemPrompt(goal: string, context: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean, variant?: ConversationVariant): string;
4686
5050
  /** The one-shot planner's core rules for tasks that share the workspace root. */
4687
5051
  declare const CORE_PLANNER_PROMPT: string;
4688
5052
  declare function buildResearchPrompt(userGoal: string, context: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes): string;
4689
- declare function buildPlanWithResults(userGoal: string, context: string, researchResults: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes): string;
4690
- declare function buildModifyPlanPrompt(existingPlan: LegacyPlanState, userRequest: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, aiflowContext?: string, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean): string;
5053
+ declare function buildPlanWithResults(userGoal: string, context: string, researchResults: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes, tools?: boolean): string;
5054
+ declare function buildModifyPlanPrompt(existingPlan: LegacyPlanState, userRequest: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, aiflowContext?: string, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean, tools?: boolean): string;
4691
5055
  declare function modelContextBlock(modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean): string;
4692
5056
  declare function executionLogBlock(executionLog: TaskSnapshot[]): string;
4693
5057
  declare function pendingEditRulesBlock(): string;
4694
- declare function buildModifyDuringExecutionPrompt(executionLog: TaskSnapshot[], pendingTasks: string, userMessage: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: Pick<PlannerModes, 'autonomousDefault' | 'isolatedExecution'>): string;
5058
+ declare function buildModifyDuringExecutionPrompt(executionLog: TaskSnapshot[], pendingTasks: string, userMessage: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: Pick<PlannerModes, 'autonomousDefault' | 'isolatedExecution'>, tools?: boolean): string;
4695
5059
  /**
4696
5060
  * User-message prompt for a planner-driven merge: asks the model to combine the
4697
5061
  * selected tasks into one. Routed through the conversation loop, so the
@@ -4795,6 +5159,14 @@ declare const JSON_REPAIR_INSTRUCTION: string;
4795
5159
  declare const TRUNCATED_PLAN_REPAIR_INSTRUCTION: string;
4796
5160
  /** A plan attempt was botched (invalid or truncated JSON): re-emit the whole plan. */
4797
5161
  declare function reEmitPlanPrompt(detail: string): string;
5162
+ /**
5163
+ * A planner that submits through Ordewell's tools ended without an accepted
5164
+ * plan, or one it submitted was refused: call the tool again. A plan written
5165
+ * in its reply is never read (ADR-0025), so asking for JSON would be ignored.
5166
+ */
5167
+ declare function resubmitPlanPrompt(errors: string[]): string;
5168
+ /** A tool planner's task edit was refused as its turn settled: correct it through edit_plan. */
5169
+ declare function resubmitEditPrompt(errors: string[]): string;
4798
5170
  /** A plan emission was truncated mid-JSON; the caller may also have compacted the history to free input context. */
4799
5171
  declare function truncatedPlanReEmitPrompt(historyCompacted: boolean): string;
4800
5172
  /** A task-ops attempt was botched (unparseable JSON): re-emit the ops object. */
@@ -4803,8 +5175,8 @@ declare function reEmitTaskOpsPrompt(detail: string): string;
4803
5175
  declare function reEmitTaskQueryPrompt(detail: string): string;
4804
5176
  /** Task ops parsed but failed semantic validation (cycles, unknown refs, locked tasks). */
4805
5177
  declare function taskOpsRejectedPrompt(errors: string[]): string;
4806
- /** A modified plan failed validation during execution: full re-prompt feedback block. */
4807
- declare function modifyValidationFeedback(errors: string[]): string;
5178
+ /** A modified plan failed validation during execution: full re-prompt feedback block, asking for the planner's own transport. */
5179
+ declare function modifyValidationFeedback(errors: string[], tools?: boolean): string;
4808
5180
  type PlannerReplyClassification = {
4809
5181
  kind: 'plan';
4810
5182
  tasks: Task[];
@@ -4854,23 +5226,6 @@ declare function classifyPlannerReply(text: string, opts: {
4854
5226
  */
4855
5227
  declare function generatePlanWithRepair(generate: (repairHint?: string) => Promise<string>, runners: RunnerId[], maxAttempts?: number, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean): Promise<Task[]>;
4856
5228
 
4857
- type RunnerMode = string;
4858
-
4859
- declare function buildRunnerInvocation(opts: {
4860
- runner: string;
4861
- prompt: string;
4862
- modelId?: string;
4863
- thinkingEffort?: string;
4864
- modelVariants?: string[];
4865
- mode?: RunnerMode;
4866
- headless?: boolean;
4867
- interactive?: boolean;
4868
- cwd?: string;
4869
- /** The installed runner's `--version`, for a manifest whose command line differs by major version. */
4870
- runnerVersion?: string;
4871
- registry: RunnerRegistry;
4872
- }): RunnerInvocation;
4873
-
4874
5229
  interface PriorOutput {
4875
5230
  order: number;
4876
5231
  title: string;
@@ -4883,18 +5238,6 @@ declare function summarizeOutput(reviewReason: string | undefined, output: strin
4883
5238
  capturedAt: string;
4884
5239
  };
4885
5240
  declare function collectDirectDependencyOutputs(task: Task, allTasks: readonly Task[]): PriorOutput[];
4886
- /**
4887
- * Break marker tokens carried in a predecessor's captured output. Interactive
4888
- * runners echo the prompt they are given, and the watcher scans that terminal
4889
- * output — a quoted marker would settle this task on the previous one's
4890
- * evidence. Hyphenated rather than spaced: the scanner also reads a
4891
- * whitespace-flattened view of the terminal, which would rejoin a space.
4892
- *
4893
- * A completion marker also loses its id: transcripts are bound to a task by
4894
- * that id, and a dependent's transcript quoting it would answer for the
4895
- * predecessor the next time the predecessor runs.
4896
- */
4897
- declare function defuseMarkers(text: string): string;
4898
5241
  declare function renderPriorOutputs(outputs: PriorOutput[]): string;
4899
5242
  declare function augmentPromptWithPriorOutputs(task: Task, allTasks: readonly Task[]): string;
4900
5243
  /**
@@ -4909,11 +5252,10 @@ declare function renderPlanMap(planTasks: readonly Task[], currentTaskId: string
4909
5252
  interface ComposeOptions {
4910
5253
  planMapEnabled?: boolean;
4911
5254
  planMapMaxEntries?: number;
4912
- tddEnabled?: boolean;
5255
+ /** The task's skills as resolved where this attempt runs. */
5256
+ skills?: readonly TaskSkillSnapshot[];
4913
5257
  /** An ops task's last attempt, as its output ended (ADR-0020); absent on a first attempt. */
4914
5258
  previousAttempt?: string;
4915
- /** The runner is given the `task_complete` and `checkpoint` tools (ADR-0022); the markers stay as their fallback. */
4916
- completionTool?: boolean;
4917
5259
  }
4918
5260
  declare function composeAugmentedPrompt(task: Task, allTasks: readonly Task[], opts?: ComposeOptions): string;
4919
5261
  /**
@@ -4924,143 +5266,44 @@ declare function composeAugmentedPrompt(task: Task, allTasks: readonly Task[], o
4924
5266
  */
4925
5267
  declare function composeContinuationPrompt(task: Task, message: string, opts?: {
4926
5268
  ops?: boolean;
4927
- completionTool?: boolean;
4928
5269
  }): string;
4929
5270
 
4930
- type ExecFileFn = (command: string, args: string[]) => Promise<{
4931
- stdout: string;
4932
- stderr: string;
4933
- }>;
4934
- /** Test seam: every OS touchpoint is injectable; production uses the defaults. */
4935
- interface TmuxRunnerDeps {
4936
- port: number;
4937
- execFileImpl?: ExecFileFn;
4938
- resolvePath?: () => Promise<string>;
4939
- pollIntervalMs?: number;
4940
- logDir?: string;
4941
- clipboardCommand?: () => string | null;
4942
- /** Reads the runner's version, for a manifest whose command line differs by major version. */
4943
- runnerVersion?: RunnerVersionProbe;
4944
- }
4945
- declare class TmuxSession extends AbstractTerminalSession {
4946
- private tmuxSession;
4947
- private windowName;
4948
- private execFileImpl;
4949
- private pollIntervalMs;
4950
- private logPath;
4951
- private socket;
4952
- /** A tmux window is a real TTY running the agent's TUI, so it submits on Enter. */
4953
- readonly interactive = true;
4954
- private outputBuffer;
4955
- private offset;
4956
- private timer;
4957
- private quietPolls;
4958
- private misses;
4959
- private looking;
4960
- constructor(id: string, taskId: string, tmuxSession: string, windowName: string, execFileImpl: ExecFileFn, pollIntervalMs: number, logPath: string, socket: string);
4961
- private get target();
4962
- /** Every tmux call must name the daemon's own socket; an unprefixed one
4963
- * silently targets the shared default server (see `tmuxSocketName`). */
4964
- private tmux;
4965
- /**
4966
- * Runs the invocation in a fresh tmux window, tailed via `pipe-pane` into a
4967
- * log file rather than polling `capture-pane` snapshots — a byte-exact
4968
- * stream needs no diff/resync heuristics. The command is wrapped so the
4969
- * window survives its own exit (an `exec`'d login shell), which is what
4970
- * lets the user keep poking at a finished task's terminal.
4971
- */
4972
- start(command: string, args: string[], cwd: string, env?: Record<string, string>): Promise<void>;
4973
- private poll;
4974
- /** Emit what the log gained since the last read; false when it gained nothing. */
4975
- private readLog;
4976
- /**
4977
- * The sentinel is printed by the wrapper shell, so a window closed from
4978
- * outside — killed by the user, or with the whole tmux server — never prints
4979
- * it, and the session would count as running forever. A silent window is
4980
- * therefore looked up by exact name (a `-t` target falls back to another
4981
- * window once its own is gone), and once it is confirmed missing the session
4982
- * ends as a kill does. Whatever the log still held is read first, so a
4983
- * completion marker printed just before the close still counts.
4984
- */
4985
- private checkWindow;
4986
- /**
4987
- * A task finishing (or being killed) stops observation, but never the
4988
- * window itself on the sentinel path — only `kill()` closes the window.
4989
- * Piping is turned off and the log file removed either way, so a finished
4990
- * task doesn't leave a `cat` process appending to it for the rest of the
4991
- * daemon's life.
4992
- */
4993
- private finish;
4994
- kill(): void;
4995
- getOutput(): string;
4996
- write(text: string): void;
4997
- }
4998
- /**
4999
- * Runs tasks in a real tmux window instead of a piped subprocess, so a user
5000
- * can open a genuine, interactive terminal on any task (running or
5001
- * finished) from outside Ordewell's own process. `ITerminalSession` hides the
5002
- * transport from `VerdictEngine`/`PoolAwareRunner`, so orchestration is
5003
- * unchanged — this is a drop-in replacement for `HeadlessRunner`.
5004
- */
5005
- declare class TmuxRunner extends AbstractRunner<TmuxSession> {
5006
- private sessionName;
5007
- private socket;
5008
- private execFileImpl;
5009
- private resolvePath;
5010
- private pollIntervalMs;
5011
- private logDir;
5012
- private clipboardCommand;
5013
- private runnerVersion;
5014
- /** Retries get a freshly named window so a failed attempt's output stays inspectable. */
5015
- private attempts;
5016
- private ready;
5017
- constructor(deps: TmuxRunnerDeps);
5018
- /**
5019
- * Reaps a session orphaned by a crashed prior daemon on the same port, then
5020
- * creates a fresh one. Memoized: `spawn` awaits it too, so a task spawned
5021
- * before startup's own call has settled never lands in a missing session —
5022
- * and a settled failure clears the memo so the next spawn can retry.
5023
- */
5024
- ensureSession(): Promise<void>;
5025
- /** Every tmux call must name this daemon's socket; see `tmuxSocketName`. */
5026
- private tmux;
5027
- private createFreshSession;
5028
- /**
5029
- * Gives an attached runner terminal wheel/PageUp scrolling over a deep
5030
- * scrollback, and a mouse selection that reaches the system clipboard — none
5031
- * of which a default tmux session does. Server-wide scoping is harmless: the
5032
- * daemon owns a private socket (`tmuxSocketName`).
5033
- *
5034
- * `mouse on` is what costs the emulator's own drag-select, so tmux must hand
5035
- * the selection back out itself: via a clipboard binary when there is one,
5036
- * and via OSC 52 (`set-clipboard`) for what that cannot cover, such as
5037
- * viewing over SSH. Naming the binary on the binding as well as in
5038
- * `copy-command` keeps drag-to-copy working on tmux older than 3.2, which has
5039
- * no `copy-command`.
5040
- */
5041
- private configureScrolling;
5042
- /**
5043
- * Called on daemon shutdown — the one guarantee against leaked tmux
5044
- * processes. Kills the whole server, not just the session: the socket
5045
- * belongs to this daemon alone, so nothing else can be on it, and a runner
5046
- * that somehow escaped its session would otherwise keep running (and keep
5047
- * billing) unattached for as long as the server lived.
5048
- */
5049
- killSession(): Promise<void>;
5050
- spawn(opts: {
5051
- taskId: string;
5052
- runner: string;
5053
- prompt: string;
5054
- modelId?: string;
5055
- thinkingEffort?: string;
5056
- modelVariants?: string[];
5057
- mode?: string;
5058
- headless?: boolean;
5059
- cwd: string;
5060
- registry?: RunnerRegistry;
5061
- planSessionId?: string;
5062
- env?: Record<string, string>;
5063
- }): Promise<ITerminalSession>;
5271
+ /** One source for the spawn options, so a runner built on this base cannot drift from the interface. */
5272
+ type RunnerSpawnOptions = Parameters<IRunner['spawn']>[0];
5273
+ declare abstract class AbstractRunnerSession implements IRunnerSession {
5274
+ id: string;
5275
+ taskId: string;
5276
+ protected exited: boolean;
5277
+ protected outputEmitter: EventEmitter<any>;
5278
+ protected exitEmitter: EventEmitter<any>;
5279
+ constructor(id: string, taskId: string);
5280
+ protected baseHandleExit(code: number): void;
5281
+ onOutput(callback: (text: string) => void): void;
5282
+ onExit(callback: (code: number) => void): void;
5283
+ abstract kill(): void;
5284
+ abstract getOutput(): string;
5285
+ abstract write(text: string): void;
5286
+ abstract turnState(): 'working' | 'idle';
5287
+ abstract onTurnEnd(listener: (reason: StructuredTurnEnd) => void): void;
5288
+ abstract onEvent(listener: (event: StructuredEvent) => void): void;
5289
+ abstract sendMessage(text: string): string;
5290
+ abstract forceSend(text: string): string;
5291
+ abstract forceSendQueued(id: string): boolean;
5292
+ abstract removeQueued(id: string): boolean;
5293
+ abstract queued(): QueuedTaskMessage[];
5294
+ abstract interrupt(): Promise<void>;
5295
+ abstract nativeSessionId(): string | null;
5296
+ abstract answerPermission(id: string, decision: ApprovalDecision): boolean;
5297
+ abstract onTaskComplete(listener: (report: TaskCompleteArgs) => void): void;
5298
+ abstract onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void;
5299
+ }
5300
+ declare abstract class AbstractRunner<S extends IRunnerSession> implements IRunner {
5301
+ protected sessions: Map<string, S>;
5302
+ get activeCount(): number;
5303
+ stop(sessionId: string): void;
5304
+ stopAll(): void;
5305
+ protected registerSession(id: string, session: S): void;
5306
+ abstract spawn(opts: RunnerSpawnOptions): Promise<IRunnerSession>;
5064
5307
  }
5065
5308
 
5066
5309
  interface StructuredRunnerDeps {
@@ -5078,26 +5321,27 @@ interface SessionLaunch {
5078
5321
  createAdapter: (runner: string, deps: AgentProcessDeps) => TaskModeAgentAdapter;
5079
5322
  startOptions: TaskStartOptions;
5080
5323
  interruptGraceMs: number;
5081
- /** Where this attempt's task tools are served, when its runner takes them (ADR-0022). */
5082
- tools?: {
5324
+ /** Where this attempt's task tools are served (ADR-0022). */
5325
+ tools: {
5083
5326
  server: OrdewellMcpServer;
5084
5327
  scope: TaskTokenScope;
5085
5328
  };
5329
+ onNotice?: (message: string) => void;
5086
5330
  }
5087
5331
  /**
5088
5332
  * One task driven over its runner's programmatic protocol (ADR-0018). It *is*
5089
- * an `ITerminalSession`, so everything downstream of `onOutput` is unchanged;
5090
- * what a terminal cannot do sits on {@link StructuredSessionCapability}.
5333
+ * an `IRunnerSession`, with plain output and structured events.
5091
5334
  *
5092
5335
  * Ordewell owns the message queue (M1). A message sent mid-turn is handed to a
5093
5336
  * runner that can take one at its next step (ADR-0023); otherwise it waits for
5094
5337
  * the turn to end rather than being typed into a runner that is busy.
5095
5338
  */
5096
- declare class StructuredSession extends AbstractTerminalSession implements StructuredSessionCapability {
5339
+ declare class StructuredSession extends AbstractRunnerSession {
5097
5340
  private readonly launch;
5098
- readonly transport: "structured";
5099
5341
  private adapter;
5100
5342
  private adapterStarted;
5343
+ private adapterAttached;
5344
+ private startupExit;
5101
5345
  /** Bumped whenever the adapter is replaced, so the old one's late events and exit are ignored. */
5102
5346
  private generation;
5103
5347
  private output;
@@ -5123,16 +5367,25 @@ declare class StructuredSession extends AbstractTerminalSession implements Struc
5123
5367
  private toolToken;
5124
5368
  private checkpointHandler;
5125
5369
  constructor(id: string, taskId: string, launch: SessionLaunch);
5126
- /** Start the runner and send the task's prompt as its first turn. */
5370
+ /**
5371
+ * Start the runner with the attempt's tools attached and send the task's
5372
+ * prompt as its first turn. `task_complete` is the only way a task reports
5373
+ * it is done, so a runner without the tools is never sent the task: it is
5374
+ * killed and respawned once, and a second failure fails the start.
5375
+ */
5127
5376
  start(prompt: string): Promise<void>;
5128
5377
  getOutput(): string;
5129
5378
  onTaskComplete(listener: (report: TaskCompleteArgs) => void): void;
5130
5379
  onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void;
5131
5380
  /**
5132
- * A server that cannot start costs the task its tools, not its run: the
5133
- * prompt still teaches the marker (ADR-0022, S2).
5381
+ * One spawn of the runner on a fresh token. Null once the runner reports
5382
+ * the server connected — or once the session ended meanwhile, which leaves
5383
+ * nothing to start; otherwise why not, with the process killed and the
5384
+ * token revoked. A runner that cannot be spawned at all throws instead:
5385
+ * spawning it again would fail the same way.
5134
5386
  */
5135
- private issueTools;
5387
+ private startAttached;
5388
+ private startWithTools;
5136
5389
  private revokeTools;
5137
5390
  protected baseHandleExit(code: number): void;
5138
5391
  /**
@@ -5222,8 +5475,7 @@ declare class StructuredSession extends AbstractTerminalSession implements Struc
5222
5475
  /**
5223
5476
  * The structured transport (ADR-0018): a task's runner as a plain child
5224
5477
  * process speaking its protocol — no tmux, no `script` (W2). Only runners
5225
- * with a task-mode connector can be spawned here; routing the rest to the
5226
- * terminal transport is the caller's decision, not a silent downgrade here.
5478
+ * with a task-mode connector can be spawned here.
5227
5479
  */
5228
5480
  declare class StructuredRunner extends AbstractRunner<StructuredSession> {
5229
5481
  private spawnCount;
@@ -5232,44 +5484,7 @@ declare class StructuredRunner extends AbstractRunner<StructuredSession> {
5232
5484
  private readonly interruptGraceMs;
5233
5485
  private readonly mcp;
5234
5486
  constructor(deps?: StructuredRunnerDeps);
5235
- spawn(opts: RunnerSpawnOptions): Promise<ITerminalSession>;
5236
- }
5237
-
5238
- interface TransportRoute {
5239
- transport: RunnerTransport;
5240
- /** Why a structured request runs on the terminal instead, in words a surface shows as is. */
5241
- fallback?: string;
5242
- }
5243
- /**
5244
- * Where one task runs (ADR-0018, S3): structured only when the plan asks for
5245
- * it and the task's runner has a task-mode connector. Anything else runs on
5246
- * the terminal, and a structured request says why.
5247
- */
5248
- declare function routeTransport(requested: RunnerTransport | undefined, runner: string, registry?: RunnerRegistry | null): TransportRoute;
5249
- /**
5250
- * The runner a host hands the orchestrator: its own terminal runner (tmux,
5251
- * headless, the VS Code terminal) and the structured one behind a single
5252
- * `ITerminalRunner`, picking per spawn by {@link routeTransport}.
5253
- *
5254
- * `stopAll` and `activeCount` reach both inner runners, so a host that shares
5255
- * one across plans (the daemon's tmux) keeps its per-plan wrapper above this.
5256
- */
5257
- declare class TransportRouter implements ITerminalRunner {
5258
- private readonly runners;
5259
- /**
5260
- * Session ids the structured runner owns; every other id is the terminal's.
5261
- * Kept past exit, so a stop that arrives late never reaches the terminal
5262
- * runner with an id it does not know.
5263
- */
5264
- private readonly structuredIds;
5265
- constructor(runners: {
5266
- terminal: ITerminalRunner;
5267
- structured: ITerminalRunner;
5268
- });
5269
- get activeCount(): number;
5270
- spawn(opts: RunnerSpawnOptions): Promise<ITerminalSession>;
5271
- stop(sessionId: string): void;
5272
- stopAll(): void;
5487
+ spawn(opts: RunnerSpawnOptions): Promise<IRunnerSession>;
5273
5488
  }
5274
5489
 
5275
5490
  /** Whether a task can be continued in its saved runner session (ADR-0018, K1), and in which. */
@@ -5282,9 +5497,8 @@ type Continuability = {
5282
5497
  };
5283
5498
  /**
5284
5499
  * The rule every surface offers Continue by and the orchestrator enforces: a
5285
- * completed or failed task whose last attempt ran structured and left a
5286
- * session id behind. A conflict is resolved by its repair, not a new turn, and
5287
- * a terminal task has no session Ordewell can reach — Retry covers both.
5500
+ * completed or failed task whose last attempt left a session id behind. A
5501
+ * conflict is resolved by its repair, not a new turn — Retry covers it.
5288
5502
  */
5289
5503
  declare function continuability(task: Task): Continuability;
5290
5504
  declare function canContinue(task: Task): boolean;
@@ -5304,8 +5518,7 @@ interface TaskLogRecorderDeps {
5304
5518
  * batches, so what a surface saw live and what it reloads are one sequence.
5305
5519
  *
5306
5520
  * It sits around the runner rather than inside the orchestrator, which stays
5307
- * unaware of logs; a terminal-transport session has no event stream and is
5308
- * passed through untouched.
5521
+ * unaware of logs.
5309
5522
  */
5310
5523
  declare class TaskLogRecorder {
5311
5524
  private readonly deps;
@@ -5313,10 +5526,130 @@ declare class TaskLogRecorder {
5313
5526
  private readonly flushMs;
5314
5527
  private readonly logger;
5315
5528
  constructor(deps: TaskLogRecorderDeps);
5316
- wrap(runner: ITerminalRunner): ITerminalRunner;
5317
- /** Start a new attempt's log for `session` and keep it until the session exits. */
5318
- record(taskId: string, session: ITerminalSession & StructuredSessionCapability): void;
5529
+ wrap(runner: IRunner): IRunner;
5530
+ /** Start a new attempt's log for `session`, opening with the skills it was given, and keep it until the session exits. */
5531
+ record(taskId: string, session: IRunnerSession, skills?: readonly TaskSkillSnapshot[]): void;
5532
+ }
5533
+
5534
+ declare function stripAnsi(text: string): string;
5535
+
5536
+ /**
5537
+ * cmd.exe's command-line buffer. A longer line is truncated rather than
5538
+ * rejected, which would corrupt a planner's system prompt or a task's prompt
5539
+ * mid-sentence and produce a confident answer to half a question — so the
5540
+ * batch route refuses instead. See {@link CommandLineTooLongError}.
5541
+ */
5542
+ declare const CMD_EXE_MAX_COMMAND_LINE = 8191;
5543
+ /**
5544
+ * CreateProcess's own ceiling, which the native and PowerShell routes are
5545
+ * bounded by instead. Windows truncates here too, so the same refusal applies —
5546
+ * it is simply four times further away.
5547
+ */
5548
+ declare const WINDOWS_MAX_COMMAND_LINE = 32767;
5549
+ interface LaunchPlan {
5550
+ /** The executable handed to `spawn()`. */
5551
+ file: string;
5552
+ /** Arguments for `file`. Pass verbatim when {@link verbatim} is set. */
5553
+ args: string[];
5554
+ /**
5555
+ * Windows batch route only: `args` is already a quoted command line and must
5556
+ * not be re-quoted. Maps to `windowsVerbatimArguments` for `spawn`.
5557
+ */
5558
+ verbatim?: boolean;
5319
5559
  }
5560
+ /** Test seam: every OS touchpoint is injectable, and production uses the defaults. */
5561
+ interface LaunchDeps {
5562
+ platform?: NodeJS.Platform;
5563
+ /** The PATH executables are looked up on. Defaults to the augmented PATH. */
5564
+ resolvePath?: () => Promise<string>;
5565
+ /** True when `candidate` names an existing file. */
5566
+ exists?: (candidate: string) => boolean;
5567
+ /** Absolute path to the Windows command interpreter. */
5568
+ comSpec?: () => string;
5569
+ /** Absolute path to Windows PowerShell. */
5570
+ powerShell?: () => string;
5571
+ /** PATHEXT, as the environment reports it. */
5572
+ pathExt?: () => string;
5573
+ }
5574
+ /**
5575
+ * Thrown when a command's arguments do not fit the buffer of the only
5576
+ * interpreter that can start it. Windows truncates rather than rejecting, and a
5577
+ * system prompt cut off mid-sentence makes the planner answer half a question
5578
+ * confidently — the silent success this repo refuses — so this is raised
5579
+ * instead. `TaskOrchestrator.startTask` catches it and holds the task, so the
5580
+ * message is what the user reads: it names the fix, because they cannot infer
5581
+ * it from a truncated prompt.
5582
+ */
5583
+ declare class CommandLineTooLongError extends Error {
5584
+ readonly command: string;
5585
+ readonly length: number;
5586
+ readonly limit: number;
5587
+ constructor(command: string, length: number, limit?: number);
5588
+ }
5589
+ /**
5590
+ * Thrown when only a batch shim resolved for a multi-line argument. cmd.exe
5591
+ * reads up to the first CR/LF and discards the rest with no error and exit code
5592
+ * 0 — quoting does not help — so the agent would get the first paragraph of its
5593
+ * prompt without the instruction to report through `task_complete`, then exit looking successful.
5594
+ */
5595
+ declare class EmbeddedNewlineError extends Error {
5596
+ readonly command: string;
5597
+ constructor(command: string);
5598
+ }
5599
+ /**
5600
+ * Thrown when `command` cannot be resolved to a real file. Kept distinguishable
5601
+ * from `WorkspaceNotFoundError` (utils/workspace) even though both a missing
5602
+ * cwd and a missing binary surface as the same `spawn` ENOENT to Node — the two
5603
+ * are checked, and named, separately so the failure names the actual cause.
5604
+ */
5605
+ declare class ExecutableNotFoundError extends Error {
5606
+ readonly command: string;
5607
+ readonly searchedPath: string;
5608
+ constructor(command: string, searchedPath: string);
5609
+ }
5610
+ /**
5611
+ * Whether `command` actually resolves to a file, given the plan
5612
+ * {@link planDirectLaunch} produced for it and the PATH it was resolved
5613
+ * against.
5614
+ *
5615
+ * POSIX is deliberately identity in `planDirectLaunch` (execvp does its own
5616
+ * PATH search), so the search is repeated here instead. Windows already did
5617
+ * the search inside `planDirectLaunch` — signalled by the returned file
5618
+ * differing from the bare command name it was given; an unresolved command
5619
+ * comes back unchanged.
5620
+ */
5621
+ declare function isExecutableResolved(command: string, plan: LaunchPlan, PATH: string, deps?: LaunchDeps): boolean;
5622
+ /** The verbatim command line cmd.exe receives after `/d /s /c`, before wrapping. */
5623
+ declare function windowsCommandLine(file: string, args: string[]): string;
5624
+ /**
5625
+ * How to start `command` with `args` through `spawn()`, with no shell.
5626
+ *
5627
+ * POSIX returns its input unchanged — execvp already searches PATH, and adding
5628
+ * a resolution step there would be a new way for a working setup to break.
5629
+ *
5630
+ * Windows resolves the command against PATH × PATHEXT, preferring a native
5631
+ * executable (spawned directly) over a batch shim (through cmd.exe) over a
5632
+ * PowerShell script shim (through `powershell.exe -File`). A command that
5633
+ * resolves to nothing is returned unchanged, so the caller's existing ENOENT —
5634
+ * which names what the user typed — is what surfaces rather than a second,
5635
+ * vaguer error from here.
5636
+ *
5637
+ * The tiers are tried in preference order and the first that *fits* wins, with
5638
+ * one deliberate exception: an overflowing batch shim does not fall through to
5639
+ * PowerShell. Overflow means a very large prompt, which is precisely where
5640
+ * `-File` argument fidelity is least worth betting on, and where a clear held
5641
+ * task beats a plausibly-mangled one. So capacity does not reorder the tiers —
5642
+ * a `.ps1` beside a too-long `.cmd` still raises.
5643
+ *
5644
+ * A line break does reorder them: cmd.exe cannot carry one at any length, so a
5645
+ * `.ps1` beside a `.cmd` wins, and a lone `.cmd` raises.
5646
+ *
5647
+ * @throws {CommandLineTooLongError} when the selected route's buffer cannot
5648
+ * carry the arguments.
5649
+ * @throws {EmbeddedNewlineError} when the arguments span lines and only the
5650
+ * batch route resolved.
5651
+ */
5652
+ declare function planDirectLaunch(command: string, args: string[], deps?: LaunchDeps): Promise<LaunchPlan>;
5320
5653
 
5321
5654
  /**
5322
5655
  * A workspace path that does not exist, or exists but is not a directory.
@@ -5435,6 +5768,8 @@ interface KillTreeDeps {
5435
5768
  unref?: () => void;
5436
5769
  };
5437
5770
  clearTimeoutImpl?: (handle: unknown) => void;
5771
+ /** Signals a pid, or a process group as its negated id. Injected so the group path needs no real processes. */
5772
+ killImpl?: (pid: number, signal: NodeJS.Signals | 0) => void;
5438
5773
  }
5439
5774
  /**
5440
5775
  * Terminate `proc` and everything it started.
@@ -5481,137 +5816,42 @@ declare function clearAugmentedPathCache(): void;
5481
5816
  */
5482
5817
  declare function withPath(base: NodeJS.ProcessEnv, resolvedPath: string, overrides?: Record<string, string>): NodeJS.ProcessEnv;
5483
5818
 
5484
- /** One shared tmux session per daemon, scoped by port so multiple daemons never collide. */
5485
- declare function tmuxSessionName(port: number): string;
5486
- /**
5487
- * Each daemon gets its own tmux *server*, not just its own session name.
5488
- *
5489
- * A plain `tmux new-session` attaches to whatever server already owns the
5490
- * default socket, and a session created on an existing server inherits that
5491
- * **server's** environment — not the environment of the process that asked for
5492
- * it (tmux only refreshes `update-environment` vars, and only on attach). So a
5493
- * daemon started with, say, a particular provider API key would silently hand
5494
- * its runners a stale key left behind by whoever started the server first,
5495
- * possibly hours earlier under a different configuration entirely.
5496
- *
5497
- * A private socket makes the server a child of this daemon, so the runner
5498
- * inherits the daemon's environment the way any child process would, and
5499
- * `kill-server` at shutdown is authoritative — a runner cannot outlive the
5500
- * daemon by hiding on a shared server. Users attaching by hand need the same
5501
- * flag: `tmux -L ordewell-<port> attach -t ordewell-<port>`.
5502
- */
5503
- declare function tmuxSocketName(port: number): string;
5504
- /**
5505
- * tmux window targeting breaks on `:` and other punctuation, so ids are
5506
- * slugged. Task ids are only unique within one plan ("task-1" is every
5507
- * planner's favourite), so the window is also scoped by the plan session id —
5508
- * without it, the second plan run in a daemon's lifetime would collide with
5509
- * the first plan's still-open windows and pipe its output into the wrong one.
5510
- */
5511
- declare function tmuxWindowName(taskId: string, planSessionId?: string): string;
5512
- type ProbeFn = () => void;
5513
- /** Feature-detects tmux the same way `HeadlessRunner` detects `script`. */
5514
- declare function hasTmux(probe?: ProbeFn): boolean;
5515
5819
  type HasBinFn = (bin: string) => boolean;
5516
5820
  /**
5517
5821
  * Shell command tmux should pipe a copy-mode selection into, so selecting text
5518
- * in a task's terminal puts it on the *system* clipboard rather than in a tmux
5822
+ * in a tmux pane puts it on the *system* clipboard rather than in a tmux
5519
5823
  * paste buffer no other application can read. Preferred over tmux's OSC 52
5520
5824
  * bridge, which plenty of emulators (VTE before 0.76, xterm without
5521
5825
  * `allowWindowOps`) drop on the floor.
5522
5826
  */
5523
5827
  declare function clipboardCopyCommand(hasBin?: HasBinFn, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string | null;
5524
5828
 
5525
- declare class FsPluginStore implements IPluginStore {
5526
- getUserPluginsDir(): string;
5527
- listUserPluginDirs(): string[];
5528
- listDir(dir: string): string[];
5529
- loadManifest(pluginDir: string): RunnerPluginManifest | null;
5530
- copyDir(sourceDir: string, destDir: string): void;
5531
- removeDir(dir: string): void;
5532
- ensureDir(dir: string): void;
5533
- writeFile(filePath: string, content: string): void;
5534
- readFile(filePath: string): string | null;
5535
- dirExists(path: string): boolean;
5536
- exists(path: string): boolean;
5537
- }
5538
-
5539
- /**
5540
- * Shape check for a manifest read off disk or out of a cloned repository. The
5541
- * name is constrained here rather than at each install route, so every store
5542
- * implementation gets the same guarantee that a manifest name is a plain
5543
- * segment and never a traversal sequence.
5544
- */
5545
- declare function isValidManifest(obj: unknown): obj is RunnerPluginManifest;
5829
+ declare function removedPluginNotice(dataDir?: string): string | undefined;
5546
5830
 
5547
- /**
5548
- * A plugin name is a single plain path segment: alphanumeric at both ends,
5549
- * dots/dashes/underscores in between. This is what keeps a manifest name from
5550
- * being a traversal sequence (`..`, `../../etc`), an absolute path, or a
5551
- * dash-prefixed string that a downstream tool reads as a flag.
5552
- */
5553
- declare const PLUGIN_NAME_PATTERN: RegExp;
5554
- declare function isPlainPluginName(name: unknown): name is string;
5555
- declare function assertPlainPluginName(name: unknown): asserts name is string;
5556
- /**
5557
- * Resolve where a plugin of this name installs, and assert the result is still
5558
- * a direct child of the plugins directory. Validation and use share this one
5559
- * call so they cannot disagree about the path being checked.
5560
- */
5561
- declare function resolvePluginInstallDir(pluginsDir: string, name: unknown): string;
5562
-
5563
- /** Hosts a plugin may be cloned from. Exact hostname match, case-insensitive. */
5564
- declare const ALLOWED_PLUGIN_HOSTS: readonly string[];
5565
- type PluginSource = {
5566
- kind: 'git';
5567
- url: string;
5568
- } | {
5569
- kind: 'path';
5570
- path: string;
5571
- };
5572
- /**
5573
- * Validate a plugin repository URL. Requires an ordinary HTTPS repository on an
5574
- * allowed host, which is also what rejects git's transport helpers (`ext::`,
5575
- * `git://`, `ssh://`, `file://`) — they never reach the clone because they are
5576
- * not the https scheme.
5577
- *
5578
- * Returns the parsed URL so callers clone the normalised form they validated.
5579
- */
5580
- declare function assertInstallablePluginUrl(url: unknown): URL;
5581
- /**
5582
- * Decide whether an install source names a remote repository or a local
5583
- * directory. Anything that carries a scheme or looks like an scp-style remote
5584
- * goes down the URL route so it is rejected as a bad URL rather than silently
5585
- * retried as a (nonexistent) directory.
5586
- */
5587
- declare function classifyPluginSource(source: string): PluginSource;
5588
-
5589
- declare function resolveArgs(manifest: RunnerPluginManifest, ctx: ResolveContext): RunnerInvocation;
5590
- /**
5591
- * The manifest's meaning of a task's mode and effort, for a runner driven over
5592
- * its programmatic protocol rather than a command line built from
5593
- * `argsTemplate` (ADR-0018, C1). The effort stays a raw id behind the same
5594
- * `{{if thinking}}` gate as the template path: how an effort reaches the
5595
- * runner is protocol, which each adapter owns.
5596
- */
5597
- declare function resolveTaskRunnerFlags(manifest: RunnerPluginManifest, ctx: Pick<ResolveContext, 'mode' | 'model' | 'thinkingEffort'>): TaskRunnerFlags;
5831
+ declare function resolveTaskRunnerFlags(manifest: RunnerManifest, ctx: {
5832
+ mode: string;
5833
+ model?: string;
5834
+ thinkingEffort?: string;
5835
+ }): TaskRunnerFlags;
5598
5836
 
5599
- declare const CLAUDE_CODE_MANIFEST: RunnerPluginManifest;
5837
+ declare const CLAUDE_CODE_MANIFEST: RunnerManifest;
5600
5838
 
5601
- declare const OPENCODE_MANIFEST: RunnerPluginManifest;
5839
+ declare const OPENCODE_MANIFEST: RunnerManifest;
5602
5840
 
5603
5841
  declare const STATE_DIR = ".ordewell";
5604
5842
  declare function ensureDir(dir: string): void;
5605
5843
  declare function getStateDir(baseDir?: string): string;
5606
5844
  /**
5607
- * A match-everything ignore rule, written *inside* the state directory. A
5608
- * `.gitignore` containing `*` inside a directory excludes that directory's
5609
- * whole contents, itself included, so plans and sessions stop being
5610
- * accidentally committable — without ever touching a file the developer wrote.
5611
- * Editing their root `.gitignore` would be the intrusive version of this.
5612
- *
5613
- * Idempotent, and never overwrites an existing file: a developer who has
5614
- * customised these rules (to commit their plans deliberately, say) keeps them.
5845
+ * Ignores the state directory's contents except this file and `skills/`, so a
5846
+ * project's skills are committed and reach every task worktree through git —
5847
+ * the bootstrap never links `.ordewell/` (ADR-0013). Plans and sessions stay
5848
+ * uncommittable without touching a file the developer wrote; editing their
5849
+ * root `.gitignore` would be the intrusive version of this.
5850
+ */
5851
+ declare const STATE_DIR_IGNORE = "*\n!.gitignore\n!skills/\n!skills/**\n";
5852
+ /**
5853
+ * Idempotent, and never overwrites a file the developer customised (to commit
5854
+ * their plans deliberately, say). Only the exact legacy rule is upgraded.
5615
5855
  */
5616
5856
  declare function ensureStateDirIgnored(baseDir?: string): void;
5617
5857
 
@@ -5664,57 +5904,6 @@ declare function sanitizeSlug(raw: string): string;
5664
5904
  /** Save the PRD to `<workspace>/.scratch/<slug>/PRD.md` (to-prd native convention). */
5665
5905
  declare function savePrdMarkdown(workspace: string, slug: string, markdown: string): string;
5666
5906
 
5667
- /** What the user last picked for one planner backend. */
5668
- interface PlannerModelChoice {
5669
- model: string;
5670
- effort?: string;
5671
- }
5672
- /**
5673
- * A candidate model as this planner's catalog lists it. Deliberately plain
5674
- * data: a harness planner's catalog is its runner's own model list and a
5675
- * vendor's is a picker list (ADR-0009), and the rule below is the same for
5676
- * both — so the caller normalizes, and this module never reaches for a
5677
- * ModelResolver, a config or the network.
5678
- */
5679
- interface PlannerModelCandidate {
5680
- id: string;
5681
- variants?: {
5682
- id: string;
5683
- }[];
5684
- }
5685
- interface PlannerModelRecall {
5686
- model: string;
5687
- effort: string;
5688
- source: 'remembered' | 'catalog-default' | 'none';
5689
- }
5690
- /** The slice of `SettingsService` this needs, so tests can pass a plain object. */
5691
- interface PlannerModelStore {
5692
- getPlannerModel(provider: string): PlannerModelChoice | undefined;
5693
- setPlannerModel(provider: string, entry: PlannerModelChoice | undefined): void;
5694
- }
5695
- /**
5696
- * Remembers, per planner backend, the model the user last chose for it.
5697
- *
5698
- * Switching the planner has to clear the model — an id is scoped to the agent
5699
- * that listed it, so an OpenRouter slug handed to Claude Code points it at
5700
- * something it cannot spawn. That clear is correct and stays; what was missing
5701
- * is that switching *back* then forced a re-pick of a model the user had
5702
- * already chosen. Both surfaces' clears and the restore now read one rule from
5703
- * here rather than three copies drifting apart.
5704
- */
5705
- declare class PlannerModelMemory {
5706
- private store;
5707
- constructor(store: PlannerModelStore);
5708
- /** Record what the user picked for `provider`; a blank model forgets it instead. */
5709
- remember(provider: string, model: string, effort?: string): void;
5710
- /**
5711
- * What the model and effort should become when `provider` takes over the
5712
- * planner, given the models it actually serves. The `source` is what the
5713
- * three surfaces word their notice from — restored, defaulted, or nothing.
5714
- */
5715
- recall(provider: string, catalog: PlannerModelCandidate[]): PlannerModelRecall;
5716
- }
5717
-
5718
5907
  /** How many AI tasks may run at once: `ORDEWELL_MAX_PARALLEL`, `/parallel`, `ordewell.maxParallelSessions`. */
5719
5908
  declare const DEFAULT_MAX_PARALLEL = 3;
5720
5909
  /**
@@ -5725,4 +5914,4 @@ declare const DEFAULT_MAX_PARALLEL = 3;
5725
5914
  */
5726
5915
  declare function parseMaxParallel(value: unknown): number | null;
5727
5916
 
5728
- export { ALLOWED_PLUGIN_HOSTS, ALL_PROVIDERS, AUTO_COMMANDS, AbstractRunner, AbstractTerminalSession, ActiveTaskSession, type Adr0013IsolationRun, type Adr0013PlanIsolation, type Adr0013TaskRecord, AgentAdapter, AgentEvent, AgentProcessDeps, AgentStartOptions, AiProvider, ApplyTaskOpsResult, ApprovalAnswer, ApprovalDecision, ApprovalMode, ApprovalPolicy, ApprovalRequest, type AskOptions, AwaitingReason, BUILTIN_SKILL_NAMES, BaseAiService, BaseConfig, BaseFileSystem, BufferedTaskOutputSource, CLAUDE_CODE_MANIFEST, CLI_PROVIDERS, CORE_PLANNER_PROMPT, type CappedRows, CheckpointAnswer, type CheckpointListener, ClaudeCodeAdapter, CliAgentAiService, type CliAgentAiServiceDeps, CodexAdapter, type CommandClassification, type CommandTier, type ComposeOptions, ConsoleLogger, ContextCollector, type Continuability, ConversationBusyError, type ConversationCompaction, ConversationEditError, type ConversationFork, ConversationMessage, type ConversationRequest, type ConversationRewind, type ConversationTurn, type ConversationVariant, DAEMON_SUBPROTOCOL, DAEMON_TOKEN_SUBPROTOCOL_PREFIX, DEFAULT_MAX_PARALLEL, DiscoveredModel, type EnvAdmission, EnvConfig, type ExecFileFn, type ExecImpl, FindSymbolOptions, FsPluginStore, GIT_READONLY_SUBCOMMANDS, GREP_PCRE_PROBE_ARGS, GeminiService, type GitExecFn, GlobOptions, type GrepInvocation, GrepOptions, type GrepRegex, type HasBinFn, HomeTranscriptReader, type IAiService, IApproval, IConfig, IFileSystem, type ILogger, type INotification, IPluginStore, ITerminalRunner, ITerminalSession, IWorktreeIsolation, IsolationHandoff, IsolationLandedTask, IsolationMergeResult, IsolationOutcome, IsolationRun, IsolationTaskRecord, IsolationTaskStatus, IsolationView, JSON_REPAIR_INSTRUCTION, type KillTreeDeps, LegacyPlanState, type LiveOutputLookup, type LiveTail, type LiveTailOptions, type MappedTool, MergeGateView, ModelResolver, type ModelResolverDeps, type ModifyPlanRequest, type NotificationAction, OPENCODE_MANIFEST, ORDEWELL_SETTABLE_ENV, OUTPUT_LINES_DEFAULT, OUTPUT_LINES_MAX, OpenAiService, OpenCodeAdapter, type OrchestratorObserver, OrchestratorOption, OrdewellMcpServer, PLUGIN_NAME_PATTERN, PROVIDER_CREDENTIAL_ENV, PROVIDER_DETECT_PRIORITY, PROVIDER_LABEL, PROVIDER_PRIORITY, PROVIDER_SHORT_LABEL, type PendingApproval, PendingApprovals, type PendingApprovalsOptions, PlanEditError, PlanIsolation, PlanParseError, type PlanRequest, PlanState, PlanStatus, PlanStore, Planner, type PlannerModelCandidate, type PlannerModelChoice, PlannerModelMemory, type PlannerModelRecall, type PlannerModelStore, type PlannerReplyClassification, type PlannerRuntimeToggles, type PlannerSubmission, PlannerToolHandler, PlannerUsage, type PluginSource, type PrdBlock, type PriorOutput, type ProbeFn, ProviderModelLists, type ProviderRegistration, QueuedMessage, QueuedTaskMessage, REFUSED_COMMANDS, ReadFileOpts, RepairEvidence, type RepairLoopOpts, type RepairVerdict, RepoGroupLayout, type ResearchChat, ResearchLogEntry, ResearchProgress, type ResearchShell, type ResearchShellDeps, ResearchToolType, type ResearchTurn, ResolveContext, type RewindTarget, RunnerApprovals, type RunnerCatalog, RunnerId, RunnerInstallation, RunnerInvocation, type RunnerMode, RunnerModeInfo, RunnerPluginManifest, RunnerRegistry, RunnerSpawnOptions, RunnerTransport, SETTINGS_ENV_ALLOWLIST, SETTINGS_ENV_REFUSED, STATE_DIR, STOPPED_TOOL_RESULT, SYMBOL_LANGUAGES, type SaveSession, Session, SessionBroadcaster, type SessionData, type SessionDeps, SessionMessage, type SessionMeta, SessionNotice, type SessionPlanner, type SessionRuntimeSettings, SettingsService, type ShellDialect, type SkillInfo, type SkillMetadata, SkillsService, type SpawnSpec, StdioAgentAdapter, StructuredEvent, StructuredRunner, type StructuredRunnerDeps, StructuredSession, StructuredSessionCapability, StructuredTurnEnd, TASK_QUERY_ANSWER_MAX_CHARS, TASK_QUERY_ANSWER_OR_OPS, TASK_QUERY_FIELDS, TASK_QUERY_PROTOCOL, TASK_QUERY_REMINDER, TRUNCATED_PLAN_REPAIR_INSTRUCTION, Task, type TaskAttemptSnapshot, TaskCompleteArgs, TaskControlError, TaskIsolation, TaskLogEvent, type TaskLogFile, type TaskLogLocation, TaskLogRecorder, type TaskLogRecorderDeps, TaskModeAgentAdapter, TaskOp, TaskOrchestrator, type TaskOrchestratorDeps, type TaskOrchestratorOptions, type TaskOutputAttempt, type TaskOutputSource, type TaskQuery, type TaskQueryCatalog, type TaskQueryField, TaskRunnerFlags, TaskSnapshot, TaskStartOptions, TaskTokenScope, TmuxRunner, type TmuxRunnerDeps, type TokenCarriers, type ToolCall, ToolOutcome, type ToolResult, type TranscriptQuery, type TranscriptReader, type TransportRoute, TransportRouter, TreeSnapshot, UsageRecord, type UserSettings, Verdict, VerdictEngine, type VerdictListener, type WorkspaceCheckDeps, WorkspaceNotAProjectError, WorkspaceNotFoundError, type WorkspaceProjectCheckDeps, type WorktreeIsolationDeps, admitSettingsEnv, applyHeadLimit, assertInstallablePluginUrl, assertPlainPluginName, assertWorkspaceExists, assertWorkspaceIsProject, augmentPromptWithPriorOutputs, augmentedPath, bearerHeaderValue, buildConflictRepairPrompt, buildConflictResolutionPrompt, buildConversationSystemPrompt, buildFallbackGrepArgs, buildGlobArgs, buildGrepArgs, buildMergePrompt, buildModifyDuringExecutionPrompt, buildModifyPlanPrompt, buildPlanWithResults, buildResearchPrompt, buildResearchToolsPrompt, buildRunnerInvocation, buildSplitPrompt, buildSubagentSystemPrompt, canContinue, clampThinkingEffort, classifyCommand, classifyPlannerReply, classifyPluginSource, clearAugmentedPathCache, clearDaemonToken, clearResearchShellCache, clearState, clipboardCopyCommand, coerceAssignments, collectDirectDependencyOutputs, composeAugmentedPrompt, composeContinuationPrompt, configuredProviders, continuability, createAiService, createSession, createSkillsService, createTaskAdapter, createTaskOrchestrator, createWorktreeIsolation, daemonTokenPath, defaultLogger, definitionPattern, defuseMarkers, deleteSession, describeMergeResult, discoverGeminiModels, effectiveAllowlist, ensureDir, ensurePrivateDir, ensureStateDirIgnored, executionLogBlock, extractPrdBlock, extractPresentedToken, filterFallbackByAnchoredInclude, filterModelsForPrompt, formatSearchOutput, generatePlanWithRepair, getLatestSession, getProviderMeta, getSettingsPath, getStateDir, globalDataDir, grantScopeFor, grepRegexFromProbe, hasTmux, idSegment, includeGlobFor, isCliProvider, isOpenAiProvider, isPlainPluginName, isValidManifest, killTree, languageForId, listSessions, listTaskLogAttempts, loadSession, loadSessionPlanState, loadState, mapAgentTool, migrateOldConfigDir, migratePlanIsolation, mintDaemonToken, mintSessionId, modelContextBlock, modifyValidationFeedback, normalizeAgentArgs, normalizeGeminiModel, openTaskLog, parseMaxParallel, parseTaskQueryJson, pendingEditRulesBlock, prefixModelId, providerForRunner, reEmitPlanPrompt, reEmitTaskOpsPrompt, reEmitTaskQueryPrompt, readDaemonToken, readTaskLog, referencePattern, renderPlanMap, renderPriorOutputs, renderTaskQueryAnswer, repairLoop, researchShellWarning, researchToolsPath, resolveArgs, resolvePluginInstallDir, resolveProviderFromPrefix, resolveResearchShell, resolveSkillInvocation, resolveTaskRunnerFlags, resolveWithin, retargetTaskRunner, routeTransport, runnerAssignment, runnerForProvider, sanitizeSlug, savePrdMarkdown, saveSession, saveState, sessionDataDir, sessionRuntimeSettings, stateExists, stripModelPrefix, summarizeOutput, supportsTaskMode, taskOpsRejectedPrompt, taskQuerySignature, textHasTaskQuery, tmuxSessionName, tmuxSocketName, tmuxWindowName, tokenSubprotocols, tokensMatch, truncatedPlanReEmitPrompt, wellKnownBinDirs, withPath, writePrivateFile };
5917
+ export { ALL_PROVIDERS, AUTO_COMMANDS, AbstractRunner, AbstractRunnerSession, ActiveTaskSession, type AdoptSessionResponse, type Adr0013IsolationRun, type Adr0013PlanIsolation, type Adr0013TaskRecord, AgentAdapter, AgentEvent, AgentProcessDeps, AgentStartOptions, AiProvider, AlreadyExecutingError, ApplyTaskOpsResult, ApprovalAnswer, type ApprovalAnswerResponse, ApprovalDecision, ApprovalMode, ApprovalPolicy, ApprovalRequest, type ApprovalsResponse, type AskOptions, AwaitingReason, BaseAiService, BaseConfig, BaseFileSystem, BufferedTaskOutputSource, CLAUDE_CODE_MANIFEST, CLI_PROVIDERS, CMD_EXE_MAX_COMMAND_LINE, CORE_PLANNER_PROMPT, type CancelPlanningResponse, type CappedRows, CheckpointAnswer, type CheckpointListener, ClaudeCodeAdapter, CliAgentAiService, type CliAgentAiServiceDeps, CodexAdapter, type CommandClassification, type CommandDescriptor, CommandLineTooLongError, type CommandTier, type CommandsResponse, type ComposeOptions, ConsoleLogger, ContextCollector, type Continuability, ConversationBusyError, type ConversationCompactResponse, type ConversationCompaction, ConversationEditError, type ConversationFork, type ConversationForkResponse, ConversationMessage, type ConversationRequest, type ConversationRewind, type ConversationRewindResponse, type ConversationTurn, type ConversationVariant, DAEMON_SUBPROTOCOL, DAEMON_TOKEN_SUBPROTOCOL_PREFIX, DEFAULT_MAX_PARALLEL, type DaemonErrorCode, DiscoveredModel, EmbeddedNewlineError, type EnvAdmission, EnvConfig, type ErrorBody, type ExecImpl, ExecutableNotFoundError, type ExecuteResponse, FindSymbolOptions, type ForceSendResponse, GIT_READONLY_SUBCOMMANDS, GREP_PCRE_PROBE_ARGS, GeminiService, type GeneratePlanResponse, type GitExecFn, GlobOptions, type GrepInvocation, GrepOptions, type GrepRegex, HARNESS_PLANNER_REASON, type HasBinFn, type IAiService, IApproval, IConfig, IFileSystem, type ILogger, type INotification, IRunner, IRunnerSession, IWorktreeIsolation, type IsolationDiffResponse, IsolationHandoff, IsolationLandedTask, type IsolationMergeResponse, IsolationMergeResult, IsolationOutcome, IsolationRun, IsolationTaskRecord, IsolationTaskStatus, IsolationView, JSON_REPAIR_INSTRUCTION, type KillTreeDeps, type LaunchDeps, type LaunchPlan, LegacyPlanState, type LiveOutputLookup, type LiveTail, type LiveTailOptions, type MappedTool, type MergeGateEntry, type MergeGateResponse, MergeGateView, ModelResolver, type ModelResolverDeps, type ModelsResponse, type ModifyPlanRequest, NoPlanError, type NotificationAction, OPENCODE_MANIFEST, ORDEWELL_SETTABLE_ENV, OUTPUT_LINES_DEFAULT, OUTPUT_LINES_MAX, type OkResponse, OpenAiService, OpenCodeAdapter, type OrchestratorObserver, OrchestratorOption, OrdewellMcpServer, PROVIDER_CREDENTIAL_ENV, PROVIDER_DETECT_PRIORITY, PROVIDER_LABEL, PROVIDER_PRIORITY, PROVIDER_SHORT_LABEL, type PendingApproval, PendingApprovals, type PendingApprovalsOptions, PlanEditError, PlanIsolation, PlanParseError, type PlanRequest, type PlanResponse, PlanState, PlanStatus, PlanStore, Planner, type PlannerBackendEntry, type PlannerModelCandidate, type PlannerModelChoice, PlannerModelMemory, type PlannerModelRecall, type PlannerModelStore, type PlannerReplyClassification, type PlannerSubmission, PlannerToolHandler, PlannerTurnDiscardedError, PlannerTurnStoppedError, type PlannerUsability, PlannerUsage, type PrdBlock, type PrdResponse, type PriorOutput, ProviderModelLists, type ProviderRegistration, QueuedMessage, QueuedTaskMessage, REFUSED_COMMANDS, ReadFileOpts, type RemoveMessageResponse, RepairEvidence, type RepairLoopOpts, type RepairVerdict, RepoGroupLayout, type ResearchChat, ResearchLogEntry, ResearchProgress, type ResearchShell, type ResearchShellDeps, ResearchToolType, type ResearchTurn, type ResolveConflictResponse, type RewindTarget, type RewindTargetsResponse, RunnerApprovals, type RunnerCatalog, RunnerId, RunnerInstallation, RunnerManifest, RunnerModeInfo, RunnerRegistry, type RunnerSpawnOptions, type RunnersResponse, SETTINGS_ENV_ALLOWLIST, SETTINGS_ENV_REFUSED, STATE_DIR, STATE_DIR_IGNORE, STOPPED_TOOL_RESULT, SYMBOL_LANGUAGES, type SaveSession, Session, SessionBroadcaster, type SessionData, type SessionDeps, type SessionListResponse, SessionMessage, type SessionMeta, SessionNotFoundError, SessionNotice, type SessionPlanner, type SessionResponse, type SessionRuntimeSettings, type SettingsResponse, SettingsService, type SettingsUpdateResponse, type ShellDialect, SkillInfo, type SkillInvocation, SkillLoad, SkillsService, type SpawnSpec, StdioAgentAdapter, type StopResponse, StructuredEvent, StructuredRunner, type StructuredRunnerDeps, StructuredSession, StructuredTurnEnd, SurfacePlan, SurfacePlanState, TASK_QUERY_ANSWER_MAX_CHARS, TASK_QUERY_ANSWER_OR_OPS, TASK_QUERY_FIELDS, TASK_QUERY_PROTOCOL, TASK_QUERY_REMINDER, TRUNCATED_PLAN_REPAIR_INSTRUCTION, Task, type TaskAttemptSnapshot, TaskCompleteArgs, TaskControlError, TaskIsolation, type TaskLogAttemptsResponse, TaskLogEvent, type TaskLogFile, type TaskLogLocation, TaskLogRecorder, type TaskLogRecorderDeps, type TaskLogResponse, type TaskMessageResponse, TaskModeAgentAdapter, TaskOp, TaskOrchestrator, type TaskOrchestratorDeps, type TaskOrchestratorOptions, type TaskOutputSource, type TaskQuery, type TaskQueryCatalog, type TaskQueryField, TaskRunnerFlags, TaskSkillSnapshot, TaskSnapshot, TaskStartOptions, TaskTokenScope, type TokenCarriers, type ToolCall, ToolOutcome, type ToolResult, TreeSnapshot, UsageRecord, type UserSettings, Verdict, VerdictEngine, type VerdictListener, WINDOWS_MAX_COMMAND_LINE, type WorkspaceCheckDeps, WorkspaceNotAProjectError, WorkspaceNotFoundError, type WorkspaceProjectCheckDeps, type WorkspacesResponse, type WorktreeIsolationDeps, abbreviateHome, admitSettingsEnv, applyHeadLimit, assertWorkspaceExists, assertWorkspaceIsProject, augmentPromptWithPriorOutputs, augmentedPath, bearerHeaderValue, buildConflictRepairPrompt, buildConflictResolutionPrompt, buildConversationSystemPrompt, buildFallbackGrepArgs, buildGlobArgs, buildGrepArgs, buildMergePrompt, buildModifyDuringExecutionPrompt, buildModifyPlanPrompt, buildPlanWithResults, buildResearchPrompt, buildResearchToolsPrompt, buildSplitPrompt, buildSubagentSystemPrompt, canContinue, clampThinkingEffort, classifyCommand, classifyPlannerReply, clearAugmentedPathCache, clearDaemonToken, clearResearchShellCache, clearState, clipboardCopyCommand, coerceAssignments, collectDirectDependencyOutputs, composeAugmentedPrompt, composeContinuationPrompt, configuredProviders, continuability, createAiService, createSession, createTaskAdapter, createWorktreeIsolation, daemonTokenPath, defaultLogger, definitionPattern, deleteSession, describeMergeResult, discoverGeminiModels, effectiveAllowlist, ensureDir, ensurePrivateDir, ensureStateDirIgnored, executionLogBlock, extractPrdBlock, extractPresentedToken, filterFallbackByAnchoredInclude, filterModelsForPrompt, formatSearchOutput, generatePlanWithRepair, getLatestSession, getProviderMeta, getSettingsPath, getStateDir, globalDataDir, grantScopeFor, grepRegexFromProbe, idSegment, includeGlobFor, isCliProvider, isExecutableResolved, isOpenAiProvider, killTree, languageForId, listSessions, listTaskLogAttempts, loadSession, loadSessionPlanState, loadState, mapAgentTool, migrateOldConfigDir, migratePlanIsolation, mintDaemonToken, mintSessionId, modelContextBlock, modifyValidationFeedback, normalizeAgentArgs, normalizeGeminiModel, openTaskLog, parseMaxParallel, parseTaskQueryJson, pendingEditRulesBlock, planDirectLaunch, plannerBackendEntries, plannerMessage, plannerTranscript, prefixModelId, providerForRunner, reEmitPlanPrompt, reEmitTaskOpsPrompt, reEmitTaskQueryPrompt, readDaemonToken, readTaskLog, referencePattern, removedPluginNotice, renderPlanMap, renderPriorOutputs, renderTaskQueryAnswer, repairLoop, researchShellWarning, researchToolsPath, resolveProviderFromPrefix, resolveResearchShell, resolveSkillInvocation, resolveTaskRunnerFlags, resolveWithin, resubmitEditPrompt, resubmitPlanPrompt, retargetTaskRunner, runnerAssignment, runnerForProvider, sanitizeSlug, savePrdMarkdown, saveSession, saveState, sessionDataDir, sessionRuntimeSettings, stateExists, stripAnsi, stripModelPrefix, summarizeOutput, supportsTaskMode, taskOpsRejectedPrompt, taskQuerySignature, textHasTaskQuery, tokenSubprotocols, tokensMatch, truncatedPlanReEmitPrompt, wellKnownBinDirs, windowsCommandLine, withPath, writePrivateFile };