@ordewell/core 0.10.11 → 0.11.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 (50) hide show
  1. package/AUTHORS.md +37 -0
  2. package/NOTICE +71 -0
  3. package/README.md +1 -1
  4. package/dist/{IFileSystem-1PqRCjhu.d.mts → IRunner-B5l9PCTv.d.ts} +926 -102
  5. package/dist/{IFileSystem-lTRL_G9Z.d.ts → IRunner-DBgDx_Mr.d.mts} +926 -102
  6. package/dist/{ModeResolver-CoJAJD-f.d.ts → ModeResolver-BDAgouuE.d.mts} +9 -2
  7. package/dist/{ModeResolver-6TQXVsde.d.mts → ModeResolver-DyXeZ9ib.d.ts} +9 -2
  8. package/dist/{Task-CfBM0wkV.d.mts → Task-BL4Idm9r.d.mts} +656 -26
  9. package/dist/{Task-CfBM0wkV.d.ts → Task-BL4Idm9r.d.ts} +656 -26
  10. package/dist/{chunk-ESKK5I4F.mjs → chunk-4QXOWEGC.mjs} +1888 -334
  11. package/dist/chunk-4QXOWEGC.mjs.map +1 -0
  12. package/dist/{chunk-KWS3VEER.mjs → chunk-MYXMIVWA.mjs} +6 -6
  13. package/dist/chunk-MYXMIVWA.mjs.map +1 -0
  14. package/dist/{chunk-PFH7WFIB.mjs → chunk-QTZ5XTBY.mjs} +228 -15
  15. package/dist/chunk-QTZ5XTBY.mjs.map +1 -0
  16. package/dist/{chunk-24EL4Z2W.mjs → chunk-YAFNYNFX.mjs} +342 -36
  17. package/dist/chunk-YAFNYNFX.mjs.map +1 -0
  18. package/dist/index.d.mts +1490 -769
  19. package/dist/index.d.ts +1490 -769
  20. package/dist/index.js +32009 -20354
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +27785 -17956
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/parsing.d.mts +2 -2
  25. package/dist/parsing.d.ts +2 -2
  26. package/dist/parsing.js +232 -25
  27. package/dist/parsing.js.map +1 -1
  28. package/dist/parsing.mjs +3 -3
  29. package/dist/plan-utils.d.mts +4 -4
  30. package/dist/plan-utils.d.ts +4 -4
  31. package/dist/plan-utils.js +1156 -96
  32. package/dist/plan-utils.js.map +1 -1
  33. package/dist/plan-utils.mjs +144 -8
  34. package/dist/{runSettingsText-Jb_efnI4.d.mts → runSettingsText-BKwWs052.d.mts} +2882 -621
  35. package/dist/{runSettingsText-C5uv2axI.d.ts → runSettingsText-CAXiTpsj.d.ts} +2882 -621
  36. package/dist/testing.d.mts +10 -4
  37. package/dist/testing.d.ts +10 -4
  38. package/dist/testing.js +30 -7
  39. package/dist/testing.js.map +1 -1
  40. package/dist/testing.mjs +26 -7
  41. package/dist/testing.mjs.map +1 -1
  42. package/package.json +7 -3
  43. package/skills/diagnose/SKILL.md +52 -0
  44. package/skills/explore/SKILL.md +65 -0
  45. package/dist/chunk-24EL4Z2W.mjs.map +0 -1
  46. package/dist/chunk-ESKK5I4F.mjs.map +0 -1
  47. package/dist/chunk-KWS3VEER.mjs.map +0 -1
  48. package/dist/chunk-NJGID6BG.mjs +0 -115
  49. package/dist/chunk-NJGID6BG.mjs.map +0 -1
  50. package/dist/chunk-PFH7WFIB.mjs.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
- import { t as IsolationMergeResult, am as Task, E as ExecutionActor, h as ExecutionRecord, i as GuardTrip, al as SupervisorLevel, g as ExecutionCommandName, G as GuardTarget, p as IsolationLandedTask, I as IWorktreeIsolation, J as IsolationTaskStatus, N as PlanIsolation, a7 as RunnerId, a2 as ResearchProgress, az as UsageRecord, a1 as ResearchLogEntry, f as DiscoveredModel, L as LegacyPlanState, a8 as RunnerSessionCost, a5 as ResearchToolType, a0 as ReportedWindow, T as PlannerUsage, at as TaskSkillSnapshot, aj as SkillsService, au as TaskSnapshot, O as PlanState, K as IsolationView, aB as Verdict } from './Task-CfBM0wkV.js';
2
- export { A as ApprovalRecord, a as AttemptUsage, b as AwaitingReason, B as BUILTIN_SKILL_NAMES, C as ContextFill, c as ConversationMessage, d as CostProvenance, D as DEFAULT_RUNNERS, e as DEFAULT_SUPERVISOR_RETRY_LIMIT, j as IntegrationDisposal, k as InvalidSkill, l as IsolationAvailability, m as IsolationHandoff, n as IsolationHandoffRepo, o as IsolationInactiveReason, q as IsolationLanding, r as IsolationMergeBlock, s as IsolationMergeBlockReason, u as IsolationOutcome, v as IsolationPruneResult, w as IsolationRemoval, x as IsolationRepo, y as IsolationRun, z as IsolationTaskRecord, F as IsolationTaskRef, H as IsolationTaskRepo, M as ModelUsage, Q as PlanStatus, R as PlanUsage, S as PlannerResendState, U as PrepareIntent, V as PreparedTask, W as PreservedWork, X as PreviousWork, Y as RefusedRemoval, Z as RepairEvidence, _ as ReplyRun, $ as RepoGroupLayout, a3 as ResearchStep, a4 as ResearchStepOutcome, a6 as RunSettings, a9 as SessionBilling, aa as SetupFailure, ab as ShadowedSkill, ac as SkillAppliesTo, ad as SkillCatalog, ae as SkillInfo, af as SkillLoad, ag as SkillLoadNotice, ah as SkillMetadata, ai as SkillSource, ap as TaskIsolation, aq as TaskIsolationState, ar as TaskModelAssignment, av as TaskStatus, aw as TaskUsageSummary, ax as TreeSnapshot, ay as UsageLine, aA as UsageTotals, aC as VerificationCheck, aD as addAttemptUsage, aE as addModelUsage, aF as addPlannerUsage, aG as addUsage, aH as addUsageTotals, aI as contextMeterText, aJ as contextMeterTitle, aK as costText, aL as countedSessionCost, aM as createEmptyPlan, aN as createSkillsService, aO as createTask, aP as endAttemptUsage, aQ as flattenTasks, aR as flattenTasksWithParents, aS as formatCost, aT as formatTokenCount, aU as isAwaitingReason, aV as isMeasured, aW as isSupervisorLevel, aX as isUserMessage, aY as migratePlanState, aZ as modelCostTitle, a_ as partedPromptUsage, a$ as pausedOnLimit, b0 as planUsage, b1 as plannerContextFill, b2 as rejoinPlanTree, b3 as resumedSessionWindow, b4 as startAttemptUsage, b5 as subagentCostTitle, b6 as taskUsageSummary, b7 as tasksTotalText, b8 as usageLine, b9 as usageText, ba as waitsForApproval } from './Task-CfBM0wkV.js';
3
- import { bv as StartAdmission, bw as StartWait, aY as ResolveAll, a8 as IWebFetcher, cp as WebReadPolicy, cn as WebRead, co as WebReadDecision, aw as PendingApprovals, K as ConversationTurn, aL as PlannerModes, a6 as IAiService, I as ConversationRequest, n as AssignmentWriter, m as AssignmentScope, c1 as TaskQueryCatalog, k as AssignmentCatalog, b7 as RunnerCatalog, bl as SessionMessage, bh as SessionBroadcaster, bn as SessionNotice, aC as PlanStore, bX as TaskOrchestrator, ab as IsolationRunController, at as OrchestratorObserver, bQ as TaskLogEvent, a7 as INotification, a2 as GlobalRunSettings, aF as PlannerConversationHost, aE as PlannerConversation, av as PendingApproval, E as ConversationCompaction, a_ as RewindTarget, b6 as RunView, b1 as RunMergeView, b_ as TaskOutputSource, cl as UserSettings, ae as LiveTailOptions, ad as LiveTail, bm as SessionMeta, aK as PlannerModelStore, aJ as PlannerModelRecall, aG as PlannerModelCandidate } from './runSettingsText-C5uv2axI.js';
4
- export { A as ACT_RISK, a as AUTORUN_RISK, b as AdoptSessionResponse, c as ApplyTaskOpsResult, d as ApprovalAnswerResponse, e as ApprovalBlock, f as ApprovalRequestMessage, g as ApprovalStatus, h as ApprovalsResponse, i as ApproveReviewResponse, j as AskOptions, l as AssignmentIssue, o as AutoMergeTrigger, B as BlockingSubject, C as CHAT_COMMANDS, p as CHECKPOINT_TRUNCATE_LENGTH, q as CancelPlanningResponse, r as CancelResumeResponse, s as CliAgentAiService, t as CliAgentAiServiceDeps, u as CommandBlock, v as CommandDescriptor, w as CommandStatus, x as CommandsResponse, y as CompactionBlock, z as ConversationBusyError, D as ConversationCompactResponse, F as ConversationEditError, G as ConversationForkResponse, H as ConversationInput, J as ConversationRewindResponse, L as ConversationView, M as DEFAULT_WEB_READ_BUDGET, N as DaemonErrorCode, O as DecideOptions, P as DiffRow, Q as DiffRowKind, R as DiffStat, S as DisplayBlock, T as EMPTY_CONVERSATION, U as EMPTY_HOLD, V as EMPTY_RUN, W as EMPTY_TASK_LOG, X as EMPTY_TASK_LOG_FOLLOW, Y as ErrorBody, Z as ExecuteResponse, _ as ExecutionSummary, $ as ForceSendResponse, a0 as GatedConversation, a1 as GeneratePlanResponse, a3 as HEADLESS_CLI_COMMANDS, a4 as HeldEdit, a5 as HeldEditMark, a9 as IsolationDiffResponse, aa as IsolationMergeResponse, ac as LiveOutputLookup, af as LocalEntry, ag as MarkdownToken, ah as MergeGateEntry, ai as MergeGateResponse, aj as MessageBlock, ak as MessageRole, al as ModelsResponse, am as NOT_STARTED, an as NO_TURN, ao as NewTaskFields, ap as NotificationAction, aq as OUTPUT_LINES_DEFAULT, ar as OUTPUT_LINES_MAX, as as OkResponse, au as OutputPreview, ax as PendingApprovalsOptions, ay as PlanBlock, az as PlanMarkerStatus, aA as PlanResponse, aB as PlanSnapshot, aD as PlannerCommandSyntax, aH as PlannerModelChoice, aI as PlannerModelMemory, aM as PlannerNotReady, aN as PlannerNotReadyError, aO as PlannerReadinessConfig, aP as PlannerSubmission, aQ as PlannerTurnDiscardedError, aR as PlannerTurnStoppedError, aS as PlannerUsageLimitError, aT as PrdResponse, aU as PromptHold, aV as RUN_SETTINGS_DEFAULTS, aX as RemoveMessageResponse, aZ as ResolveConflictResponse, a$ as RewindTargetsResponse, b0 as RunInput, b2 as RunSettingsError, b3 as RunSnapshotFrame, b4 as RunTask, b5 as RunTaskStatus, b8 as RunnerLoadedBlock, b9 as RunnersResponse, ba as STOPPED_BEFORE_START, bb as SUPERVISOR_LEVEL_LABEL, bc as SerializedConversationMessage, bd as SerializedPendingTurn, be as SerializedPlan, bf as SerializedTask, bg as SerializedTaskStatus, bi as SessionData, bj as SessionFrame, bk as SessionListResponse, bo as SessionResponse, bp as SettingsResponse, bq as SettingsService, br as SettingsUpdateResponse, bs as SkillInvocation, bt as SkillLoadBlock, bu as SkillToken, bx as StopResponse, by as StreamStamp, bz as SubagentBlock, bA as SubagentChild, bB as SubagentStatus, bC as SurfacePlan, bD as SurfacePlanState, bE as SurfaceTask, bF as TASK_QUERY_ANSWER_MAX_CHARS, bG as TASK_QUERY_ANSWER_OR_OPS, bH as TASK_QUERY_FIELDS, bI as TASK_QUERY_PROTOCOL, bJ as TASK_QUERY_REMINDER, bK as TERMINAL_UI_COMMANDS, bL as TakenPrompt, bM as TaskAttemptSnapshot, bN as TaskControlError, bO as TaskLogAttemptsResponse, bP as TaskLogBatch, bR as TaskLogFollow, bS as TaskLogFollowInput, bT as TaskLogResponse, bU as TaskLogView, bV as TaskMessageResponse, bW as TaskOp, bY as TaskOrchestratorDeps, bZ as TaskOrchestratorOptions, b$ as TaskPlacement, c0 as TaskQuery, c2 as TaskQueryField, c3 as TaskRef, c4 as TaskRowAction, c5 as TaskRowContext, c6 as TaskRowIsolation, c7 as TaskRowKind, c8 as TaskRowPlacement, c9 as TaskRowSubject, ca as TaskRowUsage, cb as TaskRowView, cc as TaskStartResponse, cd as TaskStatusKind, ce as ThinkingDisplayBlock, cf as ToolBlock, cg as ToolHeadline, ch as ToolStatus, ci as TreeTask, cj as TurnGate, ck as UsageBlock, cm as WAITS_FOR_APPROVAL_LABEL, cq as Worded, cr as WorkspacesResponse, cs as abbreviateHome, ct as aheadOfDraft, cu as applyTaskOps, cv as approvalLabel, cw as assignmentIssue, cx as assignmentIssueText, cy as automergeSummary, cz as autorunSummary, cA as awaitingLabel, cB as blockedByLabel, cC as blockingFailures, cD as canMergeTasks, cE as canSetDependencies, cF as canSplitTask, cG as capConflictFiles, cH as classifyOutcome, cI as coalesceTaskLog, cJ as createAiService, cK as dependencyCandidates, cL as dependentsNotice, cM as dependentsOf, cN as describePlannerNotReady, cO as diffRows, cP as diffStat, cQ as diffSummary, cR as digestTaskLog, cS as drainNext, cT as executionSummary, cU as followTaskLog, cV as followTurn, cW as fromTranscript, cX as getSettingsPath, cY as hasHiddenDetail, cZ as holdPrompt, c_ as isTaskRunning, c$ as loadedSkillTokens, d0 as markAction, d1 as markRequestFor, d2 as markdownHtml, d3 as markdownTokens, d4 as namedRepos, d5 as newTaskFields, d6 as opsEditable, d7 as outputLines, d8 as outputPreview, d9 as parseCount, da as parseSwitch, db as parseTaskOpsJson, dc as parseTaskQueryJson, dd as pastGateConfirmation, de as plannerMessage, df as plannerNotReady, dg as plannerNotReadyFor, dh as plannerTranscript, di as reconcileApprovals, dj as reduceConversation, dk as reduceRun, dl as reduceTaskLog, dm as renderTaskQueryAnswer, dn as replayTaskLog, dp as resolveSkillInvocation, dq as resolveTaskRef, dr as resolveUserTaskRef, ds as resumeLabel, dt as resumeTime, du as runSettingsOf, dv as runnerToolSubject, dw as serializePlan, dx as serializeTask, dy as serializeTaskStatus, dz as skillTokens, dA as skippedLabel, dB as stopTurn, dC as subtreeIds, dD as summarizeToolCall, dE as supervisorSummary, dF as surfacePlan, dG as surfacePlanState, dH as takeAt, dI as taskLogForSurface, dJ as taskOrderLabel, dK as taskPlacement, dL as taskQuerySignature, dM as taskRef, dN as taskRefOf, dO as taskRowActions, dP as taskRowView, dQ as taskStartedNotice, dR as taskStatusKind, dS as textHasTaskOps, dT as textHasTaskQuery, dU as titledRefs, dV as titledTaskRef, dW as titledTaskRefOf, dX as toTaskLogEvent, dY as toolHeadline, dZ as trimToolOutput, d_ as truncateCheckpointSummary, d$ as unsendAll, e0 as unsendLatest, e1 as walkPlan } from './runSettingsText-C5uv2axI.js';
5
- import { j as ApprovalDecision, J as IApproval, L as IFileSystem, ak as ResearchShell, aj as ReadFileOpts, aU as ToolOutcome, H as GrepOptions, B as GlobOptions, y as FindSymbolOptions, M as IRunner, K as IConfig, a9 as PlannerBackendId, af as ProviderModelLists, f as AiProvider, m as ApprovalModeSetting, ac as PlannerToolHandler, ay as RunnerRegistry, a3 as OrchestratorOption, d as AgentProcessDeps, aM as TaskModeAgentAdapter, a as AgentAdapter, e as AgentStartOptions, c as AgentEvent, ap as RunnerCommand, ad as ProjectMcpServer, N as IRunnerSession, n as ApprovalPolicy, i as ApprovalAnswer, a4 as OrdewellMcpServer, aJ as TaskCompleteArgs, r as CheckpointAnswer, aE as StructuredTurnEnd, aD as StructuredEvent, aL as TaskMessageFrom, ah as QueuedTaskMessage, aR as TaskStartOptions, aS as TaskTokenScope, at as RunnerManifest, aQ as TaskRunnerFlags } from './IFileSystem-lTRL_G9Z.js';
6
- export { A as AUTO_COMMANDS, b as AgentAdapterFactory, g as AllProviderModels, h as AnswerApprovalArgs, k as ApprovalKind, l as ApprovalMode, o as ApprovalPolicyOptions, p as ApprovalRequest, q as ApprovalSource, C as CatalogModel, s as CheckpointArgs, t as CommandClassification, u as CommandTier, v as CommandWords, D as DEFAULT_PLANNER_ALLOWLIST, w as DENY_ALL, x as DiscoveryCommand, E as EditPlanArgs, F as FetchAllProviderModelsOptions, G as GIT_READONLY_SUBCOMMANDS, z as GREP_DEFAULT_HEAD_LIMIT, I as GrepOutputMode, O as InvokedCommand, P as LineBuffer, Q as ListModelsArgs, R as ListRunnersArgs, S as LoadSkillArgs, T as McpClientConfig, U as McpCredential, V as McpTool, W as McpToolContext, X as McpToolReply, Y as MessageTaskArgs, Z as ModelCatalog, _ as ModelShortcut, $ as ORCHESTRATOR_SHORTCUTS, a0 as ORDEWELL_MCP_PATH, a1 as ORDEWELL_MCP_SERVER_NAME, a2 as OneTaskArgs, a5 as OwnerOnlyFile, a6 as PLANNER_EXECUTION_TOOLS, a7 as PLANNER_TOOLS, a8 as PlannerAllowlist, aa as PlannerStartOptions, ab as PlannerTokenScope, ae as ProviderCredentialSource, ag as ProviderModelsResult, ai as REFUSED_COMMANDS, al as ResearchShellDeps, am as RetryTaskArgs, an as RunCommandArgs, ao as RunPlanArgs, aq as RunnerCommandKind, ar as RunnerEntry, as as RunnerFeatures, au as RunnerMcpServer, av as RunnerMode, aw as RunnerModelDiscovery, ax as RunnerModelRef, az as SEARCH_EXCLUSIONS, aA as ShellDialect, aB as SpawnFn, aC as StandingDecision, aF as SubmitPlanArgs, aG as TASK_COMPLETE_TOOL, aH as TASK_COMPLETION_CONTRACT, aI as TASK_TOOLS, aK as TaskDiffArgs, aN as TaskModeUnsupportedError, aO as TaskOutputArgs, aP as TaskQueryArgs, aT as TaskToolHandler, aW as approvalScopes, aX as classifyCommand, aY as clearResearchShellCache, aZ as collectProviderCredentials, a_ as commandAllowedBy, a$ as effectiveApprovalMode, b0 as enabledRunners, b1 as fetchAllProviderModels, b2 as findRunnerCommand, b3 as isGranted, b4 as isRunnerApproval, b5 as knownModelId, b6 as mcpClientConfig, b7 as ownerOnlyConfigFile, b8 as parseAllowlist, b9 as parseApprovalModeSetting, ba as researchShellWarning, bb as researchToolsPath, bc as resolveModelShortcut, bd as resolveProvider, be as resolveResearchShell, bf as scopeAllowed, bg as sharedMcpServer, bh as toApprovalDecision, bi as toOrchestratorOptions, bj as toolAllowed } from './IFileSystem-lTRL_G9Z.js';
7
- import { R as RunnerModeInfo } from './ModeResolver-CoJAJD-f.js';
8
- export { a as autonomyLevelLabel, p as parseAutonomyLevel, r as runnerModesFrom } from './ModeResolver-CoJAJD-f.js';
1
+ import { az as RunnerId, au as ResearchProgress, b6 as UsageRecord, at as ResearchLogEntry, aS as Task, i as DiscoveredModel, I as IIsolationPreview, x as IWorktreeIsolation, $ as IsolationTaskStatus, a0 as IsolationView, a8 as PlanIsolation, a1 as LegacyPlanState, aA as RunnerSessionCost, as as ReportedWindow, ag as PlannerUsage, a$ as TaskSkillSnapshot, aO as SkillsService, E as ExecutionActor, b0 as TaskSnapshot, a9 as PlanState, b8 as Verdict, aP as StoredHandoff, p as HandoffCheck } from './Task-BL4Idm9r.js';
2
+ export { A as ApprovalRecord, a as AttemptUsage, b as AwaitingReason, B as BUILTIN_SKILL_NAMES, C as ContextCheckpoint, c as ContextFill, d as ConversationMessage, e as CostProvenance, D as DEFAULT_LOOP_ROUNDS, f as DEFAULT_RUNNERS, g as DEFAULT_SUPERVISOR_RETRY_LIMIT, h as DIGEST_FINDINGS, j as ExecutionCommandName, k as ExecutionRecord, F as FindingSeverity, G as GuardTarget, l as GuardTrip, H as HANDOFF_LIMITS, m as HANDOFF_PUSH_CHARS, n as Handoff, o as HandoffArtifact, q as HandoffCommit, r as HandoffDigest, s as HandoffEvidence, t as HandoffFinding, u as HandoffPatch, v as HandoffPreviewOptions, w as HandoffTaskName, y as IntegrationDisposal, z as InvalidSkill, J as IsolationAvailability, K as IsolationHandoff, L as IsolationHandoffRepo, M as IsolationInactiveReason, N as IsolationLandedTask, O as IsolationLanding, P as IsolationMergeBlock, Q as IsolationMergeBlockReason, R as IsolationMergeResult, S as IsolationOutcome, T as IsolationPreview, U as IsolationPruneResult, V as IsolationRemoval, W as IsolationRepo, X as IsolationRun, Y as IsolationTaskRecord, Z as IsolationTaskRef, _ as IsolationTaskRepo, a2 as LoopSubject, a3 as MAX_LOOP_ROUNDS, a4 as ModelUsage, a5 as PatchFlag, a6 as PatchView, aa as PlanStatus, ab as PlanUsage, ac as PlannerCompaction, ad as PlannerCompactionOutcome, ae as PlannerContextPause, af as PlannerResendState, ah as PrepareIntent, ai as PreparedTask, aj as PreservedWork, ak as PreviewRepo, al as PreviousWork, am as REPORT_MAX_CHARS, an as RefusedRemoval, ao as RepairEvidence, ap as ReplyRun, aq as RepoGroupLayout, ar as RepoHead, av as ResearchStep, aw as ResearchStepOutcome, ay as RunSettings, aB as SEVERITY_LABEL, aC as ScriptConsent, aD as ScriptResult, aE as SessionBilling, aF as SetupFailure, aG as ShadowedSkill, aH as SkillAppliesTo, aI as SkillCatalog, aJ as SkillInfo, aK as SkillLoad, aL as SkillLoadNotice, aM as SkillMetadata, aN as SkillSource, aR as SupervisorLevel, aV as TaskIsolation, aW as TaskIsolationState, aX as TaskLoop, aY as TaskModelAssignment, b1 as TaskStatus, b2 as TaskType, b3 as TaskUsageSummary, b4 as TreeSnapshot, b5 as UsageLine, b7 as UsageTotals, b9 as VerificationCheck, ba as addAttemptUsage, bb as addModelUsage, bc as addPlannerUsage, bd as addUsage, be as addUsageTotals, bf as artifactLines, bg as betweenRounds, bh as boundedHandoffLines, bi as byteSize, bj as checkHandoff, bk as checkOf, bl as contextMeterText, bm as contextMeterTitle, bn as costText, bo as countedSessionCost, bp as createEmptyPlan, bq as createSkillsService, br as createTask, bs as endAttemptUsage, bt as findingLines, bu as flattenTasks, bv as flattenTasksWithParents, bw as formatCost, bx as formatTokenCount, by as handoffDigest, bz as handoffLines, bA as handoffPreviewLines, bB as handoffReferenceText, bC as handoffWakeLines, bD as inLoop, bE as isAwaitingReason, bF as isMeasured, bG as isSupervisorLevel, bH as isUserMessage, bI as loopRemovalRefusal, bJ as loopRoundOf, bK as migratePlanState, bL as modelCostTitle, bM as namedHandoff, bN as openingLines, bO as outOfRounds, bP as partedPromptUsage, bQ as patchChangesText, bR as patchFlagText, bS as patchLines, bT as patchLinesText, bU as patchText, bV as patchView, bW as pausedOnLimit, bX as planUsage, bY as plannerContextFill, bZ as readableTaskIds, b_ as rejoinPlanTree, b$ as resumedSessionWindow, c0 as savedPatchOf, c1 as savedPatchView, c2 as savedPatchViews, c3 as severityLabel, c4 as startAttemptUsage, c5 as storedHandoff, c6 as subagentCostTitle, c7 as taskUsageSummary, c8 as tasksTotalText, c9 as usageLine, ca as usageText, cb as waitsForApproval, cc as withPatch, cd as worstSeverity } from './Task-BL4Idm9r.js';
3
+ import { da as TurnBudgets, b4 as PlanIntake, ao as IWebFetcher, V as ConversationTurn, bj as PlannerModes, by as ResolveAll, dj as WebReadPolicy, dg as WebRead, dh as WebReadDecision, di as WebReadOptions, b0 as PendingApprovals, al as IAiService, ba as PlannerContextDeps, bb as PlannerContextStatus, M as ContextCondenseRequest, T as ConversationRequest, bL as RunnerAuthExec, bK as RunnerAuth, b as AccountLimitBucket, c as AccountLimitSnapshot, t as AssignmentWriter, s as AssignmentScope, cS as TaskQueryCatalog, q as AssignmentCatalog, bO as RunnerCatalog, c6 as SessionMessage, b_ as SessionBroadcaster, c8 as SessionNotice, b8 as PlanStore, cM as TaskOrchestrator, at as IsolationRunController, aX as OrchestratorObserver, cB as TaskLogEvent, bx as ResearchTasks, an as INotification, ag as GlobalRunSettings, c1 as SessionHeld, c3 as SessionLease, bd as PlannerConversationHost, bc as PlannerConversation, aa as ExecutionCommand, F as CommandOutcome, a$ as PendingApproval, P as ConversationCompaction, bA as RewindTarget, bJ as RunView, bE as RunMergeView, bC as RunActivity, cP as TaskOutputSource, aw as LeaseHost, dd as UserSettings, aB as LiveTailOptions, aA as LiveTail, c7 as SessionMeta, bi as PlannerModelStore, bh as PlannerModelRecall, be as PlannerModelCandidate } from './runSettingsText-CAXiTpsj.js';
4
+ export { A as ACT_RISK, a as AUTORUN_RISK, d as AccountLimitStatus, e as AccountLimitWindow, f as AccountLimitsFrame, g as AccountLimitsMessage, h as AdoptSessionResponse, i as ApplyTaskOpsResult, j as ApprovalAnswerResponse, k as ApprovalBlock, l as ApprovalRequestMessage, m as ApprovalStatus, n as ApprovalsResponse, o as ApproveReviewResponse, p as AskOptions, r as AssignmentIssue, u as AutoMergeTrigger, B as BlockingSubject, C as CHAT_COMMANDS, v as CHECKPOINT_TRUNCATE_LENGTH, w as CancelPlanningResponse, x as CancelResumeResponse, y as CliAgentAiService, z as CliAgentAiServiceDeps, D as CommandBlock, E as CommandDescriptor, G as CommandPayload, H as CommandStatus, I as CommandsResponse, J as CompactionBlock, K as CompactionStatus, L as Consent, N as ConversationBusyError, O as ConversationCompactResponse, Q as ConversationEditError, R as ConversationForkResponse, S as ConversationInput, U as ConversationRewindResponse, W as ConversationView, X as DEFAULT_WEB_READ_BUDGET, Y as DaemonErrorCode, Z as DecideOptions, _ as DiffRow, $ as DiffRowKind, a0 as DiffStat, a1 as DisplayBlock, a2 as EMPTY_CONVERSATION, a3 as EMPTY_HOLD, a4 as EMPTY_RUN, a5 as EMPTY_TASK_LOG, a6 as EMPTY_TASK_LOG_FOLLOW, a7 as ErrorBody, a8 as ExecuteCommandResponse, a9 as ExecuteResponse, ab as ExecutionCommands, ac as ExecutionHost, ad as ExecutionSummary, ae as GatedConversation, af as GeneratePlanResponse, ah as HEADLESS_CLI_COMMANDS, ai as HandoffBlock, aj as HandoffForkRepo, ak as HandoffPeekSubject, am as IDLE_ACTIVITY, ap as IsolationDiffResponse, aq as IsolationMergeResponse, ar as IsolationPreviewNotice, as as IsolationPreviewResponse, au as LEASE_HEARTBEAT_MS, av as LEASE_STALE_MS, ax as LeaseHostKind, ay as LeaseHostOptions, az as LiveOutputLookup, aC as LocalEntry, aD as LoopCheckFacts, aE as LoopCheckState, aF as LoopGuard, aG as MappedTool, aH as MarkdownToken, aI as MergeGateEntry, aJ as MergeGateResponse, aK as MergeWordingHandoff, aL as MessageBlock, aM as MessageRole, aN as ModelsResponse, aO as NOT_STARTED, aP as NO_TURN, aQ as NewTaskFields, aR as NotificationAction, aS as OUTPUT_LINES_DEFAULT, aT as OUTPUT_LINES_MAX, aU as OkResponse, aV as OpRef, aW as OpRefusal, aY as OutputPreview, aZ as OverrideDirection, a_ as PLANNED_WITH_LABEL, b1 as PendingApprovalsOptions, b2 as PlanBlock, b3 as PlanBlocks, b5 as PlanMarkerStatus, b6 as PlanResponse, b7 as PlanSnapshot, b9 as PlannerCommandSyntax, bf as PlannerModelChoice, bg as PlannerModelMemory, bk as PlannerNotReady, bl as PlannerNotReadyError, bm as PlannerReadinessConfig, bn as PlannerSubmission, bo as PlannerTurnDiscardedError, bp as PlannerTurnStoppedError, bq as PlannerUsageLimitError, br as PrdResponse, bs as PromptHold, bt as REPORT_PREVIEW_LINES, bu as RUN_SETTINGS_DEFAULTS, bv as ReadRunnerAuthOptions, bz as ResolveConflictResponse, bB as RewindTargetsResponse, bD as RunInput, bF as RunSettingsError, bG as RunSnapshotFrame, bH as RunTask, bI as RunTaskStatus, bM as RunnerAuthExecOptions, bN as RunnerAuthExecResult, bP as RunnerLoadedBlock, bQ as RunnersResponse, bR as STOPPED_BEFORE_START, bS as SUPERVISOR_LEVELS, bT as SUPERVISOR_LEVEL_LABEL, bU as SerializedConversationMessage, bV as SerializedPendingTurn, bW as SerializedPlan, bX as SerializedTask, bY as SerializedTaskStatus, bZ as ServerInfoResponse, b$ as SessionData, c0 as SessionFrame, c2 as SessionHeldError, c4 as SessionLeaseRecord, c5 as SessionListResponse, c9 as SessionResponse, ca as SettingsResponse, cb as SettingsService, cc as SettingsUpdateResponse, cd as SkillInvocation, ce as SkillLoadBlock, cf as SkillToken, cg as StartAdmission, ch as StopResponse, ci as StreamStamp, cj as SubagentBlock, ck as SubagentChild, cl as SubagentStatus, cm as SupervisorPolicy, cn as SurfacePlan, co as SurfacePlanState, cp as SurfaceTask, cq as TASK_QUERY_ANSWER_MAX_CHARS, cr as TASK_QUERY_ANSWER_OR_OPS, cs as TASK_QUERY_FIELDS, ct as TASK_QUERY_PROTOCOL, cu as TASK_QUERY_REMINDER, cv as TERMINAL_UI_COMMANDS, cw as TakenPrompt, cx as TaskAction, cy as TaskAttemptSnapshot, cz as TaskLogAttemptsResponse, cA as TaskLogBatch, cC as TaskLogFollow, cD as TaskLogFollowInput, cE as TaskLogHeaderKind, cF as TaskLogHeaderTask, cG as TaskLogHeaderUsage, cH as TaskLogHeaderView, cI as TaskLogResponse, cJ as TaskLogView, cK as TaskMessageResponse, cL as TaskOp, cN as TaskOrchestratorDeps, cO as TaskOrchestratorOptions, cQ as TaskPlacement, cR as TaskQuery, cT as TaskQueryField, cU as TaskRef, cV as TaskRowAction, cW as TaskRowContext, cX as TaskRowHandoff, cY as TaskRowIsolation, cZ as TaskRowKind, c_ as TaskRowLoop, c$ as TaskRowPlacement, d0 as TaskRowSubject, d1 as TaskRowUsage, d2 as TaskRowView, d3 as TaskRunFacts, d4 as TaskStatusKind, d5 as ThinkingDisplayBlock, d6 as ToolBlock, d7 as ToolHeadline, d8 as ToolStatus, d9 as TreeTask, db as TurnGate, dc as UsageBlock, de as WAITS_FOR_APPROVAL_LABEL, df as WakeUpItem, dk as Worded, dl as WorkspacesResponse, dm as abbreviateHome, dn as accountLimitPercentText, dp as accountLimitWindowLabel, dq as accountLimitsOf, dr as accountLimitsText, ds as accountLimitsTitle, dt as aheadOfDraft, du as applyTaskOps, dv as approvalKindText, dw as approvalLabel, dx as approvalStatusText, dy as assignedRunnerOf, dz as assignmentIssue, dA as assignmentIssueText, dB as automergeSummary, dC as autorunSummary, dD as awaitingLabel, dE as blockNotice, dF as blockNotices, dG as blockedByLabel, dH as blockingFailures, dI as canMergeTasks, dJ as canSetDependencies, dK as canSplitTask, dL as capConflictFiles, dM as cardEditNeedsUnstarted, dN as cardEditable, dO as checkoutRule, dP as classifyOutcome, dQ as coalesceTaskLog, dR as compactionText, dS as confirmedOverride, dT as createAiService, dU as dependencyCandidates, dV as dependentsNotice, dW as dependentsOf, dX as describeHeld, dY as describeHolder, dZ as describeIsolationPreview, d_ as describeMergeResult, d$ as describePlannerNotReady, e0 as diffRows, e1 as diffStat, e2 as diffSummary, e3 as digestTaskLog, e4 as drainNext, e5 as editableWhileRunning, e6 as executionSummary, e7 as followTaskLog, e8 as followTurn, e9 as fromTranscript, ea as getSettingsPath, eb as handoffBaseLine, ec as handoffBranchName, ed as handoffFindingsText, ee as handoffPeekParts, ef as hasAccountLimits, eg as hasHiddenDetail, eh as hasLongReport, ei as holdLabel, ej as holdPrompt, el as isRepoGroup, em as isTaskRunning, en as lastServedModel, eo as loadedSkillTokens, ep as logContinues, eq as logResumes, er as loopBodyLabel, es as loopBodyRound, et as loopCheckLabel, eu as loopCheckState, ev as loopLeaveNotice, ew as loopRowParts, ex as manualOverrideConfirmation, ey as manualOverrideStopFirst, ez as mapAgentTool, eA as markAction, eB as markdownHtml, eC as markdownTokens, eD as mergeOutcome, eE as mergeTarget, eF as namedRepos, eG as newTaskFields, eH as noExecutorRefusal, eI as normalizeAgentArgs, eJ as opRefusalText, eK as opsEditable, eL as outputLines, eM as outputPreview, eN as overrideNeedsConfirmation, eO as parseCount, eP as parseSwitch, eQ as parseTaskOpsJson, eR as parseTaskQueryJson, eS as pastGateConfirmation, eT as planBlocks, eU as planMarkerParts, eV as plannerMessage, eW as plannerNotReady, eX as plannerNotReadyFor, eY as plannerTranscript, eZ as processLeaseHost, e_ as ranSealed, e$ as readRunnerAuth, f0 as receiptLabel, f1 as reconcileApprovals, f2 as recordRunnerAuth, f3 as recordedRunnerAuth, f4 as reduceConversation, f5 as reduceRun, f6 as reduceTaskLog, f7 as renderTaskQueryAnswer, f8 as repairedLanded, f9 as repairedNotice, fa as repeatingChecks, fb as replayTaskLog, fc as repoBaseLine, fd as repoResultLines, fe as reposWithWork, ff as resetRunnerAuth, fg as resolveSkillInvocation, fh as resolveTaskRef, fi as resolveUserTaskRef, fj as resumeLabel, fk as resumeTime, fl as runHoldsEditors, fm as runSettingsOf, fn as runnerToolSubject, fo as scriptExitText, fp as scriptRunText, fq as secondsText, fr as serializePlan, fs as serializeTask, ft as serializeTaskStatus, fu as skillTokens, fv as skippedLabel, fw as stopTurn, fx as subtreeIds, fy as summarizeToolCall, fz as supervisorSummary, fA as surfacePlan, fB as surfacePlanState, fC as takeAt, fD as taskActionCommand, fE as taskChatAvailable, fF as taskLogCommands, fG as taskLogForSurface, fH as taskLogHeaderView, fI as taskOrderLabel, fJ as taskPlacement, fK as taskQuerySignature, fL as taskRef, fM as taskRefOf, fN as taskRowActions, fO as taskRowView, fP as taskStartedNotice, fQ as taskStatusKind, fR as textHasTaskOps, fS as textHasTaskQuery, fT as titledRefs, fU as titledTaskRef, fV as titledTaskRefOf, fW as toTaskLogEvent, fX as toolHeadline, fY as trailerSummary, fZ as trimToolOutput, f_ as truncateCheckpointSummary, f$ as unsendAll, g0 as unsendLatest, g1 as wakeUpItems, g2 as walkPlan, g3 as withoutSealedMark, g4 as workspaceEnvOf } from './runSettingsText-CAXiTpsj.js';
5
+ import { O as IConfig, P as IFileSystem, am as PlannerToolHandler, aj as PlannerShellSandbox, aw as ResearchShell, N as IApproval, au as ReadFileOpts, bq as ToolOutcome, L as GrepOptions, b1 as ShellLaunch, K as GlobOptions, H as FindSymbolOptions, B as BashOptions, bl as TaskShellSite, bk as TaskShell, az as RuleWord, af as PlannerAllowlist, aV as ScriptBody, Q as IRunner, ag as PlannerBackendId, ap as ProviderModelLists, f as AiProvider, n as ApprovalModeSetting, aN as RunnerRegistry, a8 as OrchestratorOption, d as AgentProcessDeps, be as TaskModeAgentAdapter, e as AgentStartOptions, a as AgentAdapter, c as AgentEvent, aE as RunnerCommand$1, s as AttachState, k as ApprovalDecision, an as ProjectMcpServer, R as IRunnerSession, o as ApprovalPolicy, j as ApprovalAnswer, a9 as OrdewellMcpServer, bb as TaskCompleteArgs, aW as ScriptExit, t as CheckpointAnswer, b5 as StructuredTurnEnd, b4 as StructuredEvent, bd as TaskMessageFrom, ar as QueuedTaskMessage, bm as TaskStartOptions, bn as TaskTokenScope, a0 as McpToolReply, bp as TaskWebReads, av as ReadTaskArgs, i as ApplyPatchArgs, aI as RunnerManifest, bj as TaskRunnerFlags } from './IRunner-B5l9PCTv.js';
6
+ export { A as AUTO_COMMANDS, b as AgentAdapterFactory, g as AllProviderModels, h as AnswerApprovalArgs, l as ApprovalKind, m as ApprovalMode, p as ApprovalPolicyOptions, q as ApprovalRequest, r as ApprovalSource, C as CatalogModel, u as CheckpointArgs, v as CommandClassification, w as CommandTier, x as CommandWords, D as DEFAULT_PLANNER_ALLOWLIST, y as DEFAULT_SCRIPT_TIMEOUT_MS, z as DENY_ALL, E as DiscoveryCommand, F as EditPlanArgs, G as FetchAllProviderModelsOptions, I as GIT_READONLY_SUBCOMMANDS, J as GREP_DEFAULT_HEAD_LIMIT, M as GrepOutputMode, S as InvokedCommand, T as LineBuffer, U as ListModelsArgs, V as ListRunnersArgs, W as LoadSkillArgs, X as McpClientConfig, Y as McpCredential, Z as McpRefusal, _ as McpTool, $ as McpToolContext, a1 as MessageTaskArgs, a2 as ModelCatalog, a3 as ModelShortcut, a4 as ORCHESTRATOR_SHORTCUTS, a5 as ORDEWELL_MCP_PATH, a6 as ORDEWELL_MCP_SERVER_NAME, a7 as OneTaskArgs, aa as OwnSealedRuns, ab as OwnerOnlyFile, ac as PLANNER_EXECUTION_TOOLS, ad as PLANNER_SANDBOX_DOCS, ae as PLANNER_TOOLS, ah as PlannerSandboxAvailability, ai as PlannerSandboxDeps, ak as PlannerStartOptions, al as PlannerTokenScope, ao as ProviderCredentialSource, aq as ProviderModelsResult, as as READ_ONLY_TASK_TOOLS, at as REFUSED_COMMANDS, ax as ResearchShellDeps, ay as RetryTaskArgs, aA as RunCommandArgs, aB as RunPlanArgs, aC as RunnerAuthSpec, aD as RunnerAuthStatus, aF as RunnerCommandKind, aG as RunnerEntry, aH as RunnerFeatures, aJ as RunnerMcpServer, aK as RunnerMode, aL as RunnerModelDiscovery, aM as RunnerModelRef, aO as SEALED_LIMITS, aP as SEARCH_EXCLUSIONS, aQ as SandboxBackend, aR as SandboxInvocation, aS as SandboxLaunch, aT as SandboxLimits, aU as SandboxProfile, aX as ScriptFullOutput, aY as ScriptRun, aZ as ScriptStatus, a_ as SealedAvailability, a$ as SealedRunOptions, b0 as ShellDialect, b2 as SpawnFn, b3 as StandingDecision, b6 as SubmitPlanArgs, b7 as TASK_COMPLETE_TOOL, b8 as TASK_COMPLETION_CONTRACT, b9 as TASK_SEALED_LIMITS, ba as TASK_TOOLS, bc as TaskDiffArgs, bf as TaskModeUnsupportedError, bg as TaskOutputArgs, bh as TaskQueryArgs, bi as TaskRunCommandArgs, bo as TaskToolHandler, bs as approvalScopes, bt as classifyCommand, bu as clearPlannerSandboxCache, bv as clearResearchShellCache, bw as collectProviderCredentials, bx as commandAllowedBy, by as commandLine, bz as effectiveApprovalMode, bA as enabledRunners, bB as fetchAllProviderModels, bC as findRunnerCommand, bD as isGranted, bE as isRunnerApproval, bF as knownModelId, bG as mcpClientConfig, bH as ownerOnlyConfigFile, bI as parseAllowlist, bJ as parseApprovalModeSetting, bK as researchShellWarning, bL as researchToolsPath, bM as resolveModelShortcut, bN as resolvePlannerSandbox, bO as resolveProvider, bP as resolveResearchShell, bQ as scopeAllowed, bR as scriptBodyOf, bS as scriptBodyText, bT as scriptTaskText, bU as sharedMcpServer, bV as taskCompletionContract, bW as timeLimitText, bX as toApprovalDecision, bY as toOrchestratorOptions, bZ as toolAllowed } from './IRunner-B5l9PCTv.js';
7
+ import { R as RunnerModeInfo } from './ModeResolver-DyXeZ9ib.js';
8
+ export { a as autonomyLevelLabel, p as parseAutonomyFlag, b as parseAutonomyLevel, r as runnerModesFrom } from './ModeResolver-DyXeZ9ib.js';
9
+ import { WorkerOptions, Worker } from 'worker_threads';
9
10
  import { ChildProcess } from 'child_process';
10
11
  import { EventEmitter } from 'events';
11
12
  import 'zod/v4';
@@ -18,427 +19,195 @@ type GitExecFn = (file: string, args: string[], opts: {
18
19
  stderr: string;
19
20
  }>;
20
21
 
21
- /** One call at the command seam, as every route words it (ADR-0029). Task references are resolved here. */
22
- type ExecutionCommand = {
23
- command: 'run_plan';
24
- } | {
25
- command: 'run_task';
26
- task: string;
27
- } | {
28
- command: 'stop_task';
29
- task: string;
30
- } | {
31
- command: 'stop_run';
32
- }
33
22
  /**
34
- * `session`: continue the saved runner session with `message` (ADR-0018, K1),
35
- * or resume a task paused on a usage limit in it, the message optional;
36
- * `fresh`: a new attempt from the task's prompt.
23
+ * A message or interrupt a task cannot take: it is not running, or it is
24
+ * waiting on something a message does not answer.
25
+ * The request is wrong, not the orchestrator, so a surface says why.
37
26
  */
38
- | {
39
- command: 'retry_task';
40
- task: string;
41
- from: 'fresh' | 'session';
42
- message?: string;
43
- }
44
- /** `force`: the user's force send (ADR-0023, F1), which interrupts the running turn and is read next. */
45
- | {
46
- command: 'message_task';
47
- task: string;
48
- text: string;
49
- force?: boolean;
50
- }
51
- /** `answer` is checked here rather than typed: an `allow` must reach the seam to be refused with its reason. */
52
- | {
53
- command: 'answer_approval';
54
- task: string;
55
- approval?: string;
56
- answer: string;
57
- note?: string;
58
- }
59
- /** Merge all: the run's landed work into the user's branch (ADR-0013). */
60
- | {
61
- command: 'merge_all';
62
- }
63
- /** End a task as skipped: nothing it did lands, and its dependents go ahead without it once the work it waited on is done. `reason` is required of anyone but the user. */
64
- | {
65
- command: 'skip_task';
66
- task: string;
67
- reason?: string;
68
- }
69
- /** The user's force start: past a failed dependency or a merge gate, queued behind dependencies still under way (ADR-0035). */
70
- | {
71
- command: 'force_start';
72
- task: string;
73
- }
74
- /** The user vouches for the task's work, which lands as a passed verdict's would; never anyone else (ADR-0029). */
75
- | {
76
- command: 'mark_complete';
77
- task: string;
78
- }
79
- /** The user takes a completion back: the task is not started again, and held until it is retried or started. */
80
- | {
81
- command: 'mark_not_done';
82
- task: string;
83
- }
84
- /** Stop a structured task's running turn, keeping its session: it then waits for the user's message. */
85
- | {
86
- command: 'interrupt_task';
87
- task: string;
88
- }
89
- /** Force send a message queued behind the running turn (ADR-0023, F1); `id` is the one `message_task` answered with. */
90
- | {
91
- command: 'force_send_queued';
92
- task: string;
93
- id: string;
94
- }
95
- /** Take back a message still queued behind the running turn. */
96
- | {
97
- command: 'remove_queued';
98
- task: string;
99
- id: string;
27
+ declare class TaskControlError extends Error {
28
+ constructor(message: string);
100
29
  }
101
- /** Keep a task paused on a usage limit from resuming on its own at the reset: it waits for the user's Resume now or Retry. */
102
- | {
103
- command: 'cancel_resume';
104
- task: string;
30
+
31
+ /** Structural subset of an OpenAI-style chat message that compaction reads and rewrites. */
32
+ interface CompactableMessage {
33
+ role: string;
34
+ content?: unknown;
35
+ tool_call_id?: string;
36
+ tool_calls?: Array<{
37
+ id?: string;
38
+ function?: {
39
+ name?: string;
40
+ arguments?: string;
41
+ };
42
+ }>;
105
43
  }
106
- /** The user's answer to the checkpoint a task waits at: it goes on, or is told `reason` to act on. */
107
- | {
108
- command: 'answer_checkpoint';
109
- task: string;
110
- answer: 'approve' | 'reject';
111
- reason?: string;
44
+
45
+ interface ToolCall {
46
+ name: string;
47
+ args: Record<string, unknown>;
48
+ id?: string;
112
49
  }
113
- /** The user signs off the plan's review: every task in it is approved, as executing it approves them, and it runs. */
114
- | {
115
- command: 'approve_review';
116
- };
117
- /** What a control answers besides its line, for the surface whose button asked. */
118
- type CommandPayload =
119
- /** `message_task`: the queued message's id, which the surface's button answers with. */
120
- {
121
- kind: 'message';
122
- id: string;
50
+ interface ToolResult {
51
+ name: string;
52
+ output: string;
53
+ truncated: boolean;
54
+ totalChars: number;
55
+ id?: string;
123
56
  }
124
- /** `force_start` queued behind dependencies still under way, rather than started: the line saying so. */
125
- | {
126
- kind: 'queued';
127
- line: string;
57
+ interface ResearchTurn {
58
+ text: string;
59
+ toolCalls: ToolCall[];
60
+ hasToolCalls: boolean;
61
+ /** Reasoning/chain-of-thought captured separately from `text` so it never pollutes plan JSON. */
62
+ reasoning?: string;
63
+ /** Provider finish reason, normalized: 'length' means the output-token limit cut the reply mid-stream. */
64
+ finishReason?: string;
65
+ /** Exact prompt tokens this turn consumed, when the provider reports usage. */
66
+ promptTokens?: number;
67
+ /** The segment `text` streamed under, where the provider streams it. */
68
+ segmentId?: string;
128
69
  }
129
- /**
130
- * The user's `merge_all`, merged or stopped: what the Merge all card
131
- * renders. Whether it closed the run is the Session's `run_closed` (ADR-0013).
132
- */
133
- | {
134
- kind: 'merge';
135
- result: IsolationMergeResult;
136
- };
137
- /**
138
- * What a call came to: `message` is written for the caller, a model included,
139
- * to act on. A refusal carries a payload only where the act ran and stopped:
140
- * a Merge all that git would not take.
141
- */
142
- type CommandOutcome = {
143
- ok: true;
144
- message: string;
145
- payload?: CommandPayload;
146
- } | {
147
- ok: false;
148
- outcome: 'refused' | 'declined';
149
- message: string;
150
- payload?: CommandPayload;
151
- };
152
- /** What the seam does with a valid command: run it, ask the user first, or refuse with a note the caller can act on. */
153
- type Consent = {
154
- kind: 'allow';
155
- } | {
156
- kind: 'ask';
157
- } | {
158
- kind: 'refuse';
159
- reason: string;
160
- };
161
- /**
162
- * What a supervisor at `level` may do. The mandate (#23) replaces this check;
163
- * nothing above it moves.
164
- */
165
- type SupervisorPolicy = (level: SupervisorLevel, command: ExecutionCommandName) => Consent;
166
- /**
167
- * The supervisor levels (ADR-0029). Off: it does not act. Watch: it reads and
168
- * suggests, and every execution command it calls is refused. Act: every
169
- * command runs without asking — choosing Act is the consent — inside the loop
170
- * guards; what a command may never do, like allowing an approval, is still
171
- * refused by the command itself.
172
- */
173
- declare const SUPERVISOR_LEVELS: SupervisorPolicy;
174
- /**
175
- * A check that stops an actor spinning (ADR-0029's loop guards): the trip —
176
- * the note naming the guard and what would be accepted, and what the user is
177
- * told — or null to let the call through. Run once the call is valid and
178
- * consented to, before anyone is asked.
179
- */
180
- type LoopGuard = (actor: ExecutionActor, command: ExecutionCommand, target: GuardTarget | undefined) => GuardTrip | null;
181
- /** A runner request waiting on a task, as the caller is shown it. */
182
- interface WaitingRequest {
183
- id: string;
184
- subject: string;
70
+ interface ResearchChat {
71
+ sendMessage(text: string, signal?: AbortSignal): Promise<ResearchTurn>;
72
+ sendToolResults(results: ToolResult[], signal?: AbortSignal): Promise<ResearchTurn>;
73
+ /**
74
+ * Optional: prune older workspace reads from the history (everything else,
75
+ * and the newest rounds, are kept) so a follow-up emission has context to
76
+ * spend on output. Returns the number of characters removed.
77
+ */
78
+ compactHistory?(): number;
79
+ /**
80
+ * Whether a call's input is held: the call could not fit the model's window
81
+ * (`PlannerContextExhaustedError`), so its user message or tool results were
82
+ * kept out of the history. The next message drops a held message and files
83
+ * held results as a turn before it.
84
+ */
85
+ holdsInput?(): boolean;
86
+ /** Send the held input again, once room is made: no tool runs a second time. */
87
+ resend?(signal?: AbortSignal): Promise<ResearchTurn>;
88
+ /** Whether {@link retry} can send the held input: a window is known to compact against. */
89
+ retryable?(): boolean;
90
+ /** The user's Retry compaction: condense with their consent, then send the held input once. */
91
+ retry?(signal?: AbortSignal): Promise<ResearchTurn>;
92
+ /**
93
+ * Take `history` in place of the whole message list in one step — a summary
94
+ * swapped in for what it condensed. Refused, changing nothing, unless each
95
+ * tool result answers a call of the assistant message just before it, once.
96
+ */
97
+ reseed?(history: readonly CompactableMessage[]): void;
98
+ /**
99
+ * Optional: the answer to a call the open turn already made in rounds
100
+ * condensed out of the model's sight, in place of running it again
101
+ * (ADR-0002). Undefined for a call that runs.
102
+ */
103
+ answered?(call: ToolCall): string | undefined;
185
104
  }
186
- /**
187
- * What the seam reads and calls. The Session supplies it from the operations
188
- * behind the surfaces' buttons, so a command lands exactly as the matching
189
- * button press does, whoever asked.
190
- */
191
- interface ExecutionHost {
192
- /** The plan's task tree; null when there is no plan. */
193
- tasks(): readonly Task[] | null;
194
- /** The user approved the whole plan by executing it. */
195
- planApproved(): boolean;
196
- /** The user approved this task: it was in the plan they executed, or they started it themselves. */
197
- taskApproved(taskId: string): boolean;
198
- /** A task the scheduler leaves for the user to approve: added to the plan since they approved it (ADR-0035). */
199
- waitsForApproval(taskId: string): boolean;
200
- /** A start the user consented to approves these tasks, before it acts. */
201
- approve(taskIds: readonly string[]): void;
202
- /** Full autonomy: starts follow plan approval without a question. */
203
- autonomous(): boolean;
204
- /** The user's autorun setting, as it is now (ADR-0030). */
205
- autorun(): boolean;
206
- /** The scheduler is driving the plan: ready tasks start as slots free. */
207
- scheduling(): boolean;
208
- /** An open planner turn holds the scheduler's new starts until its reply ends. */
209
- startsHeld(): boolean;
210
- /** Work is running right now. */
211
- hasLiveWork(): boolean;
212
- /** Something will start without being asked again: a start admitted and not yet claimed, or a scheduled resume. */
213
- startsPending(): boolean;
214
- /** Admit a start that waits before it claims; see {@link StartAdmission}. */
215
- admitStart(): StartAdmission;
216
- /** The task's attempt is live: starting, running or landing. */
217
- running(taskId: string): boolean;
218
- maxParallel(): number;
219
- /** The run waits on the user to stash or skip isolation before anything starts. */
220
- runBlocked(): boolean;
221
- /**
222
- * Every reason the task cannot start now, from the scheduler's one start
223
- * rule (ADR-0034). Takes the task, not its id: one a held edit changes or
224
- * adds is judged as the edit leaves it.
225
- */
226
- startWaits(task: Readonly<Task>): readonly StartWait[];
227
- /** The runner requests waiting on a task for an answer. */
228
- waitingRequests(taskId: string): readonly WaitingRequest[];
229
- /** A start is told who asked: whether a run it opens continues the last one depends on it (ADR-0029). */
230
- runPlan(actor: ExecutionActor): Promise<void>;
231
- /**
232
- * The user's Execute while a run is under way, once it approved every task:
233
- * the run takes them in, each starting once its dependencies finish. Answers
234
- * the line that says so, naming how many had waited for approval.
235
- */
236
- joinRun(approved: number): Promise<string>;
237
- startTask(taskId: string, actor: ExecutionActor): Promise<void>;
238
- /** Force start the task; answers the line saying it waits for its dependencies, when it was queued behind them rather than started. */
239
- forceStart(taskId: string): Promise<string | undefined>;
240
- markComplete(taskId: string): Promise<void>;
241
- markNotDone(taskId: string): Promise<void>;
242
- interrupt(taskId: string): Promise<void>;
243
- stopTask(taskId: string): Promise<void>;
244
- stopRun(): void;
245
- retryTask(taskId: string, actor: ExecutionActor): Promise<void>;
246
- /** End the task as skipped, landing nothing of it; see {@link TaskOrchestrator.skipTask}. */
247
- skipTask(taskId: string, reason: string | undefined, actor: ExecutionActor): Promise<void>;
248
- continueTask(taskId: string, message: string, actor: ExecutionActor): Promise<void>;
249
- /** Resume a task paused on a usage limit now; see {@link TaskOrchestrator.resumeNow}. */
250
- resumeNow(taskId: string, message: string | undefined, actor: ExecutionActor): Promise<void>;
251
- messageTask(taskId: string, text: string): string;
252
- /** Force send (ADR-0023, F1): interrupt the running turn and deliver this next. */
253
- forceMessage(taskId: string, text: string): string;
254
- /** False once the message was delivered. */
255
- forceSendQueued(taskId: string, id: string): boolean;
256
- /** False once the message was delivered or taken back. */
257
- removeQueued(taskId: string, id: string): boolean;
258
- /** False when no resume was scheduled. */
259
- cancelResume(taskId: string): Promise<boolean>;
260
- /** The task waits at a checkpoint, and a runner is left to hear the answer. */
261
- awaitsCheckpoint(taskId: string): boolean;
262
- answerCheckpoint(taskId: string, approve: boolean, reason?: string): void;
263
- /** Sign off the review: the plan is the user's, and runs. */
264
- approveReview(): Promise<void>;
265
- /** Answer a runner's request, as its card does; false once it was answered or withdrawn. */
266
- answerRequest(requestId: string, decision: ApprovalDecision): boolean;
267
- /** Merge all, through the queue it shares with landings. */
268
- mergeAll(): Promise<IsolationMergeResult>;
269
- /** Keep the call in the timeline, worded as `text`. */
270
- record(record: ExecutionRecord, text: string): void;
271
- /** A loop guard stopped a call: the user is told, as the supervisor's message. */
272
- guardTripped(trip: GuardTrip): void;
273
- /**
274
- * The open planner turn holds an edit — to this task, or to any when none is
275
- * named — that lands only when its reply ends.
276
- */
277
- heldEdit(taskId?: string): boolean;
278
- /** The plan's task tree as the open turn's held edit leaves it: as it is, when it holds none or one that would not apply. */
279
- editedTasks(): readonly Task[];
280
- /** The id of the task the open turn's held edit makes under this batch handle; undefined when it names none. */
281
- heldHandle(name: string): string | undefined;
282
- /**
283
- * Run the command again, as the same actor, once the open turn's reply has
284
- * landed its edit; `intent` names it if it never does. `admission` is
285
- * released once it has run or been dropped.
286
- */
287
- deferUntilLanded(command: ExecutionCommand, actor: ExecutionActor, intent: string, admission: StartAdmission): void;
288
- /** A command deferred behind an edit did not run once the edit landed: the user is told, and so is the planner's next turn. */
289
- queuedNotRun(text: string): void;
290
- }
291
- interface ExecutionCommandsDeps {
292
- host: ExecutionHost;
293
- /** The approval card channel a Guarded start asks through. */
294
- consent: Pick<IApproval, 'request'>;
295
- supervisor?: SupervisorPolicy;
296
- guards?: readonly LoopGuard[];
297
- now?: () => string;
105
+ /** Everything one conversation turn needs beyond the user message itself. */
106
+ interface ConversationTurnContext {
107
+ chat: ResearchChat;
108
+ fs: IFileSystem;
109
+ runners: RunnerId[];
110
+ runnerModes?: Record<RunnerId, RunnerModeInfo[]>;
111
+ /** See {@link ConversationRequest.liveScope}. */
112
+ liveScope?: () => PlanIntake;
113
+ autonomousDefault?: boolean;
114
+ fetcher?: IWebFetcher;
115
+ /** The session's planner tools; the conversation offers their task reads and execution tools (ADR-0029) when set. */
116
+ plannerTools?: PlannerToolHandler;
298
117
  }
299
- /**
300
- * The command seam (ADR-0029, #91): the one place an execution action
301
- * reaches the scheduler, whether the user, a planner turn or the supervisor
302
- * asked. It resolves the task, checks the call against the plan's state, who
303
- * asked and the user's approval, asks the user where consent needs a person,
304
- * runs the Session operation behind the matching button, and records the call
305
- * in the timeline with who asked. A refusal names the task and the reason,
306
- * worded for whoever asked to act on ({@link voiceOf}).
307
- */
308
- declare class ExecutionCommands {
309
- private readonly deps;
310
- private readonly host;
311
- private readonly supervisor;
312
- private readonly guards;
313
- private readonly now;
314
- constructor(deps: ExecutionCommandsDeps);
118
+ declare abstract class BaseAiService {
119
+ protected config: IConfig;
120
+ protected turnBudgets?: TurnBudgets | undefined;
121
+ protected conversation: {
122
+ ctx: ConversationTurnContext;
123
+ setProgress: (fn: (p: ResearchProgress) => void) => void;
124
+ } | null;
125
+ protected activeAbort: AbortController | null;
126
+ /** See {@link compactions}. */
127
+ private compacted;
128
+ constructor(config: IConfig, turnBudgets?: TurnBudgets | undefined);
129
+ abstract reset(): void;
130
+ abstract ensureInit(): void;
131
+ hasActiveConversation(): boolean;
132
+ pruneContext(): number;
133
+ /**
134
+ * The compactions of a conversation's history that changed something, which
135
+ * the chat reports through {@link countCompaction}. A pruning stubs tool
136
+ * output alone, never the user messages a plan block rides in, but it counts
137
+ * all the same: the conversation would rather send the plan whole again than
138
+ * trust a context changed under it (ADR-0012 T10).
139
+ */
140
+ compactions(): number;
141
+ protected countCompaction(): void;
142
+ protected startAbortScope(callerSignal?: AbortSignal): AbortSignal;
143
+ protected stopAbortScope(): void;
144
+ continueConversation(userMessage: string, onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal): Promise<ConversationTurn>;
145
+ heldInputRetryable(): boolean;
146
+ /** The turn a held input was for, carried on from it: its first call is the retry, every later one as in any turn. */
147
+ retryHeldInput(onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal): Promise<ConversationTurn>;
148
+ protected abstract streamPlanText(prompt: string, repairHint: string | undefined, onToken: (token: string) => void, onReasoning?: (token: string) => void, signal?: AbortSignal): Promise<string>;
149
+ /**
150
+ * Build a fresh chat for one research subagent (own history, subagent system
151
+ * prompt, cheap model). Null means the provider does not support subagents —
152
+ * the spawn tool then degrades to a steering message. `onReasoning` streams
153
+ * live reasoning deltas on models that expose them, same as the top-level loop;
154
+ * `onUsage` takes each call's usage, as the planner's own chat reports it.
155
+ */
156
+ protected createSubagentChat(_onReasoning?: (delta: string) => void, _onUsage?: (record: UsageRecord) => void): ResearchChat | null;
157
+ /**
158
+ * One spawn_research_agent tool call, executed at the service layer (not in
159
+ * executeTool — that would cycle the imports). Every failure path returns a
160
+ * steering/failed result and the loop continues sequentially: a subagent can
161
+ * never fail a plan or a turn.
162
+ */
163
+ private executeSpawnAgent;
164
+ /** Collect project context for the planning phase. Shared with the harness backend. */
165
+ protected static collectResearchContext(fs: IFileSystem, runners: RunnerId[]): Promise<string>;
166
+ /**
167
+ * Execute one round of tool calls and report each through onProgress.
168
+ * Returns the results to feed back plus the log entries produced.
169
+ */
170
+ private executeToolCalls;
315
171
  /**
316
- * `admitted`: a deferred start's admission, taken as it was queued, which
317
- * its owner releases; a start asked for now takes its own.
318
- */
319
- run(asked: ExecutionCommand, actor: ExecutionActor, admitted?: StartAdmission): Promise<CommandOutcome>;
320
- /** A valid, consented call, from the user's answer where one is needed to the act and its record. */
321
- private carryOut;
172
+ * Countdown appended to the last tool result of the final few rounds, so the
173
+ * model lands the turn on its own terms instead of being cut off exactly at
174
+ * the budget boundary mid-exploration.
175
+ */
176
+ private static appendBudgetCountdown;
177
+ /**
178
+ * Run one planner conversation turn (ADR-0002): send the message, satisfy
179
+ * tool calls until the model answers in prose or JSON, and settle the reply
180
+ * through {@link settleReply}, which owns classification and every
181
+ * corrective retry. The model decides transitions — there are no sentinels
182
+ * and no question tags. A turn whose final text parses as a `{tasks:[...]}`
183
+ * object commits the plan; anything else is a message to the user.
184
+ */
185
+ protected runConversationTurn(ctx: ConversationTurnContext, message: string, onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal,
186
+ /** What the turn's first call is, in place of sending `message`: a held input sent again. */
187
+ firstCall?: () => Promise<ResearchTurn>): Promise<ConversationTurn>;
188
+ /** One model call of a conversation turn: the message, then tool rounds until the model replies without tools. */
189
+ private runToolRounds;
322
190
  /**
323
- * A command deferred behind the turn's edit, run now that the edit landed.
324
- * Nobody reads its answer, so one that does not run is kept as refused and
325
- * said, to the user and to the planner's next turn.
191
+ * One round's calls run, except those the chat answers as already made in
192
+ * the turn's condensed rounds (ADR-0002): each of those is answered with
193
+ * what it returned and shown as not executed, before anything runs.
194
+ * Results and steps stay in call order.
326
195
  */
327
- replay(command: ExecutionCommand, actor: ExecutionActor, admission: StartAdmission): Promise<void>;
196
+ private runRound;
328
197
  /**
329
- * A start that waits for the turn's edit is judged now, against the plan as
330
- * that edit leaves it — the edit counts as the change a retry needs — so one
331
- * that could never run is refused while the caller still reads the answer,
332
- * and one the user will be asked about says so. `located` is the task as the
333
- * plan has it now, absent for a task the edit adds.
198
+ * Run the LLM tool-calling research loop for one-shot planning. Returns
199
+ * parsed tasks if a plan was emitted mid-loop, or null if the loop
200
+ * exhausted without a valid plan.
334
201
  */
335
- private judgeAsEdited;
336
- private tripped;
337
- /** `approves` is null for a start that runs without asking, else what the user's yes would approve. */
338
- private defer;
339
- /**
340
- * The tasks a start approves, read from the plan as it stands at the
341
- * command's last check. Autorun starts a plan as the user's standing
342
- * instruction, so its start approves what it runs as the user's does; so
343
- * does a start under its exception for added tasks. A yes on the card
344
- * approves the tasks the card named, and no others.
345
- */
346
- private consented;
347
- /**
348
- * With autorun on and Full, a planner turn may start a task added after the
349
- * user approved the plan (ADR-0030). Read at each start, so switching either
350
- * off stops it at once; never on a plan the user has not approved.
351
- */
352
- private addedMayStart;
353
- /**
354
- * Guarded means "ask the user first": a planner start in the user's own
355
- * turn asks, so tasks they have not approved go into the question rather
356
- * than refusing it. The supervisor's starts stay within what the user
357
- * approved (ADR-0029).
358
- */
359
- private asksToApprove;
360
- /**
361
- * Who may do what without the mandate (#23): the user's own call needs no
362
- * consent, and their own controls are theirs alone; a planner turn may
363
- * always end or steer work, and starts it only as the autonomy level says;
364
- * the supervisor's level decides for it.
365
- */
366
- private consent;
367
- /**
368
- * Whether the command can run now, and how, judged against `tasks`: the
369
- * plan, or the plan as the turn's held edit leaves it. Read afresh after a
370
- * question, since answering takes time.
371
- */
372
- private prepare;
373
- private prepareRunPlan;
374
- /** A start of one task: `run_task`, or a fresh `retry_task`, which also takes a task that failed or waits on the user. */
375
- private prepareStart;
376
- /**
377
- * The user's force start goes past what the scheduler waits for, so only
378
- * what no start could get past is refused here; the start rule decides the
379
- * rest at the claim, and a dependency still under way queues it.
380
- */
381
- private prepareForceStart;
382
- /** Signing off the review approves every task in the plan, as executing it does: the seam records that before it acts. */
383
- private prepareApproveReview;
384
- private prepareMarkComplete;
385
- private prepareMarkNotDone;
386
- private prepareInterrupt;
387
- private prepareCancelResume;
388
- private prepareCheckpoint;
389
- private prepareContinue;
390
- /**
391
- * A pause is not a failure: the attempt stopped on its account's limit, so
392
- * its saved session goes on where it was, told the limit has reset. The
393
- * message is what to add to that; none is needed.
394
- */
395
- private prepareResume;
396
- private prepareStop;
397
- private prepareStopRun;
398
- private prepareMessage;
399
- /** A message queued behind the running turn, sent now or taken back; one delivered meanwhile is past either. */
400
- private prepareQueued;
401
- /**
402
- * The user answers a runner's request as its card does. Anyone else only
403
- * narrows or hands it on: allowing would widen the mode the plan gave the
404
- * task (ADR-0029).
405
- */
406
- private prepareAnswer;
407
- private prepareMergeAll;
408
- /**
409
- * A skip is a decision not to wait for the task. Running it again undoes
410
- * that decision, so the supervisor leaves it to the user rather than going
411
- * round between the two.
412
- */
413
- private skippedRefusal;
414
- /**
415
- * A skip ends a task without a verdict and lands nothing, so it never
416
- * vouches for work. Anyone but the user names why, and the supervisor only
417
- * gives up on a task that already needs someone: failed, blocked, or
418
- * waiting for input.
419
- */
420
- private prepareSkip;
421
- /**
422
- * Why a start is not the user's to have made, or the task it asks them to
423
- * approve, or null: the user's own call is its own approval.
424
- */
425
- private unapproved;
426
- /**
427
- * The Guarded question, on the card the user answers other planner requests
428
- * on. It names every task a yes approves, since a yes is the user's
429
- * approval of them as Execute plan would be.
430
- */
431
- private question;
432
- /** What was asked, as "start task #4 "Title"". */
433
- private intent;
434
- private refuse;
435
- private keep;
436
- private all;
437
- private located;
438
- /** A reference read against the plan as the held edit leaves it, where the handles its batch gives come first. */
439
- private locateAsEdited;
440
- /** A reference as a caller writes one — see {@link resolveTaskRef}; the user's as they type one ({@link resolveUserTaskRef}). */
441
- private locate;
202
+ protected runResearchLoop(chat: ResearchChat, firstMessage: string, fs: IFileSystem, onProgress: (progress: ResearchProgress) => void, runners: RunnerId[], initialLog?: ResearchLogEntry[], fetcher?: IWebFetcher, userGoal?: string, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean, signal?: AbortSignal): Promise<{
203
+ tasks: Task[] | null;
204
+ researchLog: ResearchLogEntry[];
205
+ researchResults: string;
206
+ }>;
207
+ protected generatePlanFallback(userDescription: string, contextStr: string, researchResults: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], onProgress: (progress: ResearchProgress) => void, researchLog?: ResearchLogEntry[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean, signal?: AbortSignal, modes?: PlannerModes): Promise<{
208
+ tasks: Task[];
209
+ researchLog: ResearchLogEntry[];
210
+ }>;
442
211
  }
443
212
 
444
213
  /**
@@ -455,9 +224,10 @@ declare class ExecutionCommands {
455
224
  */
456
225
  declare abstract class BaseFileSystem implements IFileSystem {
457
226
  private readonly resolveWebHost;
227
+ private readonly sandbox;
458
228
  private approval;
459
229
  private outboundSecrets;
460
- constructor(resolveWebHost?: ResolveAll);
230
+ constructor(resolveWebHost?: ResolveAll, sandbox?: PlannerShellSandbox);
461
231
  /**
462
232
  * The interpreter `execBashImpl` will hand the command to. Owned here rather
463
233
  * than per-adapter because {@link classifyCommand} has to be told the same
@@ -468,12 +238,20 @@ declare abstract class BaseFileSystem implements IFileSystem {
468
238
  /** Surfaces inject the human channel here; without it, external access is denied. */
469
239
  setApproval(approval: IApproval): void;
470
240
  setOutboundSecrets(secrets: () => Iterable<string>): void;
241
+ setNotice(notice: (message: string) => void): void;
242
+ endSession(): void;
243
+ canSeal(): Promise<boolean>;
471
244
  abstract getWorkspaceRoot(): string;
472
245
  protected abstract readFileImpl(absPath: string, opts?: ReadFileOpts): Promise<ToolOutcome>;
473
246
  protected abstract globImpl(pattern: string, absRoot: string, headLimit: number): Promise<ToolOutcome>;
474
247
  protected abstract grepImpl(pattern: string, absRoot: string, opts: GrepOptions): Promise<ToolOutcome>;
475
248
  protected abstract listDirImpl(absPath: string, depth: number): Promise<ToolOutcome>;
476
- protected abstract execBashImpl(command: string, signal?: AbortSignal): Promise<ToolOutcome>;
249
+ /**
250
+ * Run `command` through `shell` from `cwd`, appending it after
251
+ * `shell.args`; `shell.file: null` is `shell: true`. `cwd` is the workspace
252
+ * root, or a read-only task's working directory (ADR-0046).
253
+ */
254
+ protected abstract execBashImpl(command: string, shell: ShellLaunch, signal: AbortSignal | undefined, cwd: string): Promise<ToolOutcome>;
477
255
  /**
478
256
  * Resolve `p` and confirm the planner may touch it. In-workspace paths pass
479
257
  * silently; anything else needs one approval, remembered per containing
@@ -506,17 +284,66 @@ declare abstract class BaseFileSystem implements IFileSystem {
506
284
  */
507
285
  private outsidePaths;
508
286
  private policyOptions;
287
+ private plannerSite;
509
288
  /**
510
289
  * Three tiers (see `commandPolicy.ts`): read-only inspection runs silently,
511
290
  * anything else asks once and is remembered, and writes are refused outright
512
291
  * because a planner that mutates the workspace has stopped being a planner.
292
+ * Where the sandbox can seal a command, what the envelope would refuse
293
+ * without asking anyone runs sealed instead (ADR-0036 S2): the classifier's
294
+ * refusal, and the approval mode's. A person's denial still refuses.
513
295
  *
514
296
  * Everything a command needs — its own approval and every outside directory
515
297
  * it touches — is one request, so one command is one prompt. Each scope is
516
298
  * still granted separately: approving the pair does not approve another
517
299
  * directory the next command names.
518
300
  */
519
- bash(command: string, signal?: AbortSignal): Promise<ToolOutcome>;
301
+ bash(command: string, signal?: AbortSignal, opts?: BashOptions): Promise<ToolOutcome>;
302
+ /**
303
+ * A read-only task's `run_command` (ADR-0046): the planner's classifier,
304
+ * path confinement, allowlist and exclusions, at the task's working
305
+ * directory, asking nobody. What would ask or be refused runs sealed, the
306
+ * workspace readable and the task's worktree writable, or is refused where
307
+ * no sandbox can seal it. What the allowlist or the read-only tier runs
308
+ * runs as the planner's command would, until the shell's first sealed run;
309
+ * from then on every command runs sealed. Its scratch directory is its own.
310
+ */
311
+ taskShell({ cwd, writable, worktrees }: TaskShellSite): TaskShell;
312
+ /**
313
+ * Ordewell's own script in a read-only task's shell (ADR-0050), sealed from
314
+ * one of its worktree roots. No command rule judges it, since the task did
315
+ * not write it; it runs under the task's sealed profile and is checked
316
+ * after as the task's own sealed runs are, so git that reads a worktree a
317
+ * sealed run wrote runs only inside the seal (ADR-0046).
318
+ */
319
+ private runOwn;
320
+ private runIn;
321
+ /**
322
+ * A scope inside `command` the user denied on the card this session, or
323
+ * undefined (ADR-0036 S3). The whole command's request is peeked by the
324
+ * caller; this reaches the commands a refused wrapper hides, each remembered
325
+ * under its own scope, so wrapping a denied command never routes around the
326
+ * denial.
327
+ */
328
+ private deniedInnerScope;
329
+ /** A refusal the envelope reached without asking anyone, run sealed where this host can (ADR-0036 S2), or `refused` as it was (S6). */
330
+ private sealedInstead;
331
+ /**
332
+ * One sealed run (S1): no network, no home, nothing it writes reaching the
333
+ * workspace, and nobody asked. What the user said no to stays refused (S3):
334
+ * a `!` exclusion naming the command or a path it touches, and a denial
335
+ * given on the card this session.
336
+ */
337
+ private runSealed;
338
+ /**
339
+ * One sealed run from `cwd`, and what follows every one in a read-only
340
+ * task's shell (ADR-0046): the `.git` record checked, a planted `.git` and
341
+ * every link out of the worktree removed, and the shell stopped where that
342
+ * could not hold. `swept` says what was removed, for the result.
343
+ */
344
+ private sealedRun;
345
+ /** Every command the planner's shell starts goes through here, inside the sandbox where this host has one (ADR-0011 B1, B6). */
346
+ private runShell;
520
347
  }
521
348
 
522
349
  /** One directory entry, as the host's file API reports it. */
@@ -719,10 +546,10 @@ interface HttpWebFetcherOptions {
719
546
  cache?: WebResponseCache;
720
547
  /** Shared across sessions so the search cooldown is process-wide. */
721
548
  searchProvider?: ISearchProvider;
722
- /** False for a research subagent, which must never prompt (see {@link forSubagent}). */
549
+ /** False for a research subagent or a read-only task, which must never prompt (see {@link forSubagent}, {@link forTask}). */
723
550
  prompting?: boolean;
724
551
  }
725
- type WebReadDecider = Pick<WebReadPolicy, 'decide'> & Partial<Pick<WebReadPolicy, 'searchesUnasked'>>;
552
+ type WebReadDecider = Pick<WebReadPolicy, 'decide'> & Partial<Pick<WebReadPolicy, 'searchesUnasked' | 'remaining' | 'forTask'>>;
726
553
  declare class HttpWebFetcher implements IWebFetcher {
727
554
  private readonly policy;
728
555
  private readonly fetchImpl;
@@ -735,10 +562,20 @@ declare class HttpWebFetcher implements IWebFetcher {
735
562
  constructor(opts: HttpWebFetcherOptions);
736
563
  decide(read: WebRead): Promise<WebReadDecision>;
737
564
  private get decideOptions();
565
+ private optionsOf;
738
566
  forSubagent(): IWebFetcher;
567
+ /**
568
+ * The fetcher for one read-only task attempt (ADR-0046): a subagent's, never
569
+ * prompting, over the policy's {@link WebReadPolicy.forTask}, so the attempt
570
+ * has a budget of its own while the cache, transport and search provider,
571
+ * and with them the search cooldown, stay the session's.
572
+ */
573
+ forTask(task: string): IWebFetcher | undefined;
574
+ private nonPrompting;
739
575
  searchesUnasked(provider: string): boolean;
740
- fetch(url: string): Promise<ToolOutcome>;
741
- search(query: string): Promise<ToolOutcome>;
576
+ remaining(): number | undefined;
577
+ fetch(url: string, opts?: WebReadOptions): Promise<ToolOutcome>;
578
+ search(query: string, opts?: WebReadOptions): Promise<ToolOutcome>;
742
579
  private get;
743
580
  private render;
744
581
  private getSearchPage;
@@ -753,6 +590,106 @@ declare class HttpWebFetcher implements IWebFetcher {
753
590
  private hop;
754
591
  }
755
592
 
593
+ /**
594
+ * The daily version check (ADR-0048): one ping a day that counts installs
595
+ * under an install ID replaced every 30 days, and may answer with a newer
596
+ * version.
597
+ */
598
+ declare const VERSION_CHECK_URL = "https://ordewell.ai/api/v1/ping";
599
+ declare const VERSION_CHECK_NOTICE: string;
600
+ /** The slice of `fetch` the check uses; the platform `fetch` satisfies it. */
601
+ type PingFetch = (url: string, init: {
602
+ method: 'GET';
603
+ signal: AbortSignal;
604
+ redirect: 'manual';
605
+ }) => Promise<{
606
+ status: number;
607
+ text(): Promise<string>;
608
+ }>;
609
+ interface VersionCheckOptions {
610
+ /** The running version, sent as `v` and compared against the answer. */
611
+ version: string;
612
+ /** The one-time notice, on the first start on a machine. */
613
+ onNotice(text: string): void;
614
+ /** A newer version is out; `text` is the line to show. */
615
+ onUpdate(text: string, latest: string): void;
616
+ /** False turns the whole check off, as the off-switch variables do. */
617
+ telemetryEnabled?: boolean;
618
+ env?: Record<string, string | undefined>;
619
+ /** Where the state file lives; `globalDataDir()` by default. */
620
+ stateDir?: string;
621
+ fetch?: PingFetch;
622
+ now?: () => number;
623
+ }
624
+ interface VersionCheck {
625
+ /** Settles once no ping is in flight. Never rejects. */
626
+ settled(): Promise<void>;
627
+ /** Stops the daily re-check. */
628
+ stop(): void;
629
+ }
630
+ /**
631
+ * Starts the check for this process: the one-time notice on the first start
632
+ * on a machine, otherwise a ping if a day has passed since the last one, and
633
+ * the same test again on an unref'd timer for as long as the process lives.
634
+ * Never throws and never waits on the network.
635
+ */
636
+ declare function startVersionCheck(options: VersionCheckOptions): VersionCheck;
637
+
638
+ /**
639
+ * The effect allowlist (ADR-0035): the user's standing approval for named
640
+ * effects, beside the planner allowlist, which names reads. It pre-approves a
641
+ * script task whose `commands` are all covered; it never covers a `script`
642
+ * body, whose shell text no rule can be matched against.
643
+ *
644
+ * A rule is matched against the whole command, every word in order and flags
645
+ * included, because an effect's flags change what it does: `git push origin
646
+ * feature/*` covers `git push origin feature/login`, never the same push with
647
+ * `--force`. `*` stands for one word, or inside a word for any run of
648
+ * characters, as in the planner allowlist, except that it never stands for
649
+ * what changes the effect: a flag (`--force`, and `/force` on Windows), a
650
+ * refspec's force or delete (`+main`, `:main`), or a `:` outside a URL
651
+ * (`feature/x:main`, which pushes to `main`). The rule writes any of those
652
+ * itself. A rule spells out its operation before any `*`, so
653
+ * `az webapp config * set` is no rule at all.
654
+ *
655
+ * Exclusions are the planner allowlist's `!` entries, with any `!` entry
656
+ * written here; an exclusion wins. A command exclusion is matched as the
657
+ * planner allowlist matches it; a path or URL exclusion holds for an argument
658
+ * that names an absolute path or URL it covers, whole or after a `=`, `@`,
659
+ * `:` or `,` or a short flag (`--url=…`, `-d @…`). A relative path is not
660
+ * resolved, since the body does not say where it runs. A program named by its
661
+ * path is a file the repository ships (ADR-0026), so it is never covered.
662
+ * There are no built-in rules: a default would pre-approve an effect the user
663
+ * never chose.
664
+ */
665
+ interface EffectRule {
666
+ text: string;
667
+ binary: string;
668
+ words: RuleWord[];
669
+ }
670
+ interface EffectRules {
671
+ rules: EffectRule[];
672
+ exclusions: PlannerAllowlist;
673
+ platform: NodeJS.Platform;
674
+ /** The entries that are no rule, each with the warning that says why it is ignored. */
675
+ ignored: {
676
+ rule: string;
677
+ warning: string;
678
+ }[];
679
+ }
680
+ /**
681
+ * `entries` are the effect allowlist's; `plannerEntries` the planner allowlist
682
+ * in force, whose `!` entries hold here too; `platform` the one the commands
683
+ * run on, which decides how a flag is spelled and how a path resolves.
684
+ */
685
+ declare function parseEffectAllowlist(entries: readonly string[], plannerEntries?: readonly string[], platform?: NodeJS.Platform): EffectRules;
686
+ /**
687
+ * The rules, by name, that cover every command of a `commands` body, or null
688
+ * when one command is not covered, an exclusion holds for one, or the body is
689
+ * a `script`.
690
+ */
691
+ declare function effectRuleFor(list: EffectRules, body: ScriptBody): string | null;
692
+
756
693
  /**
757
694
  * Carries a structured task's tool requests to the session's one approval
758
695
  * seam (ADR-0018, A1), so a runner's request is answered through
@@ -1004,8 +941,11 @@ declare abstract class BaseConfig implements IConfig {
1004
941
  get conflictRepairAttempts(): number;
1005
942
  get approvalMode(): ApprovalModeSetting;
1006
943
  get webReadBudget(): number;
944
+ /** The native-shell opt-in (ADR-0036 N1): off unless the launching environment says on. Never a workspace variable. */
945
+ get plannerNativeShell(): boolean;
1007
946
  get approvalDefaults(): boolean;
1008
947
  get approvalPreApproved(): string[];
948
+ get effectAllowlist(): string[];
1009
949
  /**
1010
950
  * Who plans when no surface setting names a planner: an explicit
1011
951
  * AI_PROVIDER, then a vendor whose key is in the environment,
@@ -1084,18 +1024,6 @@ declare class EnvConfig extends BaseConfig {
1084
1024
  setProviderModelLists(): void;
1085
1025
  }
1086
1026
 
1087
- /**
1088
- * How "Merge all" went, in words every surface shares. `group` is whether the
1089
- * run spans more than a lone repo at `.`; only a merge into every repository
1090
- * has something to say about that. `repaired` names the landed tasks that only
1091
- * landed after a conflict repair (ADR-0015), so a clean-looking merge still
1092
- * points review at what changed to get there.
1093
- */
1094
- declare function describeMergeResult(result: IsolationMergeResult, branch: string, group: boolean, repaired?: IsolationLandedTask[]): {
1095
- level: 'info' | 'warn' | 'error';
1096
- message: string;
1097
- };
1098
-
1099
1027
  interface WorktreeIsolationDeps {
1100
1028
  config: Pick<IConfig, 'worktreeIsolation' | 'worktreeSetupCommand' | 'workspaceRepos' | 'worktreeLinks'>;
1101
1029
  /** Test seam: every git invocation goes through this. */
@@ -1103,7 +1031,11 @@ interface WorktreeIsolationDeps {
1103
1031
  resolvePath?: () => Promise<string>;
1104
1032
  platform?: NodeJS.Platform;
1105
1033
  mintRunId?: () => string;
1034
+ /** Whether the commits Ordewell writes end with the Planned-with trailer; read as each is written, so a change reaches the next landing. */
1035
+ plannedWith?: () => boolean;
1106
1036
  }
1037
+ /** The preview alone, for a surface that shows isolation without ever running it. */
1038
+ declare function createIsolationPreview(deps: Pick<WorktreeIsolationDeps, 'config' | 'execFileImpl' | 'resolvePath'>): IIsolationPreview;
1107
1039
  declare function createWorktreeIsolation(deps: WorktreeIsolationDeps): IWorktreeIsolation;
1108
1040
 
1109
1041
  /** A task record as ADR-0013 persisted it, for one repository. */
@@ -1136,35 +1068,17 @@ interface Adr0013PlanIsolation {
1136
1068
  * off exactly as it would have.
1137
1069
  */
1138
1070
  declare function migratePlanIsolation(state: PlanIsolation | Adr0013PlanIsolation): PlanIsolation;
1139
-
1140
- /** The part of a run's handoff a Merge confirmation is worded from; structural, so each surface passes the handoff it holds. */
1141
- interface MergeWordingHandoff {
1142
- repos: readonly {
1143
- path: string;
1144
- baseBranch?: string;
1145
- landed: readonly unknown[];
1146
- }[];
1147
- }
1148
- /**
1149
- * Whether the run spans more than a lone repo at the workspace root. A group of
1150
- * one at `.` is worded and drawn exactly as it was before repo groups; anything
1151
- * else names its repos.
1152
- */
1153
- declare function isRepoGroup(handoff: {
1154
- repos: readonly {
1155
- path: string;
1156
- }[];
1157
- }): boolean;
1158
- /** The repos Merge all will merge: those with work on their integration branch, else all of them. */
1159
- declare function reposWithWork(handoff: MergeWordingHandoff): string[];
1160
1071
  /**
1161
- * What a Merge confirmation says the branch lands on: the branch the run
1162
- * started on, which Merge all insists is checked out. A repo that started
1163
- * detached has none, and keeps the old wording.
1072
+ * What a persisted plan says about its isolated run, as a surface shows it: the
1073
+ * handoff and each task's mark. `null` when the payload carries no record — a
1074
+ * planner reply, or a plan that never isolated — which says nothing either way.
1075
+ *
1076
+ * This is the one reading of a saved record outside the daemon: the migration
1077
+ * from the ADR-0013 shape happens here, so no surface grows its own parser.
1078
+ * `repairLimit` is server config a saved plan never carries; without it the
1079
+ * repair attempt is left for a live status update to fill in.
1164
1080
  */
1165
- declare function mergeTarget(handoff: MergeWordingHandoff): string;
1166
- /** The refusal Merge all makes when a repo is on another branch, for a confirmation to warn of; empty when no repo named a branch. */
1167
- declare function checkoutRule(handoff: MergeWordingHandoff): string;
1081
+ declare function isolationViewOfPlan(plan: unknown, repairLimit?: number): IsolationView | null;
1168
1082
 
1169
1083
  interface ILogger {
1170
1084
  warn(scope: string, message: string, err?: unknown): void;
@@ -1174,139 +1088,49 @@ declare class ConsoleLogger implements ILogger {
1174
1088
  }
1175
1089
  declare const defaultLogger: ILogger;
1176
1090
 
1177
- interface ToolCall {
1178
- name: string;
1179
- args: Record<string, unknown>;
1180
- id?: string;
1181
- }
1182
- interface ToolResult {
1183
- name: string;
1184
- output: string;
1185
- truncated: boolean;
1186
- totalChars: number;
1187
- id?: string;
1091
+ /** What a Gemini planner is built with beyond its config. */
1092
+ interface GeminiServiceDeps extends PlannerContextDeps {
1093
+ turnBudgets?: TurnBudgets;
1188
1094
  }
1189
- interface ResearchTurn {
1190
- text: string;
1191
- toolCalls: ToolCall[];
1192
- hasToolCalls: boolean;
1193
- /** Reasoning/chain-of-thought captured separately from `text` so it never pollutes plan JSON. */
1194
- reasoning?: string;
1195
- /** Provider finish reason, normalized: 'length' means the output-token limit cut the reply mid-stream. */
1196
- finishReason?: string;
1197
- /** Exact prompt tokens this turn consumed, when the provider reports usage — drives proactive compaction. */
1198
- promptTokens?: number;
1199
- /** The segment `text` streamed under, where the provider streams it. */
1200
- segmentId?: string;
1201
- }
1202
- interface ResearchChat {
1203
- sendMessage(text: string, signal?: AbortSignal): Promise<ResearchTurn>;
1204
- sendToolResults(results: ToolResult[], signal?: AbortSignal): Promise<ResearchTurn>;
1205
- /**
1206
- * Optional: prune bulky raw tool outputs from the history in place (subagent
1207
- * digests are kept) so a follow-up emission has context to spend on output.
1208
- * Returns the number of characters removed. Providers whose SDK owns the
1209
- * history (Gemini) may not support it.
1210
- */
1211
- compactHistory?(): number;
1212
- }
1213
- /** Everything one conversation turn needs beyond the user message itself. */
1214
- interface ConversationTurnContext {
1215
- chat: ResearchChat;
1216
- fs: IFileSystem;
1217
- runners: RunnerId[];
1218
- runnerModes?: Record<RunnerId, RunnerModeInfo[]>;
1219
- autonomousDefault?: boolean;
1220
- fetcher?: IWebFetcher;
1221
- /** The session's planner tools; the conversation offers their task reads and execution tools (ADR-0029) when set. */
1222
- plannerTools?: PlannerToolHandler;
1223
- }
1224
- declare abstract class BaseAiService {
1225
- protected config: IConfig;
1226
- protected conversation: {
1227
- ctx: ConversationTurnContext;
1228
- setProgress: (fn: (p: ResearchProgress) => void) => void;
1229
- } | null;
1230
- protected activeAbort: AbortController | null;
1231
- constructor(config: IConfig);
1232
- abstract reset(): void;
1233
- abstract ensureInit(): void;
1234
- hasActiveConversation(): boolean;
1235
- pruneContext(): number;
1236
- protected startAbortScope(callerSignal?: AbortSignal): AbortSignal;
1237
- protected stopAbortScope(): void;
1238
- continueConversation(userMessage: string, onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal): Promise<ConversationTurn>;
1239
- protected abstract streamPlanText(prompt: string, repairHint: string | undefined, onToken: (token: string) => void, onReasoning?: (token: string) => void, signal?: AbortSignal): Promise<string>;
1240
- /**
1241
- * Build a fresh chat for one research subagent (own history, subagent system
1242
- * prompt, cheap model). Null means the provider does not support subagents —
1243
- * the spawn tool then degrades to a steering message. `onReasoning` streams
1244
- * live reasoning deltas on models that expose them, same as the top-level loop;
1245
- * `onUsage` takes each call's usage, as the planner's own chat reports it.
1246
- */
1247
- protected createSubagentChat(_onReasoning?: (delta: string) => void, _onUsage?: (record: UsageRecord) => void): ResearchChat | null;
1248
- /**
1249
- * One spawn_research_agent tool call, executed at the service layer (not in
1250
- * executeTool — that would cycle the imports). Every failure path returns a
1251
- * steering/failed result and the loop continues sequentially: a subagent can
1252
- * never fail a plan or a turn.
1253
- */
1254
- private executeSpawnAgent;
1255
- /** Collect project context for the planning phase. Shared with the harness backend. */
1256
- protected static collectResearchContext(fs: IFileSystem, runners: RunnerId[]): Promise<string>;
1257
- /**
1258
- * Execute one round of tool calls and report each through onProgress.
1259
- * Returns the results to feed back plus the log entries produced.
1260
- */
1261
- private executeToolCalls;
1262
- /**
1263
- * Countdown appended to the last tool result of the final few rounds, so the
1264
- * model lands the turn on its own terms instead of being cut off exactly at
1265
- * the budget boundary mid-exploration.
1266
- */
1267
- private static appendBudgetCountdown;
1268
- /**
1269
- * Run one planner conversation turn (ADR-0002): send the message, satisfy
1270
- * tool calls until the model answers in prose or JSON, and settle the reply
1271
- * through {@link settleReply}, which owns classification and every
1272
- * corrective retry. The model decides transitions — there are no sentinels
1273
- * and no question tags. A turn whose final text parses as a `{tasks:[...]}`
1274
- * object commits the plan; anything else is a message to the user.
1275
- */
1276
- protected runConversationTurn(ctx: ConversationTurnContext, message: string, onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal): Promise<ConversationTurn>;
1277
- /** One model call of a conversation turn: the message, then tool rounds until the model replies without tools. */
1278
- private runToolRounds;
1279
- /**
1280
- * Run the LLM tool-calling research loop for one-shot planning. Returns
1281
- * parsed tasks if a plan was emitted mid-loop, or null if the loop
1282
- * exhausted without a valid plan.
1283
- */
1284
- protected runResearchLoop(chat: ResearchChat, firstMessage: string, fs: IFileSystem, onProgress: (progress: ResearchProgress) => void, runners: RunnerId[], initialLog?: ResearchLogEntry[], fetcher?: IWebFetcher, userGoal?: string, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean, signal?: AbortSignal): Promise<{
1285
- tasks: Task[] | null;
1286
- researchLog: ResearchLogEntry[];
1287
- researchResults: string;
1288
- }>;
1289
- protected generatePlanFallback(userDescription: string, contextStr: string, researchResults: string, modelsByRunner: Partial<Record<RunnerId, DiscoveredModel[]>>, runners: RunnerId[], onProgress: (progress: ResearchProgress) => void, researchLog?: ResearchLogEntry[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean, signal?: AbortSignal, modes?: PlannerModes): Promise<{
1290
- tasks: Task[];
1291
- researchLog: ResearchLogEntry[];
1292
- }>;
1293
- }
1294
-
1295
1095
  declare class GeminiService extends BaseAiService implements IAiService {
1096
+ private readonly deps;
1296
1097
  private genAI;
1297
1098
  private genAIKey;
1298
1099
  private model;
1299
1100
  /** The id `model` was made for; the SDK's own `model.model` is its `models/…` resource path. */
1300
1101
  private modelId;
1301
- constructor(config: IConfig);
1102
+ /** Outlives a conversation: what a model's provider measured still holds for the next one on it. */
1103
+ private readonly gauge;
1104
+ constructor(config: IConfig, deps?: GeminiServiceDeps);
1302
1105
  private init;
1303
1106
  /** The planner model picked for this session, as the API names it. */
1304
1107
  private plannerModel;
1305
1108
  private getPlanningModel;
1109
+ /**
1110
+ * Gemini's catalog states a model's prompt limit (`inputTokenLimit`, which
1111
+ * the catalog carries as the model's window) apart from its reply limit
1112
+ * (`outputTokenLimit`): the reply is never taken out of the prompt's room.
1113
+ */
1114
+ private limitsOf;
1115
+ private autoCompaction;
1116
+ /** Read at each call, so a model or setting changed mid-turn applies from its next call. */
1117
+ private plannerBudget;
1118
+ /** What automatic compaction does for the planner model configured now. */
1119
+ plannerContextStatus(): PlannerContextStatus;
1120
+ /** A chat on the planner model, whichever it is at each call. */
1121
+ private chat;
1122
+ /**
1123
+ * One call on the planner's own model and key that declares no functions:
1124
+ * what a summary of its context is written by. Its usage is reported like
1125
+ * any call's, for the caller to mark as measuring no context.
1126
+ */
1127
+ private readonly summaryCall;
1128
+ private summarizer;
1129
+ condenseContext(request: ContextCondenseRequest): Promise<string>;
1306
1130
  ensureInit(): void;
1307
1131
  reset(): void;
1308
1132
  startConversation(req: ConversationRequest): Promise<ConversationTurn>;
1309
- protected streamPlanText(prompt: string, repairHint: string | undefined, onToken: (token: string) => void, onReasoning?: (token: string) => void, _signal?: AbortSignal): Promise<string>;
1133
+ protected streamPlanText(prompt: string, repairHint: string | undefined, onToken: (token: string) => void, onReasoning?: (token: string) => void, signal?: AbortSignal): Promise<string>;
1310
1134
  /**
1311
1135
  * Drain a Gemini content stream, routing "thought" parts to the reasoning channel
1312
1136
  * and answer text to the parser. `chunk.text()` throws when a chunk is reasoning-only,
@@ -1315,18 +1139,41 @@ declare class GeminiService extends BaseAiService implements IAiService {
1315
1139
  private consumePlanStream;
1316
1140
  /** Gemini-specific stream over raw Parts for generatePlanDirect. */
1317
1141
  private streamPlanTextWithParts;
1142
+ /** One plan emission, asking for no longer a reply than the model states it serves. */
1143
+ private emitPlan;
1318
1144
  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<{
1319
1145
  tasks: Task[];
1320
1146
  researchLog: ResearchLogEntry[];
1321
1147
  researchResults: string;
1322
1148
  }>;
1323
- generatePlanDirect(userDescription: string, runners?: RunnerId[], modelsByRunner?: Partial<Record<RunnerId, DiscoveredModel[]>>, onToken?: (token: string) => void, fs?: IFileSystem, _fetcher?: IWebFetcher, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes, _signal?: AbortSignal): Promise<Task[]>;
1149
+ generatePlanDirect(userDescription: string, runners?: RunnerId[], modelsByRunner?: Partial<Record<RunnerId, DiscoveredModel[]>>, onToken?: (token: string) => void, fs?: IFileSystem, _fetcher?: IWebFetcher, runnerModes?: Record<RunnerId, RunnerModeInfo[]>, modes?: PlannerModes, signal?: AbortSignal): Promise<Task[]>;
1324
1150
  }
1325
1151
 
1152
+ /** What an API planner is built with beyond its config. */
1153
+ interface OpenAiServiceDeps extends PlannerContextDeps {
1154
+ turnBudgets?: TurnBudgets;
1155
+ }
1326
1156
  declare class OpenAiService extends BaseAiService implements IAiService {
1157
+ private readonly deps;
1327
1158
  private client;
1328
1159
  private clientCredentials;
1329
- constructor(config: IConfig);
1160
+ /** Outlives a conversation: what a model's provider measured still holds for the next one on it. */
1161
+ private readonly gauge;
1162
+ constructor(config: IConfig, deps?: OpenAiServiceDeps);
1163
+ private limitsOf;
1164
+ private autoCompaction;
1165
+ /** Read at each call, so a model or setting changed mid-turn applies from its next call. */
1166
+ private plannerBudget;
1167
+ /**
1168
+ * One call on the planner's own model and credentials that offers no tools:
1169
+ * what a summary of its context is written by. Its usage is reported like
1170
+ * any call's, for the caller to mark as measuring no context.
1171
+ */
1172
+ private readonly summaryCall;
1173
+ private summarizer;
1174
+ condenseContext(request: ContextCondenseRequest): Promise<string>;
1175
+ /** What automatic compaction does for the planner model configured now. */
1176
+ plannerContextStatus(): PlannerContextStatus;
1330
1177
  /**
1331
1178
  * Rebuilt whenever the key or endpoint differs from the one the client was
1332
1179
  * made with. A Session outlives `/key` and endpoint edits, so a client cached
@@ -1434,11 +1281,20 @@ declare class ModelResolver {
1434
1281
  private discovery;
1435
1282
  private fetchImpl?;
1436
1283
  private cachedPickerOptions;
1284
+ /** A catalog load under way: a second ask, or a warm-up, shares it rather than fetching again. */
1285
+ private pickerLoad;
1437
1286
  private lastDiscoveryErrors;
1438
1287
  constructor(registry: RunnerRegistry, config: IConfig, deps?: ModelResolverDeps);
1439
1288
  static builtinOptions(): OrchestratorOption[];
1440
1289
  modelsForRunners(runners: RunnerId[]): Promise<Partial<Record<RunnerId, DiscoveredModel[]>>>;
1441
1290
  pickerOptions(): Promise<OrchestratorOption[]>;
1291
+ /**
1292
+ * Start loading the vendor catalogs without waiting for them, unless they
1293
+ * are held or loading already: an API planner whose model's window is not
1294
+ * known yet reads it from them on a later call, never waiting on the fetch.
1295
+ */
1296
+ warmPickerOptions(): void;
1297
+ private loadPickerOptions;
1442
1298
  /**
1443
1299
  * Per-provider failures from the most recent `pickerOptions()` fetch, keyed
1444
1300
  * by provider id. Empty when every configured provider's catalog loaded (or
@@ -1465,9 +1321,32 @@ declare class ModelResolver {
1465
1321
  * omitted rather than guessed.
1466
1322
  */
1467
1323
  contextWindowFor(source: string, model: string): number | undefined;
1324
+ /**
1325
+ * The longest reply a vendor's model is served with, from the picker
1326
+ * catalog already held, when that catalog states one. Reads only, like
1327
+ * {@link contextWindowFor}.
1328
+ */
1329
+ maxOutputTokensFor(source: string, model: string): number | undefined;
1468
1330
  invalidate(): void;
1469
1331
  }
1470
1332
 
1333
+ interface RunnerInstallationOptions {
1334
+ /**
1335
+ * The workspace's own variables (ADR-0016), asked afresh for each auth probe
1336
+ * and told which root it is for, so a folder change mid-probe cannot mix one
1337
+ * workspace's account into another's verdict. A `.envrc` can choose another
1338
+ * account, so the probe must ask the same account the planner will use.
1339
+ */
1340
+ env?: (workspace?: string) => Promise<Record<string, string>>;
1341
+ /**
1342
+ * The workspace whose account this installation judges. The verdict is
1343
+ * recorded under it, so a planner gate in that workspace reads it and one in
1344
+ * another cannot (ADR-0009).
1345
+ */
1346
+ workspace?: () => string | undefined;
1347
+ /** Injectable status command runner, so tests need no real agent binary. */
1348
+ authExec?: RunnerAuthExec;
1349
+ }
1471
1350
  /**
1472
1351
  * Detects whether a runner's underlying CLI is actually installed on the host,
1473
1352
  * by invoking `<command> --version`. Mirrors ModelDiscovery: injectable exec,
@@ -1477,10 +1356,15 @@ declare class ModelResolver {
1477
1356
  declare class RunnerInstallation {
1478
1357
  private cache;
1479
1358
  private inflight;
1359
+ private authCache;
1360
+ private authInflight;
1480
1361
  private registry;
1481
1362
  private execImpl;
1482
1363
  private now;
1483
- constructor(registry: RunnerRegistry, execImpl?: ExecImpl, now?: () => number);
1364
+ private env;
1365
+ private workspace;
1366
+ private authExec;
1367
+ constructor(registry: RunnerRegistry, execImpl?: ExecImpl, now?: () => number, options?: RunnerInstallationOptions);
1484
1368
  /** True if the runner's CLI responds to `--version`. Cached per process. */
1485
1369
  isInstalled(runner: string): Promise<boolean>;
1486
1370
  /**
@@ -1497,15 +1381,21 @@ declare class RunnerInstallation {
1497
1381
  * typing a real goal is the failure this exists to prevent.
1498
1382
  *
1499
1383
  * "Usable" is deliberately shallow: the binary answers `--version`, and the
1500
- * runner has a connector to plan over. Whether the user's subscription is
1501
- * live cannot be known without spending a turn on it, so an expired login
1502
- * surfaces where it actually bites — as the agent's own error text on the
1503
- * first turn.
1384
+ * runner has a connector to plan over. A signed-out runner stays usable —
1385
+ * it is installed, and the user may log in from the same picker — so the
1386
+ * auth verdict rides beside usability, never inside it.
1504
1387
  */
1505
1388
  plannerUsability(runner: string): Promise<{
1506
1389
  usable: boolean;
1507
1390
  reason?: string;
1391
+ auth?: RunnerAuth;
1508
1392
  }>;
1393
+ /**
1394
+ * The runner's own login verdict, cached briefly and asked once when several
1395
+ * callers arrive together. Recorded per workspace for the synchronous planner
1396
+ * gate, which cannot await this.
1397
+ */
1398
+ private authFor;
1509
1399
  /**
1510
1400
  * Whether the CLI can be started the way the harness planner actually starts
1511
1401
  * it: `spawn` with no shell.
@@ -1524,6 +1414,212 @@ declare class RunnerInstallation {
1524
1414
  clear(): void;
1525
1415
  }
1526
1416
 
1417
+ /** What one acquisition found. `owner` is a key to compare accounts by, never shown or logged. */
1418
+ type AccountLimitReading =
1419
+ /** A full read: it replaces every bucket and window known before. `followUp` asks for one more, coalesced. */
1420
+ {
1421
+ kind: 'limits';
1422
+ owner: string;
1423
+ plan?: string;
1424
+ buckets: AccountLimitBucket[];
1425
+ followUp?: boolean;
1426
+ }
1427
+ /**
1428
+ * A transient failure; `owner` when the account was still seen signed in
1429
+ * before it failed, `retryAfterMs` when the service said how long to wait.
1430
+ */
1431
+ | {
1432
+ kind: 'failed';
1433
+ owner?: string;
1434
+ retryAfterMs?: number;
1435
+ }
1436
+ /** The account changed while it was being read: what was read may be the old one's. */
1437
+ | {
1438
+ kind: 'account_changed';
1439
+ } | {
1440
+ kind: 'signed_out' | 'unsupported' | 'not_installed';
1441
+ };
1442
+ /**
1443
+ * One runner's way of reading its account's limits. Registered with the
1444
+ * monitor once; the monitor alone decides when it runs, and never runs two of
1445
+ * its acquisitions at once.
1446
+ */
1447
+ interface AccountLimitCollector {
1448
+ /** The runner id the snapshot is filed under, and what a task's assignment is matched against. */
1449
+ readonly harness: string;
1450
+ readonly label: string;
1451
+ /** The one model provider its limits meter, when the runner serves others too (`AccountLimitSnapshot.provider`). */
1452
+ readonly provider?: string;
1453
+ /** The effective auth and config the reads see; a change clears what was read under the old one. */
1454
+ scope(): string;
1455
+ /** Must settle on its own deadline; `signal` aborts it early, and its result is then ignored. */
1456
+ acquire(signal: AbortSignal): Promise<AccountLimitReading>;
1457
+ }
1458
+ type AccountLimitListener = (snapshots: readonly AccountLimitSnapshot[]) => void;
1459
+ interface AccountLimitMonitorOptions {
1460
+ /** One per runner, in the order surfaces list them. */
1461
+ collectors: readonly AccountLimitCollector[];
1462
+ /** The clock reads are stamped with; timers are the platform's, so a test fakes both together. */
1463
+ now?: () => number;
1464
+ }
1465
+ /**
1466
+ * Every installed runner's account limits, owned once per host above any
1467
+ * panel, task or planner (the planner footer and each task log only
1468
+ * subscribe). It decides when each collector runs — on request, about once a
1469
+ * minute while in the foreground, again when a reported window resets — and
1470
+ * never runs two acquisitions of one account at once.
1471
+ *
1472
+ * It believes only the newest acquisition of the scope it started under: an
1473
+ * answer that arrives after a newer start, a scope change, an account change
1474
+ * or dispose is dropped. A full read replaces what was known; a failure keeps
1475
+ * the last read, marked stale, only when the same account was seen; anything
1476
+ * else forgets it. Nothing is persisted: a new host starts knowing nothing.
1477
+ */
1478
+ declare class AccountLimitMonitor {
1479
+ private readonly entries;
1480
+ private readonly listeners;
1481
+ private readonly now;
1482
+ private foreground;
1483
+ private disposed;
1484
+ private published;
1485
+ constructor(options: AccountLimitMonitorOptions);
1486
+ snapshots(): readonly AccountLimitSnapshot[];
1487
+ /** Hear every change; the returned function stops it. */
1488
+ subscribe(listener: AccountLimitListener): () => void;
1489
+ /**
1490
+ * Read now, or as soon as the spacing between reads allows; with no
1491
+ * harness, every one. Asks made while a read is out become one follow-up.
1492
+ */
1493
+ refresh(harness?: string): void;
1494
+ /**
1495
+ * A surface connected. It is drawn what is known, so only an account that
1496
+ * is not being read and was not read within the poll interval is read again:
1497
+ * a webview made ready just after a focused start does not read twice.
1498
+ */
1499
+ surfaceConnected(): void;
1500
+ /**
1501
+ * Something spent from the account — a model call, a task's turn — so a
1502
+ * read would show more. Folded into the next poll, or the backoff after a
1503
+ * failure, so a busy planner does not read the account on every call; in
1504
+ * the background, where nothing polls, into the read coming back makes.
1505
+ */
1506
+ invalidate(harness?: string): void;
1507
+ /** Polling runs only in the foreground; coming back reads at once. */
1508
+ setForeground(active: boolean): void;
1509
+ dispose(): void;
1510
+ private each;
1511
+ private request;
1512
+ /**
1513
+ * When the last read started, never later than now: a wall clock set back
1514
+ * since would put it ahead, and every wait measured from it would grow by
1515
+ * the step, focus and refresh included.
1516
+ */
1517
+ private lastStart;
1518
+ /** How long after one read the next poll comes: a minute, longer for an account with nothing to show, backed off after failures. */
1519
+ private interval;
1520
+ /** The auth or config the reads see has changed: what was read under the old one, or is being read, is not this one's. */
1521
+ private fenceScopeChange;
1522
+ private schedule;
1523
+ private clearTimer;
1524
+ private start;
1525
+ private apply;
1526
+ /** After an acquisition: the follow-up it owes, else the next poll. */
1527
+ private next;
1528
+ /** At the earliest future reset the share shown belongs to a window that is over: stale until read again. */
1529
+ private watchResets;
1530
+ private clearResetTimer;
1531
+ private forget;
1532
+ private show;
1533
+ private collect;
1534
+ }
1535
+
1536
+ interface CodexAccountLimitOptions {
1537
+ /** The manifest's `app-server` launch. */
1538
+ command?: string;
1539
+ args?: string[];
1540
+ timeoutMs?: number;
1541
+ /** Installed, not enabled: an account's limits are worth showing before Codex is given a task. */
1542
+ isInstalled: () => Promise<boolean>;
1543
+ /** The workspace the planner and its tasks run in; its environment can choose another account than the host's (ADR-0016). */
1544
+ workspaceRoot?: () => string | undefined;
1545
+ /** Tests inject the process boundary; the real call stays on the defaults. */
1546
+ deps?: Partial<AgentProcessDeps>;
1547
+ }
1548
+ /**
1549
+ * Codex's account limits, read the way its own clients read them:
1550
+ * `initialize` → `initialized` → `account/read` → `account/rateLimits/read`,
1551
+ * on a short-lived `app-server` that is taken down after every read. Nothing
1552
+ * here opens a thread, starts a turn, logs in or out, or asks Codex to
1553
+ * refresh its token, and Codex's `auth.json` is never read.
1554
+ *
1555
+ * It runs from the temp directory, so a project's config and hooks are not
1556
+ * run on every poll, but with the workspace's environment on top of the
1557
+ * host's, as the planner and its tasks run: a `CODEX_HOME` the workspace sets
1558
+ * decides whose account it is, not the host's.
1559
+ */
1560
+ declare function codexAccountLimitCollector(options: CodexAccountLimitOptions): AccountLimitCollector;
1561
+
1562
+ interface ClaudeAccountLimitOptions {
1563
+ timeoutMs?: number;
1564
+ /** Installed, not enabled: an account's limits are worth showing before Claude Code is given a task. */
1565
+ isInstalled: () => Promise<boolean>;
1566
+ /** The workspace the planner and its tasks run in; its environment can choose another account than the host's (ADR-0016). */
1567
+ workspaceRoot?: () => string | undefined;
1568
+ /** Tests inject the process boundary; the real call stays on the defaults. */
1569
+ deps?: Partial<AgentProcessDeps>;
1570
+ }
1571
+ /**
1572
+ * Claude Code's plan limits, read the way the Agent SDK reads them: the
1573
+ * `initialize` and `get_usage` control requests, on a process of its own
1574
+ * started for nothing else and taken down after every read. Nothing here
1575
+ * writes a user message, so no model is called, and nothing logs in or out,
1576
+ * resumes or saves a session, or reads a credential: the CLI uses its own
1577
+ * login, as it would for a planner.
1578
+ *
1579
+ * It runs from an empty directory of its own, so no project's settings or
1580
+ * hooks run on every poll, but with the workspace's environment on top of the
1581
+ * host's, as the planner and its tasks run: a `CLAUDE_CONFIG_DIR` the
1582
+ * workspace sets decides whose account it is, not the host's.
1583
+ */
1584
+ declare function claudeAccountLimitCollector(options: ClaudeAccountLimitOptions): AccountLimitCollector;
1585
+
1586
+ interface OpenCodeGoLoginReaderDeps {
1587
+ homedir?: () => string;
1588
+ /** How long one read of the database may take, worker and all. */
1589
+ timeoutMs?: number;
1590
+ /** The worker boundary; a test counts what it starts. */
1591
+ spawnWorker?: (source: string, options: WorkerOptions) => Worker;
1592
+ /** Node options the worker starts with: a test turns SQLite off to stand for a runtime without it. */
1593
+ workerExecArgv?: string[];
1594
+ }
1595
+
1596
+ interface OpenCodeGoAccountLimitOptions {
1597
+ /** Installed, not enabled: an account's limits are worth showing before OpenCode is given a task. */
1598
+ isInstalled: () => Promise<boolean>;
1599
+ /** The workspace the planner and its tasks run in; its environment can point OpenCode at another store (ADR-0016). */
1600
+ workspaceRoot?: () => string | undefined;
1601
+ /** Tests inject the network, the environment and the store's location; the real call stays on the defaults. */
1602
+ deps?: {
1603
+ fetch?: typeof fetch;
1604
+ now?: () => number;
1605
+ env?: () => NodeJS.ProcessEnv;
1606
+ workspaceEnv?: (root: string) => Promise<Record<string, string>>;
1607
+ reader?: OpenCodeGoLoginReaderDeps;
1608
+ timeoutMs?: number;
1609
+ };
1610
+ }
1611
+ /**
1612
+ * OpenCode Go's plan windows — five hours, the week and the plan month — for
1613
+ * the login OpenCode would pay for a Go model with (ADR-0051). The login is
1614
+ * read from OpenCode's own store, the one it selects and nothing else, and
1615
+ * sent only to OpenCode's fixed quota addresses: no OpenCode process is
1616
+ * started, no model is called, nothing is refreshed, written or kept.
1617
+ *
1618
+ * The limits meter `opencode-go/…` models only, so a task OpenCode runs on
1619
+ * another provider is not shown them.
1620
+ */
1621
+ declare function openCodeGoAccountLimitCollector(options: OpenCodeGoAccountLimitOptions): AccountLimitCollector;
1622
+
1527
1623
  /** The first of {@link findInstalledAgents}; null when none is usable. */
1528
1624
  declare function findInstalledPlanner(installation: Pick<RunnerInstallation, 'plannerUsability'>): Promise<PlannerBackendId | null>;
1529
1625
  /**
@@ -1619,6 +1715,8 @@ declare function providerForRunner(runner: RunnerId): PlannerBackendId | null;
1619
1715
  interface PlannerUsability {
1620
1716
  usable: boolean;
1621
1717
  reason?: string;
1718
+ /** The runner's own login verdict, beside usability: a signed-out agent is still offered. */
1719
+ auth?: RunnerAuth;
1622
1720
  }
1623
1721
  /**
1624
1722
  * One entry of the planner backend list (ADR-0009). `id` is the
@@ -1633,6 +1731,8 @@ interface PlannerBackendEntry {
1633
1731
  runner?: string;
1634
1732
  usable: boolean;
1635
1733
  reason?: string;
1734
+ /** Set for a harness planner whose login status was asked; never a gate on `usable`. */
1735
+ auth?: RunnerAuth;
1636
1736
  }
1637
1737
  /** The detail a usable harness planner is always shown with. */
1638
1738
  declare const HARNESS_PLANNER_REASON = "coding agent \u00B7 no API key needed";
@@ -1691,7 +1791,8 @@ declare const ORDEWELL_SETTABLE_ENV: readonly string[];
1691
1791
  *
1692
1792
  * - The approval-mode and pre-approved-scope variables switch the consent gate
1693
1793
  * off, so every later command runs unprompted; the web-read budget bounds
1694
- * how much a planner may send out unasked in Full.
1794
+ * how much a planner may send out unasked in Full; the effect allowlist
1795
+ * pre-approves script tasks that change real systems (ADR-0035).
1695
1796
  * - The settings-path override is honoured by the settings service, which
1696
1797
  * creates directories recursively when persisting — admitting it turns the
1697
1798
  * same write into an arbitrary-path file write.
@@ -1730,10 +1831,205 @@ declare function supportsTaskMode(runner: string): boolean;
1730
1831
  /** The adapter that drives one task. Throws {@link TaskModeUnsupportedError} for a runner without a connector. */
1731
1832
  declare function createTaskAdapter(runner: string, deps: AgentProcessDeps): TaskModeAgentAdapter;
1732
1833
 
1834
+ interface RunnerCommand {
1835
+ command: string;
1836
+ args: string[];
1837
+ /**
1838
+ * Variables set over the workspace's own (ADR-0016), so they win. Handed the
1839
+ * workspace's, for a runner whose value merges into one of them.
1840
+ */
1841
+ env?: (workspace: Record<string, string>) => Record<string, string>;
1842
+ /**
1843
+ * Names this launch must not inherit from the host or the workspace — the
1844
+ * credentials a native-shell planner keeps out of its process (ADR-0036 N2.2).
1845
+ */
1846
+ envDrop?: (name: string) => boolean;
1847
+ }
1848
+ interface RunnerExit {
1849
+ code: number | null;
1850
+ signal: string | null;
1851
+ }
1852
+ /**
1853
+ * The one runner process a structured adapter drives (ADR-0018), whatever its
1854
+ * protocol: launched under the cleaned environment in its own process group,
1855
+ * its output decoded as UTF-8, its stderr tail kept for the failure message,
1856
+ * its end observable, and its whole tree killed on dispose.
1857
+ *
1858
+ * Constructed before it is started, so an adapter can tie a resource to
1859
+ * {@link ended} while it is still deciding what to launch.
1860
+ */
1861
+ declare class RunnerProcess {
1862
+ private readonly deps;
1863
+ /** Resolves when the process ends — or fails to spawn — so a handshake can lose the race instead of waiting out its timeout. */
1864
+ readonly ended: Promise<void>;
1865
+ private markEnded;
1866
+ private child;
1867
+ /** Disposed before, or while, it launched: nothing may be spawned for an owner that has gone. */
1868
+ private disposed;
1869
+ private exitStatus;
1870
+ /** The process itself reported its end, before its pipes drained; see {@link hasExited}. */
1871
+ private exitReported;
1872
+ private stderrTail;
1873
+ private spawnEnv;
1874
+ /** Stdout readers' end-of-stream flushes, for an end the pipe itself never reports. */
1875
+ private readonly stdoutEnds;
1876
+ constructor(deps: AgentProcessDeps);
1877
+ /**
1878
+ * Launch the process, then run the adapter's `handshake` on it. A handshake
1879
+ * that throws takes the process down with it: no caller keeps an adapter
1880
+ * whose start threw, so a process left running here — a refused handshake,
1881
+ * one that timed out — would have no owner.
1882
+ *
1883
+ * `command` is asked for only once `cwd` is known to exist, and `handshake`
1884
+ * is called in the same tick as the spawn, so a listener it attaches hears
1885
+ * the process's first output.
1886
+ */
1887
+ start(cwd: string, command: (workspace: Record<string, string>) => RunnerCommand, handshake: (child: ChildProcess) => Promise<void>): Promise<void>;
1888
+ private launch;
1889
+ /**
1890
+ * Hear the process's stdout as text, until the returned function is called.
1891
+ * A read can end inside a multibyte character; decoding each chunk alone
1892
+ * turns both halves into U+FFFD.
1893
+ *
1894
+ * `onEnd`, when given, fires once the stream has ended — after the decoder's
1895
+ * own remainder is handed over. Its job is the framing layer's leftover: a
1896
+ * pipe can close with a complete line still buffered but unterminated, and
1897
+ * nothing else will come to flush it.
1898
+ */
1899
+ readStdout(onText: (text: string) => void, onEnd?: () => void): () => void;
1900
+ /** The environment the process was spawned under, for any side process a handshake needs. */
1901
+ get env(): NodeJS.ProcessEnv;
1902
+ /** How the process ended; null while it runs. */
1903
+ get exit(): RunnerExit | null;
1904
+ /**
1905
+ * Whether the process has ended, for any reason. Unlike {@link exit} this is
1906
+ * true from its own 'exit', not only once 'close' has drained the pipes: a
1907
+ * runner that has exited can take no further turn even while a descendant
1908
+ * holds its stdout or stderr.
1909
+ */
1910
+ get hasExited(): boolean;
1911
+ /** The exit code a listener is told: -1 for a signal or a spawn failure. */
1912
+ get exitCode(): number;
1913
+ /** How the process ended, as the end of a sentence. */
1914
+ describeExit(): string;
1915
+ /** Fail-safe contract: a dead runner reports its own last words, never an empty bubble. */
1916
+ withStderrTail(headline: string): string;
1917
+ /**
1918
+ * Tree-wide, because the direct child may be a CLI whose shells, MCP servers
1919
+ * and test runs would outlive it — or, on Windows, the cmd.exe shim rather
1920
+ * than the runner. The forced follow-up is scheduled and unref'd, so a
1921
+ * disposed runner is never the reason the host refuses to exit.
1922
+ */
1923
+ dispose(): void;
1924
+ private end;
1925
+ private appendStderr;
1926
+ }
1927
+
1928
+ /**
1929
+ * The launcher a harness's own shell runs through (ADR-0036 N2).
1930
+ *
1931
+ * Claude Code names one program in `CLAUDE_CODE_SHELL_PREFIX` and hands it the
1932
+ * whole command string as one argument, so the program is a script: its fixed
1933
+ * part is this session's sandbox launch, and its last argument is the command.
1934
+ * A harness that takes a shell instead (OpenCode's `shell`) can name the same
1935
+ * script the same way.
1936
+ */
1937
+ interface NativeShellLauncher {
1938
+ /** `CLAUDE_CODE_SHELL_PREFIX`: the script's path. */
1939
+ prefix: string;
1940
+ /**
1941
+ * `CLAUDE_CODE_TMPDIR`: a short per-session directory, writable in the
1942
+ * profile. Claude Code writes the shell's working directory there after
1943
+ * every command, and warns when the path passes about 30 bytes.
1944
+ */
1945
+ tmpdir: string;
1946
+ /** The harness settings `env` block that names both (N2). */
1947
+ env: Record<string, string>;
1948
+ /** Removes the script and its directory; the next process gets its own. */
1949
+ remove(): void;
1950
+ }
1951
+ /** The shell a command runs in: the user's own, `/bin/zsh` on the one platform N5 offers. */
1952
+ declare function nativeShellInvocation(platform: NodeJS.Platform, env?: NodeJS.ProcessEnv): {
1953
+ shell: string;
1954
+ args: string[];
1955
+ };
1956
+ /**
1957
+ * Build the launcher for one planner process. The script lives in the OS's own
1958
+ * temporary directory, outside every directory the sealed profile can read or
1959
+ * write, so a command cannot rewrite what the next one starts through. Its
1960
+ * short temp folder is under `/tmp` instead, because the harness warns past
1961
+ * about thirty bytes and `/private/tmp` on macOS keeps the path short; the
1962
+ * profile is given only that folder, never the script.
1963
+ */
1964
+ declare function createNativeShellLauncher(opts: {
1965
+ workspace: string;
1966
+ sandbox: PlannerShellSandbox;
1967
+ platform?: NodeJS.Platform;
1968
+ env?: NodeJS.ProcessEnv;
1969
+ }): Promise<NativeShellLauncher>;
1970
+
1971
+ /**
1972
+ * What every structured adapter owes the one runner process it drives
1973
+ * (ADR-0018 L1), whatever it speaks to it: the {@link RunnerProcess}, whether
1974
+ * it is still alive, who hears it end, how a dead one is reported, and what the
1975
+ * process owns being freed with it.
1976
+ *
1977
+ * Stdio line framing sits on top of this for Claude Code and Codex; OpenCode,
1978
+ * an HTTP server process, uses it without any framing.
1979
+ */
1980
+ declare abstract class ProcessLifecycle {
1981
+ protected deps: AgentProcessDeps;
1982
+ protected process: ChildProcess | null;
1983
+ /** What this process is to the user — a planner or a task's runner — for the words a failure is reported in. */
1984
+ protected role: 'planner' | 'task';
1985
+ protected readonly runner: RunnerProcess;
1986
+ /**
1987
+ * What the process owns, by name: the MCP config file holding its token, the
1988
+ * sealed shell launcher and its sandbox. One registry rather than a field
1989
+ * and a cleanup per adapter, so a resource an adapter claims cannot be the
1990
+ * one its cleanup forgot.
1991
+ */
1992
+ private readonly owned;
1993
+ private disposed;
1994
+ constructor(deps: AgentProcessDeps);
1995
+ /** Fail-safe contract: a dead process reports its own last words, never an empty bubble. */
1996
+ protected abstract exitMessage(): string;
1997
+ get exited(): boolean;
1998
+ /** Resolves when the process ends, so a handshake can lose the race instead of waiting out its timeout. */
1999
+ protected get processEnded(): Promise<void>;
2000
+ /** Whether {@link dispose} has run, for a loop that must stop once the process is gone. */
2001
+ protected get isDisposed(): boolean;
2002
+ onProcessExit(listener: (code: number, lastWords?: string) => void): void;
2003
+ /**
2004
+ * Launch the process and run `handshake` on it. A start that throws takes
2005
+ * the process down and frees what the adapter owns with it: no caller keeps
2006
+ * an adapter whose start threw.
2007
+ */
2008
+ protected startProcess(cwd: string, command: (workspace: Record<string, string>) => RunnerCommand, handshake: (child: ChildProcess) => Promise<void>): Promise<void>;
2009
+ /**
2010
+ * Hand `release` to the process: it runs once, when the process ends or the
2011
+ * adapter is disposed, whichever comes first. A second claim on a name frees
2012
+ * the first, so a process launched again does not leak its predecessor's.
2013
+ * A claim made after the adapter was disposed is freed at once.
2014
+ */
2015
+ protected own(name: string, release: () => void): void;
2016
+ /** Prepare this planner's own shell (ADR-0036 N2) and own it, or null when the start did not ask for one. */
2017
+ protected ownNativeShell(opts: AgentStartOptions): Promise<NativeShellLauncher | null>;
2018
+ /**
2019
+ * Frees what the process owns, then ends its whole process tree. Idempotent:
2020
+ * every path that gives up on the process — a stop, a refused start, a
2021
+ * turn's abort — ends here.
2022
+ */
2023
+ dispose(): void;
2024
+ private freeOwned;
2025
+ }
2026
+
1733
2027
  interface SpawnSpec {
1734
2028
  command: string;
1735
2029
  args: string[];
1736
2030
  env?: Record<string, string>;
2031
+ /** Names the launch must not inherit from the host or the workspace (ADR-0036 N2.2). */
2032
+ envDrop?: (name: string) => boolean;
1737
2033
  }
1738
2034
  /**
1739
2035
  * Shared plumbing for the agents that speak newline-delimited JSON over stdio.
@@ -1746,16 +2042,13 @@ interface SpawnSpec {
1746
2042
  *
1747
2043
  * Subclasses own their agent's protocol: what to spawn, how to phrase a user
1748
2044
  * turn, and how to read one protocol line. Everything below — line framing,
1749
- * turn lifecycle, abort — is the same for all of them, and the process itself
1750
- * is a {@link RunnerProcess}.
2045
+ * turn lifecycle, abort — is the same for all of them; the process and what it
2046
+ * owns are {@link ProcessLifecycle}'s.
1751
2047
  */
1752
- declare abstract class StdioAgentAdapter implements AgentAdapter {
1753
- protected deps: AgentProcessDeps;
2048
+ declare abstract class StdioAgentAdapter extends ProcessLifecycle implements AgentAdapter {
1754
2049
  abstract readonly agentId: string;
1755
- protected process: ChildProcess | null;
1756
2050
  protected sessionId: string | null;
1757
2051
  private readonly stdout;
1758
- private readonly runner;
1759
2052
  /** Set for the duration of a turn. Outside one, events are buffered rather than dropped. */
1760
2053
  private turnEmit;
1761
2054
  /** Set for the duration of a turn. Pinged on every raw line, whether or not `handleLine` turns it into an event. */
@@ -1776,9 +2069,6 @@ declare abstract class StdioAgentAdapter implements AgentAdapter {
1776
2069
  private hadTurn;
1777
2070
  /** A task's session, which wants what the agent says on its own after a turn closed; a planner registers none and drops it. */
1778
2071
  private outOfTurnListener;
1779
- private disposed;
1780
- /** What this process is to the user — a planner or a task's runner — for the words a failure is reported in. */
1781
- protected role: 'planner' | 'task';
1782
2072
  constructor(deps: AgentProcessDeps);
1783
2073
  /**
1784
2074
  * The command line that starts this agent in its read-only mode, given the
@@ -1795,6 +2085,12 @@ declare abstract class StdioAgentAdapter implements AgentAdapter {
1795
2085
  /** Protocol handshake, if the agent needs one before it accepts a turn. */
1796
2086
  protected handshake(_opts: AgentStartOptions): Promise<void>;
1797
2087
  start(opts: AgentStartOptions): Promise<void>;
2088
+ /**
2089
+ * Spawn `spec`, read its protocol, then run `handshake`. {@link start} is a
2090
+ * planner's or a task's launch; a launch that is neither — a query that
2091
+ * never opens a turn — comes through here with its own command line.
2092
+ */
2093
+ protected launch(cwd: string, spec: (workspace: Record<string, string>) => SpawnSpec, handshake: () => Promise<void>): Promise<void>;
1798
2094
  /**
1799
2095
  * Every protocol line goes to `handleLine`, turn or no turn: an agent's
1800
2096
  * handshake and its session-id announcement both arrive before the first
@@ -1807,24 +2103,34 @@ declare abstract class StdioAgentAdapter implements AgentAdapter {
1807
2103
  * to the open turn, or held for the first one.
1808
2104
  */
1809
2105
  protected announce(event: AgentEvent): void;
1810
- /** Resolves when the process ends, so a handshake can lose the race instead of waiting out its timeout. */
1811
- protected get processEnded(): Promise<void>;
1812
2106
  /** The environment the agent was spawned under, for any side process a handshake needs. */
1813
2107
  protected get spawnEnv(): NodeJS.ProcessEnv;
1814
2108
  send(message: string, onEvent: (event: AgentEvent) => void, signal?: AbortSignal, onActivity?: () => void): Promise<void>;
1815
2109
  onOutOfTurn(listener: (event: AgentEvent) => void): void;
1816
2110
  nativeSessionId(): string | null;
1817
- get exited(): boolean;
1818
- onProcessExit(listener: (code: number, lastWords?: string) => void): void;
1819
2111
  dispose(): void;
1820
2112
  /** Write one raw protocol line to the agent. */
1821
2113
  protected writeLine(payload: unknown): void;
1822
- /** Fail-safe contract: a dead agent reports its own last words, never an empty bubble. */
1823
2114
  protected exitMessage(): string;
1824
2115
  /** Parse a protocol line, ignoring the non-JSON banners some CLIs print. */
1825
2116
  protected static parse<T>(line: string): T | null;
1826
2117
  }
1827
2118
 
2119
+ /**
2120
+ * What one control request came to: its answer's payload as the CLI sent it,
2121
+ * the CLI's refusal in its own words, or no answer in time — with the
2122
+ * process's stderr tail when it ended instead.
2123
+ */
2124
+ type ClaudeControlAnswer = {
2125
+ kind: 'answered';
2126
+ response: unknown;
2127
+ } | {
2128
+ kind: 'refused';
2129
+ error: string;
2130
+ } | {
2131
+ kind: 'silent';
2132
+ lastWords?: string;
2133
+ };
1828
2134
  declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAgentAdapter {
1829
2135
  readonly agentId = "claude-code";
1830
2136
  private controlCount;
@@ -1833,10 +2139,15 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
1833
2139
  /** An interrupt was sent during the current turn, so an aborted result is that interrupt, not a failure. */
1834
2140
  private interruptRequested;
1835
2141
  private readonly permissions;
2142
+ private readOnlySite;
2143
+ /** The sealed launcher this process's shell goes through, when the opt-in is on (ADR-0036 N2). */
2144
+ private nativeShell;
1836
2145
  /** This turn's runs of reply text, each a text block named `<message id>:<block index>`. */
1837
2146
  private replyRuns;
1838
2147
  /** The model answering the planner's current message, and the message's id, as its `message_start` named them. */
1839
2148
  private plannerModel;
2149
+ /** The CLI's version, from its first `init`; the native-shell check reads it (ADR-0036 N2). */
2150
+ private installedVersion;
1840
2151
  /**
1841
2152
  * The id the turn runs as, as `init` names it. A `[1m]` variant is the
1842
2153
  * selected id, while a message names the API's canonical id without it.
@@ -1879,8 +2190,6 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
1879
2190
  private requestedMode;
1880
2191
  /** The session `--resume` asked for, until the CLI's `init` shows it was taken up. */
1881
2192
  private pendingResume;
1882
- /** The `--mcp-config` file of the running process, which holds its token; removed with the process. */
1883
- private mcpConfig;
1884
2193
  /** Whether this process was given the server, so its tools' requests are Ordewell's own. */
1885
2194
  private givenOrdewell;
1886
2195
  /** The `CLAUDE_CONFIG_DIR` a planner runs under, where the CLI saves its long tool results. */
@@ -1889,6 +2198,8 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
1889
2198
  private readonly steers;
1890
2199
  /** The last turn's `result` ended it, so a steer's echo now opens a turn the CLI started for that message. */
1891
2200
  private turnClosed;
2201
+ /** The model is working in a turn the CLI opened itself, after the last one closed: a background wake. */
2202
+ private runnerTurn;
1892
2203
  /** The task's commands as the CLI last listed them, and what its `init` said of them (#66). */
1893
2204
  private listedCommands;
1894
2205
  private skillNames;
@@ -1908,8 +2219,16 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
1908
2219
  * `init`, which can be another `/model`'s.
1909
2220
  */
1910
2221
  private modelCommands;
2222
+ /**
2223
+ * Prepare the native shell before the process starts, or fail the start
2224
+ * rather than run it outside a sandbox. The launcher is a file this process
2225
+ * owns: `CLAUDE_CODE_SHELL_PREFIX` can only name a program, and Claude Code
2226
+ * hands it the whole command as one argument (ADR-0036 N2).
2227
+ */
2228
+ start(opts: AgentStartOptions): Promise<void>;
1911
2229
  protected spawnSpec(opts: AgentStartOptions, workspace: Record<string, string>): SpawnSpec;
1912
2230
  sessionCost(): RunnerSessionCost | null;
2231
+ version(): string | null;
1913
2232
  /**
1914
2233
  * A cleared conversation's totals start again from zero, so what follows is
1915
2234
  * all new spend, even for a resume that had no count kept.
@@ -1920,8 +2239,8 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
1920
2239
  savedOutput(abs: string): boolean;
1921
2240
  /**
1922
2241
  * A task's run: the manifest decides what its mode and effort mean
1923
- * (ADR-0001), and this adds only the protocol around them. No tool list and
1924
- * system prompt beyond the completion contract — the task's prompt is its
2242
+ * (ADR-0001). Read-only tasks withhold native writes and shell tools
2243
+ * (ADR-0046); other tasks keep the runner's native tools. The prompt is its
1925
2244
  * first turn, and the contract is where the CLI's own compaction cannot
1926
2245
  * summarise it away. `--permission-prompt-tool stdio` routes the questions the mode
1927
2246
  * leaves open to the control channel, where the adapter must answer them;
@@ -1937,21 +2256,18 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
1937
2256
  private plannerServers;
1938
2257
  /** The file holding the token lives exactly as long as the process that reads it. */
1939
2258
  private ordewellServerArgs;
1940
- private removeMcpConfig;
1941
- /**
1942
- * Under `--permission-mode plan` the CLI runs Sonnet for the `haiku` alias,
1943
- * though it runs a full id as given: a read-only task would silently run a
1944
- * dearer model than its plan names (ADR-0001). The CLI's own list says what
1945
- * the alias resolves to now; that id is pinned before the first turn.
1946
- */
1947
- protected handshake(opts: AgentStartOptions): Promise<void>;
2259
+ protected handshake(): Promise<void>;
1948
2260
  /**
1949
2261
  * What `-p` can run. The CLI lists commands only its TUI runs (`init`'s
1950
2262
  * `terminal_slash_commands`) and internal ones named `__…` all the same.
1951
2263
  */
1952
- commands(): RunnerCommand[];
1953
- /** Asks the CLI itself, which reports `pending` until its connection attempt settles. */
1954
- mcpAttached(): Promise<boolean>;
2264
+ commands(): RunnerCommand$1[];
2265
+ /**
2266
+ * Asks the CLI itself, which reports `pending` until its connection attempt
2267
+ * settles. A process that exited ends the wait at once: its report is gone
2268
+ * with it, and no answer would ever come.
2269
+ */
2270
+ mcpAttached(): Promise<AttachState>;
1955
2271
  /**
1956
2272
  * Claude Code's soft interrupt: the turn stops, the process and its session
1957
2273
  * stay. The CLI acknowledges on the control channel, then closes the turn
@@ -1975,6 +2291,17 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter implements TaskModeAge
1975
2291
  * with the message unread.
1976
2292
  */
1977
2293
  steer(id: string, text: string): Promise<boolean>;
2294
+ /**
2295
+ * Launch the process as an account query rather than a planner or a task,
2296
+ * from `cwd`, and ask it who is signed in (`initialize`). Then
2297
+ * {@link queryUsage} asks what the plan's limits stand at. Both go on the
2298
+ * control channel and no user message is ever written, so no model is
2299
+ * called and no turn opens. The caller ends input and disposes.
2300
+ */
2301
+ openAccountQuery(cwd: string, timeoutMs: number): Promise<ClaudeControlAnswer>;
2302
+ /** The plan's limits, on a process {@link openAccountQuery} opened. */
2303
+ queryUsage(timeoutMs: number): Promise<ClaudeControlAnswer>;
2304
+ private controlAnswer;
1978
2305
  /** Send a control request and wait for its answer; null when none came in time or the process ended. */
1979
2306
  private control;
1980
2307
  /**
@@ -2173,6 +2500,8 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
2173
2500
  private sandbox;
2174
2501
  /** A planner on a host where no sandbox starts: Codex's own shell is off, and `run_command` is its only one (ADR-0033). */
2175
2502
  private ownShellOff;
2503
+ /** A read-only task's: every MCP server Codex would start beside Ordewell's, each switched off for its thread (ADR-0046). */
2504
+ private withheldServers;
2176
2505
  /** The Ordewell server's startup state, as Codex last reported it for this thread. */
2177
2506
  private mcpStartup;
2178
2507
  private mcpStartupSettled;
@@ -2203,9 +2532,17 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
2203
2532
  * would pretend it was resumed.
2204
2533
  */
2205
2534
  private openThread;
2206
- commands(): RunnerCommand[];
2535
+ commands(): RunnerCommand$1[];
2207
2536
  invokeCommand(name: string, args: string, onEvent: (event: AgentEvent) => void, signal?: AbortSignal): Promise<void>;
2208
2537
  private loadCommands;
2538
+ /**
2539
+ * The MCP servers Codex's own configuration would start, every layer of it,
2540
+ * less Ordewell's. A read-only task may change nothing, and a user's server
2541
+ * can: one set to approve its tools runs them under any approval policy, so
2542
+ * none of them starts (ADR-0046), as Claude's and OpenCode's deny theirs.
2543
+ * Not knowing them, the task does not start.
2544
+ */
2545
+ private configuredServers;
2209
2546
  private requestThread;
2210
2547
  private threadParams;
2211
2548
  /**
@@ -2213,7 +2550,7 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
2213
2550
  * has tried to connect (ADR-0022, S4). A report that came before this was
2214
2551
  * asked is kept, so asking late still gets the answer.
2215
2552
  */
2216
- mcpAttached(): Promise<boolean>;
2553
+ mcpAttached(): Promise<AttachState>;
2217
2554
  private effort;
2218
2555
  /**
2219
2556
  * `turn/interrupt` names the turn as well as the thread, so an interrupt
@@ -2341,7 +2678,7 @@ declare class CodexAdapter extends StdioAgentAdapter implements TaskModeAgentAda
2341
2678
 
2342
2679
  declare const CODEX_SANDBOX_DOCS = "https://github.com/ordewell/ordewell/blob/main/docs/troubleshooting.md#codex-sandbox-unavailable-on-this-host";
2343
2680
  /** The one line a surface shows next to the runner list while Codex's sandboxed modes are off. */
2344
- declare const CODEX_SANDBOX_OFF_LINE = "Codex sandbox unavailable on this host: read-only and Guarded modes are off";
2681
+ declare const CODEX_SANDBOX_OFF_LINE = "Codex sandbox unavailable on this host: Guarded mode is off";
2345
2682
  /**
2346
2683
  * The line a runner or mode list shows, and the page that explains it, when
2347
2684
  * the catalog says Codex's sandboxed modes are off. Null when nothing is.
@@ -2399,20 +2736,18 @@ declare class CodexSandboxAvailability {
2399
2736
  * detail, never the final reply. A task turn is posted asynchronously and read
2400
2737
  * entirely from the stream — see {@link sendTask}.
2401
2738
  */
2402
- declare class OpenCodeAdapter implements TaskModeAgentAdapter {
2403
- private deps;
2739
+ declare class OpenCodeAdapter extends ProcessLifecycle implements TaskModeAgentAdapter {
2404
2740
  readonly agentId = "opencode";
2405
2741
  private nativeCommands;
2406
2742
  private readonly nativeSkills;
2407
2743
  private manualCompaction;
2408
2744
  private loaded;
2409
- private process;
2410
2745
  private baseUrl;
2746
+ /** The server's version from `/api/info`; null on 1.x, which does not report one (ADR-0036 N2). */
2747
+ private serverVersion;
2411
2748
  /** Sent on every request to a task's server, which a per-task password secures. */
2412
2749
  private authorization;
2413
2750
  private sessionId;
2414
- private readonly runner;
2415
- private disposed;
2416
2751
  private planner;
2417
2752
  private task;
2418
2753
  /** The last assistant message already settled — the baseline {@link recoverReply} measures a new reply against. */
@@ -2429,9 +2764,8 @@ declare class OpenCodeAdapter implements TaskModeAgentAdapter {
2429
2764
  private v2;
2430
2765
  /** The Ordewell server this process was configured with; null when none was given or its config could not be merged. */
2431
2766
  private ordewell;
2432
- constructor(deps: AgentProcessDeps);
2433
- private get processEnded();
2434
- get exited(): boolean;
2767
+ /** The sealed launcher this planner's own shell goes through, when the opt-in is on (ADR-0036 N2). */
2768
+ private nativeShell;
2435
2769
  start(opts: AgentStartOptions): Promise<void>;
2436
2770
  private launch;
2437
2771
  /**
@@ -2443,7 +2777,7 @@ declare class OpenCodeAdapter implements TaskModeAgentAdapter {
2443
2777
  private serverAddress;
2444
2778
  /** From the server's address to an open session — the 2.x API's, or a 1.x one created or resumed. */
2445
2779
  private connect;
2446
- commands(): RunnerCommand[];
2780
+ commands(): RunnerCommand$1[];
2447
2781
  private loadCommands;
2448
2782
  invokeCommand(name: string, args: string, onEvent: (event: AgentEvent) => void, signal?: AbortSignal): Promise<void>;
2449
2783
  private emitLoaded;
@@ -2455,9 +2789,25 @@ declare class OpenCodeAdapter implements TaskModeAgentAdapter {
2455
2789
  * to the turn before, and {@link pollStatus} stands behind the stream,
2456
2790
  * which can miss a status frame. No request is held open for the turn, so
2457
2791
  * fetch's 300s header timeout, the planner's reason for
2458
- * {@link recoverReply}, never applies.
2792
+ * {@link recoverReply}, never applies — except to a native command, whose
2793
+ * POST is held and is followed by {@link heldPost}.
2459
2794
  */
2460
2795
  private sendTask;
2796
+ /**
2797
+ * The POST of a native command, or null when it was refused before sending
2798
+ * (and said so). Unlike `prompt_async`, `/command` and `/summarize` answer
2799
+ * only when the turn is done, so the request carries no deadline.
2800
+ */
2801
+ private commandPost;
2802
+ /**
2803
+ * How a command turn's held POST came out. A socket that died under it says
2804
+ * nothing about the turn — the server works on regardless, as it does for
2805
+ * the planner's {@link recoverReply} — so that, like the session going idle
2806
+ * first, leaves the turn to the idle wait. Only a server's refusal, or a
2807
+ * transport that failed with the session never seen working, is the
2808
+ * message not having been taken.
2809
+ */
2810
+ private heldPost;
2461
2811
  /**
2462
2812
  * The prompt that carries a task message: the mode as the agent, the task
2463
2813
  * model and effort, and only `question` withheld. A steer names its own
@@ -2500,7 +2850,6 @@ declare class OpenCodeAdapter implements TaskModeAgentAdapter {
2500
2850
  * interrupted, because the request is remembered.
2501
2851
  */
2502
2852
  interrupt(timeoutMs: number): Promise<boolean>;
2503
- onProcessExit(listener: (code: number, lastWords?: string) => void): void;
2504
2853
  answerPermission(id: string, decision: ApprovalDecision): boolean | Promise<boolean>;
2505
2854
  /**
2506
2855
  * OpenCode 2.x replaced the 1.x HTTP surface rather than extending it, and
@@ -2508,7 +2857,6 @@ declare class OpenCodeAdapter implements TaskModeAgentAdapter {
2508
2857
  * answers with anything else — keeps the 1.x protocol this class speaks.
2509
2858
  */
2510
2859
  private v2Version;
2511
- private role;
2512
2860
  /**
2513
2861
  * Turn one settled assistant message into events. The settled response is
2514
2862
  * authoritative: it names the assistant message, so its parts are the ones
@@ -2572,37 +2920,55 @@ declare class OpenCodeAdapter implements TaskModeAgentAdapter {
2572
2920
  /** `/event` for one turn — see {@link openEventStream}. Load-bearing for {@link onPermissionAsked}. */
2573
2921
  private openStream;
2574
2922
  private json;
2923
+ /** `timeoutMs` null is for a turn's own POST, which the server answers when the turn is done. */
2575
2924
  private request;
2576
2925
  private headers;
2577
2926
  nativeSessionId(): string | null;
2927
+ version(): string | null;
2578
2928
  unusableProjectServers(): ProjectMcpServer[];
2579
2929
  /** 2.x only: a 1.x server's saved output was never checked, so it is asked about as any outside read. */
2580
2930
  savedOutput(abs: string): boolean;
2581
- /** Asks the server, which lists every MCP server it has with the state of its connection. */
2582
- mcpAttached(): Promise<boolean>;
2931
+ /**
2932
+ * Asks the server, which lists every MCP server it has with the state of its
2933
+ * connection. On 2.x, connected once the model would be offered the tools.
2934
+ */
2935
+ mcpAttached(): Promise<AttachState>;
2583
2936
  /** 2.x lists its servers as `{ name, status: { status } }`, and only once it has started connecting them. */
2584
2937
  private v2McpStatus;
2585
2938
  dispose(): void;
2586
- private exitMessage;
2939
+ protected exitMessage(): string;
2587
2940
  }
2588
2941
 
2589
- interface MappedTool {
2590
- tool: ResearchToolType;
2591
- /** The agent's own name, kept whenever it differs from the member it mapped to. */
2592
- toolLabel?: string;
2593
- }
2594
2942
  /**
2595
- * Classify one agent tool name. Case- and separator-insensitive, because the
2596
- * three agents disagree on `Read` vs `read` vs `read_file` for the same thing.
2943
+ * Whether a harness planner may be offered its own shell (ADR-0036 N2, N4), and
2944
+ * why not when it cannot. The table below is the live checks and nothing more:
2945
+ * a harness or version it does not name has not been checked, so it keeps
2946
+ * `run_command` alone. A new harness release is not handed an older release's
2947
+ * checks; it waits for its own.
2597
2948
  */
2598
- declare function mapAgentTool(name: string): MappedTool;
2949
+ type NativeShellOffer = {
2950
+ offered: true;
2951
+ }
2952
+ /** A whole sentence, shown once per session and in the setting's description. */
2953
+ | {
2954
+ offered: false;
2955
+ reason: string;
2956
+ };
2599
2957
  /**
2600
- * Normalize an agent's tool arguments into the shapes `summarizeToolCall`
2601
- * already knows how to read, so a harness `Read` gets a one-line summary
2602
- * instead of a bare tool name. Unknown keys are preserved — the raw args stay
2603
- * visible under the surfaces' output chevron.
2958
+ * What to tell the user about the opt-in for this harness. `version` is the
2959
+ * one the harness reported: Claude Code's `init` line, or an OpenCode 2.x
2960
+ * server's `/api/info`. Null means it reported none — Claude Code before its
2961
+ * first `init`, or OpenCode 1.x, which does not answer with one.
2962
+ *
2963
+ * Linux is deliberately not offered yet: N2 asks for checks on the installed
2964
+ * version, the checks were run on macOS, and bubblewrap passing is a sandbox
2965
+ * probe rather than a live check of either harness's shell.
2604
2966
  */
2605
- declare function normalizeAgentArgs(tool: ResearchToolType, args: Record<string, unknown>): Record<string, unknown>;
2967
+ declare function nativeShellOffer(input: {
2968
+ runner: string;
2969
+ version: string | null;
2970
+ platform?: NodeJS.Platform;
2971
+ }): NativeShellOffer;
2606
2972
 
2607
2973
  interface PlannerBackendOptions {
2608
2974
  /** The planner provider configured now. */
@@ -2796,10 +3162,15 @@ type PlannerUsageMessage = Extract<SessionMessage, {
2796
3162
  */
2797
3163
  declare class PlannerUsageLedger {
2798
3164
  private readonly sessionWindows;
3165
+ private readonly contextStatus;
2799
3166
  private usage;
2800
3167
  private windows;
2801
- /** `sessionWindows` serves the window of a planner model whose backend reports none. */
2802
- constructor(sessionWindows?: SessionWindows);
3168
+ /**
3169
+ * `sessionWindows` serves the window of a planner model whose backend
3170
+ * reports none; `contextStatus`, what an API planner's automatic compaction
3171
+ * does for its model now — undefined on a harness planner.
3172
+ */
3173
+ constructor(sessionWindows?: SessionWindows, contextStatus?: () => PlannerContextStatus | undefined);
2803
3174
  /** Fold one model call's record into the totals; returns the running ledger. */
2804
3175
  record(record: UsageRecord): PlannerUsage;
2805
3176
  /** Adopt a persisted ledger (a reopened session) or start from zero. */
@@ -2814,12 +3185,15 @@ declare class PlannerUsageLedger {
2814
3185
  message(turnId?: string): PlannerUsageMessage;
2815
3186
  }
2816
3187
 
3188
+ type PlannerCompactionMessage = Extract<SessionMessage, {
3189
+ type: 'planner_compaction';
3190
+ }>;
2817
3191
  interface SessionEventRelayDeps {
2818
3192
  broadcast: SessionBroadcaster;
2819
3193
  /** Where isolation notices go, for a host whose notifications are not seen by the user. */
2820
3194
  onNotice?: (notice: SessionNotice) => void;
2821
3195
  store: Pick<PlanStore, 'allTasks' | 'snapshot'>;
2822
- orchestrator: Pick<TaskOrchestrator, 'getIdleSince' | 'getQueuedTaskMessages' | 'getMergeGate' | 'runMergeView'>;
3196
+ orchestrator: Pick<TaskOrchestrator, 'getIdleSince' | 'getCompletionReceipt' | 'getQueuedTaskMessages' | 'getMergeGate' | 'holdReason' | 'runMergeView' | 'activity'>;
2823
3197
  runs: Pick<IsolationRunController, 'taskIsolation'>;
2824
3198
  /** Shared with the Session, which snapshots, restores and clears it. */
2825
3199
  usage: PlannerUsageLedger;
@@ -2863,6 +3237,8 @@ declare class SessionEventRelay {
2863
3237
  private statusOwed;
2864
3238
  /** The planner's requests announced and not yet settled, as they were announced: a surface joining now is owed them. */
2865
3239
  private readonly announced;
3240
+ /** The planner compaction started and not yet ended, as it was announced: a surface joining now is owed it too. */
3241
+ private compacting;
2866
3242
  constructor(deps: SessionEventRelayDeps);
2867
3243
  /** `plan` is read per event: a Session with no plan (or mid-reset) announces nothing about tasks. */
2868
3244
  observer(plan: () => LegacyPlanState | null): RelayObserver;
@@ -2887,6 +3263,8 @@ declare class SessionEventRelay {
2887
3263
  announcedApprovals(): Extract<SessionMessage, {
2888
3264
  type: 'approval_request';
2889
3265
  }>[];
3266
+ /** The planner compaction running now, as its start was announced; undefined when none is. */
3267
+ currentCompaction(): PlannerCompactionMessage | undefined;
2890
3268
  executionComplete(plan: LegacyPlanState | null): void;
2891
3269
  /** `turnId`: the planner turn whose commit this is, when one is. */
2892
3270
  planGenerated(plan: LegacyPlanState | null, goal: string, turnId?: string): void;
@@ -2912,6 +3290,22 @@ declare function listTaskLogAttempts(location: TaskLogLocation, taskId: string):
2912
3290
  * ever overwritten.
2913
3291
  */
2914
3292
  declare function openTaskLog(location: TaskLogLocation, taskId: string, attempt?: number): TaskLogFile;
3293
+ /**
3294
+ * Where a script task attempt's whole output goes, with its directory ready.
3295
+ * The caller writes the file; it is named for the attempt's number so it sits
3296
+ * beside the log, and is no attempt of its own.
3297
+ */
3298
+ declare function prepareTaskOutputFile(location: TaskLogLocation, taskId: string, attempt: number): string;
3299
+ /** Where a read-only attempt's saved patch is kept, beside its log (ADR-0050). */
3300
+ declare function taskPatchFile(location: TaskLogLocation, taskId: string, attempt: number): string;
3301
+ /**
3302
+ * Keep a read-only attempt's patch beside its log, in place of one it saved
3303
+ * before (ADR-0050). Written whole under another name first, so a reader
3304
+ * never finds half a patch. Returns where it is.
3305
+ */
3306
+ declare function saveTaskPatch(location: TaskLogLocation, taskId: string, attempt: number, patch: Buffer): string;
3307
+ /** A saved patch, or undefined when that attempt kept none. */
3308
+ declare function readTaskPatch(location: TaskLogLocation, taskId: string, attempt: number): Buffer | undefined;
2915
3309
  /**
2916
3310
  * One attempt's events, in order; empty when it has no log. A line that does
2917
3311
  * not parse is skipped — the last one, most likely, cut off by a crash
@@ -2949,12 +3343,27 @@ declare class TaskLogRecorder {
2949
3343
  * An entry the session did not emit, written into the log of the attempt
2950
3344
  * that runs `sessionId` and sent at once: what Ordewell made of one of its
2951
3345
  * events once that settled. Dropped once the task's next attempt started.
3346
+ * With no session — a user task's completion has none — the entry opens an
3347
+ * attempt log of its own, so a reader of the task's logs finds it like any other.
2952
3348
  */
2953
- note(sessionId: string, taskId: string, entry: TaskLogEvent): void;
3349
+ note(sessionId: string | undefined, taskId: string, entry: TaskLogEvent): void;
3350
+ private writeAlone;
2954
3351
  /** Start a new attempt's log for `session`, opening with the skills it was given, and keep it until the session exits. */
2955
3352
  record(taskId: string, session: IRunnerSession, skills?: readonly TaskSkillSnapshot[], attemptNumber?: number): void;
2956
3353
  }
2957
3354
 
3355
+ /**
3356
+ * How a surface's direct edit is admitted. The task card offers its editors
3357
+ * only on a task that has not started, so its edit must not land on one the
3358
+ * scheduler claimed while discovery or the skill check was still running.
3359
+ * Other direct callers (the TUI, the API, the runner's own model switch) keep
3360
+ * the wider direct-edit contract and pass nothing.
3361
+ */
3362
+ interface TaskEditOptions {
3363
+ /** Refuse the edit once the task is live or its status has left the unstarted set. */
3364
+ requireUnstarted?: boolean;
3365
+ }
3366
+
2958
3367
  /**
2959
3368
  * Options for plan generation. Progress is not overridable: every planner
2960
3369
  * progress event is translated to a SessionMessage inside the Session and
@@ -2970,8 +3379,10 @@ interface SessionRuntimeSettings {
2970
3379
  modelAllowlist?: Record<string, string[]>;
2971
3380
  /** Absent means the user never chose, and `config.enabledRunners` (the host's defaults) decides. */
2972
3381
  enabledRunners?: RunnerId[];
2973
- /** The user's run settings (ADR-0030); absent means every default. */
3382
+ /** The user's saved run settings (ADR-0030), which seed a new session; absent means every default. */
2974
3383
  run?: GlobalRunSettings;
3384
+ /** Whether an API planner compacts its own context before a call that would not fit; absent means on. */
3385
+ plannerAutoCompaction?: boolean;
2975
3386
  }
2976
3387
  declare function sessionRuntimeSettings(settings: UserSettings): SessionRuntimeSettings;
2977
3388
  /** Where a fork landed: the new session's id, and what adopting it needs. */
@@ -2997,6 +3408,8 @@ interface SessionDeps {
2997
3408
  config: IConfig;
2998
3409
  notifications: INotification;
2999
3410
  runner: IRunner;
3411
+ /** What runs script tasks' bodies (ADR-0035); the real {@link ScriptRunner} unless a test supplies one. */
3412
+ scriptRunner?: IRunner;
3000
3413
  registry: RunnerRegistry;
3001
3414
  /** Resolves the workspace root for the orchestrator (lazy — VS Code can change it). */
3002
3415
  workspaceRoot: () => string;
@@ -3008,7 +3421,11 @@ interface SessionDeps {
3008
3421
  onNotice?: (notice: SessionNotice) => void;
3009
3422
  /** Shared across sessions — sole producer of model catalogs and routing lists. */
3010
3423
  modelResolver: ModelResolver;
3011
- /** Live runtime settings (enabled runners). Read at each operation that needs them. */
3424
+ /**
3425
+ * Live runtime settings (enabled runners). Its run values seed a session
3426
+ * when it opens; a later change reaches an open session through
3427
+ * {@link Session.applyRunSettings}.
3428
+ */
3012
3429
  settings: () => SessionRuntimeSettings;
3013
3430
  /**
3014
3431
  * Host-assigned session id. When set, every persist writes under this id so
@@ -3038,10 +3455,27 @@ interface SessionDeps {
3038
3455
  isolation?: IWorktreeIsolation;
3039
3456
  /** Persistence seam. Defaults to the saved-session store under the workspace. */
3040
3457
  saveSession?: SaveSession;
3458
+ /**
3459
+ * The host whose lease the session holds while it is open (ADR-0049).
3460
+ * Defaults to this process's when saves go to the session store; a session
3461
+ * given its own `saveSession` owns nothing on disk, so it holds no lease
3462
+ * unless given a host too.
3463
+ */
3464
+ leaseHost?: LeaseHost;
3041
3465
  /** Where a structured task's log is saved (ADR-0018, P1). Defaults to a file per attempt beside the session's. */
3042
3466
  openTaskLog?: (location: TaskLogLocation, taskId: string, attempt?: number) => TaskLogFile;
3043
3467
  /** The Ordewell MCP server a harness planner's tools are served from (ADR-0022). Defaults to the process's one. */
3044
3468
  mcpServer?: OrdewellMcpServer;
3469
+ /**
3470
+ * What carries web reads (ADR-0027): the pinned transport, real DNS and the
3471
+ * process-wide cache and search provider unless a test fakes the network.
3472
+ */
3473
+ web?: {
3474
+ transport?: WebTransport;
3475
+ resolveHost?: ResolveAll;
3476
+ cache?: WebResponseCache;
3477
+ searchProvider?: ISearchProvider;
3478
+ };
3045
3479
  }
3046
3480
  /**
3047
3481
  * The composition root: builds every collaborator a Session drives and wires
@@ -3078,6 +3512,8 @@ interface SessionParts {
3078
3512
  fetcher: IWebFetcher;
3079
3513
  skillsService: Pick<SkillsService, 'findSkill' | 'listSkills' | 'searchedDirs' | 'forRoot'>;
3080
3514
  saveSession: SaveSession;
3515
+ /** Its lease, told by the Session what to do when another host takes it (ADR-0049). */
3516
+ lease: (onLost: (held: SessionHeld) => void) => SessionLease;
3081
3517
  /** The conversation's host is the Session itself, so only the Session can make it. */
3082
3518
  conversation: (host: PlannerConversationHost) => PlannerConversation;
3083
3519
  }
@@ -3094,6 +3530,7 @@ interface SessionParts {
3094
3530
  * (setModels, setRunnerList, …) stay on the host; Session never emits them.
3095
3531
  */
3096
3532
  declare class Session {
3533
+ readonly research: ResearchTasks;
3097
3534
  private readonly backend;
3098
3535
  private readonly usage;
3099
3536
  private readonly windows;
@@ -3115,22 +3552,37 @@ declare class Session {
3115
3552
  * supervisor has first.
3116
3553
  */
3117
3554
  private sentHandoff;
3555
+ /** The effect allowlist rules already warned about as ignored, so each is said once, not on every script. */
3556
+ private readonly ignoredEffectRules;
3118
3557
  /** The run was forgotten and no surface has been told yet; see {@link announceClosed}. */
3119
3558
  private closedUnannounced;
3120
- /** The edit the open planner turn holds, as last announced: a surface that joins mid-turn is shown it. */
3121
- private sentHeld;
3122
3559
  private readonly notifications;
3123
3560
  private readonly onNotice?;
3124
3561
  private readonly modelResolver;
3125
3562
  private readonly fsAdapter;
3126
3563
  private readonly approvals;
3564
+ /** What the user, and the planner for research, approved to run; kept by {@link ApprovalRecord}, saved on the plan. */
3565
+ private readonly record;
3566
+ /** Approval was withdrawn and no plan broadcast has carried it to the surfaces yet; see {@link withdrawalOwed}. */
3567
+ private withdrawalUnannounced;
3127
3568
  private readonly approvalPolicy;
3128
3569
  private readonly taskLogs;
3129
3570
  private readonly fetcher;
3130
3571
  private readonly settingsFn;
3572
+ /**
3573
+ * This session's copy of the user's run settings (ADR-0030), seeded when it
3574
+ * opens. Another session's change writes the file but does not move this one.
3575
+ */
3576
+ private runSettingsValue;
3131
3577
  private readonly save;
3578
+ /** One live host per session (ADR-0049): no save, and no run, without it. */
3579
+ private readonly lease;
3580
+ /** The host a save was refused for, so the refusal is said once rather than at every save. */
3581
+ private saveRefusedFor;
3132
3582
  /** User controls in flight; see {@link withSave}. */
3133
3583
  private controlsInFlight;
3584
+ /** A whole-plan start opening; a second Execute joins it rather than resetting the run under it. */
3585
+ private runOpening;
3134
3586
  private unsubObserver;
3135
3587
  private readonly hostSessionId?;
3136
3588
  private currentSessionId;
@@ -3139,6 +3591,8 @@ declare class Session {
3139
3591
  private readonly turns;
3140
3592
  /** Destroyed: a turn that waited for the planner to be found must not open on it. */
3141
3593
  private closed;
3594
+ /** Destroyed and let go of its lease for good: whatever settles later neither saves nor holds the session. */
3595
+ private leaseReleased;
3142
3596
  /**
3143
3597
  * Turns PlanTurns does not open, waiting at the planner gate, each ended
3144
3598
  * with why it must not open: a Stop, a newer goal, or its plan scope ending.
@@ -3151,9 +3605,9 @@ declare class Session {
3151
3605
  private readonly supervision;
3152
3606
  /** The open planner turn's, or the last one's once it settled; see {@link openTurn}. */
3153
3607
  private turnEffects;
3154
- /** Every turn's effects with starts still owed: a turn's may still be running its starts when the next opens. */
3155
- private readonly owingEffects;
3156
3608
  private unlistenApprovals;
3609
+ /** The one check of a planner's change to the plan; an Act supervisor's edits meet its guard. */
3610
+ private readonly planChanges;
3157
3611
  private readonly plannerTools;
3158
3612
  constructor(parts: SessionParts);
3159
3613
  /**
@@ -3162,13 +3616,7 @@ declare class Session {
3162
3616
  * tools come here; see {@link ExecutionCommands}.
3163
3617
  */
3164
3618
  execute(command: ExecutionCommand, actor: ExecutionActor): Promise<CommandOutcome>;
3165
- private newTurnEffects;
3166
- /**
3167
- * Run one planner turn with its own {@link TurnEffects}, settled in a
3168
- * `finally`: a turn that throws — stopped, failed, discarded — still
3169
- * reports what it deferred as not done, rather than the next turn
3170
- * silently forgetting it.
3171
- */
3619
+ /** Run one planner turn with its own {@link TurnEffects}, which only a turn that lands hands back. */
3172
3620
  private openTurn;
3173
3621
  /**
3174
3622
  * Autorun (ADR-0030): a plan the planner submitted in the turn that just
@@ -3183,62 +3631,55 @@ declare class Session {
3183
3631
  */
3184
3632
  private executionHost;
3185
3633
  /**
3186
- * Whether the open turn holds a task edit, landing as its reply ends, that
3187
- * changes this task — or any, when none is named. A whole plan held is not
3188
- * one: nothing it would start is approved until the user executes it.
3189
- */
3190
- private heldEdit;
3191
- /** The plan's tasks as the open turn's held edit leaves them: as they are, when it holds none or one that would not apply. */
3192
- private editedTasks;
3193
- /** {@link editedTasks}, with the id each of the held batch's handles names in them. */
3194
- private editedPlan;
3195
- /** The task as the open turn's held edit leaves it; undefined when that edit removes it. */
3196
- private asEdited;
3634
+ * The script task a `run_script` call would add (ADR-0035), checked as the
3635
+ * planner's own add is, against the plan as it stands, the turn's committed
3636
+ * edits included. A supervisor's is held to the oscillation guard its edits
3637
+ * are held to, and marked as its own.
3638
+ */
3639
+ private draftScript;
3640
+ /** A drafted script task into the plan now, unapproved: the seam approves it once its start is consented to. */
3641
+ private addScript;
3642
+ /** Settles once the task's attempt is over — it ended, was stopped or left the plan — or after `ms`, or as `signal` aborts. */
3643
+ private awaitEnd;
3644
+ /** A task's result is owed to the planner (ADR-0037, R2): saved on the task, so a restart still owes it. */
3645
+ private oweReport;
3646
+ /** The end of a task's output as a report carries it: the live capture while it lasts, else the tail its verdict kept. */
3647
+ private reportTail;
3197
3648
  /** The open planner turn is the supervisor's, acting at Act: its edits and added tasks are its own (ADR-0029). */
3198
3649
  private supervisorEditing;
3199
3650
  /**
3200
- * The runners a planner edit may put a task on (`assignableRunners`). An
3201
- * edit is checked as it is made and again as it lands against this one set,
3202
- * so what was accepted cannot be refused at commit.
3203
- */
3204
- private editRunners;
3205
- /**
3206
- * A planner edit checked as it is made and again as it lands: the applier's
3207
- * rules, then, for the supervisor, the oscillation guard. `held` are the
3208
- * ops the turn already holds, which `ops` join; errors are numbered by `ops`.
3651
+ * What every planner change is checked against (`PlanChangeCheck`), as it
3652
+ * is submitted and as it commits: the live catalog and plan, and the
3653
+ * handles the open turn's committed edits gave. A change may put a task
3654
+ * only on the runners enabled now (`assignableRunners`), one set at both
3655
+ * ends, so what was accepted cannot be refused at commit unless the catalog
3656
+ * moved; the plan's own runners' modes are read too, for a task left on one
3657
+ * switched off since. `guarded`: the change meets the supervisor's
3658
+ * oscillation guard.
3209
3659
  */
3210
- private checkPlannerOps;
3660
+ private changeScope;
3211
3661
  /** A line from Ordewell itself, kept in the transcript where it happened. */
3212
3662
  private systemNote;
3213
3663
  /** A message to the user in the supervisor's voice, kept in the transcript where it happened. */
3214
3664
  private supervisorNote;
3215
3665
  /**
3216
3666
  * Who the open planner turn speaks for at the seam: the supervisor in a
3217
- * wake-up turn, at its level as of this call, else the user's turn.
3667
+ * wake-up turn, at its level as of this call, else the user's turn — which a
3668
+ * report turn for a task that turn started acts as (ADR-0037, R3).
3218
3669
  */
3219
3670
  private turnActor;
3220
- /** The user's run settings as they stand now (ADR-0030). */
3671
+ /** Whether the commits Ordewell writes carry the Planned-with trailer now; the user's setting, not the run's copy, so a change reaches the next landing. */
3672
+ plannedWithTrailer(): boolean;
3673
+ /** The run settings this session shows and starts its runs with (ADR-0030): its own copy, not the file's. */
3674
+ runSettings(): GlobalRunSettings;
3675
+ /** The user's run settings as they stand for this session (ADR-0030). */
3221
3676
  private userRunSettings;
3222
3677
  /** A call at the seam, in the transcript where it happened and on every surface at once. */
3223
3678
  private recordExecution;
3224
- /**
3225
- * Whether the user approved this task to run (ADR-0029, ADR-0035): it was in
3226
- * the plan they executed, or they started it, added it or let it start since,
3227
- * and it has been in the plan ever since. The one reading of the approval
3228
- * record — the seam and the scheduler both ask it.
3229
- */
3679
+ /** Whether the user approved this task to run; see {@link ApprovalRecord.isApproved}. */
3230
3680
  isApproved(taskId: string): boolean;
3231
- /** A task the scheduler leaves for the user to approve; see {@link waitsForApproval}. */
3681
+ /** A task the scheduler leaves for the user to approve; see {@link ApprovalRecord.waitsForApproval}. */
3232
3682
  waitsForApproval(taskId: string): boolean;
3233
- /** What the user approved to run (ADR-0029): a start of their own approves the task and its subtasks. */
3234
- private approve;
3235
- /**
3236
- * A task that leaves the plan takes its approval with it, whichever route
3237
- * removed it: an edit, a merge or split, a whole plan that leaves it out.
3238
- * Approval is of the task the user saw, and a planner may later write a new
3239
- * task under the same id (ADR-0029).
3240
- */
3241
- private keepApprovals;
3242
3683
  /**
3243
3684
  * Answer an outstanding approval. Every surface funnels here — the web
3244
3685
  * server's HTTP route, the VS Code webview, the TUI prompt — so the decision
@@ -3251,14 +3692,34 @@ declare class Session {
3251
3692
  announcedApprovals(): Extract<SessionMessage, {
3252
3693
  type: 'approval_request';
3253
3694
  }>[];
3695
+ /**
3696
+ * The planner compaction in flight, as its start was announced, for a
3697
+ * surface that joins or reconnects while it runs; undefined when none is.
3698
+ */
3699
+ plannerCompaction(): Extract<SessionMessage, {
3700
+ type: 'planner_compaction';
3701
+ }> | undefined;
3254
3702
  /** Scopes the user granted this session — surfaced so a UI can show what is already allowed. */
3255
3703
  approvedScopes(): string[];
3256
3704
  /** The stable id this session persists under — matches the host's id when one was provided. */
3257
3705
  get sessionId(): string;
3706
+ /**
3707
+ * The workspace whose account this session's planner gate judges, so a host
3708
+ * can re-probe it before a turn without keeping a second copy.
3709
+ */
3710
+ get workspaceRoot(): string;
3711
+ /** The planner this session's own gate reads — not whatever the host's config says now. */
3712
+ get plannerProvider(): PlannerBackendId;
3258
3713
  /** Where this session's structured task logs are saved (ADR-0018, P1). */
3259
3714
  get taskLogLocation(): TaskLogLocation;
3260
3715
  /** The attempts of a task that have a saved log, oldest first. */
3261
3716
  taskLogAttempts(taskId: string): number[];
3717
+ /**
3718
+ * Mark tasks a saved plan has no attempted flag for but whose logs prove an
3719
+ * attempt ran. Only AI and script tasks: a manual task's completion wrote a
3720
+ * handoff log, which is not a chat.
3721
+ */
3722
+ private creditLoggedAttempts;
3262
3723
  /** One attempt's saved log, for `replayTaskLog` — what a reopened task view shows. */
3263
3724
  taskLog(taskId: string, attempt: number): TaskLogEvent[];
3264
3725
  get executionLog(): ReadonlyArray<TaskSnapshot>;
@@ -3286,13 +3747,32 @@ declare class Session {
3286
3747
  private persist;
3287
3748
  /** A new plan on a long-lived Session gets its own persisted identity (unless the host fixed one). */
3288
3749
  private remintSessionId;
3750
+ /**
3751
+ * Null while this Session may save its session and run in its isolation
3752
+ * run; otherwise the host that holds it (ADR-0049). Claims the lease when
3753
+ * no live host holds it, as a new session's first save does.
3754
+ */
3755
+ heldElsewhere(): SessionHeld | null;
3756
+ /**
3757
+ * A new plan under the host's fixed id writes that session's file: refused
3758
+ * before any planner turn while another host has it open (ADR-0049). A
3759
+ * minted id is new, and is claimed at its first save.
3760
+ */
3761
+ private claimHostSession;
3762
+ private saveRefused;
3763
+ /**
3764
+ * Another host took the session over, judging this one gone (ADR-0049). Its
3765
+ * runners must not work on beside that host's, in the worktrees that host
3766
+ * now prepares, so the run here stops.
3767
+ */
3768
+ private leaseLost;
3289
3769
  /**
3290
3770
  * The plan scope ends: everything that belongs to the plan this Session
3291
3771
  * holds goes, so nothing of it reaches the next one — a new plan, another
3292
3772
  * session adopted, or the Session closed. Its run is stopped, the planner
3293
- * turn in flight is abandoned with everything it held, what that turn
3294
- * deferred is settled, and the conversation, supervision, usage ledger,
3295
- * execution log and planner prompts are cleared. A new plan on the same
3773
+ * turn in flight is abandoned with everything it held, and the
3774
+ * conversation, supervision, usage ledger, execution log and planner
3775
+ * prompts are cleared. A new plan on the same
3296
3776
  * Session keeps the scopes the user granted (`keepGrants`); a session
3297
3777
  * boundary does not (ADR-0008).
3298
3778
  */
@@ -3331,9 +3811,9 @@ declare class Session {
3331
3811
  * A task is running right now. Deliberately live work, not the scheduler's
3332
3812
  * armed flag: a run paused on a user task, a hold or a cancellation has
3333
3813
  * nothing executing, and reporting it as executing is what left the plan
3334
- * unstartable after its last live task was cancelled.
3814
+ * unstartable after its last live task was cancelled. See
3815
+ * {@link TaskOrchestrator.hasLiveWork}: a spawned runner, not merely an armed scheduler.
3335
3816
  */
3336
- /** See {@link TaskOrchestrator.hasLiveWork} — a spawned runner, not merely an armed scheduler. */
3337
3817
  get isExecuting(): boolean;
3338
3818
  get status(): 'approved' | 'running' | 'completed';
3339
3819
  /**
@@ -3365,6 +3845,11 @@ declare class Session {
3365
3845
  * that read the wait as idle sent a follow-up as a new goal.
3366
3846
  */
3367
3847
  get isPlannerBusy(): boolean;
3848
+ /**
3849
+ * The planner turn answering right now, if one is: a surface whose stream
3850
+ * lost that turn's end reads this back to tell a stale gate from a live one.
3851
+ */
3852
+ get plannerTurnId(): string | undefined;
3368
3853
  /**
3369
3854
  * Stop the planner turn in flight. It still settles — a backend hands back
3370
3855
  * what it had — and ends as stopped. A turn still waiting at the planner
@@ -3379,6 +3864,8 @@ declare class Session {
3379
3864
  * answers, the enabled default is a stale guess a request must not freeze.
3380
3865
  */
3381
3866
  private chosenRunners;
3867
+ /** Whether a first turn for `requested` would be refused for want of an enabled runner: a host calls it before it replaces a live session. */
3868
+ assertRunnersEnabled(requested: RunnerId[] | undefined): void;
3382
3869
  generatePlan(goal: string, runners?: RunnerId[], options?: GeneratePlanOptions): Promise<LegacyPlanState>;
3383
3870
  /**
3384
3871
  * Kick off the planner conversation (ADR-0002): research + the first planner
@@ -3428,23 +3915,31 @@ declare class Session {
3428
3915
  private saveFork;
3429
3916
  /**
3430
3917
  * Condense the conversation on the user's say-so: a hidden planner turn
3431
- * summarises it, and the summary replaces everything but the last two
3432
- * exchanges. Conversation only — the tasks, and any run
3918
+ * summarises it, and the summary stands in for everything but the last two
3919
+ * exchanges in the planner's context, the transcript keeping every message.
3920
+ * Conversation only — the tasks, and any run
3433
3921
  * executing them, are untouched. Atomic: a failed or stopped turn changes
3434
3922
  * nothing.
3435
3923
  */
3436
3924
  compactConversation(signal?: AbortSignal): Promise<ConversationCompaction>;
3437
3925
  rewindTargets(): RewindTarget[];
3438
- /** A plan the planner submitted through its tool, already checked against the live catalog. */
3926
+ /**
3927
+ * A plan the planner submitted through its tool, already checked against
3928
+ * the live catalog: committed now, in the turn the call was made in
3929
+ * (ADR-0022 P2), and answered with the tasks it holds.
3930
+ */
3439
3931
  private submitPlanFromTool;
3440
3932
  /**
3441
- * A task edit the planner made through its tool. Checked against the live
3442
- * catalog — an edit to a running or completed task is refused here, with
3443
- * what would be accepted — then handed to the open turn, which commits it as
3444
- * it would the same ops in a taskOps reply. Edits made in one reply join one
3445
- * batch, which applies whole or not at all.
3933
+ * A task edit the planner made through its tool, checked against the live
3934
+ * catalog and committed now, in the turn the call was made in, as a batch
3935
+ * of its own: whole or not at all, against the plan as it stands
3936
+ * (ADR-0022 P2). An edit to a running or completed task is refused, with
3937
+ * what would be accepted. Answered with the tasks it added and the task
3938
+ * each handle it gave names, for a later call to read or start them by.
3446
3939
  */
3447
3940
  private editPlanFromTool;
3941
+ /** Tasks as a planner tool's answer names them: by id and by the reference the plan gives each now. */
3942
+ private taskHeads;
3448
3943
  /** Whether the planner conversation is live (started and not yet committed to a plan). */
3449
3944
  get isConversationActive(): boolean;
3450
3945
  /**
@@ -3464,12 +3959,10 @@ declare class Session {
3464
3959
  private skillsAt;
3465
3960
  private conversationOpening;
3466
3961
  /**
3467
- * Load planner-produced tasks, coerced to the allowlist read live — it may
3468
- * have changed since planning started. Tasks adopted on an armed scheduler,
3469
- * or on a run that awaits a resume (ADR-0034), are reconciled rather than
3470
- * reloaded: `loadPlan` clears the on-hold set and the review approval, so a
3471
- * task the user cancelled would be re-armed and re-spawned by the re-tick
3472
- * that follows, and a retry after the edit would run the task alone.
3962
+ * Load planner-produced tasks, as the check they landed through resolved
3963
+ * them against the catalog read live: it may have changed since planning
3964
+ * started. The scheduler adopts them into the run under way, or as the plan
3965
+ * the next run starts from (`adoptEdit`).
3473
3966
  *
3474
3967
  * A whole-plan commit is the planner restating every task, so it is laid
3475
3968
  * over the plan's execution state rather than replacing it: a planner that
@@ -3478,17 +3971,20 @@ declare class Session {
3478
3971
  * tasks, and `rearm`, the one op meant to change a status, must stand.
3479
3972
  */
3480
3973
  private adoptPlannerTasks;
3974
+ private adoptResearch;
3481
3975
  private preparePlannerTaskUpdates;
3482
3976
  /**
3483
3977
  * PRD mode: when the planner writes the full markdown PRD, save it to
3484
3978
  * .scratch/<slug>/PRD.md (Matt Pocock to-prd convention) and keep it on the plan.
3485
3979
  */
3486
3980
  private capturePrd;
3981
+ /** Merge the run settings this session owns, ignoring a field it does not know from a client's body. */
3982
+ private mergeRunSettings;
3487
3983
  /**
3488
3984
  * The user changed run settings in this session (ADR-0030), which the host
3489
3985
  * has already saved: the run under way takes them at once, in either
3490
3986
  * direction — the wake-up turn under way too, since the seam reads the run's
3491
- * level at each call. Other sessions' runs keep theirs until they end.
3987
+ * level at each call. Other sessions keep the values they opened with.
3492
3988
  */
3493
3989
  applyRunSettings(changes: Partial<GlobalRunSettings>): void;
3494
3990
  private idlePlan;
@@ -3497,7 +3993,8 @@ declare class Session {
3497
3993
  * Execute while a run is under way (#95): the seam has approved every task,
3498
3994
  * as executing does, and the run takes them in. A run the scheduler was not
3499
3995
  * driving, one task started on its own, becomes the plan's run, as Execute
3500
- * would make it.
3996
+ * would make it. The planner's run_plan joins with `approved` 0: the
3997
+ * scheduler starts only approved tasks, so `left` tasks still wait.
3501
3998
  */
3502
3999
  private joinRun;
3503
4000
  private startOneTask;
@@ -3511,6 +4008,16 @@ declare class Session {
3511
4008
  reschedule(): Promise<void>;
3512
4009
  private restartTask;
3513
4010
  private resumeTask;
4011
+ /**
4012
+ * The user's Retry compaction of a planner paused on its context (ADR-0002):
4013
+ * the paused turn opens again on the inputs it kept. On the live context it
4014
+ * condenses what it can, then sends the held input once; with the live
4015
+ * context gone — a reload, another provider — a turn that only read starts
4016
+ * again from the transcript. One whose calls changed something is refused,
4017
+ * the pause kept, since no note could stop an effect running twice. Settles
4018
+ * with the plan the turn lands, or paused again on; a Stop ends the pause.
4019
+ */
4020
+ retryPlannerCompaction(signal?: AbortSignal): Promise<LegacyPlanState>;
3514
4021
  /**
3515
4022
  * Keep what waits on the planner's usage limit from being sent at the reset:
3516
4023
  * it goes with the user's next message instead. False when nothing waits on
@@ -3612,6 +4119,13 @@ declare class Session {
3612
4119
  stopExecution(): void;
3613
4120
  /** Something will start without being asked again; see {@link TaskOrchestrator.startsPending}. */
3614
4121
  get startsPending(): boolean;
4122
+ /**
4123
+ * What the run is doing — starting, running, scheduled, resumable, and
4124
+ * whether it advances on its own — for a surface or a guard that must tell a
4125
+ * start on its way from real execution.
4126
+ * See {@link TaskOrchestrator.activity}.
4127
+ */
4128
+ get activity(): RunActivity;
3615
4129
  /**
3616
4130
  * Run a user control and save what it changed before any surface hears of
3617
4131
  * it. Not a status hold, as {@link saved} is: these await spawns and git, so
@@ -3634,31 +4148,27 @@ declare class Session {
3634
4148
  * reschedule they owe.
3635
4149
  */
3636
4150
  private mutatePlan;
4151
+ /** The plan to every surface, with the approvals it holds now. */
4152
+ private announcePlan;
4153
+ /**
4154
+ * Approval was taken from a task, by a rule that runs wherever the tree
4155
+ * changes (ADR-0029). A mutation that is under way announces the plan as it
4156
+ * ends, and that broadcast carries this; one that is not, a load or a route
4157
+ * that announces nothing of the plan, is followed by one of its own as soon
4158
+ * as the synchronous work is done, so no surface keeps showing the task
4159
+ * approved.
4160
+ */
4161
+ private withdrawalOwed;
3637
4162
  /** Patch one task's fields, checked as the planner's task ops are; see {@link PlanEditor.updateTask}. */
3638
- updateTask(taskId: string, changes: Partial<Task>): Promise<LegacyPlanState | null>;
4163
+ updateTask(taskId: string, changes: Partial<Task>, options?: TaskEditOptions): Promise<LegacyPlanState | null>;
3639
4164
  /** Move one task onto a different runner, re-deriving what is scoped to it; see {@link PlanEditor.setTaskRunner}. */
3640
- setTaskRunner(taskId: string, runner: RunnerId): Promise<LegacyPlanState | null>;
4165
+ setTaskRunner(taskId: string, runner: RunnerId, options?: TaskEditOptions): Promise<LegacyPlanState | null>;
3641
4166
  /** Replace one task's dependency list, under the same guard as a field patch; see {@link PlanEditor.setTaskDependencies}. */
3642
- setTaskDependencies(taskId: string, dependencies: string[]): Promise<LegacyPlanState | null>;
4167
+ setTaskDependencies(taskId: string, dependencies: string[], options?: TaskEditOptions): Promise<LegacyPlanState | null>;
3643
4168
  /** Delete one task, cancelling its runner first if it runs; see {@link PlanEditor.removeTask}. */
3644
4169
  removeTask(taskId: string): Promise<LegacyPlanState | null>;
3645
4170
  /** Add one task, deriving what the caller left unset; see {@link PlanEditor.addTask}. */
3646
4171
  addTask(draft: Partial<Task>): Promise<LegacyPlanState | null>;
3647
- /**
3648
- * Planner-driven merge: validate compatibility up front, then ask the planner
3649
- * LLM to produce a single "merge" taskOps op combining the selected tasks.
3650
- * Goes through the same conversation loop + validated-atomic-edit + corrective
3651
- * retry flow as every other task_ops edit (ADR-0002). Throws on a
3652
- * pre-flight compatibility failure so the host surfaces an inline error
3653
- * before any LLM call.
3654
- */
3655
- requestMerge(taskIds: string[], options?: GeneratePlanOptions): Promise<LegacyPlanState>;
3656
- /**
3657
- * Planner-driven split: ask the planner LLM to decompose one task into a
3658
- * sequence of smaller tasks. The model generates the breakdown (no manual
3659
- * per-task specs from the user). Same conversation-loop/repair path as merge.
3660
- */
3661
- requestSplit(taskId: string, options?: GeneratePlanOptions): Promise<LegacyPlanState>;
3662
4172
  loadPlan(plan: LegacyPlanState, goal: string, workspace: string, opts?: {
3663
4173
  sessionId?: string;
3664
4174
  persist?: boolean;
@@ -3716,6 +4226,20 @@ declare class AlreadyExecutingError extends Error {
3716
4226
  constructor();
3717
4227
  }
3718
4228
 
4229
+ type ParsedCommand = {
4230
+ ok: true;
4231
+ command: ExecutionCommand;
4232
+ } | {
4233
+ ok: false;
4234
+ message: string;
4235
+ };
4236
+ /**
4237
+ * An `ExecutionCommand` out of a request body, or the sentence saying what is
4238
+ * wrong with it. Only the command's own fields are kept, so a body cannot carry
4239
+ * anything the seam does not read.
4240
+ */
4241
+ declare function parseExecutionCommand(body: unknown): ParsedCommand;
4242
+
3719
4243
  /**
3720
4244
  * Where Ordewell keeps machine-global data (settings, daemon tokens, caches,
3721
4245
  * credentials). A single home for everything not tied to a project directory,
@@ -3730,6 +4254,9 @@ declare function globalDataDir(): string;
3730
4254
  * on disk, which would otherwise permanently skip lifting anything added to
3731
4255
  * this list later (`.env` did exactly that: real API keys stranded in the old
3732
4256
  * dir because the settings.json move had already made the new dir exist).
4257
+ *
4258
+ * Best-effort: a home it cannot write (read-only, full disk) must not take the
4259
+ * CLI down before `--help`, and the old file stays where it is.
3733
4260
  */
3734
4261
  declare function migrateOldConfigDir(): void;
3735
4262
 
@@ -3801,7 +4328,8 @@ declare class VerdictEngine {
3801
4328
  /**
3802
4329
  * Attach to a spawned session. The runner's `task_complete` call on the
3803
4330
  * attempt's token is the only completion evidence (ADR-0022, V2); a session
3804
- * that exits before one gets a failed verdict.
4331
+ * that exits before one gets a failed verdict. A script task's session has
4332
+ * no runner to call it: its exit status is the evidence instead (ADR-0035).
3805
4333
  *
3806
4334
  * Returns the attempt's generation, what {@link signalComplete} is checked against.
3807
4335
  */
@@ -3812,6 +4340,12 @@ declare class VerdictEngine {
3812
4340
  * earlier call already settled it.
3813
4341
  */
3814
4342
  signalComplete(taskId: string, generation: number, report: TaskCompleteArgs): void;
4343
+ /**
4344
+ * A script task's exit status (ADR-0035): its one evidence, as a runner's
4345
+ * `task_complete` call is a runner's. It settles the attempt unless that
4346
+ * attempt is no longer the task's current one.
4347
+ */
4348
+ signalScriptExit(taskId: string, generation: number, exit: ScriptExit): void;
3815
4349
  /** The runner was told something after its evidence so far, so only what it reports from here counts. */
3816
4350
  private supersede;
3817
4351
  private acceptVerdict;
@@ -3851,6 +4385,7 @@ declare class BufferedTaskOutputSource implements TaskOutputSource {
3851
4385
  reset(): void;
3852
4386
  finalText(taskId: string): string;
3853
4387
  report(taskId: string): string | undefined;
4388
+ handoff(taskId: string): StoredHandoff | undefined;
3854
4389
  liveTail(taskId: string, opts: LiveTailOptions): LiveTail | null;
3855
4390
  private append;
3856
4391
  }
@@ -3881,11 +4416,12 @@ declare abstract class AbstractRunnerSession implements IRunnerSession {
3881
4416
  abstract queued(): QueuedTaskMessage[];
3882
4417
  abstract interrupt(): Promise<void>;
3883
4418
  abstract nativeSessionId(): string | null;
3884
- abstract commands(): RunnerCommand[];
4419
+ abstract commands(): RunnerCommand$1[];
3885
4420
  abstract invokeCommand(name: string, args?: string): string;
3886
4421
  abstract answerPermission(id: string, decision: ApprovalDecision): boolean | Promise<boolean>;
3887
4422
  abstract onTaskComplete(listener: (report: TaskCompleteArgs) => void): void;
3888
4423
  abstract onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void;
4424
+ abstract onScriptExit(listener: (exit: ScriptExit) => void): void;
3889
4425
  }
3890
4426
  declare abstract class AbstractRunner<S extends IRunnerSession> implements IRunner {
3891
4427
  protected sessions: Map<string, S>;
@@ -3912,11 +4448,17 @@ interface SessionLaunch {
3912
4448
  createAdapter: (runner: string, deps: AgentProcessDeps) => TaskModeAgentAdapter;
3913
4449
  startOptions: TaskStartOptions;
3914
4450
  interruptGraceMs: number;
3915
- /** Where this attempt's task tools are served (ADR-0022). */
4451
+ /** Where this attempt's task tools are served (ADR-0022), and a read-only attempt's shell and web reads (ADR-0046) and patch (ADR-0050). */
3916
4452
  tools: {
3917
4453
  server: OrdewellMcpServer;
3918
4454
  scope: TaskTokenScope;
4455
+ shell?: TaskShell;
4456
+ savePatch?: (signal: AbortSignal) => Promise<McpToolReply>;
4457
+ webReads?: TaskWebReads;
3919
4458
  };
4459
+ checkHandoff: (completion: TaskCompleteArgs) => HandoffCheck<TaskCompleteArgs>;
4460
+ readTask?: (args: ReadTaskArgs) => McpToolReply;
4461
+ applyPatch?: (args: ApplyPatchArgs) => Promise<McpToolReply>;
3920
4462
  onNotice?: (message: string) => void;
3921
4463
  }
3922
4464
  /**
@@ -3952,6 +4494,8 @@ declare class StructuredSession extends AbstractRunnerSession {
3952
4494
  private interrupting;
3953
4495
  /** The wind-down once {@link stop} was asked for: the same for every caller, and nothing new is sent meanwhile. */
3954
4496
  private stopping;
4497
+ /** The process is handling its exit: nothing new may be queued, so a held report can still publish. */
4498
+ private exiting;
3955
4499
  private lastSessionId;
3956
4500
  private lastSessionCost;
3957
4501
  private readonly structuredEmitter;
@@ -3972,12 +4516,14 @@ declare class StructuredSession extends AbstractRunnerSession {
3972
4516
  * Start the runner with the attempt's tools attached and send the task's
3973
4517
  * prompt as its first turn. `task_complete` is the only way a task reports
3974
4518
  * it is done, so a runner without the tools is never sent the task: it is
3975
- * killed and respawned once, and a second failure fails the start.
4519
+ * killed and respawned once, and a second miss is a timeout the task holds.
3976
4520
  */
3977
4521
  start(prompt: string): Promise<void>;
3978
4522
  getOutput(): string;
3979
4523
  onTaskComplete(listener: (report: TaskCompleteArgs) => void): void;
3980
4524
  onToolCheckpoint(handler: (question: string, signal: AbortSignal) => Promise<CheckpointAnswer>): void;
4525
+ /** A runner's model reports through `task_complete`; only a script task's session ends on its exit status (ADR-0035). */
4526
+ onScriptExit(): void;
3981
4527
  /**
3982
4528
  * The runner on a fresh token, attached or respawned once (ADR-0025). Done
3983
4529
  * once the runner reports the server connected — or once the session ended
@@ -4013,7 +4559,7 @@ declare class StructuredSession extends AbstractRunnerSession {
4013
4559
  onTurnEnd(listener: (reason: StructuredTurnEnd) => void): void;
4014
4560
  onEvent(listener: (event: StructuredEvent) => void): void;
4015
4561
  sendMessage(text: string, from?: TaskMessageFrom): string;
4016
- commands(): RunnerCommand[];
4562
+ commands(): RunnerCommand$1[];
4017
4563
  invokeCommand(name: string, args?: string): string;
4018
4564
  private enqueue;
4019
4565
  /**
@@ -4115,25 +4661,6 @@ declare class StructuredRunner extends AbstractRunner<StructuredSession> {
4115
4661
  spawn(opts: RunnerSpawnOptions): Promise<IRunnerSession>;
4116
4662
  }
4117
4663
 
4118
- /** Whether a task can be continued in its saved runner session (ADR-0018, K1), and in which. */
4119
- type Continuability = {
4120
- ok: true;
4121
- sessionId: string;
4122
- } | {
4123
- ok: false;
4124
- reason: string;
4125
- };
4126
- /**
4127
- * The rule every surface offers Continue by and the orchestrator enforces: a
4128
- * completed, failed or skipped task, or one paused on a usage limit, whose last
4129
- * attempt left a session id behind. A conflict is resolved by its repair, not a
4130
- * new turn — Retry covers it.
4131
- */
4132
- declare function continuability(task: Task): Continuability;
4133
- declare function canContinue(task: Task): boolean;
4134
-
4135
- declare function stripAnsi(text: string): string;
4136
-
4137
4664
  /**
4138
4665
  * cmd.exe's command-line buffer. A longer line is truncated rather than
4139
4666
  * rejected, which would corrupt a planner's system prompt or a task's prompt
@@ -4197,6 +4724,16 @@ declare class EmbeddedNewlineError extends Error {
4197
4724
  readonly command: string;
4198
4725
  constructor(command: string);
4199
4726
  }
4727
+ /**
4728
+ * Thrown when only a batch shim resolved for an argument holding a double
4729
+ * quote. cmd.exe flips its quote state at every `"`, whether or not a backslash
4730
+ * precedes it, so the quote ends the quoting we added and the rest of the
4731
+ * argument is read as commands: `--model "x\" & calc & \"y"` runs `calc`.
4732
+ */
4733
+ declare class BatchQuoteError extends Error {
4734
+ readonly command: string;
4735
+ constructor(command: string);
4736
+ }
4200
4737
  /**
4201
4738
  * Thrown when `command` cannot be resolved to a real file. Kept distinguishable
4202
4739
  * from `WorkspaceNotFoundError` (utils/workspace) even though both a missing
@@ -4243,15 +4780,185 @@ declare function windowsCommandLine(file: string, args: string[]): string;
4243
4780
  * a `.ps1` beside a too-long `.cmd` still raises.
4244
4781
  *
4245
4782
  * A line break does reorder them: cmd.exe cannot carry one at any length, so a
4246
- * `.ps1` beside a `.cmd` wins, and a lone `.cmd` raises.
4783
+ * `.ps1` beside a `.cmd` wins, and a lone `.cmd` raises. So does a double
4784
+ * quote, which cmd.exe cannot carry safely.
4247
4785
  *
4248
4786
  * @throws {CommandLineTooLongError} when the selected route's buffer cannot
4249
4787
  * carry the arguments.
4250
4788
  * @throws {EmbeddedNewlineError} when the arguments span lines and only the
4251
4789
  * batch route resolved.
4790
+ * @throws {BatchQuoteError} when an argument holds a double quote and only the
4791
+ * batch route resolved.
4252
4792
  */
4253
4793
  declare function planDirectLaunch(command: string, args: string[], deps?: LaunchDeps): Promise<LaunchPlan>;
4254
4794
 
4795
+ /**
4796
+ * What runs a script task's attempt, where a runner id would name a coding
4797
+ * agent: no registered runner does, and no model is involved (ADR-0035).
4798
+ */
4799
+ declare const SCRIPT_RUNNER = "script";
4800
+ type ScriptSpawnFn = (file: string, args: string[], options: {
4801
+ cwd: string;
4802
+ env: NodeJS.ProcessEnv;
4803
+ stdio: ['ignore', 'pipe', 'pipe'];
4804
+ detached: boolean;
4805
+ shell: boolean;
4806
+ windowsVerbatimArguments?: boolean;
4807
+ windowsHide: true;
4808
+ }) => ChildProcess;
4809
+ /** Every OS touchpoint is injectable, and production uses the defaults. */
4810
+ interface ScriptRunnerDeps {
4811
+ spawn?: ScriptSpawnFn;
4812
+ platform?: NodeJS.Platform;
4813
+ /** The PATH steps run under. Defaults to the augmented PATH a runner gets. */
4814
+ resolvePath?: () => Promise<string>;
4815
+ /** The interpreter a `script` body runs in. Defaults to the research shell (ADR-0011). */
4816
+ shell?: () => ResearchShell;
4817
+ isDirectory?: (dir: string) => boolean;
4818
+ /** How the launch seam looks a program up on Windows (ADR-0010); the real filesystem and environment unless set. */
4819
+ launch?: Pick<LaunchDeps, 'exists' | 'comSpec' | 'powerShell' | 'pathExt'>;
4820
+ /**
4821
+ * Where an attempt's whole output is streamed, beside its task log: the log
4822
+ * keeps only the ends of a long output, and a failure is often in the
4823
+ * middle. Prepares the file's directory and returns its path; none is kept
4824
+ * without it.
4825
+ */
4826
+ fullOutputFile?: (taskId: string, attempt: number) => string;
4827
+ /** The most characters that file keeps; the {@link FULL_OUTPUT_MAX_CHARS} guard unless a test sets one. */
4828
+ fullOutputMaxChars?: number;
4829
+ }
4830
+ interface ScriptSessionSetup {
4831
+ body: ScriptBody;
4832
+ cwd: string;
4833
+ env: NodeJS.ProcessEnv;
4834
+ spawn: ScriptSpawnFn;
4835
+ platform: NodeJS.Platform;
4836
+ launch: LaunchDeps;
4837
+ shell: () => ResearchShell;
4838
+ fullOutput?: {
4839
+ file: string;
4840
+ maxChars: number;
4841
+ };
4842
+ }
4843
+ /**
4844
+ * One attempt of a script task (ADR-0035): its body run at the workspace root
4845
+ * with no model. `commands` run in order with no shell, each program resolved
4846
+ * through the launch seam (ADR-0010), stopping at the first that fails; a
4847
+ * `script` runs once, in the research shell's interpreter. Each step's command,
4848
+ * output and exit status are its events, which are the task's log. Its end is
4849
+ * handed on as the attempt's evidence ({@link onScriptExit}), and only then
4850
+ * does the session exit.
4851
+ *
4852
+ * Every step leads its own process group, so the time limit, a stop and the
4853
+ * task's end reach whatever it started in that group. A process that leaves
4854
+ * the group (`setsid`, `setpgrp`) is out of reach of a group signal, and once
4855
+ * its parent exits nothing ties it to the step: holding it is the work of a
4856
+ * boundary around the task, a container or a lease (#97, #99), not of a signal.
4857
+ */
4858
+ declare class ScriptSession extends AbstractRunnerSession {
4859
+ private readonly setup;
4860
+ private readonly events;
4861
+ private readonly endListeners;
4862
+ private readonly steps;
4863
+ private readonly children;
4864
+ private child;
4865
+ private output;
4866
+ private running;
4867
+ private stopped;
4868
+ private timedOut;
4869
+ private errorTail;
4870
+ private logged;
4871
+ private logTail;
4872
+ private omitted;
4873
+ private full;
4874
+ private markGone;
4875
+ private readonly gone;
4876
+ constructor(id: string, taskId: string, setup: ScriptSessionSetup);
4877
+ /** Whoever spawned it attaches its listeners when the spawn resolves, so the body starts a macrotask later. */
4878
+ begin(): void;
4879
+ private run;
4880
+ private runStep;
4881
+ /**
4882
+ * An argument vector is never joined into a shell string: on POSIX it is
4883
+ * exec'd as written, and on Windows the launch seam finds the program the
4884
+ * way CreateProcess will not. A script goes to the research shell's
4885
+ * interpreter, the one the planner's own commands use.
4886
+ */
4887
+ private launchOf;
4888
+ private spawnStep;
4889
+ private expire;
4890
+ private print;
4891
+ private printError;
4892
+ private openFullOutput;
4893
+ private saveOutput;
4894
+ /** Resolves once the file is on disk, so a reader of the exit finds it whole. */
4895
+ private closeFullOutput;
4896
+ /** Streamed into the log until the step's head is full; past it, only a tail is kept, for {@link flushLogTail}. */
4897
+ private logOutput;
4898
+ private flushLogTail;
4899
+ private emit;
4900
+ /** Ends the step under way and every one after it; resolves once the session has exited. */
4901
+ stop(): Promise<void>;
4902
+ kill(): void;
4903
+ getOutput(): string;
4904
+ /** Nothing reads a step's input. */
4905
+ write(): void;
4906
+ turnState(): 'working' | 'idle';
4907
+ /** A script has no turns. */
4908
+ onTurnEnd(): void;
4909
+ onEvent(listener: (event: StructuredEvent) => void): void;
4910
+ sendMessage(): string;
4911
+ forceSend(): string;
4912
+ forceSendQueued(): boolean;
4913
+ removeQueued(): boolean;
4914
+ queued(): QueuedTaskMessage[];
4915
+ /** There is no turn to interrupt: Stop ends a script. */
4916
+ interrupt(): Promise<void>;
4917
+ nativeSessionId(): string | null;
4918
+ commands(): RunnerCommand$1[];
4919
+ invokeCommand(): string;
4920
+ answerPermission(): boolean;
4921
+ /** With no runner, nothing calls `task_complete`: the exit status is the evidence. */
4922
+ onTaskComplete(): void;
4923
+ onToolCheckpoint(): void;
4924
+ onScriptExit(listener: (exit: ScriptExit) => void): void;
4925
+ }
4926
+ /**
4927
+ * Starts script task attempts (ADR-0035), as a runner starts a coding agent's,
4928
+ * so the task log, the merge gate, the Merge-all exclusion and the
4929
+ * tracked-file check meet one the way they meet an ops task. It reads only the
4930
+ * spawn's `script` body, `cwd` and `env`: there is no model, mode or prompt.
4931
+ * The step environment is the one a runner gets — the host's, cleaned, under
4932
+ * the workspace's own variables (ADR-0016).
4933
+ */
4934
+ declare class ScriptRunner extends AbstractRunner<ScriptSession> {
4935
+ private readonly deps;
4936
+ private spawned;
4937
+ constructor(deps?: ScriptRunnerDeps);
4938
+ spawn(opts: RunnerSpawnOptions): Promise<IRunnerSession>;
4939
+ /** An attempt with no number has no place to keep a file: a log would not know which attempt it was. */
4940
+ private fullOutputFor;
4941
+ }
4942
+
4943
+ /** Whether a task can be continued in its saved runner session (ADR-0018, K1), and in which. */
4944
+ type Continuability = {
4945
+ ok: true;
4946
+ sessionId: string;
4947
+ } | {
4948
+ ok: false;
4949
+ reason: string;
4950
+ };
4951
+ /**
4952
+ * The rule every surface offers Continue by and the orchestrator enforces: a
4953
+ * completed, failed or skipped task, or one paused on a usage limit, whose last
4954
+ * attempt left a session id behind. A conflict is resolved by its repair, not a
4955
+ * new turn — Retry covers it.
4956
+ */
4957
+ declare function continuability(task: Task): Continuability;
4958
+ declare function canContinue(task: Task): boolean;
4959
+
4960
+ declare function stripAnsi(text: string): string;
4961
+
4255
4962
  /**
4256
4963
  * A workspace path that does not exist, or exists but is not a directory.
4257
4964
  * Checked at every surface that starts a planner or spawns an agent inside
@@ -4441,6 +5148,15 @@ declare const OPENCODE_MANIFEST: RunnerManifest;
4441
5148
 
4442
5149
  declare const STATE_DIR = ".ordewell";
4443
5150
  declare function ensureDir(dir: string): void;
5151
+ /**
5152
+ * Replace `filePath` with `content` in one step. Writing in place truncates
5153
+ * the old bytes first, so a crash, power loss or a full disk mid-write leaves
5154
+ * an empty or half-written file where the last good one was — silent loss of
5155
+ * the only copy, for a session or a saved plan. The bytes go to a fresh temp
5156
+ * file beside the target and a rename swaps it in; within one directory that
5157
+ * is atomic, so a reader sees the old file or the new one.
5158
+ */
5159
+ declare function writeFileAtomic(filePath: string, content: string): void;
4444
5160
  declare function getStateDir(baseDir?: string): string;
4445
5161
  /**
4446
5162
  * Ignores the state directory's contents except this file and `skills/`, so a
@@ -4557,4 +5273,9 @@ declare const DEFAULT_MAX_PARALLEL = 5;
4557
5273
  */
4558
5274
  declare function parseMaxParallel(value: unknown): number | null;
4559
5275
 
4560
- export { ALL_PROVIDERS, AbstractRunner, AbstractRunnerSession, type Adr0013IsolationRun, type Adr0013PlanIsolation, type Adr0013TaskRecord, AgentAdapter, AgentEvent, AgentProcessDeps, AgentStartOptions, AiProvider, AlreadyExecutingError, ApprovalAnswer, ApprovalDecision, ApprovalModeSetting, ApprovalPolicy, AssignmentScope, BaseAiService, BaseConfig, BaseFileSystem, BufferedTaskOutputSource, type ByPlannerBackend, CLAUDE_CODE_MANIFEST, CLI_PROVIDERS, CMD_EXE_MAX_COMMAND_LINE, CODEX_SANDBOX_DOCS, CODEX_SANDBOX_OFF_LINE, type CachedPage, type CappedRows, CheckpointAnswer, type CheckpointListener, ClaudeCodeAdapter, CodexAdapter, CodexSandboxAvailability, CommandLineTooLongError, type CommandOutcome, type CommandPayload, type ConfiguredPlanner, type Consent, ConsoleLogger, ContextCollector, type Continuability, ConversationCompaction, type ConversationFork, ConversationRequest, type ConversationRewind, ConversationTurn, DAEMON_SUBPROTOCOL, DAEMON_TOKEN_SUBPROTOCOL_PREFIX, DEFAULT_MAX_PARALLEL, type DirEntry, DiscoveredModel, DuckDuckGoSearchProvider, EmbeddedNewlineError, type EnvAdmission, EnvConfig, type ExecImpl, ExecutableNotFoundError, ExecutionActor, type ExecutionCommand, ExecutionCommandName, ExecutionCommands, type ExecutionHost, ExecutionRecord, FindSymbolOptions, GREP_PCRE_PROBE_ARGS, GeminiService, type GitExecFn, GlobOptions, GlobalRunSettings, type GrepInvocation, GrepOptions, type GrepRegex, GuardTarget, GuardTrip, HARNESS_PLANNER_REASON, type HasBinFn, HttpWebFetcher, type HttpWebFetcherOptions, IAiService, IApproval, IConfig, IFileSystem, type ILogger, INotification, IRunner, IRunnerSession, type ISearchProvider, IWorktreeIsolation, IsolationLandedTask, IsolationMergeResult, IsolationTaskStatus, IsolationView, type KillTreeDeps, type LaunchDeps, type LaunchPlan, LegacyPlanState, LiveTail, LiveTailOptions, type LoopGuard, type MappedTool, type MergeWordingHandoff, ModelResolver, type ModelResolverDeps, NoPlanError, OPENCODE_MANIFEST, ORDEWELL_SETTABLE_ENV, OpenAiService, OpenCodeAdapter, OrchestratorObserver, OrchestratorOption, OrdewellMcpServer, PROVIDER_CREDENTIAL_ENV, PROVIDER_DETECT_PRIORITY, PROVIDER_LABEL, PROVIDER_PRIORITY, PROVIDER_SHORT_LABEL, PendingApproval, PendingApprovals, PlanEditError, PlanIsolation, type PlanRequest, PlanState, PlanStore, Planner, type PlannerBackendEntry, PlannerBackendId, type PlannerChoice, PlannerModelCandidate, type PlannerModelLookup, PlannerModelRecall, type PlannerModelSource, PlannerModelStore, type PlannerSelectionChange, type PlannerSelectionCurrent, type PlannerSelectionResult, type PlannerSelectionState, PlannerToolHandler, type PlannerUsability, PlannerUsage, type PrdBlock, ProviderModelLists, type ProviderRegistration, QueuedTaskMessage, ReadFileOpts, ReportedWindow, type ResearchChat, ResearchLogEntry, ResearchShell, type ResearchTurn, type ResolvedChoice, type ResolvedPlanner, RewindTarget, RunMergeView, RunView, RunnerApprovals, RunnerCommand, RunnerId, RunnerInstallation, RunnerManifest, RunnerModeInfo, RunnerRegistry, RunnerSessionCost, type RunnerSpawnOptions, SETTINGS_ENV_ALLOWLIST, SETTINGS_ENV_REFUSED, STATE_DIR, STATE_DIR_IGNORE, STOPPED_TOOL_RESULT, SUPERVISOR_LEVELS, SYMBOL_LANGUAGES, type SaveSession, type SavedState, type SearchHit, type SearchOutcome, type SearchPage, type SearchTransport, Session, SessionBroadcaster, type SessionDeps, SessionMessage, SessionMeta, SessionNotFoundError, SessionNotice, type SessionPlanner, type SessionRuntimeSettings, SkillsService, type SpawnSpec, StartAdmission, StdioAgentAdapter, StructuredEvent, StructuredRunner, type StructuredRunnerDeps, StructuredSession, StructuredTurnEnd, SupervisorLevel, type SupervisorPolicy, Task, TaskCompleteArgs, TaskLogEvent, type TaskLogFile, type TaskLogLocation, TaskLogRecorder, type TaskLogRecorderDeps, TaskMessageFrom, TaskModeAgentAdapter, TaskOrchestrator, TaskOutputSource, TaskQueryCatalog, TaskRunnerFlags, TaskStartOptions, TaskTokenScope, type TokenCarriers, type ToolCall, ToolOutcome, type ToolResult, UsageRecord, UserSettings, VerdictEngine, type VerdictListener, WINDOWS_MAX_COMMAND_LINE, WebRead, WebReadDecision, type WebResponse, WebResponseCache, type WebResponseCacheOptions, type WebTransport, type WorkspaceCheckDeps, WorkspaceNotAProjectError, WorkspaceNotFoundError, type WorkspaceProjectCheckDeps, type WorktreeIsolationDeps, admitSettingsEnv, applyHeadLimit, assertWorkspaceExists, assertWorkspaceIsProject, augmentedPath, bearerHeaderValue, buildFallbackGrepArgs, buildGlobArgs, buildGrepArgs, canContinue, checkoutRule, clearAugmentedPathCache, clearDaemonToken, clearState, clipboardCopyCommand, codexSandboxNotice, configuredProviders, continuability, createSession, createTaskAdapter, createWorktreeIsolation, daemonTokenPath, defaultEnabledRunners, defaultLogger, definitionPattern, deleteSession, describeMergeResult, detectInstalledPlanner, discoverGeminiModels, ensureDir, ensurePrivateDir, ensureStateDirIgnored, extractPrdBlock, extractPresentedToken, filterFallbackByAnchoredInclude, findInstalledPlanner, formatSearchOutput, getLatestSession, getProviderMeta, getStateDir, globalDataDir, grantScopeFor, grepRegexFromProbe, idSegment, includeGlobFor, installedPlannerAnswered, installedPlannerDetected, isApiProvider, isCliProvider, isExecutableResolved, isOpenAiProvider, isPlannerBackend, isRepoGroup, isVendorModelFor, killTree, languageForId, listSessions, listTaskLogAttempts, listTree, loadSession, loadSessionPlanState, loadState, looksLikeChallenge, mapAgentTool, mergeTarget, migrateOldConfigDir, migratePlanIsolation, mintDaemonToken, mintSessionId, normalizeAgentArgs, normalizeGeminiModel, openTaskLog, parseMaxParallel, parseSearchHits, planDirectLaunch, plannerBackendEntries, prefixModelId, providerForRunner, readDaemonToken, readTaskLog, recordInstalledPlanner, recordedInstalledPlanner, referencePattern, removedPluginNotice, reposWithWork, resetInstalledPlanner, resolveProviderFromPrefix, resolveTaskRunnerFlags, resolveWithin, runnerForProvider, sanitizeSlug, savePrdMarkdown, saveSession, saveState, selectPlanner, sessionDataDir, sessionRuntimeSettings, sharedSearchProvider, sharedWebResponseCache, stateExists, stripAnsi, stripModelPrefix, supportsTaskMode, tokenSubprotocols, tokensMatch, wellKnownBinDirs, windowsCommandLine, withPath, writePrivateFile };
5276
+ /** The numeric components of a version, or null when it cannot be read. */
5277
+ declare function parseVersion(version: string): number[] | null;
5278
+ /** Negative, zero or positive as `a` is older than, the same as, or newer than `b`; an unreadable version sorts oldest. */
5279
+ declare function compareVersions(a: string, b: string): number;
5280
+
5281
+ export { ALL_PROVIDERS, AbstractRunner, AbstractRunnerSession, AccountLimitBucket, type AccountLimitCollector, type AccountLimitListener, AccountLimitMonitor, type AccountLimitMonitorOptions, type AccountLimitReading, AccountLimitSnapshot, type Adr0013IsolationRun, type Adr0013PlanIsolation, type Adr0013TaskRecord, AgentAdapter, AgentEvent, AgentProcessDeps, AgentStartOptions, AiProvider, AlreadyExecutingError, ApprovalAnswer, ApprovalDecision, ApprovalModeSetting, ApprovalPolicy, AssignmentScope, BaseAiService, BaseConfig, BaseFileSystem, BashOptions, BatchQuoteError, BufferedTaskOutputSource, type ByPlannerBackend, CLAUDE_CODE_MANIFEST, CLI_PROVIDERS, CMD_EXE_MAX_COMMAND_LINE, CODEX_SANDBOX_DOCS, CODEX_SANDBOX_OFF_LINE, type CachedPage, type CappedRows, CheckpointAnswer, type CheckpointListener, type ClaudeAccountLimitOptions, ClaudeCodeAdapter, type CodexAccountLimitOptions, CodexAdapter, CodexSandboxAvailability, CommandLineTooLongError, CommandOutcome, type ConfiguredPlanner, ConsoleLogger, ContextCollector, type Continuability, ConversationCompaction, type ConversationFork, ConversationRequest, type ConversationRewind, ConversationTurn, DAEMON_SUBPROTOCOL, DAEMON_TOKEN_SUBPROTOCOL_PREFIX, DEFAULT_MAX_PARALLEL, type DirEntry, DiscoveredModel, DuckDuckGoSearchProvider, type EffectRules, EmbeddedNewlineError, type EnvAdmission, EnvConfig, type ExecImpl, ExecutableNotFoundError, ExecutionActor, ExecutionCommand, FindSymbolOptions, GREP_PCRE_PROBE_ARGS, GeminiService, type GitExecFn, GlobOptions, GlobalRunSettings, type GrepInvocation, GrepOptions, type GrepRegex, HARNESS_PLANNER_REASON, HandoffCheck, type HasBinFn, HttpWebFetcher, type HttpWebFetcherOptions, IAiService, IApproval, IConfig, IFileSystem, IIsolationPreview, type ILogger, INotification, IRunner, IRunnerSession, type ISearchProvider, IWorktreeIsolation, IsolationTaskStatus, IsolationView, type KillTreeDeps, type LaunchDeps, type LaunchPlan, LeaseHost, LegacyPlanState, LiveTail, LiveTailOptions, McpToolReply, ModelResolver, type ModelResolverDeps, type NativeShellLauncher, type NativeShellOffer, NoPlanError, OPENCODE_MANIFEST, ORDEWELL_SETTABLE_ENV, OpenAiService, OpenCodeAdapter, type OpenCodeGoAccountLimitOptions, OrchestratorObserver, OrchestratorOption, OrdewellMcpServer, PROVIDER_CREDENTIAL_ENV, PROVIDER_DETECT_PRIORITY, PROVIDER_LABEL, PROVIDER_PRIORITY, PROVIDER_SHORT_LABEL, type ParsedCommand, PendingApproval, PendingApprovals, type PingFetch, PlanEditError, PlanIsolation, type PlanRequest, PlanState, PlanStore, Planner, PlannerAllowlist, type PlannerBackendEntry, PlannerBackendId, type PlannerChoice, PlannerModelCandidate, type PlannerModelLookup, PlannerModelRecall, type PlannerModelSource, PlannerModelStore, type PlannerSelectionChange, type PlannerSelectionCurrent, type PlannerSelectionResult, type PlannerSelectionState, PlannerShellSandbox, PlannerToolHandler, type PlannerUsability, PlannerUsage, type PrdBlock, ProviderModelLists, type ProviderRegistration, QueuedTaskMessage, ReadFileOpts, ReadTaskArgs, ReportedWindow, type ResearchChat, ResearchLogEntry, ResearchShell, type ResearchTurn, type ResolvedChoice, type ResolvedPlanner, RewindTarget, RunActivity, RunMergeView, RunView, RunnerApprovals, RunnerAuth, RunnerAuthExec, RunnerCommand$1 as RunnerCommand, RunnerId, RunnerInstallation, type RunnerInstallationOptions, RunnerManifest, RunnerModeInfo, RunnerRegistry, RunnerSessionCost, type RunnerSpawnOptions, SCRIPT_RUNNER, SETTINGS_ENV_ALLOWLIST, SETTINGS_ENV_REFUSED, STATE_DIR, STATE_DIR_IGNORE, STOPPED_TOOL_RESULT, SYMBOL_LANGUAGES, type SaveSession, type SavedState, ScriptBody, ScriptExit, ScriptRunner, ScriptSession, type SearchHit, type SearchOutcome, type SearchPage, type SearchTransport, Session, SessionBroadcaster, type SessionDeps, SessionHeld, SessionLease, SessionMessage, SessionMeta, SessionNotFoundError, SessionNotice, type SessionPlanner, type SessionRuntimeSettings, ShellLaunch, SkillsService, type SpawnSpec, StdioAgentAdapter, StoredHandoff, StructuredEvent, StructuredRunner, type StructuredRunnerDeps, StructuredSession, StructuredTurnEnd, Task, TaskCompleteArgs, TaskControlError, type TaskEditOptions, TaskLogEvent, type TaskLogFile, type TaskLogLocation, TaskLogRecorder, type TaskLogRecorderDeps, TaskMessageFrom, TaskModeAgentAdapter, TaskOrchestrator, TaskOutputSource, TaskQueryCatalog, TaskRunnerFlags, TaskShell, TaskShellSite, TaskStartOptions, TaskTokenScope, type TokenCarriers, type ToolCall, ToolOutcome, type ToolResult, UsageRecord, UserSettings, VERSION_CHECK_NOTICE, VERSION_CHECK_URL, VerdictEngine, type VerdictListener, type VersionCheck, type VersionCheckOptions, WINDOWS_MAX_COMMAND_LINE, WebRead, WebReadDecision, type WebResponse, WebResponseCache, type WebResponseCacheOptions, type WebTransport, type WorkspaceCheckDeps, WorkspaceNotAProjectError, WorkspaceNotFoundError, type WorkspaceProjectCheckDeps, type WorktreeIsolationDeps, admitSettingsEnv, applyHeadLimit, assertWorkspaceExists, assertWorkspaceIsProject, augmentedPath, bearerHeaderValue, buildFallbackGrepArgs, buildGlobArgs, buildGrepArgs, canContinue, claudeAccountLimitCollector, clearAugmentedPathCache, clearDaemonToken, clearState, clipboardCopyCommand, codexAccountLimitCollector, codexSandboxNotice, compareVersions, configuredProviders, continuability, createIsolationPreview, createNativeShellLauncher, createSession, createTaskAdapter, createWorktreeIsolation, daemonTokenPath, defaultEnabledRunners, defaultLogger, definitionPattern, deleteSession, detectInstalledPlanner, discoverGeminiModels, effectRuleFor, ensureDir, ensurePrivateDir, ensureStateDirIgnored, extractPrdBlock, extractPresentedToken, filterFallbackByAnchoredInclude, findInstalledPlanner, formatSearchOutput, getLatestSession, getProviderMeta, getStateDir, globalDataDir, grantScopeFor, grepRegexFromProbe, idSegment, includeGlobFor, installedPlannerAnswered, installedPlannerDetected, isApiProvider, isCliProvider, isExecutableResolved, isOpenAiProvider, isPlannerBackend, isVendorModelFor, isolationViewOfPlan, killTree, languageForId, listSessions, listTaskLogAttempts, listTree, loadSession, loadSessionPlanState, loadState, looksLikeChallenge, migrateOldConfigDir, migratePlanIsolation, mintDaemonToken, mintSessionId, nativeShellInvocation, nativeShellOffer, normalizeGeminiModel, openCodeGoAccountLimitCollector, openTaskLog, parseEffectAllowlist, parseExecutionCommand, parseMaxParallel, parseSearchHits, parseVersion, planDirectLaunch, plannerBackendEntries, prefixModelId, prepareTaskOutputFile, providerForRunner, readDaemonToken, readTaskLog, readTaskPatch, recordInstalledPlanner, recordedInstalledPlanner, referencePattern, removedPluginNotice, resetInstalledPlanner, resolveProviderFromPrefix, resolveTaskRunnerFlags, resolveWithin, runnerForProvider, sanitizeSlug, savePrdMarkdown, saveSession, saveState, saveTaskPatch, selectPlanner, sessionDataDir, sessionRuntimeSettings, sharedSearchProvider, sharedWebResponseCache, startVersionCheck, stateExists, stripAnsi, stripModelPrefix, supportsTaskMode, taskPatchFile, tokenSubprotocols, tokensMatch, wellKnownBinDirs, windowsCommandLine, withPath, writeFileAtomic, writePrivateFile };