@ordewell/core 0.5.3 → 0.5.5

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 (44) hide show
  1. package/dist/{ITerminalRunner-Bd-vAJnw.d.ts → ITerminalRunner-BV9Rd2o9.d.ts} +3 -1
  2. package/dist/{ITerminalRunner-ByeoLF57.d.mts → ITerminalRunner-C77ZNZS9.d.mts} +3 -1
  3. package/dist/{ModeResolver-DVJ7HV3k.d.mts → ModeResolver-D-SUFRNF.d.mts} +1 -1
  4. package/dist/{ModeResolver-Dkig8ghQ.d.ts → ModeResolver-D3XO0fT9.d.ts} +1 -1
  5. package/dist/{Task-BxQkPlXO.d.mts → Task-Vl5Zq_D-.d.mts} +168 -7
  6. package/dist/{Task-BxQkPlXO.d.ts → Task-Vl5Zq_D-.d.ts} +168 -7
  7. package/dist/{chunk-JVMDEHRQ.mjs → chunk-HD2FWPRV.mjs} +72 -37
  8. package/dist/chunk-HD2FWPRV.mjs.map +1 -0
  9. package/dist/{chunk-GWPIYDQW.mjs → chunk-KLN7ELXO.mjs} +897 -18
  10. package/dist/chunk-KLN7ELXO.mjs.map +1 -0
  11. package/dist/{chunk-XWOUIA6A.mjs → chunk-UUBGVCGJ.mjs} +2 -2
  12. package/dist/index.d.mts +320 -60
  13. package/dist/index.d.ts +320 -60
  14. package/dist/index.js +2497 -672
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +1605 -659
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/order-labels.d.mts +1 -1
  19. package/dist/order-labels.d.ts +1 -1
  20. package/dist/{parsing-CF_grC29.d.ts → parsing-BTP4bwkk.d.ts} +11 -3
  21. package/dist/{parsing-DRp4dPC0.d.mts → parsing-CDtRSxBY.d.mts} +11 -3
  22. package/dist/parsing.d.mts +3 -3
  23. package/dist/parsing.d.ts +3 -3
  24. package/dist/parsing.js.map +1 -1
  25. package/dist/parsing.mjs +2 -2
  26. package/dist/plan-utils-BFaPo-IT.d.ts +708 -0
  27. package/dist/plan-utils-pE4TBwxl.d.mts +708 -0
  28. package/dist/plan-utils.d.mts +3 -3
  29. package/dist/plan-utils.d.ts +3 -3
  30. package/dist/plan-utils.js +668 -7
  31. package/dist/plan-utils.js.map +1 -1
  32. package/dist/plan-utils.mjs +38 -4
  33. package/dist/testing.d.mts +5 -3
  34. package/dist/testing.d.ts +5 -3
  35. package/dist/testing.js +3 -0
  36. package/dist/testing.js.map +1 -1
  37. package/dist/testing.mjs +3 -0
  38. package/dist/testing.mjs.map +1 -1
  39. package/package.json +1 -1
  40. package/dist/chunk-GWPIYDQW.mjs.map +0 -1
  41. package/dist/chunk-JVMDEHRQ.mjs.map +0 -1
  42. package/dist/plan-utils-CkNbqAmS.d.ts +0 -329
  43. package/dist/plan-utils-CtB3_Ovf.d.mts +0 -329
  44. /package/dist/{chunk-XWOUIA6A.mjs.map → chunk-UUBGVCGJ.mjs.map} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,17 +1,17 @@
1
- import { F as RunnerId, u as PlanStatus, L as LegacyPlanState, k as IsolationMergeResult, g as IsolationLandedTask, I as IWorktreeIsolation, w as RepoGroupLayout, a as DiscoveredModel, T as Task, y as ResearchProgress, x as ResearchLogEntry, C as ConversationMessage, V as TaskSnapshot, d as IsolationHandoff, J as TaskIsolation, r as IsolationView, P as PlanIsolation, Q as QueuedMessage, A as ActiveTaskSession, E as ResearchToolType, t as PlanState, a3 as Verdict } from './Task-BxQkPlXO.js';
2
- export { D as DiscoveredMode, b as IntegrationDisposal, c as IsolationAvailability, e as IsolationHandoffRepo, f as IsolationInactiveReason, h as IsolationLanding, i as IsolationMergeBlock, j as IsolationMergeBlockReason, l as IsolationOutcome, m as IsolationRepo, n as IsolationRun, o as IsolationTaskRecord, p as IsolationTaskRepo, q as IsolationTaskStatus, M as Message, s as PlanModificationWarnings, v as PreparedTask, R as RepairEvidence, z as ResearchStep, B as ResearchStepOutcome, S as StreamEvent, G as StreamStepEvent, H as StreamThinkingEvent, K as TaskIsolationState, N as TaskMode, O as TaskModelAssignment, U as TaskOutputSummary, W as TaskStatus, X as TaskType, Y as TaskWithParent, Z as ThinkingBlock, _ as UserPromptEntry, $ as UserStep, a0 as ValidationCheck, a1 as ValidationContext, a2 as ValidationResult, a4 as VerificationCheck, a5 as addTaskToPlan, a6 as createEmptyPlan, a7 as createTask, a8 as emptyWarnings, a9 as flattenTasks, aa as flattenTasksWithParents, ab as migrateLegacyPlan, ac as migratePlanState, ad as migrateTask, ae as removeTaskFromPlan, af as renumberTasks, ag as updateTaskInPlan, ah as validateModifiedPlan, ai as warningsText } from './Task-BxQkPlXO.js';
3
- import { f as IFileSystem, R as ReadFileOpts, T as ToolOutcome, d as GrepOptions, c as GlobOptions, b as FindSymbolOptions, I as IConfig, A as AiProvider, r as ProviderModelLists, i as ITerminalSession, h as ITerminalRunner, w as RunnerRegistry, k as OrchestratorOption, u as RunnerInvocation, g as IPluginStore, v as RunnerPluginManifest, t as ResolveContext } from './ITerminalRunner-Bd-vAJnw.js';
4
- export { a as AllProviderModels, B as BlockingPrompt, C as CatalogModel, D as DiscoveryCommand, F as FetchAllProviderModelsOptions, G as GREP_DEFAULT_HEAD_LIMIT, e as GrepOutputMode, M as ModelCatalog, j as ModelShortcut, O as ORCHESTRATOR_SHORTCUTS, P as PluginCloneFn, l as PluginEntry, m as PluginFeatures, n as PluginMode, o as PluginModelDiscovery, p as PluginRunnerDef, q as ProviderCredentialSource, s as ProviderModelsResult, S as SEARCH_EXCLUSIONS, x as collectProviderCredentials, y as enabledRunners, z as fetchAllProviderModels, E as isReservedRunnerName, H as knownModelId, J as resolveModelShortcut, K as resolveProvider, L as toOrchestratorOptions } from './ITerminalRunner-Bd-vAJnw.js';
1
+ import { H as RunnerId, v as PlanStatus, L as LegacyPlanState, k as IsolationMergeResult, g as IsolationLandedTask, I as IWorktreeIsolation, y as RepoGroupLayout, a as DiscoveredModel, T as Task, a3 as UsageRecord, O as SubagentOutcome, B as ResearchProgress, z as ResearchLogEntry, C as ConversationMessage, Z as TaskSnapshot, d as IsolationHandoff, U as TaskIsolation, s as IsolationView, P as PlanIsolation, Q as QueuedMessage, A as ActiveTaskSession, G as ResearchToolType, u as PlanState, aa as Verdict } from './Task-Vl5Zq_D-.js';
2
+ export { D as DiscoveredMode, b as IntegrationDisposal, c as IsolationAvailability, e as IsolationHandoffRepo, f as IsolationInactiveReason, h as IsolationLanding, i as IsolationMergeBlock, j as IsolationMergeBlockReason, l as IsolationOutcome, m as IsolationPruneResult, n as IsolationRepo, o as IsolationRun, p as IsolationTaskRecord, q as IsolationTaskRepo, r as IsolationTaskStatus, M as Message, t as PlanModificationWarnings, w as PlannerUsage, x as PreparedTask, R as RepairEvidence, E as ResearchStep, F as ResearchStepOutcome, S as StreamEvent, J as StreamStepEvent, K as StreamThinkingEvent, N as SubagentLogEntry, V as TaskIsolationState, W as TaskMode, X as TaskModelAssignment, Y as TaskOutputSummary, _ as TaskStatus, $ as TaskType, a0 as TaskWithParent, a1 as ThinkingBlock, a2 as UsageLine, a4 as UsageTotals, a5 as UserPromptEntry, a6 as UserStep, a7 as ValidationCheck, a8 as ValidationContext, a9 as ValidationResult, ab as VerificationCheck, ac as addPlannerUsage, ad as addTaskToPlan, ae as addUsage, af as createEmptyPlan, ag as createTask, ah as emptyWarnings, ai as flattenTasks, aj as flattenTasksWithParents, ak as isMeasured, al as keepExecutionState, am as migrateLegacyPlan, an as migratePlanState, ao as migrateTask, ap as partedPromptUsage, aq as plannerContextFill, ar as removeTaskFromPlan, as as renumberTasks, at as updateTaskInPlan, au as usageLine, av as validateModifiedPlan, aw as warningsText } from './Task-Vl5Zq_D-.js';
3
+ import { f as IFileSystem, R as ReadFileOpts, T as ToolOutcome, d as GrepOptions, c as GlobOptions, b as FindSymbolOptions, I as IConfig, A as AiProvider, r as ProviderModelLists, i as ITerminalSession, h as ITerminalRunner, w as RunnerRegistry, k as OrchestratorOption, u as RunnerInvocation, g as IPluginStore, v as RunnerPluginManifest, t as ResolveContext } from './ITerminalRunner-BV9Rd2o9.js';
4
+ export { a as AllProviderModels, B as BlockingPrompt, C as CatalogModel, D as DiscoveryCommand, F as FetchAllProviderModelsOptions, G as GREP_DEFAULT_HEAD_LIMIT, e as GrepOutputMode, M as ModelCatalog, j as ModelShortcut, O as ORCHESTRATOR_SHORTCUTS, P as PluginCloneFn, l as PluginEntry, m as PluginFeatures, n as PluginMode, o as PluginModelDiscovery, p as PluginRunnerDef, q as ProviderCredentialSource, s as ProviderModelsResult, S as SEARCH_EXCLUSIONS, x as collectProviderCredentials, y as enabledRunners, z as fetchAllProviderModels, E as isReservedRunnerName, H as knownModelId, J as resolveModelShortcut, K as resolveProvider, L as toOrchestratorOptions } from './ITerminalRunner-BV9Rd2o9.js';
5
5
  import { I as IApproval, d as ApprovalRequest, a as ApprovalMode } from './ApprovalPolicy-BVhGdECT.js';
6
6
  export { A as ApprovalKind, b as ApprovalPolicy, c as ApprovalPolicyOptions, e as ApprovalSource, D as DENY_ALL } from './ApprovalPolicy-BVhGdECT.js';
7
- import { T as TaskOp, f as SessionBroadcaster, h as SessionNotice } from './plan-utils-CkNbqAmS.js';
8
- export { A as Adr0013IsolationRun, a as Adr0013PlanIsolation, b as Adr0013TaskRecord, c as ApplyTaskOpsResult, C as CHECKPOINT_TRUNCATE_LENGTH, S as SerializedPlan, d as SerializedTask, e as SerializedTaskStatus, g as SessionMessage, i as TaskRef, j as applyTaskOps, k as canMergeTasks, l as canSetDependencies, m as canSplitTask, n as capConflictFiles, o as classifyOutcome, p as dependencyCandidates, q as dependentsOf, r as executionSummary, s as migratePlanIsolation, t as parseTaskOpsJson, u as serializePlan, v as serializeTask, w as serializeTaskStatus, x as summarizeToolCall, y as textHasTaskOps, z as truncateCheckpointSummary } from './plan-utils-CkNbqAmS.js';
9
- import { R as RunnerModeInfo } from './ModeResolver-Dkig8ghQ.js';
10
- export { M as ManifestLookup, b as buildModeGuide, f as filteredBuildModes, r as resolveDefaultMode, a as resolveTaskMode, c as runnerModesFrom } from './ModeResolver-Dkig8ghQ.js';
7
+ import { t as TaskOp, n as SessionBroadcaster, p as SessionNotice } from './plan-utils-BFaPo-IT.js';
8
+ export { A as Adr0013IsolationRun, a as Adr0013PlanIsolation, b as Adr0013TaskRecord, c as ApplyTaskOpsResult, d as ApprovalBlock, e as ApprovalStatus, C as CHECKPOINT_TRUNCATE_LENGTH, f as ConversationInput, g as ConversationView, D as DisplayBlock, E as EMPTY_CONVERSATION, h as EMPTY_HOLD, G as GatedConversation, L as LocalEntry, M as MessageBlock, i as MessageRole, N as NO_TURN, O as OutputPreview, P as PlanBlock, j as PlanMarkerStatus, k as PromptHold, S as SerializedPlan, l as SerializedTask, m as SerializedTaskStatus, o as SessionMessage, q as SubagentBlock, r as SubagentChild, s as SubagentStatus, T as TakenPrompt, u as TaskRef, v as ThinkingDisplayBlock, w as ToolBlock, x as ToolHeadline, y as ToolStatus, z as TurnGate, U as UsageBlock, B as aheadOfDraft, F as applyTaskOps, H as canMergeTasks, I as canSetDependencies, J as canSplitTask, K as capConflictFiles, Q as classifyOutcome, R as dependencyCandidates, V as dependentsOf, W as drainNext, X as executionSummary, Y as followTurn, Z as fromTranscript, _ as hasHiddenDetail, $ as holdPrompt, a0 as migratePlanIsolation, a1 as outputLines, a2 as outputPreview, a3 as parseTaskOpsJson, a4 as reduceConversation, a5 as serializePlan, a6 as serializeTask, a7 as serializeTaskStatus, a8 as stopTurn, a9 as summarizeToolCall, aa as taskStartedNotice, ab as textHasTaskOps, ac as toolHeadline, ad as truncateCheckpointSummary, ae as unsendAll, af as unsendLatest } from './plan-utils-BFaPo-IT.js';
9
+ import { R as RunnerModeInfo } from './ModeResolver-D3XO0fT9.js';
10
+ export { M as ManifestLookup, b as buildModeGuide, f as filteredBuildModes, r as resolveDefaultMode, a as resolveTaskMode, c as runnerModesFrom } from './ModeResolver-D3XO0fT9.js';
11
11
  import { ChildProcess } from 'child_process';
12
12
  import { EventEmitter } from 'events';
13
- import { b as PlanParseError } from './parsing-CF_grC29.js';
14
- export { P as PLAN_ENVELOPE_KEY, a as PartialPlanTask, T as TASK_OPS_ENVELOPE_KEY, c as TASK_QUERY_ENVELOPE_KEY, d as checkDepsResolve, e as checkImmutableLog, f as checkInProgress, g as checkNoCycles, h as checkUniqueIds, i as escapeControlCharsInStrings, j as extractJsonObject, k as extractObjectWithBalance, l as extractObjectsWithKey, m as looksLikePlanAttempt, p as parsePartialPlan, n as parsePlanJson, s as stripModelNoise, o as stripTrailingCommas, v as validatePlanModification } from './parsing-CF_grC29.js';
13
+ import { b as PlanParseError } from './parsing-BTP4bwkk.js';
14
+ export { P as PLAN_ENVELOPE_KEY, a as PartialPlanTask, T as TASK_OPS_ENVELOPE_KEY, c as TASK_QUERY_ENVELOPE_KEY, d as checkDepsResolve, e as checkImmutableLog, f as checkInProgress, g as checkNoCycles, h as checkUniqueIds, i as escapeControlCharsInStrings, j as extractJsonObject, k as extractObjectWithBalance, l as extractObjectsWithKey, m as looksLikePlanAttempt, o as opensWithJsonObject, p as parsePartialPlan, n as parsePlanJson, s as stripModelNoise, q as stripTrailingCommas, v as validatePlanModification } from './parsing-BTP4bwkk.js';
15
15
  export { resolveOrderLabel, taskOrderLabel } from './order-labels.js';
16
16
 
17
17
  interface SessionMeta {
@@ -1129,26 +1129,69 @@ declare class HeadlessRunner extends AbstractRunner<HeadlessSession> {
1129
1129
  * turn that emits nothing but `assistant_text` and `turn_end`.
1130
1130
  */
1131
1131
  type AgentEvent =
1132
- /** A chunk of the assistant's reply. Concatenated in order to form the turn's text. */
1132
+ /**
1133
+ * A complete run of the assistant's reply. Concatenated in order to form the
1134
+ * turn's text. When the same run already streamed as `assistant_text_delta`,
1135
+ * this is the authoritative copy of it — it replaces the deltas, it is not
1136
+ * appended after them.
1137
+ */
1133
1138
  {
1134
1139
  type: 'assistant_text';
1135
1140
  text: string;
1136
1141
  }
1137
- /** Reasoning the agent chose to expose. Never contributes to the reply text. */
1142
+ /**
1143
+ * An incremental piece of the assistant's reply, for agents that stream
1144
+ * partial messages. The planner's own text only: a subagent's words never
1145
+ * arrive here, so they can never become the reply.
1146
+ */
1147
+ | {
1148
+ type: 'assistant_text_delta';
1149
+ text: string;
1150
+ }
1151
+ /**
1152
+ * Reasoning the agent chose to expose. Never contributes to the reply text.
1153
+ * Like `assistant_text`, it supersedes deltas already streamed for it.
1154
+ */
1138
1155
  | {
1139
1156
  type: 'thinking';
1140
1157
  text: string;
1158
+ subagentId?: string;
1141
1159
  } | {
1160
+ type: 'thinking_delta';
1161
+ text: string;
1162
+ subagentId?: string;
1163
+ }
1164
+ /** `subagentId` marks a call made inside a subagent rather than by the planner itself. */
1165
+ | {
1142
1166
  type: 'tool_call';
1143
1167
  id: string;
1144
1168
  name: string;
1145
1169
  args: Record<string, unknown>;
1170
+ subagentId?: string;
1146
1171
  } | {
1147
1172
  type: 'tool_result';
1148
1173
  id: string;
1149
1174
  name: string;
1150
1175
  output: string;
1151
1176
  success: boolean;
1177
+ subagentId?: string;
1178
+ }
1179
+ /** One model call's usage, as the agent reported it — never estimated. */
1180
+ | {
1181
+ type: 'usage';
1182
+ record: UsageRecord;
1183
+ }
1184
+ /** The agent delegated `brief` to a subagent, whose events carry `subagentId` until it finishes. */
1185
+ | {
1186
+ type: 'subagent_started';
1187
+ subagentId: string;
1188
+ brief: string;
1189
+ model?: string;
1190
+ } | {
1191
+ type: 'subagent_finished';
1192
+ subagentId: string;
1193
+ outcome: SubagentOutcome;
1194
+ digest: string;
1152
1195
  }
1153
1196
  /**
1154
1197
  * The agent asked to do something its read-only mode does not cover. Always
@@ -1269,8 +1312,8 @@ interface CliAgentAiServiceDeps extends Partial<AgentProcessDeps> {
1269
1312
  * `GeminiService`. It deliberately does **not** extend {@link BaseAiService}:
1270
1313
  * that class's body is Ordewell executing research tools on a model's behalf,
1271
1314
  * which is precisely the part a coding agent replaces. What it reuses instead
1272
- * is everything above the transport — `classifyPlannerReply`, the bounded
1273
- * corrective re-emit loop, `parsePlanJson`, the `ResearchProgress` events the
1315
+ * is everything above the transport — `settleReply` (reply classification
1316
+ * and the bounded corrective retries), `parsePlanJson`, the `ResearchProgress` events the
1274
1317
  * four surfaces already render. That is why this backend reaches VS Code, the
1275
1318
  * web UI, the CLI and the TUI without any of them learning a coding agent is
1276
1319
  * on the other end.
@@ -1290,6 +1333,12 @@ declare class CliAgentAiService implements IAiService {
1290
1333
  private lastNativeSessionId;
1291
1334
  private conversation;
1292
1335
  private activeAbort;
1336
+ /**
1337
+ * Every subagent this conversation has reported starting, and finishing.
1338
+ * Agents restate a subagent's state as it changes, and one can finish a turn
1339
+ * or a process restart after it started; surfaces get each once, in order.
1340
+ */
1341
+ private readonly subagents;
1293
1342
  constructor(config: IConfig, deps?: CliAgentAiServiceDeps);
1294
1343
  hasActiveConversation(): boolean;
1295
1344
  /**
@@ -1308,10 +1357,10 @@ declare class CliAgentAiService implements IAiService {
1308
1357
  continueConversation(userMessage: string, onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal): Promise<ConversationTurn>;
1309
1358
  private openingMessage;
1310
1359
  /**
1311
- * Drive one user message to a settled planner turn. Same shape as the API
1312
- * backend's conversation loop minus the tool rounds — those belong to the
1313
- * agent now — and with the same two policies layered on top: the
1314
- * empty-reply nudge, and a bounded corrective re-emit for botched JSON.
1360
+ * Drive one user message to a settled planner turn through
1361
+ * {@link settleReply}, the loop the API backend settles through too. The
1362
+ * tool rounds belong to the agent now, so one call is one agent turn —
1363
+ * continued while it left subagents running in the background.
1315
1364
  */
1316
1365
  private runConversation;
1317
1366
  /**
@@ -1330,12 +1379,13 @@ declare class CliAgentAiService implements IAiService {
1330
1379
  * degradation `restoreChat` already performs on every surface.
1331
1380
  */
1332
1381
  private ensureAdapter;
1333
- private startAbortScope;
1334
1382
  private plannerModel;
1335
1383
  /**
1336
1384
  * A single agent session that answers one prompt and exits. Used by every
1337
1385
  * non-conversational entry point; the plan is parsed from the reply text by
1338
- * the same extractor the conversational path uses.
1386
+ * the same extractor the conversational path uses. Its envelope streams to
1387
+ * the plan display, as a vendor planner's one-shot does, and the prose
1388
+ * around it is not streamed at all, since no turn is open to show it in.
1339
1389
  */
1340
1390
  private oneShot;
1341
1391
  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<{
@@ -1365,6 +1415,12 @@ interface ConversationRequest {
1365
1415
  runnerModes?: Record<RunnerId, RunnerModeInfo[]>;
1366
1416
  autonomousDefault?: boolean;
1367
1417
  verificationEnabled?: boolean;
1418
+ /**
1419
+ * The planner model's context window when the catalog knows it. Threaded here
1420
+ * so a usage record can carry it (and the UI can show context fill); absent
1421
+ * when unknown, never guessed (#49).
1422
+ */
1423
+ contextWindow?: number;
1368
1424
  /** Where tasks will run (ADR-0013, ADR-0014): in worktrees, the prompt drops file-overlap ordering and describes a repo group. */
1369
1425
  isolatedExecution?: IsolatedExecution;
1370
1426
  signal?: AbortSignal;
@@ -1533,7 +1589,7 @@ declare abstract class BaseAiService {
1533
1589
  abstract ensureInit(): void;
1534
1590
  hasActiveConversation(): boolean;
1535
1591
  pruneContext(): number;
1536
- protected startAbortScope(callerSignal?: AbortSignal): AbortSignal | undefined;
1592
+ protected startAbortScope(callerSignal?: AbortSignal): AbortSignal;
1537
1593
  protected stopAbortScope(): void;
1538
1594
  sendPlanningPrompt(prompt: string, runners: RunnerId[], runnerModes?: Record<RunnerId, RunnerModeInfo[]>, autonomousDefault?: boolean): Promise<Task[]>;
1539
1595
  continueConversation(userMessage: string, onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal): Promise<ConversationTurn>;
@@ -1542,9 +1598,10 @@ declare abstract class BaseAiService {
1542
1598
  * Build a fresh chat for one research subagent (own history, subagent system
1543
1599
  * prompt, cheap model). Null means the provider does not support subagents —
1544
1600
  * the spawn tool then degrades to a steering message. `onReasoning` streams
1545
- * live reasoning deltas on models that expose them, same as the top-level loop.
1601
+ * live reasoning deltas on models that expose them, same as the top-level loop;
1602
+ * `onUsage` takes each call's usage, as the planner's own chat reports it.
1546
1603
  */
1547
- protected createSubagentChat(_onReasoning?: (delta: string) => void): ResearchChat | null;
1604
+ protected createSubagentChat(_onReasoning?: (delta: string) => void, _onUsage?: (record: UsageRecord) => void): ResearchChat | null;
1548
1605
  /**
1549
1606
  * One spawn_research_agent tool call, executed at the service layer (not in
1550
1607
  * executeTool — that would cycle the imports). Every failure path returns a
@@ -1567,13 +1624,15 @@ declare abstract class BaseAiService {
1567
1624
  private static appendBudgetCountdown;
1568
1625
  /**
1569
1626
  * Run one planner conversation turn (ADR-0002): send the message, satisfy
1570
- * tool calls until the model answers in prose or JSON, then classify the
1571
- * result. The model decides transitions — there are no sentinels, no
1572
- * question tags, and no correction nags. A turn whose final text parses as
1573
- * a `{tasks:[...]}` object commits the plan; anything else is a message to
1574
- * the user.
1627
+ * tool calls until the model answers in prose or JSON, and settle the reply
1628
+ * through {@link settleReply}, which owns classification and every
1629
+ * corrective retry. The model decides transitions — there are no sentinels
1630
+ * and no question tags. A turn whose final text parses as a `{tasks:[...]}`
1631
+ * object commits the plan; anything else is a message to the user.
1575
1632
  */
1576
1633
  protected runConversationTurn(ctx: ConversationTurnContext, message: string, onProgress: (progress: ResearchProgress) => void, signal?: AbortSignal): Promise<ConversationTurn>;
1634
+ /** One model call of a conversation turn: the message, then tool rounds until the model replies without tools. */
1635
+ private runToolRounds;
1577
1636
  /**
1578
1637
  * Run the LLM tool-calling research loop for one-shot planning. Returns
1579
1638
  * parsed tasks if a plan was emitted mid-loop, or null if the loop
@@ -1628,7 +1687,7 @@ declare class OpenAiService extends BaseAiService implements IAiService {
1628
1687
  reset(): void;
1629
1688
  protected streamPlanText(prompt: string, repairHint: string | undefined, onToken: (token: string) => void, onReasoning?: (token: string) => void, signal?: AbortSignal): Promise<string>;
1630
1689
  /** A research subagent: fresh history, digest contract, cheap model, read-only tools. */
1631
- protected createSubagentChat(onReasoning?: (delta: string) => void): ResearchChat | null;
1690
+ protected createSubagentChat(onReasoning?: (delta: string) => void, onUsage?: (record: UsageRecord) => void): ResearchChat | null;
1632
1691
  startConversation(req: ConversationRequest): Promise<ConversationTurn>;
1633
1692
  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<{
1634
1693
  tasks: Task[];
@@ -1805,7 +1864,7 @@ interface OrchestratorObserver {
1805
1864
  * daemon may leave unwired, so a surface without toasts can still show it.
1806
1865
  */
1807
1866
  onIsolationNotice?(data: {
1808
- level: 'info' | 'warn';
1867
+ level: 'info' | 'warn' | 'error';
1809
1868
  message: string;
1810
1869
  }): void;
1811
1870
  }
@@ -1841,6 +1900,7 @@ declare class TaskOrchestrator {
1841
1900
  private running;
1842
1901
  private planStatus;
1843
1902
  private messageQueue;
1903
+ private queueSeq;
1844
1904
  private reviewApproved;
1845
1905
  private retryCounts;
1846
1906
  private spawnCounts;
@@ -1976,6 +2036,8 @@ declare class TaskOrchestrator {
1976
2036
  get isolationRecord(): PlanIsolation | null;
1977
2037
  queueMessage(text: string): void;
1978
2038
  getQueuedMessages(): QueuedMessage[];
2039
+ /** Take one unsent message back out of the queue; false when it was never there (or already drained). */
2040
+ removeQueuedMessage(id: string): boolean;
1979
2041
  setQueuedMessages(messages: QueuedMessage[]): void;
1980
2042
  clearQueuedMessages(): void;
1981
2043
  processNextQueuedMessage(): QueuedMessage | null;
@@ -1997,6 +2059,8 @@ declare class TaskOrchestrator {
1997
2059
  stop(): void;
1998
2060
  onUserTaskComplete(taskId: string): Promise<void>;
1999
2061
  private onVerdict;
2062
+ /** Whether a stopped runner's own tail says its account, not the task, ran out. */
2063
+ private stoppedOnUsageLimit;
2000
2064
  private afterVerdict;
2001
2065
  /**
2002
2066
  * A repair's verdict decides only whether its work may try to land. It never
@@ -2059,11 +2123,17 @@ declare class TaskOrchestrator {
2059
2123
  * Cancel a running (or scheduled) task: kill its session and return it to
2060
2124
  * 'pending' — "not executed". The task is put on hold so the scheduler
2061
2125
  * doesn't immediately restart it; Retry / Force Start release the hold.
2126
+ *
2127
+ * The attempt's worktree is kept, as a stopped or failed one is: a runner is
2128
+ * often cancelled because it looked stuck after doing the work, and Mark
2129
+ * complete can still land that work. The next attempt replaces it.
2062
2130
  */
2063
2131
  cancelTask(taskId: string): Promise<void>;
2132
+ private cancelAttempt;
2064
2133
  /**
2065
2134
  * Let go of a task that is leaving the plan. A live runner is cancelled
2066
- * through {@link cancelTask}; a spawn still in flight just loses its attempt,
2135
+ * as {@link cancelTask} does, but its worktree goes: no task is left to land
2136
+ * it into. A spawn still in flight just loses its attempt,
2067
2137
  * which is what makes {@link startTask} kill the session it is about to
2068
2138
  * receive. The id's cross-attempt bookkeeping goes too — a hold or retry
2069
2139
  * count kept for a task that no longer exists would be inherited by nothing.
@@ -2091,9 +2161,9 @@ declare class TaskOrchestrator {
2091
2161
  forceStartTask(taskId: string): Promise<void>;
2092
2162
  /**
2093
2163
  * Run exactly one task outside full-plan scheduling. The active/starting
2094
- * session still contributes to isRunning so every surface exposes Stop and
2095
- * disables Execute Plan, but onVerdict cannot auto-schedule other tasks
2096
- * because the plan scheduler's `running` flag remains false.
2164
+ * session still contributes to {@link hasLiveWork} so every surface exposes
2165
+ * Stop and disables Execute Plan, but onVerdict cannot auto-schedule other
2166
+ * tasks because the plan scheduler's `running` flag remains false.
2097
2167
  */
2098
2168
  runTask(taskId: string): Promise<void>;
2099
2169
  getCompletedCount(): number;
@@ -2146,16 +2216,18 @@ declare class TaskOrchestrator {
2146
2216
  /** What earlier runs left merged in the group goes; a failure here is worth a word, never a stopped run. */
2147
2217
  private sweep;
2148
2218
  /**
2149
- * The plan's run carries on while anything has landed on its integration
2150
- * branch: a resumed plan's dependents must start from a tip that holds their
2151
- * predecessors' work, and a fresh branch from the checked-out commit does not.
2219
+ * The plan's run carries on while it still has any record: anything landed
2220
+ * means a resumed plan's dependents must start from a tip that holds their
2221
+ * predecessors' work, and anything held — a kept attempt, a conflict, a
2222
+ * repair — is work the user may still want, which a fresh run's mint would
2223
+ * delete. Only a run with no records at all is superseded.
2152
2224
  */
2153
2225
  private continuableRun;
2154
2226
  /**
2155
- * A run with nothing landed holds only superseded attempts, so it goes whole.
2156
- * One that cannot be continued for another reason — it ran from a different
2157
- * workspace path — keeps its integration branch in each repo that has not
2158
- * merged it: only the user gives landed work up.
2227
+ * A run with no records at all holds only superseded attempts, so it goes
2228
+ * whole. One that cannot be continued for another reason — it ran from a
2229
+ * different workspace path — keeps its integration branch in each repo that
2230
+ * has not merged it: only the user gives landed work up.
2159
2231
  */
2160
2232
  private mintRun;
2161
2233
  /** Close the open run. An isolated one hands its integration branch over for review. */
@@ -2312,6 +2384,14 @@ declare class ModelResolver {
2312
2384
  */
2313
2385
  getCachedRunnerModels(runner: string): DiscoveredModel[];
2314
2386
  getCachedPickerOptions(): OrchestratorOption[];
2387
+ /**
2388
+ * The planner model's context window, from whatever catalog is already
2389
+ * cached (#49): a vendor model comes from the picker catalog, a harness
2390
+ * planner's model from the runner's own discovered models. Reads only —
2391
+ * never triggers a fetch or discovery of its own, so an unknown window stays
2392
+ * unknown and context fill is omitted rather than guessed.
2393
+ */
2394
+ contextWindowFor(modelId: string): number | undefined;
2315
2395
  invalidate(): void;
2316
2396
  }
2317
2397
 
@@ -2579,9 +2659,55 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter {
2579
2659
  readonly agentId = "claude-code";
2580
2660
  /** Whether this turn has already emitted reply text — see {@link handleLine}. */
2581
2661
  private turnHasText;
2662
+ /** The text block streaming now follows earlier reply text, so its first delta opens the paragraph. */
2663
+ private pendingBreak;
2664
+ /** The model answering the planner's current message, as its `message_start` named it. */
2665
+ private plannerModel;
2666
+ /**
2667
+ * The session's `total_cost_usd` as last reported. Undefined after a resume:
2668
+ * the CLI restores the resumed session's running total, and what Ordewell
2669
+ * already counted of it is not ours to know here.
2670
+ */
2671
+ private reportedCostUsd;
2672
+ /**
2673
+ * Subagents started and not yet finished, keyed by the `Agent` call's id.
2674
+ * Kept across turns: a backgrounded one reports after its turn has ended.
2675
+ */
2676
+ private readonly openSubagents;
2677
+ /** Subagent messages already counted. A message arrives as one line per content block, each repeating its usage. */
2678
+ private readonly countedSubagentMessages;
2582
2679
  protected spawnSpec(opts: AgentStartOptions): SpawnSpec;
2583
2680
  protected turnPayload(message: string): string;
2584
2681
  protected handleLine(line: string, emit: (event: AgentEvent) => void): void;
2682
+ private startSubagent;
2683
+ /**
2684
+ * A subagent's messages do not stream, so each line's usage is the snapshot
2685
+ * taken before generation: the prompt is real, the output a placeholder.
2686
+ * Only the prompt side is reported — an absent output count reads as "not
2687
+ * reported", a placeholder would read as a measurement.
2688
+ */
2689
+ private handleSubagentMessage;
2690
+ /**
2691
+ * The `Agent` call returned, so the subagent is done. Its last call — the
2692
+ * report — never appears as a line of its own; the result carries its
2693
+ * complete usage instead.
2694
+ */
2695
+ private finishForegroundSubagent;
2696
+ /**
2697
+ * Partial output of the planner's own message. The complete `assistant` line
2698
+ * for each block follows its deltas and is authoritative for that block (see
2699
+ * {@link AgentEvent}), so nothing here has to reconcile with it.
2700
+ */
2701
+ private handleStreamEvent;
2702
+ /**
2703
+ * What only the result line knows: the cost, which covers every call the
2704
+ * session made — subagents included, since none of their lines carries one —
2705
+ * and the planner model's window. `total_cost_usd` is a running total, so a
2706
+ * turn reports its growth. The first turn after a resume only sets the
2707
+ * baseline: its total includes turns counted before, and a turn's own share
2708
+ * cannot be told apart from them.
2709
+ */
2710
+ private reportSessionCost;
2585
2711
  }
2586
2712
 
2587
2713
  /**
@@ -2601,6 +2727,8 @@ declare class ClaudeCodeAdapter extends StdioAgentAdapter {
2601
2727
  declare class CodexAdapter extends StdioAgentAdapter {
2602
2728
  readonly agentId = "codex";
2603
2729
  private threadId;
2730
+ /** The model Codex opened the thread with — usage records name it. */
2731
+ private threadModel;
2604
2732
  private nextRequestId;
2605
2733
  private settleHandshake;
2606
2734
  private handshakeError;
@@ -2610,6 +2738,12 @@ declare class CodexAdapter extends StdioAgentAdapter {
2610
2738
  private resumeAttempted;
2611
2739
  private resumeFallbackSent;
2612
2740
  private sandbox;
2741
+ /**
2742
+ * Subagent threads this session has spawned, keyed by the child thread id.
2743
+ * Codex runs a subagent in its own thread and replays both threads' events on
2744
+ * one stream; the thread id is what tells them apart (see {@link subagentOf}).
2745
+ */
2746
+ private readonly subagents;
2613
2747
  protected spawnSpec(opts: AgentStartOptions): SpawnSpec;
2614
2748
  /**
2615
2749
  * `initialize`, then `thread/start`, both before the first user message. The
@@ -2628,6 +2762,26 @@ declare class CodexAdapter extends StdioAgentAdapter {
2628
2762
  private startThread;
2629
2763
  protected turnPayload(message: string): string;
2630
2764
  protected handleLine(line: string, emit: (event: AgentEvent) => void): void;
2765
+ /**
2766
+ * The child thread id when `threadId` names a subagent's thread, or undefined
2767
+ * for the planner's own. Codex replays both threads on one stream and only
2768
+ * the thread id separates them; the first time a thread is seen it is
2769
+ * registered so its usage and steps can be tagged with it as a subagent id.
2770
+ */
2771
+ private subagentOf;
2772
+ /**
2773
+ * One usage record per model call, from `thread/tokenUsage/updated`.
2774
+ *
2775
+ * The notification carries two breakdowns: `total` is cumulative for the
2776
+ * thread and `last` is the model call that just finished (verified against
2777
+ * the installed binary — a two-call turn ends with `last` equal to the second
2778
+ * call and `total` equal to both summed). Emitting `total` on each update
2779
+ * would count every earlier call again, so `last` is the record. Mapping
2780
+ * `last.outputTokens` already includes `reasoningOutputTokens` — the thread
2781
+ * total is `inputTokens + outputTokens`, not a sum of three — so reasoning is
2782
+ * not added a second time. Codex reports no price.
2783
+ */
2784
+ private emitUsage;
2631
2785
  /**
2632
2786
  * Refuse one server→client request. Requests whose result schema can express
2633
2787
  * a refusal get that payload; everything else — a permission grant, a
@@ -2639,6 +2793,17 @@ declare class CodexAdapter extends StdioAgentAdapter {
2639
2793
  /** A tool item entering `inProgress` — announce the call so the timeline moves. */
2640
2794
  private emitItemStart;
2641
2795
  private emitItemDone;
2796
+ /**
2797
+ * A `collabAgentToolCall` — the planner spawning, waiting on or messaging a
2798
+ * subagent. The call itself is planner-level tool activity; the lifecycle it
2799
+ * carries becomes `subagent_started` / `subagent_finished`, restated as
2800
+ * often as Codex restates it — the service reports each once. Codex tags
2801
+ * every collab item with the parent thread, so this one never runs for a
2802
+ * subagent.
2803
+ */
2804
+ private emitCollabItem;
2805
+ /** The readable result of a collab call: the brief it sent, or what came back. */
2806
+ private collabSummary;
2642
2807
  }
2643
2808
 
2644
2809
  /**
@@ -2650,10 +2815,12 @@ declare class CodexAdapter extends StdioAgentAdapter {
2650
2815
  * talks to it through the injected `fetch`. Both are part of the one seam the
2651
2816
  * tests drive.
2652
2817
  *
2653
- * The turn ends when the message POST resolves. Live events stream from the
2654
- * server's `/event` channel, but the POST is what settles the turn: an event
2655
- * name that changes between OpenCode versions then costs liveness, not
2656
- * correctness.
2818
+ * The turn ends when the message POST resolves, and its response is the
2819
+ * authoritative copy of the reply's last message. Everything else — reply text
2820
+ * as it streams, earlier model calls' text and reasoning, per-call usage, the
2821
+ * subagents a `task` call runs — arrives only on the server's `/event` channel.
2822
+ * An event name that changes between OpenCode versions therefore costs that
2823
+ * detail, never the final reply.
2657
2824
  */
2658
2825
  declare class OpenCodeAdapter implements AgentAdapter {
2659
2826
  private deps;
@@ -2675,9 +2842,14 @@ declare class OpenCodeAdapter implements AgentAdapter {
2675
2842
  /**
2676
2843
  * Turn one settled assistant message into events. The settled response is
2677
2844
  * authoritative: it names the assistant message, so its parts are the ones
2678
- * that make up the reply. Tool parts already seen live are deduplicated by
2679
- * call id; anything the stream missed (including a stream that never
2680
- * connected) arrives here.
2845
+ * that make up the reply. Parts already completed live are deduplicated;
2846
+ * anything the stream missed (including a stream that never connected)
2847
+ * arrives here.
2848
+ *
2849
+ * It is only the turn's *last* message, though. OpenCode writes one
2850
+ * assistant message per model call, so the calls before the final one —
2851
+ * their text, reasoning and usage — reach Ordewell over the stream or not at
2852
+ * all.
2681
2853
  */
2682
2854
  private settle;
2683
2855
  /**
@@ -2694,11 +2866,39 @@ declare class OpenCodeAdapter implements AgentAdapter {
2694
2866
  private recoverReply;
2695
2867
  private newestAssistantId;
2696
2868
  /**
2697
- * Emit one message part, once. OpenCode reports a tool part repeatedly as it
2698
- * moves through pending → running → completed, so parts are keyed by id and
2699
- * only the terminal state produces a result.
2869
+ * Emit one complete message part, once. OpenCode reports a tool part
2870
+ * repeatedly as it moves through pending → running → completed, so parts are
2871
+ * keyed by id and only the terminal state produces a result. A subagent's
2872
+ * text is its report to the planner, not the reply, so it is dropped; the
2873
+ * `task` call's result carries it.
2700
2874
  */
2701
2875
  private emitPart;
2876
+ /**
2877
+ * A `task` call runs a subagent in a child session. The call's part names
2878
+ * that session once it exists, which is what ties the child's frames to the
2879
+ * call; the subagent ends when the call does. The part is restated at every
2880
+ * status change, and so is what it says here — the service reports each
2881
+ * start and finish once, and no finish for a call that never had a child.
2882
+ */
2883
+ private trackSubagent;
2884
+ /**
2885
+ * One message's usage, once, when it has completed. Every assistant message
2886
+ * is one model call; until it completes its counts are zeros.
2887
+ */
2888
+ private countUsage;
2889
+ /**
2890
+ * One `/event` frame. Only the planner's session and its children are
2891
+ * followed: the server's stream is global, and another client's session is
2892
+ * none of this turn's business.
2893
+ */
2894
+ private onFrame;
2895
+ /**
2896
+ * Stream one piece of a reply text part. The part's paragraph break goes out
2897
+ * with its first visible delta, so the deltas add up to exactly the text the
2898
+ * completed part then re-sends; a part that is only whitespace so far is
2899
+ * held back, for the reason {@link emitPart} drops one.
2900
+ */
2901
+ private onTextDelta;
2702
2902
  /**
2703
2903
  * Deny one permission request (T1). OpenCode blocks the turn until the
2704
2904
  * request is answered, so this must answer — `reject` rather than a silent
@@ -2708,8 +2908,8 @@ declare class OpenCodeAdapter implements AgentAdapter {
2708
2908
  */
2709
2909
  private denyPermission;
2710
2910
  /**
2711
- * Server-sent events from `/event`. Tool activity on it is liveness only —
2712
- * the settled POST repeats it — but permission requests arrive nowhere else,
2911
+ * Server-sent events from `/event`: the turn's live text, reasoning, tool
2912
+ * activity and usage, and the only channel permission requests arrive on —
2713
2913
  * so the stream is load-bearing for {@link denyPermission}.
2714
2914
  */
2715
2915
  private streamEvents;
@@ -2935,6 +3135,13 @@ declare class Session {
2935
3135
  /** Injected by a test; when present it is the service, forever. */
2936
3136
  private readonly pinnedAiService?;
2937
3137
  private liveAiService;
3138
+ private readonly usageLedger;
3139
+ /**
3140
+ * The in-flight turn's subagent activity, grouped one run per subagent so a
3141
+ * replay nests each step under its own brief/result. Flushed into the plan's
3142
+ * researchLog at persist — see {@link flushSubagentRuns}.
3143
+ */
3144
+ private pendingSubagents;
2938
3145
  private liveAiProvider;
2939
3146
  private readonly workspaceRootFn;
2940
3147
  private planner;
@@ -2995,6 +3202,18 @@ declare class Session {
2995
3202
  private buildObserver;
2996
3203
  private broadcastStatus;
2997
3204
  private translateProgress;
3205
+ /** The run for `subagentId`, created on first sighting so a step arriving
3206
+ * before (or without) its started event still gets a home. */
3207
+ private subagentRun;
3208
+ /**
3209
+ * Fold the turn's subagent runs into the plan's researchLog as one contiguous
3210
+ * group per subagent — its entry then its steps, in the order they started —
3211
+ * so a replay nests each step under its own subagent however the live stream
3212
+ * interleaved. A harness planner already logs its child steps through the
3213
+ * turn's researchLog; they are pulled out of that position and re-grouped
3214
+ * rather than duplicated.
3215
+ */
3216
+ private flushSubagentRuns;
2998
3217
  /** Persists PlanStore state to disk. PlanStore is the single authority;
2999
3218
  * LegacyPlanState.tasks is populated only here, at persist time. */
3000
3219
  private persist;
@@ -3051,6 +3270,12 @@ declare class Session {
3051
3270
  get currentPlanState(): PlanState | null;
3052
3271
  get currentGoal(): string;
3053
3272
  get isPlanning(): boolean;
3273
+ /**
3274
+ * A task is running right now. Deliberately live work, not the scheduler's
3275
+ * armed flag: a run paused on a user task, a hold or a cancellation has
3276
+ * nothing executing, and reporting it as executing is what left the plan
3277
+ * unstartable after its last live task was cancelled.
3278
+ */
3054
3279
  get isExecuting(): boolean;
3055
3280
  /** See {@link TaskOrchestrator.hasLiveWork} — a spawned runner, not merely an armed scheduler. */
3056
3281
  get hasLiveWork(): boolean;
@@ -3130,10 +3355,16 @@ declare class Session {
3130
3355
  private conversationOpening;
3131
3356
  /**
3132
3357
  * Load planner-produced tasks, coerced to the allowlist read live — it may
3133
- * have changed since planning started. An edit on an armed scheduler is
3358
+ * have changed since planning started. Tasks adopted on an armed scheduler are
3134
3359
  * reconciled rather than reloaded: `loadPlan` clears the on-hold set and the
3135
3360
  * review approval, so a task the user cancelled would be re-armed and
3136
3361
  * re-spawned by the re-tick that follows.
3362
+ *
3363
+ * A whole-plan commit is the planner restating every task, so it is laid
3364
+ * over the plan's execution state rather than replacing it: a planner that
3365
+ * answers "add a task" with the full plan must not undo the work already
3366
+ * done. Task ops need no overlay — their applier refuses to touch settled
3367
+ * tasks, and `rearm`, the one op meant to change a status, must stand.
3137
3368
  */
3138
3369
  private adoptPlannerTasks;
3139
3370
  /**
@@ -3154,10 +3385,18 @@ declare class Session {
3154
3385
  markTaskIncomplete(taskId: string): Promise<void>;
3155
3386
  tick(): Promise<void>;
3156
3387
  processQueuedMessages(): Promise<void>;
3388
+ /**
3389
+ * Every finished task as the log records it: this run's own entries, plus
3390
+ * the tasks earlier runs completed, which the log dropped when this run
3391
+ * began but which dependents still count on.
3392
+ */
3393
+ private finishedWork;
3157
3394
  approveCheckpoint(taskId: string): void;
3158
3395
  rejectCheckpoint(taskId: string, reason?: string): void;
3159
3396
  queueMessage(text: string): void;
3160
3397
  getQueuedMessages(): QueuedMessage[];
3398
+ /** Take back one unsent message; the plan's persisted queue follows so a reload cannot resurrect it. */
3399
+ removeQueuedMessage(id: string): boolean;
3161
3400
  setQueuedMessages(msgs: ReturnType<TaskOrchestrator['getQueuedMessages']>): void;
3162
3401
  processNextQueuedMessage(): QueuedMessage | null;
3163
3402
  get queuedCount(): number;
@@ -3598,11 +3837,12 @@ declare function buildConflictRepairPrompt(task: Task, conflict: {
3598
3837
  *
3599
3838
  * - {@link repairLoop} is the bounded driver (first reply → interpret →
3600
3839
  * corrective re-send), used by plan generation ({@link generatePlanWithRepair}),
3601
- * the Session's task-ops settlement, and Planner.modifyDuringExecution.
3840
+ * the Session's task-ops settlement, Planner.modifyDuringExecution, and
3841
+ * `settleReply` — the one loop both planner backends settle a conversation
3842
+ * turn's reply through.
3602
3843
  * - {@link classifyPlannerReply} decides what a planner reply *is* — a plan,
3603
3844
  * targeted task edits, a botched attempt at either (worth a corrective
3604
- * retry), or prose. The conversation loop in BaseAiService keeps its own
3605
- * driver (it also runs the tool rounds) but delegates classification here.
3845
+ * retry), or prose.
3606
3846
  * - The corrective prompt texts live here, once.
3607
3847
  *
3608
3848
  * Policies stay at the call sites (ops validation, abort guards) —
@@ -3610,10 +3850,15 @@ declare function buildConflictRepairPrompt(task: Task, conflict: {
3610
3850
  */
3611
3851
  type RepairVerdict<T> = {
3612
3852
  done: T;
3613
- } | {
3853
+ }
3854
+ /**
3855
+ * A corrective that costs something to prepare (freeing context first) is
3856
+ * passed as a function: it runs only when a re-send will actually follow.
3857
+ */
3858
+ | {
3614
3859
  retry: {
3615
3860
  errors: string[];
3616
- corrective: string;
3861
+ corrective: string | (() => string);
3617
3862
  cause?: unknown;
3618
3863
  };
3619
3864
  };
@@ -3776,6 +4021,9 @@ declare class TmuxSession extends AbstractTerminalSession {
3776
4021
  private outputBuffer;
3777
4022
  private offset;
3778
4023
  private timer;
4024
+ private quietPolls;
4025
+ private misses;
4026
+ private looking;
3779
4027
  constructor(id: string, taskId: string, tmuxSession: string, windowName: string, execFileImpl: ExecFileFn, pollIntervalMs: number, logPath: string, socket: string);
3780
4028
  private get target();
3781
4029
  /** Every tmux call must name the daemon's own socket; an unprefixed one
@@ -3790,6 +4038,18 @@ declare class TmuxSession extends AbstractTerminalSession {
3790
4038
  */
3791
4039
  start(command: string, args: string[], cwd: string, env?: Record<string, string>): Promise<void>;
3792
4040
  private poll;
4041
+ /** Emit what the log gained since the last read; false when it gained nothing. */
4042
+ private readLog;
4043
+ /**
4044
+ * The sentinel is printed by the wrapper shell, so a window closed from
4045
+ * outside — killed by the user, or with the whole tmux server — never prints
4046
+ * it, and the session would count as running forever. A silent window is
4047
+ * therefore looked up by exact name (a `-t` target falls back to another
4048
+ * window once its own is gone), and once it is confirmed missing the session
4049
+ * ends as a kill does. Whatever the log still held is read first, so a
4050
+ * completion marker printed just before the close still counts.
4051
+ */
4052
+ private checkWindow;
3793
4053
  /**
3794
4054
  * A task finishing (or being killed) stops observation, but never the
3795
4055
  * window itself on the sentinel path — only `kill()` closes the window.
@@ -4256,4 +4516,4 @@ declare const DEFAULT_MAX_PARALLEL = 3;
4256
4516
  */
4257
4517
  declare function parseMaxParallel(value: unknown): number | null;
4258
4518
 
4259
- export { ALLOWED_PLUGIN_HOSTS, ALL_PROVIDERS, AUTO_COMMANDS, AbstractRunner, AbstractTerminalSession, ActiveTaskSession, type AgentAdapter, type AgentAdapterFactory, type AgentEvent, type AgentProcessDeps, type AgentStartOptions, AiProvider, ApprovalMode, ApprovalRequest, BUILTIN_SKILL_NAMES, BaseAiService, BaseConfig, BaseFileSystem, BufferedTaskOutputSource, CLAUDE_CODE_MANIFEST, CLI_PROVIDERS, CMD_EXE_MAX_COMMAND_LINE, CORE_PLANNER_PROMPT, type CappedRows, type CheckpointListener, ClaudeCodeAdapter, CliAgentAiService, type CliAgentAiServiceDeps, CodexAdapter, type CommandClassification, CommandLineTooLongError, type CommandTier, type ComposeOptions, ConsoleLogger, ContextCollector, ConversationBusyError, type ConversationCompaction, ConversationEditError, type ConversationFork, ConversationMessage, type ConversationRequest, type ConversationRewind, type ConversationTurn, type ConversationVariant, DAEMON_SUBPROTOCOL, DAEMON_TOKEN_SUBPROTOCOL_PREFIX, DEFAULT_MAX_PARALLEL, DiscoveredModel, EmbeddedNewlineError, type EnvAdmission, EnvConfig, type ExecFileFn, type ExecImpl, ExecutableNotFoundError, FindSymbolOptions, FsPluginStore, GIT_READONLY_SUBCOMMANDS, GeminiService, type GitExecFn, GlobOptions, type GrepInvocation, GrepOptions, type HasBinFn, HeadlessRunner, type HeadlessRunnerDeps, HeadlessSession, HomeTranscriptReader, type IAiService, IApproval, IConfig, IFileSystem, type ILogger, type INotification, IPluginStore, ITerminalRunner, ITerminalSession, IWorktreeIsolation, IsolationHandoff, IsolationLandedTask, IsolationMergeResult, IsolationView, JSON_REPAIR_INSTRUCTION, type KillTreeDeps, type LaunchDeps, type LaunchPlan, LegacyPlanState, LineBuffer, type LiveOutputLookup, type LiveTail, type LiveTailOptions, type MappedTool, ModelResolver, type ModelResolverDeps, type ModifyPlanRequest, type NotificationAction, OPENCODE_MANIFEST, ORDEWELL_SETTABLE_ENV, OUTPUT_LINES_DEFAULT, OUTPUT_LINES_MAX, OpenAiService, OpenCodeAdapter, type OrchestratorObserver, OrchestratorOption, PLUGIN_NAME_PATTERN, PROVIDER_CREDENTIAL_ENV, PROVIDER_DETECT_PRIORITY, PROVIDER_LABEL, PROVIDER_PRIORITY, PROVIDER_SHORT_LABEL, type PendingApproval, PendingApprovals, type PendingApprovalsOptions, PlanEditError, PlanIsolation, PlanParseError, type PlanRequest, PlanState, PlanStatus, PlanStore, Planner, type PlannerModelCandidate, type PlannerModelChoice, PlannerModelMemory, type PlannerModelRecall, type PlannerModelStore, type PlannerReplyClassification, type PlannerRuntimeToggles, type PluginSource, type PrdBlock, type PreparedLaunch, type PriorOutput, type ProbeFn, ProviderModelLists, type ProviderRegistration, type PtySize, type PtyWrapOptions, QueuedMessage, REFUSED_COMMANDS, ReadFileOpts, type RepairLoopOpts, type RepairVerdict, RepoGroupLayout, type ResearchChat, ResearchLogEntry, ResearchProgress, type ResearchShell, type ResearchShellDeps, ResearchToolType, type ResearchTurn, ResolveContext, type RewindTarget, type RunnerCatalog, RunnerId, RunnerInstallation, RunnerInvocation, type RunnerMode, RunnerModeInfo, RunnerPluginManifest, RunnerRegistry, type RunnerSpawnOptions, SETTINGS_ENV_ALLOWLIST, SETTINGS_ENV_REFUSED, STATE_DIR, STOPPED_TOOL_RESULT, SYMBOL_LANGUAGES, Session, SessionBroadcaster, type SessionData, type SessionDeps, type SessionMeta, SessionNotice, type SessionPlanner, type SessionRuntimeSettings, SettingsService, type ShellDialect, type SkillInfo, type SkillMetadata, SkillsService, type SpawnSpec, StdioAgentAdapter, TASK_QUERY_ANSWER_MAX_CHARS, TASK_QUERY_ANSWER_OR_OPS, TASK_QUERY_FIELDS, TASK_QUERY_PROTOCOL, TASK_QUERY_REMINDER, TRUNCATED_PLAN_REPAIR_INSTRUCTION, Task, type TaskAttemptSnapshot, TaskIsolation, TaskOp, TaskOrchestrator, type TaskOutputAttempt, type TaskOutputSource, type TaskQuery, type TaskQueryCatalog, type TaskQueryField, TaskSnapshot, TmuxRunner, type TmuxRunnerDeps, type TokenCarriers, type ToolCall, ToolOutcome, type ToolResult, type TranscriptQuery, type TranscriptReader, type UserSettings, Verdict, VerdictEngine, type VerdictListener, WINDOWS_MAX_COMMAND_LINE, type WorkspaceCheckDeps, WorkspaceNotAProjectError, WorkspaceNotFoundError, type WorkspaceProjectCheckDeps, type WorktreeIsolationDeps, admitSettingsEnv, applyHeadLimit, assertInstallablePluginUrl, assertPlainPluginName, assertWorkspaceExists, assertWorkspaceIsProject, augmentPromptWithPriorOutputs, augmentedPath, bearerHeaderValue, buildConflictRepairPrompt, buildConflictResolutionPrompt, buildConversationSystemPrompt, buildFallbackGrepArgs, buildGlobArgs, buildGrepArgs, buildMergePrompt, buildModifyDuringExecutionPrompt, buildModifyPlanPrompt, buildPlanWithResults, buildResearchPrompt, buildResearchToolsPrompt, buildRunnerInvocation, buildShellInvocation, buildSplitPrompt, buildSubagentSystemPrompt, clampThinkingEffort, classifyCommand, classifyPlannerReply, classifyPluginSource, clearAugmentedPathCache, clearDaemonToken, clearResearchShellCache, clearState, clipboardCopyCommand, coerceAssignments, collectDirectDependencyOutputs, composeAugmentedPrompt, configuredProviders, createAiService, createSkillsService, createWorktreeIsolation, daemonTokenPath, defaultLogger, definitionPattern, deleteSession, describeMergeResult, discoverGeminiModels, effectiveAllowlist, ensureDir, ensureStateDirIgnored, executionLogBlock, extractPrdBlock, extractPresentedToken, filterFallbackByAnchoredInclude, filterModelsForPrompt, formatSearchOutput, generatePlanWithRepair, getLatestSession, getProviderMeta, getSettingsPath, getStateDir, globalDataDir, grantScopeFor, hasTmux, includeGlobFor, isCliProvider, isExecutableResolved, isOpenAiProvider, isPlainPluginName, isValidManifest, killTree, languageForId, listSessions, loadSession, loadSessionPlanState, loadState, mapAgentTool, migrateOldConfigDir, mintDaemonToken, mintSessionId, modelContextBlock, modifyValidationFeedback, normalizeAgentArgs, normalizeGeminiModel, parseMaxParallel, parseTaskQueryJson, pendingEditRulesBlock, planDirectLaunch, planShellLaunch, posixShellQuote, prefixModelId, providerForRunner, reEmitPlanPrompt, reEmitTaskOpsPrompt, reEmitTaskQueryPrompt, readDaemonToken, referencePattern, renderPlanMap, renderPriorOutputs, renderTaskQueryAnswer, repairLoop, researchShellWarning, researchToolsPath, resolveArgs, resolvePluginInstallDir, resolveProviderFromPrefix, resolveResearchShell, resolveSkillInvocation, resolveWithin, retargetTaskRunner, runnerAssignment, runnerForProvider, sanitizeSlug, savePrdMarkdown, saveSession, saveState, sessionRuntimeSettings, stateExists, stripAnsi, stripModelPrefix, summarizeOutput, taskOpsRejectedPrompt, taskQuerySignature, textHasTaskQuery, tmuxSessionName, tmuxSocketName, tmuxWindowName, tokenSubprotocols, tokensMatch, truncatedPlanReEmitPrompt, wellKnownBinDirs, windowsCommandLine, withPath, wrapWithPty };
4519
+ export { ALLOWED_PLUGIN_HOSTS, ALL_PROVIDERS, AUTO_COMMANDS, AbstractRunner, AbstractTerminalSession, ActiveTaskSession, type AgentAdapter, type AgentAdapterFactory, type AgentEvent, type AgentProcessDeps, type AgentStartOptions, AiProvider, ApprovalMode, ApprovalRequest, BUILTIN_SKILL_NAMES, BaseAiService, BaseConfig, BaseFileSystem, BufferedTaskOutputSource, CLAUDE_CODE_MANIFEST, CLI_PROVIDERS, CMD_EXE_MAX_COMMAND_LINE, CORE_PLANNER_PROMPT, type CappedRows, type CheckpointListener, ClaudeCodeAdapter, CliAgentAiService, type CliAgentAiServiceDeps, CodexAdapter, type CommandClassification, CommandLineTooLongError, type CommandTier, type ComposeOptions, ConsoleLogger, ContextCollector, ConversationBusyError, type ConversationCompaction, ConversationEditError, type ConversationFork, ConversationMessage, type ConversationRequest, type ConversationRewind, type ConversationTurn, type ConversationVariant, DAEMON_SUBPROTOCOL, DAEMON_TOKEN_SUBPROTOCOL_PREFIX, DEFAULT_MAX_PARALLEL, DiscoveredModel, EmbeddedNewlineError, type EnvAdmission, EnvConfig, type ExecFileFn, type ExecImpl, ExecutableNotFoundError, FindSymbolOptions, FsPluginStore, GIT_READONLY_SUBCOMMANDS, GeminiService, type GitExecFn, GlobOptions, type GrepInvocation, GrepOptions, type HasBinFn, HeadlessRunner, type HeadlessRunnerDeps, HeadlessSession, HomeTranscriptReader, type IAiService, IApproval, IConfig, IFileSystem, type ILogger, type INotification, IPluginStore, ITerminalRunner, ITerminalSession, IWorktreeIsolation, IsolationHandoff, IsolationLandedTask, IsolationMergeResult, IsolationView, JSON_REPAIR_INSTRUCTION, type KillTreeDeps, type LaunchDeps, type LaunchPlan, LegacyPlanState, LineBuffer, type LiveOutputLookup, type LiveTail, type LiveTailOptions, type MappedTool, ModelResolver, type ModelResolverDeps, type ModifyPlanRequest, type NotificationAction, OPENCODE_MANIFEST, ORDEWELL_SETTABLE_ENV, OUTPUT_LINES_DEFAULT, OUTPUT_LINES_MAX, OpenAiService, OpenCodeAdapter, type OrchestratorObserver, OrchestratorOption, PLUGIN_NAME_PATTERN, PROVIDER_CREDENTIAL_ENV, PROVIDER_DETECT_PRIORITY, PROVIDER_LABEL, PROVIDER_PRIORITY, PROVIDER_SHORT_LABEL, type PendingApproval, PendingApprovals, type PendingApprovalsOptions, PlanEditError, PlanIsolation, PlanParseError, type PlanRequest, PlanState, PlanStatus, PlanStore, Planner, type PlannerModelCandidate, type PlannerModelChoice, PlannerModelMemory, type PlannerModelRecall, type PlannerModelStore, type PlannerReplyClassification, type PlannerRuntimeToggles, type PluginSource, type PrdBlock, type PreparedLaunch, type PriorOutput, type ProbeFn, ProviderModelLists, type ProviderRegistration, type PtySize, type PtyWrapOptions, QueuedMessage, REFUSED_COMMANDS, ReadFileOpts, type RepairLoopOpts, type RepairVerdict, RepoGroupLayout, type ResearchChat, ResearchLogEntry, ResearchProgress, type ResearchShell, type ResearchShellDeps, ResearchToolType, type ResearchTurn, ResolveContext, type RewindTarget, type RunnerCatalog, RunnerId, RunnerInstallation, RunnerInvocation, type RunnerMode, RunnerModeInfo, RunnerPluginManifest, RunnerRegistry, type RunnerSpawnOptions, SETTINGS_ENV_ALLOWLIST, SETTINGS_ENV_REFUSED, STATE_DIR, STOPPED_TOOL_RESULT, SYMBOL_LANGUAGES, Session, SessionBroadcaster, type SessionData, type SessionDeps, type SessionMeta, SessionNotice, type SessionPlanner, type SessionRuntimeSettings, SettingsService, type ShellDialect, type SkillInfo, type SkillMetadata, SkillsService, type SpawnSpec, StdioAgentAdapter, SubagentOutcome, TASK_QUERY_ANSWER_MAX_CHARS, TASK_QUERY_ANSWER_OR_OPS, TASK_QUERY_FIELDS, TASK_QUERY_PROTOCOL, TASK_QUERY_REMINDER, TRUNCATED_PLAN_REPAIR_INSTRUCTION, Task, type TaskAttemptSnapshot, TaskIsolation, TaskOp, TaskOrchestrator, type TaskOutputAttempt, type TaskOutputSource, type TaskQuery, type TaskQueryCatalog, type TaskQueryField, TaskSnapshot, TmuxRunner, type TmuxRunnerDeps, type TokenCarriers, type ToolCall, ToolOutcome, type ToolResult, type TranscriptQuery, type TranscriptReader, UsageRecord, type UserSettings, Verdict, VerdictEngine, type VerdictListener, WINDOWS_MAX_COMMAND_LINE, type WorkspaceCheckDeps, WorkspaceNotAProjectError, WorkspaceNotFoundError, type WorkspaceProjectCheckDeps, type WorktreeIsolationDeps, admitSettingsEnv, applyHeadLimit, assertInstallablePluginUrl, assertPlainPluginName, assertWorkspaceExists, assertWorkspaceIsProject, augmentPromptWithPriorOutputs, augmentedPath, bearerHeaderValue, buildConflictRepairPrompt, buildConflictResolutionPrompt, buildConversationSystemPrompt, buildFallbackGrepArgs, buildGlobArgs, buildGrepArgs, buildMergePrompt, buildModifyDuringExecutionPrompt, buildModifyPlanPrompt, buildPlanWithResults, buildResearchPrompt, buildResearchToolsPrompt, buildRunnerInvocation, buildShellInvocation, buildSplitPrompt, buildSubagentSystemPrompt, clampThinkingEffort, classifyCommand, classifyPlannerReply, classifyPluginSource, clearAugmentedPathCache, clearDaemonToken, clearResearchShellCache, clearState, clipboardCopyCommand, coerceAssignments, collectDirectDependencyOutputs, composeAugmentedPrompt, configuredProviders, createAiService, createSkillsService, createWorktreeIsolation, daemonTokenPath, defaultLogger, definitionPattern, deleteSession, describeMergeResult, discoverGeminiModels, effectiveAllowlist, ensureDir, ensureStateDirIgnored, executionLogBlock, extractPrdBlock, extractPresentedToken, filterFallbackByAnchoredInclude, filterModelsForPrompt, formatSearchOutput, generatePlanWithRepair, getLatestSession, getProviderMeta, getSettingsPath, getStateDir, globalDataDir, grantScopeFor, hasTmux, includeGlobFor, isCliProvider, isExecutableResolved, isOpenAiProvider, isPlainPluginName, isValidManifest, killTree, languageForId, listSessions, loadSession, loadSessionPlanState, loadState, mapAgentTool, migrateOldConfigDir, mintDaemonToken, mintSessionId, modelContextBlock, modifyValidationFeedback, normalizeAgentArgs, normalizeGeminiModel, parseMaxParallel, parseTaskQueryJson, pendingEditRulesBlock, planDirectLaunch, planShellLaunch, posixShellQuote, prefixModelId, providerForRunner, reEmitPlanPrompt, reEmitTaskOpsPrompt, reEmitTaskQueryPrompt, readDaemonToken, referencePattern, renderPlanMap, renderPriorOutputs, renderTaskQueryAnswer, repairLoop, researchShellWarning, researchToolsPath, resolveArgs, resolvePluginInstallDir, resolveProviderFromPrefix, resolveResearchShell, resolveSkillInvocation, resolveWithin, retargetTaskRunner, runnerAssignment, runnerForProvider, sanitizeSlug, savePrdMarkdown, saveSession, saveState, sessionRuntimeSettings, stateExists, stripAnsi, stripModelPrefix, summarizeOutput, taskOpsRejectedPrompt, taskQuerySignature, textHasTaskQuery, tmuxSessionName, tmuxSocketName, tmuxWindowName, tokenSubprotocols, tokensMatch, truncatedPlanReEmitPrompt, wellKnownBinDirs, windowsCommandLine, withPath, wrapWithPty };