@ordewell/core 0.10.9 → 0.10.11

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 (40) hide show
  1. package/dist/{IFileSystem-C9W5mRYA.d.mts → IFileSystem-1PqRCjhu.d.mts} +21 -3
  2. package/dist/{IFileSystem-B8qRNC_6.d.ts → IFileSystem-lTRL_G9Z.d.ts} +21 -3
  3. package/dist/{ModeResolver-C1PN5FYG.d.mts → ModeResolver-6TQXVsde.d.mts} +1 -1
  4. package/dist/{ModeResolver-CaPnoU8N.d.ts → ModeResolver-CoJAJD-f.d.ts} +1 -1
  5. package/dist/{Task-hFfRS9rN.d.mts → Task-CfBM0wkV.d.mts} +94 -21
  6. package/dist/{Task-hFfRS9rN.d.ts → Task-CfBM0wkV.d.ts} +94 -21
  7. package/dist/{chunk-H7QX32BH.mjs → chunk-24EL4Z2W.mjs} +2 -2
  8. package/dist/{chunk-O6LQU3W6.mjs → chunk-ESKK5I4F.mjs} +138 -21
  9. package/dist/chunk-ESKK5I4F.mjs.map +1 -0
  10. package/dist/{chunk-LTHKFBKM.mjs → chunk-KWS3VEER.mjs} +3 -3
  11. package/dist/{chunk-EEE2GBLG.mjs → chunk-PFH7WFIB.mjs} +1 -1
  12. package/dist/chunk-PFH7WFIB.mjs.map +1 -0
  13. package/dist/index.d.mts +129 -20
  14. package/dist/index.d.ts +129 -20
  15. package/dist/index.js +556 -92
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +432 -76
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/parsing.d.mts +2 -2
  20. package/dist/parsing.d.ts +2 -2
  21. package/dist/parsing.js.map +1 -1
  22. package/dist/parsing.mjs +3 -3
  23. package/dist/plan-utils.d.mts +4 -4
  24. package/dist/plan-utils.d.ts +4 -4
  25. package/dist/plan-utils.js +144 -18
  26. package/dist/plan-utils.js.map +1 -1
  27. package/dist/plan-utils.mjs +22 -4
  28. package/dist/{runSettingsText-CGKX3aeV.d.ts → runSettingsText-C5uv2axI.d.ts} +29 -18
  29. package/dist/{runSettingsText-rLoYQlUH.d.mts → runSettingsText-Jb_efnI4.d.mts} +29 -18
  30. package/dist/testing.d.mts +12 -3
  31. package/dist/testing.d.ts +12 -3
  32. package/dist/testing.js +16 -1
  33. package/dist/testing.js.map +1 -1
  34. package/dist/testing.mjs +17 -2
  35. package/dist/testing.mjs.map +1 -1
  36. package/package.json +1 -1
  37. package/dist/chunk-EEE2GBLG.mjs.map +0 -1
  38. package/dist/chunk-O6LQU3W6.mjs.map +0 -1
  39. /package/dist/{chunk-H7QX32BH.mjs.map → chunk-24EL4Z2W.mjs.map} +0 -0
  40. /package/dist/{chunk-LTHKFBKM.mjs.map → chunk-KWS3VEER.mjs.map} +0 -0
package/dist/index.js CHANGED
@@ -203,7 +203,10 @@ __export(index_exports, {
203
203
  collectProviderCredentials: () => collectProviderCredentials,
204
204
  commandAllowedBy: () => commandAllowedBy,
205
205
  configuredProviders: () => configuredProviders,
206
+ contextMeterText: () => contextMeterText,
207
+ contextMeterTitle: () => contextMeterTitle,
206
208
  continuability: () => continuability,
209
+ costText: () => costText,
207
210
  countedSessionCost: () => countedSessionCost,
208
211
  createAiService: () => createAiService,
209
212
  createEmptyPlan: () => createEmptyPlan,
@@ -246,7 +249,9 @@ __export(index_exports, {
246
249
  flattenTasksWithParents: () => flattenTasksWithParents,
247
250
  followTaskLog: () => followTaskLog,
248
251
  followTurn: () => followTurn,
252
+ formatCost: () => formatCost,
249
253
  formatSearchOutput: () => formatSearchOutput,
254
+ formatTokenCount: () => formatTokenCount,
250
255
  fromTranscript: () => fromTranscript,
251
256
  getLatestSession: () => getLatestSession,
252
257
  getProviderMeta: () => getProviderMeta,
@@ -298,6 +303,7 @@ __export(index_exports, {
298
303
  migratePlanState: () => migratePlanState,
299
304
  mintDaemonToken: () => mintDaemonToken,
300
305
  mintSessionId: () => mintSessionId,
306
+ modelCostTitle: () => modelCostTitle,
301
307
  namedRepos: () => namedRepos,
302
308
  newTaskFields: () => newTaskFields,
303
309
  normalizeAgentArgs: () => normalizeAgentArgs,
@@ -383,6 +389,7 @@ __export(index_exports, {
383
389
  stopTurn: () => stopTurn,
384
390
  stripAnsi: () => stripAnsi,
385
391
  stripModelPrefix: () => stripModelPrefix,
392
+ subagentCostTitle: () => subagentCostTitle,
386
393
  subtreeIds: () => subtreeIds,
387
394
  summarizeToolCall: () => summarizeToolCall,
388
395
  supervisorSummary: () => supervisorSummary,
@@ -401,6 +408,7 @@ __export(index_exports, {
401
408
  taskStartedNotice: () => taskStartedNotice,
402
409
  taskStatusKind: () => taskStatusKind,
403
410
  taskUsageSummary: () => taskUsageSummary,
411
+ tasksTotalText: () => tasksTotalText,
404
412
  textHasTaskOps: () => textHasTaskOps,
405
413
  textHasTaskQuery: () => textHasTaskQuery,
406
414
  titledRefs: () => titledRefs,
@@ -418,6 +426,7 @@ __export(index_exports, {
418
426
  unsendAll: () => unsendAll,
419
427
  unsendLatest: () => unsendLatest,
420
428
  usageLine: () => usageLine,
429
+ usageText: () => usageText,
421
430
  waitsForApproval: () => waitsForApproval,
422
431
  walkPlan: () => walkPlan,
423
432
  wellKnownBinDirs: () => wellKnownBinDirs,
@@ -880,25 +889,66 @@ function addUsage(totals, record) {
880
889
  }
881
890
  if (record.reportedCost) {
882
891
  const { amount, currency } = record.reportedCost;
883
- next.reportedCost = { ...totals.reportedCost, [currency]: (totals.reportedCost?.[currency] ?? 0) + amount };
892
+ const provenance = record.reportedCost.kind || record.sessionBilling ? {
893
+ kind: record.reportedCost.kind ?? "unknown",
894
+ billing: record.sessionBilling?.type ?? "unknown",
895
+ ...record.reportedCost.basis ? { basis: record.reportedCost.basis } : {}
896
+ } : void 0;
897
+ addCost(next, currency, amount, provenance);
884
898
  }
885
899
  return next;
886
900
  }
901
+ function addCost(totals, currency, amount, provenance) {
902
+ const hadCost = totals.reportedCost?.[currency] !== void 0;
903
+ const previous = totals.costProvenance?.[currency];
904
+ totals.reportedCost = { ...totals.reportedCost, [currency]: (totals.reportedCost?.[currency] ?? 0) + amount };
905
+ if (!previous && !provenance) return;
906
+ const unknown = { kind: "unknown", billing: "unknown" };
907
+ const a = previous ?? unknown;
908
+ const b = provenance ?? unknown;
909
+ const merged = !hadCost ? b : {
910
+ kind: a.kind === "estimate" || b.kind === "estimate" ? "estimate" : a.kind === "billed" && b.kind === "billed" ? "billed" : "unknown",
911
+ billing: a.billing === b.billing ? a.billing : "mixed",
912
+ ...a.basis && a.basis === b.basis ? { basis: a.basis } : {}
913
+ };
914
+ totals.costProvenance = { ...totals.costProvenance, [currency]: merged };
915
+ }
887
916
  function addPlannerUsage(usage, record) {
888
- const next = { ...usage, totals: addUsage(usage.totals, record), byModel: addModelUsage(usage.byModel ?? [], record) };
917
+ if (record.compacted) {
918
+ if (record.subagentId || record.aggregate) return usage;
919
+ const next2 = { ...usage };
920
+ if (record.source !== void 0) next2.source = record.source;
921
+ if (record.model !== void 0) {
922
+ if (record.source !== usage.source || record.model !== usage.model) delete next2.contextWindow;
923
+ next2.model = record.model;
924
+ }
925
+ if (record.contextTokens !== void 0) next2.lastPromptTokens = record.contextTokens;
926
+ else delete next2.lastPromptTokens;
927
+ if (record.contextWindow !== void 0) next2.contextWindow = record.contextWindow > 0 ? record.contextWindow : void 0;
928
+ return next2;
929
+ }
930
+ if (record.sessionBilling && !record.reportedCost && record.inputTokens === void 0 && record.outputTokens === void 0 && record.cachedInputTokens === void 0 && record.contextWindow === void 0) {
931
+ return record.subagentId ? usage : { ...usage, sessionBilling: record.sessionBilling, source: record.source };
932
+ }
933
+ record = { ...record, sessionBilling: record.sessionBilling ?? (usage.source === void 0 || record.source === usage.source ? usage.sessionBilling : void 0) };
934
+ const next = { ...usage, ...!record.subagentId && record.sessionBilling ? { sessionBilling: record.sessionBilling } : {}, totals: addUsage(usage.totals, record), byModel: addModelUsage(usage.byModel ?? [], record) };
889
935
  if (record.subagentId) {
890
936
  next.bySubagent = { ...usage.bySubagent, [record.subagentId]: addUsage(usage.bySubagent?.[record.subagentId] ?? {}, record) };
891
937
  return next;
892
938
  }
893
- if (!record.aggregate) {
894
- if (record.source !== usage.source || record.model !== usage.model) {
895
- delete next.lastPromptTokens;
896
- delete next.contextWindow;
897
- }
898
- next.source = record.source;
899
- next.model = record.model;
939
+ if (record.aggregate) {
940
+ if (record.source !== usage.source && !record.sessionBilling) delete next.sessionBilling;
941
+ return next;
900
942
  }
901
- if (record.inputTokens !== void 0) next.lastPromptTokens = record.inputTokens;
943
+ if (record.source !== usage.source || record.model !== usage.model) {
944
+ delete next.lastPromptTokens;
945
+ delete next.contextWindow;
946
+ }
947
+ if (record.source !== usage.source && !record.sessionBilling) delete next.sessionBilling;
948
+ next.source = record.source;
949
+ next.model = record.model;
950
+ const used = record.contextTokens ?? record.inputTokens;
951
+ if (used !== void 0) next.lastPromptTokens = used;
902
952
  if (record.contextWindow !== void 0) next.contextWindow = record.contextWindow > 0 ? record.contextWindow : void 0;
903
953
  return next;
904
954
  }
@@ -909,6 +959,7 @@ function usageLine(usage) {
909
959
  const contextFill = plannerContextFill(usage);
910
960
  return {
911
961
  totals: usage.totals,
962
+ ...usage.sessionBilling ? { sessionBilling: usage.sessionBilling } : {},
912
963
  ...usage.byModel ? { byModel: usage.byModel } : {},
913
964
  ...usage.model ? { model: usage.model } : {},
914
965
  ...usage.bySubagent ? { bySubagent: usage.bySubagent } : {},
@@ -916,8 +967,8 @@ function usageLine(usage) {
916
967
  };
917
968
  }
918
969
  function plannerContextFill(usage) {
919
- if (usage.lastPromptTokens === void 0 || usage.contextWindow === void 0 || usage.contextWindow <= 0) return void 0;
920
- return { usedTokens: usage.lastPromptTokens, windowTokens: usage.contextWindow };
970
+ if (usage.lastPromptTokens === void 0) return void 0;
971
+ return { usedTokens: usage.lastPromptTokens, ...usage.contextWindow !== void 0 && usage.contextWindow > 0 ? { windowTokens: usage.contextWindow } : {} };
921
972
  }
922
973
  function addModelUsage(models, record) {
923
974
  const index = models.findIndex((m) => m.source === record.source && m.model === record.model);
@@ -929,13 +980,14 @@ function startAttemptUsage(attempt, runner, startedAt) {
929
980
  }
930
981
  function addAttemptUsage(usage, record, now) {
931
982
  const folded = addPlannerUsage(usage, record);
932
- return { ...usage, ...folded, lastPromptTokens: folded.lastPromptTokens, contextWindow: folded.contextWindow, wallTimeMs: Math.max(0, Date.parse(now) - Date.parse(usage.startedAt)) };
983
+ return { ...usage, ...folded, lastPromptTokens: folded.lastPromptTokens, contextWindow: folded.contextWindow, wallTimeMs: Math.max(0, Date.parse(usage.endedAt ?? now) - Date.parse(usage.startedAt)) };
933
984
  }
934
985
  function endAttemptUsage(usage, now, runnerSessionCost, sessionId) {
986
+ const endedAt = usage.endedAt ?? now;
935
987
  return {
936
988
  ...usage,
937
- endedAt: now,
938
- wallTimeMs: Math.max(0, Date.parse(now) - Date.parse(usage.startedAt)),
989
+ endedAt,
990
+ wallTimeMs: Math.max(0, Date.parse(endedAt) - Date.parse(usage.startedAt)),
939
991
  ...sessionId ? { sessionId } : {},
940
992
  ...runnerSessionCost ? { runnerSessionCost } : {}
941
993
  };
@@ -948,8 +1000,8 @@ function resumedSessionWindow(attempts, runner, attempt, sessionId) {
948
1000
  return last ? { source: last.source, model: last.model, contextWindow: last.contextWindow } : void 0;
949
1001
  }
950
1002
  function addUsageTotals(totals, addition) {
951
- let next = addUsage(totals, { source: "", inputTokens: addition.inputTokens, outputTokens: addition.outputTokens, cachedInputTokens: addition.cachedInputTokens });
952
- for (const [currency, amount] of Object.entries(addition.reportedCost ?? {})) next = addUsage(next, { source: "", reportedCost: { currency, amount } });
1003
+ const next = addUsage(totals, { source: "", inputTokens: addition.inputTokens, outputTokens: addition.outputTokens, cachedInputTokens: addition.cachedInputTokens });
1004
+ for (const [currency, amount] of Object.entries(addition.reportedCost ?? {})) addCost(next, currency, amount, addition.costProvenance?.[currency]);
953
1005
  return next;
954
1006
  }
955
1007
  function planUsage(tasks) {
@@ -984,6 +1036,71 @@ function taskUsageSummary(task) {
984
1036
  ...contextFill ? { contextFill } : {}
985
1037
  };
986
1038
  }
1039
+ function formatTokenCount(n) {
1040
+ if (n < 1e3) return String(n);
1041
+ if (n < 1e4) return `${(n / 1e3).toFixed(1).replace(/\.0$/, "")}k`;
1042
+ if (n < 999500) return `${Math.round(n / 1e3)}k`;
1043
+ return `${(n / 1e6).toFixed(1).replace(/\.0$/, "")}M`;
1044
+ }
1045
+ function formatCost(currency, amount) {
1046
+ const value = amount >= 1 ? amount.toFixed(2) : (([whole, frac = ""]) => `${whole}.${frac.padEnd(2, "0")}`)(amount.toFixed(4).replace(/0+$/, "").split("."));
1047
+ return currency.toLowerCase() === "usd" ? `$${value}` : `${value} ${currency.toUpperCase()}`;
1048
+ }
1049
+ function contextMeterText(fill) {
1050
+ if (!fill) return null;
1051
+ const used = `ctx ${formatTokenCount(fill.usedTokens)}`;
1052
+ if (!fill.windowTokens) return used;
1053
+ const share = fill.usedTokens / fill.windowTokens * 100;
1054
+ return `${used} (${share < 1 ? "<1" : Math.round(share)}%)`;
1055
+ }
1056
+ function contextMeterTitle(fill) {
1057
+ const used = fill.usedTokens.toLocaleString("en-US");
1058
+ return fill.windowTokens ? `${used} of ${fill.windowTokens.toLocaleString("en-US")} tokens` : `${used} tokens`;
1059
+ }
1060
+ function costParts(totals) {
1061
+ const rank = (currency) => currency.toLowerCase() === "usd" ? "" : currency.toUpperCase();
1062
+ return Object.entries(totals.reportedCost ?? {}).sort(([a], [b]) => rank(a).localeCompare(rank(b))).map(([currency, amount]) => {
1063
+ const provenance = totals.costProvenance?.[currency];
1064
+ const equivalent = provenance?.billing === "subscription";
1065
+ return `${provenance?.kind === "estimate" || equivalent ? "\u2248" : ""}${formatCost(currency, amount)}${equivalent ? " API-equivalent" : ""}`;
1066
+ });
1067
+ }
1068
+ function costText(totals) {
1069
+ const parts = costParts(totals);
1070
+ return parts.length > 0 ? parts.join(" \xB7 ") : null;
1071
+ }
1072
+ function usageText(usage) {
1073
+ const parts = [contextMeterText(usage.contextFill), costText(usage.totals)].filter((p) => p !== null);
1074
+ return parts.length > 0 ? parts.join(" \xB7 ") : null;
1075
+ }
1076
+ function modelCostTitle(byModel) {
1077
+ const lines2 = byModel.flatMap((m) => {
1078
+ const cost = costParts(m.totals);
1079
+ const descriptions = [...new Set(Object.values(m.totals.costProvenance ?? {}).map((p) => {
1080
+ const source = m.source === "claude-code" ? "Claude Code" : m.source === "opencode" ? "OpenCode" : m.source === "openrouter" ? "OpenRouter" : m.source;
1081
+ const basis = p.basis === "list" ? " at list price" : p.basis === "managed" ? " at managed price" : "";
1082
+ const kind = p.kind === "estimate" ? `${source}'s estimate${basis}` : p.kind === "billed" ? `Amount billed by ${source}` : "Reported cost";
1083
+ return `${kind}${p.billing === "subscription" ? "; not billed on a subscription" : ""}`;
1084
+ }))];
1085
+ return cost.length > 0 ? [`${m.source} / ${m.model ?? "unattributed"}: ${cost.join(" \xB7 ")}${descriptions.length ? ` \u2014 ${descriptions.join("; ")}` : ""}`] : [];
1086
+ });
1087
+ return lines2.length > 0 ? lines2.join("\n") : void 0;
1088
+ }
1089
+ function subagentCostTitle(bySubagent) {
1090
+ return Object.entries(bySubagent).map(([id, totals]) => `${id}: ${costParts(totals).join(" \xB7 ") || "reported no cost"}`).join("\n");
1091
+ }
1092
+ function runnersOf(tasks) {
1093
+ return tasks.flatMap((task) => [...task.attemptUsage ?? [], ...runnersOf(task.subtasks ?? [])]);
1094
+ }
1095
+ function tasksTotalText(tasks) {
1096
+ const attempts = runnersOf(tasks);
1097
+ if (attempts.length === 0) return null;
1098
+ const costed = (a) => Object.keys(a.totals.reportedCost ?? {}).length > 0;
1099
+ const costless = [...new Set(attempts.filter((a) => a.endedAt !== void 0 && isMeasured(a.totals)).map((a) => a.runner))].filter((runner) => !attempts.some((a) => a.runner === runner && costed(a)));
1100
+ const note = costless.length === 0 ? [] : [`${costless.length === 1 ? costless[0] : `${costless.slice(0, -1).join(", ")} and ${costless.at(-1)}`} ${costless.length === 1 ? "reports" : "report"} no cost`];
1101
+ const parts = [...costParts(planUsage(tasks).totals), ...note];
1102
+ return parts.length > 0 ? `Tasks total: ${parts.join(" \xB7 ")}` : null;
1103
+ }
987
1104
 
988
1105
  // src/models/TaskLog.ts
989
1106
  var HEAD_LINES = 60;
@@ -7523,7 +7640,7 @@ var RunnerApprovals = class {
7523
7640
  },
7524
7641
  stop: (sessionId) => {
7525
7642
  this.denySession(sessionId);
7526
- runner.stop(sessionId);
7643
+ return runner.stop(sessionId);
7527
7644
  },
7528
7645
  stopAll: () => {
7529
7646
  this.approvals.clear(isRunnerApproval);
@@ -11628,6 +11745,14 @@ function canUseToolAnswer({ input, suggestions }, decision) {
11628
11745
  }
11629
11746
  var MODEL_COMMAND = "model";
11630
11747
  var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
11748
+ var VARIANT_SUFFIX = /^(.*?)(\[[^\]]*\])?$/;
11749
+ function sameModel(a, b) {
11750
+ if (a === void 0 || b === void 0) return false;
11751
+ if (a === b) return true;
11752
+ const [, aBase, aVariant] = VARIANT_SUFFIX.exec(a) ?? [];
11753
+ const [, bBase, bVariant] = VARIANT_SUFFIX.exec(b) ?? [];
11754
+ return aBase === bBase && (aVariant === void 0 || bVariant === void 0 || aVariant === bVariant);
11755
+ }
11631
11756
  function usageRecord(usage, model, subagentId) {
11632
11757
  const record = {
11633
11758
  source: "claude-code",
@@ -11636,6 +11761,7 @@ function usageRecord(usage, model, subagentId) {
11636
11761
  if (model) record.model = model;
11637
11762
  if (usage.output_tokens !== void 0) record.outputTokens = usage.output_tokens;
11638
11763
  if (subagentId) record.subagentId = subagentId;
11764
+ else if (record.inputTokens !== void 0) record.contextTokens = record.inputTokens + (record.outputTokens ?? 0);
11639
11765
  return record;
11640
11766
  }
11641
11767
  function subagentFinalCall(result) {
@@ -11685,16 +11811,27 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
11685
11811
  replyRuns = new ReplyRuns();
11686
11812
  /** The model answering the planner's current message, and the message's id, as its `message_start` named them. */
11687
11813
  plannerModel;
11814
+ /**
11815
+ * The id the turn runs as, as `init` names it. A `[1m]` variant is the
11816
+ * selected id, while a message names the API's canonical id without it.
11817
+ */
11818
+ sessionModel;
11819
+ billing;
11688
11820
  streamingMessage;
11689
11821
  /** The message the last `assistant` line was part of, and how many of its blocks the lines have carried. */
11690
11822
  lineMessage = null;
11691
11823
  /**
11692
- * The session's `total_cost_usd` as last counted. A resume starts from what
11693
- * was counted of that session before, and is undefined without it: the CLI
11694
- * restores the session's running total, which holds turns counted earlier.
11824
+ * What has been counted of the session's running cost: a resume starts from
11825
+ * it, and it is undefined without a kept count — the next result then only
11826
+ * sets the baseline, since the CLI restores spend from turns before it. It
11827
+ * grows by what each result is counted for, never by a total that includes
11828
+ * cost the runner marked unknown, so that spend can be counted when its basis
11829
+ * is reported; and it never lowers, since counted dollars are not un-spent.
11695
11830
  */
11696
11831
  reportedCostUsd = 0;
11697
11832
  reportedModelCostUsd = /* @__PURE__ */ new Map();
11833
+ /** What this turn's streamed calls already reported, so its result only adds what did not stream. */
11834
+ turnCallUsage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
11698
11835
  /**
11699
11836
  * Subagents started and not yet finished, keyed by the `Agent` call's id.
11700
11837
  * Kept across turns: a backgrounded one reports after its turn has ended.
@@ -11781,6 +11918,14 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
11781
11918
  if (!sessionId || this.reportedCostUsd === void 0) return null;
11782
11919
  return { sessionId, usd: this.reportedCostUsd, modelUsd: Object.fromEntries(this.reportedModelCostUsd) };
11783
11920
  }
11921
+ /**
11922
+ * A cleared conversation's totals start again from zero, so what follows is
11923
+ * all new spend, even for a resume that had no count kept.
11924
+ */
11925
+ restartCount() {
11926
+ this.reportedCostUsd = 0;
11927
+ this.reportedModelCostUsd = /* @__PURE__ */ new Map();
11928
+ }
11784
11929
  /** A count kept for another session says nothing of this one's. */
11785
11930
  countFrom(sessionId, counted) {
11786
11931
  const known = counted?.sessionId === sessionId ? counted : void 0;
@@ -11854,11 +11999,10 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
11854
11999
  * the alias resolves to now; that id is pinned before the first turn.
11855
12000
  */
11856
12001
  async handshake(opts) {
11857
- if (opts.kind !== "task") return;
11858
12002
  const initialized = (await this.control({ subtype: "initialize" }, MODEL_PIN_TIMEOUT_MS))?.response;
11859
12003
  this.listedCommands = initialized?.commands ?? [];
11860
12004
  this.listedModels = initialized?.models ?? [];
11861
- if (opts.flags.permissionMode !== "plan" || !opts.model) return;
12005
+ if (opts.kind !== "task" || opts.flags.permissionMode !== "plan" || !opts.model) return;
11862
12006
  const resolved = initialized?.models?.find((m) => m.value === opts.model)?.resolvedModel;
11863
12007
  if (resolved && resolved !== opts.model) await this.control({ subtype: "set_model", model: resolved }, MODEL_PIN_TIMEOUT_MS);
11864
12008
  }
@@ -11899,6 +12043,15 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
11899
12043
  this.interruptRequested = true;
11900
12044
  return (await this.control({ subtype: "interrupt" }, timeoutMs))?.subtype === "success";
11901
12045
  }
12046
+ /**
12047
+ * On stdin's end the CLI exits by itself once its turn has closed, and only
12048
+ * an exit of its own saves the session's running totals for a `--resume` to
12049
+ * restore (checked on 2.1.303: about a second later, code 0, or 1 after an
12050
+ * interrupted turn).
12051
+ */
12052
+ endInput() {
12053
+ this.process?.stdin?.end();
12054
+ }
11902
12055
  /**
11903
12056
  * A `user` line written mid-turn is Claude Code's own queue (ADR-0023): the
11904
12057
  * CLI attaches it to the next tool result, or runs it as a turn of its own
@@ -11986,6 +12139,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
11986
12139
  this.resultHeld = false;
11987
12140
  this.turnClosed = false;
11988
12141
  this.clearFollowOnTimer();
12142
+ this.plannerModel = void 0;
11989
12143
  }
11990
12144
  /**
11991
12145
  * The echo of a steered message: the model has it. After a closed turn it
@@ -12040,6 +12194,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
12040
12194
  this.permissions.withdraw(msg.request_id, emit);
12041
12195
  return;
12042
12196
  case "control_response": {
12197
+ const account = msg.response?.response?.account;
12198
+ if (account) this.reportBilling(account.apiKeySource, account.subscriptionType, emit);
12043
12199
  const requestId = msg.response?.request_id;
12044
12200
  if (requestId) this.pendingControl.get(requestId)?.(msg.response ?? null);
12045
12201
  return;
@@ -12083,12 +12239,24 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
12083
12239
  emit({ type: "tool_result", id, name: "", output: diff || output, success: block.is_error !== true, subagentId });
12084
12240
  }
12085
12241
  return;
12242
+ // `/clear` (and `/reset`, `/new`) emit this top-level line before their
12243
+ // own result, and a task's chat can send them. The CLI's totals restart.
12244
+ case "conversation_reset": {
12245
+ if (subagentId) return;
12246
+ this.restartCount();
12247
+ const resetModel = this.sessionModel ?? (this.plannerModel !== "<synthetic>" ? this.plannerModel : void 0);
12248
+ emit({ type: "usage", record: { source: "claude-code", ...resetModel ? { model: resetModel } : {}, compacted: true } });
12249
+ return;
12250
+ }
12086
12251
  case "system":
12252
+ if (subagentId && (msg.subtype === "init" || msg.subtype === "compact_boundary")) return;
12087
12253
  if (msg.subtype === "init") {
12088
12254
  this.clearFollowOnTimer();
12089
12255
  if (this.refuseOtherMode(msg, emit)) return;
12256
+ if (msg.model && msg.model !== "<synthetic>") this.sessionModel = msg.model;
12090
12257
  this.readLoaded(msg, emit);
12091
12258
  this.followModel(msg.model, emit);
12259
+ this.reportBilling(msg.apiKeySource, void 0, emit);
12092
12260
  }
12093
12261
  if (msg.subtype === "commands_changed" && Array.isArray(msg.commands)) this.listedCommands = msg.commands;
12094
12262
  if (msg.subtype === "compact_boundary") {
@@ -12099,6 +12267,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
12099
12267
  ...typeof meta?.pre_tokens === "number" ? { preTokens: meta.pre_tokens } : {},
12100
12268
  ...typeof meta?.post_tokens === "number" ? { postTokens: meta.post_tokens } : {}
12101
12269
  });
12270
+ const model = this.sessionModel ?? (this.plannerModel !== "<synthetic>" ? this.plannerModel : void 0);
12271
+ emit({ type: "usage", record: { source: "claude-code", ...model ? { model } : {}, compacted: true, ...typeof meta?.post_tokens === "number" ? { contextTokens: meta.post_tokens } : {} } });
12102
12272
  }
12103
12273
  if (msg.subtype === "background_tasks_changed" && Array.isArray(msg.tasks)) {
12104
12274
  this.backgroundTaskCount = msg.tasks.length;
@@ -12110,6 +12280,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
12110
12280
  }
12111
12281
  return;
12112
12282
  case "result":
12283
+ if (subagentId) return;
12113
12284
  this.reportSessionCost(msg, emit);
12114
12285
  for (const command of this.modelCommands) command.turnsLeft = Math.max(0, command.turnsLeft - 1);
12115
12286
  if (this.interruptRequested && (msg.is_error || msg.subtype !== "success")) {
@@ -12274,16 +12445,69 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
12274
12445
  */
12275
12446
  handleStreamEvent(event, emit) {
12276
12447
  if (event.type === "message_start") {
12277
- this.plannerModel = event.message?.model;
12448
+ this.plannerModel = this.modelId(event.message?.model);
12278
12449
  this.streamingMessage = event.message?.id;
12279
12450
  this.followModel(this.plannerModel, emit);
12280
- } else if (event.type === "message_delta" && event.usage) emit({ type: "usage", record: usageRecord(event.usage, this.plannerModel) });
12281
- else if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta.text) {
12451
+ } else if (event.type === "message_delta" && event.usage) {
12452
+ const record = usageRecord(event.usage, this.plannerModel);
12453
+ this.turnCallUsage.inputTokens += record.inputTokens ?? 0;
12454
+ this.turnCallUsage.cachedInputTokens += record.cachedInputTokens ?? 0;
12455
+ this.turnCallUsage.outputTokens += record.outputTokens ?? 0;
12456
+ emit({ type: "usage", record });
12457
+ } else if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta.text) {
12282
12458
  this.replyRuns.delta(`${this.streamingMessage ?? ""}:${event.index ?? 0}`, event.delta.text, emit);
12283
12459
  } else if (event.type === "content_block_delta" && event.delta?.type === "thinking_delta" && event.delta.thinking) {
12284
12460
  emit({ type: "thinking_delta", text: event.delta.thinking });
12285
12461
  }
12286
12462
  }
12463
+ /**
12464
+ * The id a reported model runs as. `init` names the selected value — a
12465
+ * `[1m]` variant included — while a message names the API's canonical id
12466
+ * without it. Keeping the variant is what lets the meter find the window
12467
+ * `modelUsage` keys it by, and keeps that window off the base model.
12468
+ */
12469
+ modelId(reported) {
12470
+ if (!reported) return void 0;
12471
+ if (!sameModel(this.sessionModel, reported)) return reported;
12472
+ return this.sessionModel?.includes("[") ? this.sessionModel : reported;
12473
+ }
12474
+ reportBilling(apiKeySource, plan, emit) {
12475
+ const type = apiKeySource ? apiKeySource === "none" ? "subscription" : "api-key" : plan ? "subscription" : void 0;
12476
+ if (!type) return;
12477
+ const knownPlan = plan ?? (this.billing?.type === type ? this.billing.plan : void 0);
12478
+ this.billing = { type, ...knownPlan ? { plan: knownPlan } : {} };
12479
+ emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling: this.billing } });
12480
+ }
12481
+ /**
12482
+ * A turn's `result` states its own token sum. Every call that streamed its
12483
+ * `message_delta` usage has already reported, so only the remainder is added:
12484
+ * all of it when nothing streamed (a recording without partial messages), or
12485
+ * a lost call's share. It is a sum across calls, marked `aggregate` so it
12486
+ * never moves the meter — only a call says what the context was left at.
12487
+ */
12488
+ reportUnstreamedUsage(msg, emit) {
12489
+ const { inputTokens: streamedInput, cachedInputTokens: streamedCached, outputTokens: streamedOutput } = this.turnCallUsage;
12490
+ this.turnCallUsage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
12491
+ if (!msg.usage) return;
12492
+ const models = Object.keys(msg.modelUsage ?? {});
12493
+ const model = this.plannerModel ?? this.mainModel ?? (models.length === 1 ? models[0] : void 0);
12494
+ const total = usageRecord(msg.usage, model);
12495
+ const inputTokens = (total.inputTokens ?? 0) - streamedInput;
12496
+ const cachedInputTokens = (total.cachedInputTokens ?? 0) - streamedCached;
12497
+ const outputTokens = (total.outputTokens ?? 0) - streamedOutput;
12498
+ if (inputTokens <= 0 && outputTokens <= 0) return;
12499
+ emit({
12500
+ type: "usage",
12501
+ record: {
12502
+ source: "claude-code",
12503
+ ...model ? { model } : {},
12504
+ aggregate: true,
12505
+ ...inputTokens > 0 ? { inputTokens } : {},
12506
+ ...inputTokens > 0 && cachedInputTokens > 0 ? { cachedInputTokens } : {},
12507
+ ...outputTokens > 0 ? { outputTokens } : {}
12508
+ }
12509
+ });
12510
+ }
12287
12511
  /**
12288
12512
  * What only the result line knows: the cost, which covers every call the
12289
12513
  * session made — subagents included, since none of their lines carries one —
@@ -12292,24 +12516,54 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
12292
12516
  * the session before; with no count kept, the first turn only sets the
12293
12517
  * baseline: its total includes turns counted before, and a turn's own share
12294
12518
  * cannot be told apart from them.
12519
+ *
12520
+ * The running total never lowers: money already reported and counted is not
12521
+ * un-spent because a later or resumed process remembers less of it, and
12522
+ * counting its return would count the same dollars twice. A zero is not a
12523
+ * measurement at all — a crash can end with one — and only `/clear` restarts
12524
+ * the count, through {@link restartCount}. Per-model baselines never lower
12525
+ * either, for the same reason.
12526
+ *
12527
+ * A report without a model breakdown is counted as a session total; one with
12528
+ * a breakdown is counted per model. A breakdown that would count more than
12529
+ * the session total's own growth (the count before it had no model split, or
12530
+ * a lossy report) is counted as that growth alone, so the two shapes
12531
+ * together still add each dollar once.
12295
12532
  */
12296
12533
  reportSessionCost(msg, emit) {
12534
+ this.reportUnstreamedUsage(msg, emit);
12297
12535
  const total = msg.total_cost_usd;
12298
12536
  const canReport = this.reportedCostUsd !== void 0;
12299
- const modelCosts = Object.entries(msg.modelUsage ?? {}).filter((entry) => typeof entry[1].costUSD === "number");
12537
+ const modelCosts = Object.entries(msg.modelUsage ?? {}).filter((entry) => typeof entry[1].costUSD === "number" && entry[1].costUSD > 0 && entry[1].costBasis !== "unknown");
12538
+ const growth = canReport && typeof total === "number" && total > 0 ? total - this.reportedCostUsd : void 0;
12539
+ let emitted = 0;
12540
+ const emitCost = (record) => {
12541
+ if (!(record.amount > 0)) return;
12542
+ emitted += record.amount;
12543
+ emit({ type: "usage", record: { source: "claude-code", ...record.model ? { model: record.model } : {}, aggregate: true, ...this.billing ? { sessionBilling: this.billing } : {}, reportedCost: { amount: record.amount, currency: "USD", kind: "estimate", ...record.basis ? { basis: record.basis } : {} } } });
12544
+ };
12300
12545
  if (modelCosts.length > 0) {
12301
- for (const [model, usage] of modelCosts) {
12302
- const previous = this.reportedModelCostUsd.get(model) ?? 0;
12303
- const amount = usage.costUSD - previous;
12304
- if (canReport && amount >= 0) emit({ type: "usage", record: { source: "claude-code", model, aggregate: true, reportedCost: { amount, currency: "USD" } } });
12305
- this.reportedModelCostUsd.set(model, usage.costUSD);
12546
+ const modelGrowth = modelCosts.reduce((sum, [model2, usage]) => sum + Math.max(0, usage.costUSD - (this.reportedModelCostUsd.get(model2) ?? 0)), 0);
12547
+ if (canReport) {
12548
+ if (growth !== void 0 && modelGrowth > growth + 1e-9) {
12549
+ emitCost({ amount: growth });
12550
+ } else {
12551
+ for (const [model2, usage] of modelCosts) emitCost({ model: model2, amount: usage.costUSD - (this.reportedModelCostUsd.get(model2) ?? 0), ...usage.costBasis ? { basis: usage.costBasis } : {} });
12552
+ }
12306
12553
  }
12307
- } else if (typeof total === "number" && canReport && total >= this.reportedCostUsd) {
12308
- emit({ type: "usage", record: { source: "claude-code", aggregate: true, reportedCost: { amount: total - this.reportedCostUsd, currency: "USD" } } });
12554
+ for (const [model2, usage] of modelCosts) this.reportedModelCostUsd.set(model2, Math.max(this.reportedModelCostUsd.get(model2) ?? 0, usage.costUSD));
12555
+ } else if (!Object.values(msg.modelUsage ?? {}).some((usage) => usage.costBasis === "unknown") && typeof total === "number" && total > 0 && canReport && total >= this.reportedCostUsd) {
12556
+ emitCost({ amount: total - this.reportedCostUsd });
12557
+ }
12558
+ if (!canReport) {
12559
+ if (typeof total === "number" && total > 0) this.reportedCostUsd = total;
12560
+ else if (modelCosts.length > 0) this.reportedCostUsd = modelCosts.reduce((sum, [, usage]) => sum + usage.costUSD, 0);
12561
+ } else {
12562
+ this.reportedCostUsd = (this.reportedCostUsd ?? 0) + emitted;
12309
12563
  }
12310
- if (typeof total === "number") this.reportedCostUsd = total;
12311
- const contextWindow = this.plannerModel ? msg.modelUsage?.[this.plannerModel]?.contextWindow : void 0;
12312
- if (contextWindow !== void 0) emit({ type: "usage", record: { source: "claude-code", model: this.plannerModel, contextWindow } });
12564
+ const model = this.plannerModel && this.plannerModel !== "<synthetic>" ? this.plannerModel : this.sessionModel;
12565
+ const contextWindow = model ? msg.modelUsage?.[model]?.contextWindow : void 0;
12566
+ if (typeof contextWindow === "number" && contextWindow > 0) emit({ type: "usage", record: { source: "claude-code", model, contextWindow } });
12313
12567
  }
12314
12568
  };
12315
12569
 
@@ -12944,9 +13198,13 @@ function mergeCodexRateLimits(known, params) {
12944
13198
  const id = snapshot.limitId ?? "";
12945
13199
  const before = known.get(id);
12946
13200
  const next = new Map(known);
12947
- next.set(id, { limitId: id, primary: snapshot.primary ?? before?.primary, secondary: snapshot.secondary ?? before?.secondary });
13201
+ next.set(id, { limitId: id, planType: snapshot.planType ?? before?.planType, primary: snapshot.primary ?? before?.primary, secondary: snapshot.secondary ?? before?.secondary });
12948
13202
  return next;
12949
13203
  }
13204
+ function codexSessionBilling(limits) {
13205
+ const plan = [...limits.values()].find((snapshot) => snapshot.planType && snapshot.planType !== "unknown")?.planType;
13206
+ return plan ? { type: "subscription", plan } : void 0;
13207
+ }
12950
13208
  function isCodexUsageLimit(error) {
12951
13209
  const info = error?.codexErrorInfo;
12952
13210
  if (info === void 0 || info === null || info === "other") return void 0;
@@ -13505,8 +13763,21 @@ ${this.exitMessage()}`);
13505
13763
  case "thread/tokenUsage/updated":
13506
13764
  this.emitUsage(msg.params, emit);
13507
13765
  return;
13766
+ case "account/updated": {
13767
+ const account = msg.params;
13768
+ if (account?.authMode === "apikey") emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling: { type: "api-key" } } });
13769
+ else if (account?.authMode === "chatgpt" || account?.authMode === "chatgptAuthTokens") {
13770
+ const plan = account.planType;
13771
+ emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling: { type: "subscription", ...plan && plan !== "unknown" ? { plan } : {} } } });
13772
+ }
13773
+ return;
13774
+ }
13508
13775
  case "account/rateLimits/updated":
13509
13776
  this.rateLimits = mergeCodexRateLimits(this.rateLimits, msg.params);
13777
+ {
13778
+ const sessionBilling = codexSessionBilling(this.rateLimits);
13779
+ if (sessionBilling) emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling } });
13780
+ }
13510
13781
  return;
13511
13782
  case "turn/started": {
13512
13783
  if (this.subagentOf(msg.params?.threadId)) return;
@@ -13635,6 +13906,13 @@ ${this.exitMessage()}`);
13635
13906
  * `last.outputTokens` already includes `reasoningOutputTokens` — the thread
13636
13907
  * total is `inputTokens + outputTokens`, not a sum of three — so reasoning is
13637
13908
  * not added a second time. Codex reports no price.
13909
+ *
13910
+ * The main thread's context is what Codex itself shows as in its window:
13911
+ * `TokenUsage::tokens_in_context_window`, which is `total_tokens` over
13912
+ * `last` as codex-rs defines it. `total_tokens` is input plus output, and
13913
+ * reasoning is part of output, so this is not `input + output − reasoning`
13914
+ * (checked against codex-rs and codex-cli 0.163.0's token_count events,
13915
+ * where `total_tokens` is `input_tokens + output_tokens`).
13638
13916
  */
13639
13917
  emitUsage(params, emit) {
13640
13918
  const last = params?.tokenUsage?.last;
@@ -13662,6 +13940,8 @@ ${this.exitMessage()}`);
13662
13940
  } else {
13663
13941
  const window = params?.tokenUsage?.modelContextWindow;
13664
13942
  if (typeof window === "number" && window > 0) record.contextWindow = window;
13943
+ const inContext = last.totalTokens !== void 0 && last.totalTokens > 0 ? last.totalTokens : last.inputTokens !== void 0 ? last.inputTokens + (last.outputTokens ?? 0) : void 0;
13944
+ if (inContext !== void 0) record.contextTokens = Math.max(0, inContext);
13665
13945
  }
13666
13946
  emit({ type: "usage", record });
13667
13947
  }
@@ -13823,7 +14103,9 @@ ${this.exitMessage()}`);
13823
14103
  }
13824
14104
  switch (item.type) {
13825
14105
  case "contextCompaction":
13826
- if (!subagentId) emit({ type: "compaction", trigger: this.command === "compact" ? "manual" : "auto" });
14106
+ if (subagentId) return;
14107
+ emit({ type: "compaction", trigger: this.command === "compact" ? "manual" : "auto" });
14108
+ emit({ type: "usage", record: { source: this.agentId, compacted: true } });
13827
14109
  return;
13828
14110
  // Codex announces the item started and completed; whichever lands first delivers.
13829
14111
  case "userMessage":
@@ -14119,8 +14401,9 @@ function usageRecord2(tokens, call) {
14119
14401
  if ((prompt.inputTokens ?? 0) + outputTokens === 0) return null;
14120
14402
  const record = { source: "opencode", ...prompt, outputTokens };
14121
14403
  if (call.model) record.model = call.model;
14122
- if (typeof call.cost === "number" && call.cost > 0) record.reportedCost = { amount: call.cost, currency: "USD" };
14404
+ if (typeof call.cost === "number" && call.cost > 0) record.reportedCost = { amount: call.cost, currency: "USD", kind: "estimate" };
14123
14405
  if (call.subagentId) record.subagentId = call.subagentId;
14406
+ else record.contextTokens = (prompt.inputTokens ?? 0) + outputTokens;
14124
14407
  return record;
14125
14408
  }
14126
14409
  function permissionReply(decision, key) {
@@ -14683,6 +14966,7 @@ var OpenCodeV2 = class {
14683
14966
  }
14684
14967
  if (frame.type === "session.compaction.ended") {
14685
14968
  onEvent({ type: "compaction", trigger: data.reason === "manual" ? "manual" : "auto" });
14969
+ onEvent({ type: "usage", record: { source: "opencode", compacted: true } });
14686
14970
  return;
14687
14971
  }
14688
14972
  if (frame.type === "session.execution.started") {
@@ -15548,6 +15832,7 @@ var OpenCodeAdapter = class {
15548
15832
  if (session && session !== this.sessionId && !turn.children.follows(session)) return;
15549
15833
  if (frame.type === "session.compacted" && session === this.sessionId) {
15550
15834
  onEvent({ type: "compaction", trigger: this.manualCompaction ? "manual" : "auto" });
15835
+ onEvent({ type: "usage", record: { source: "opencode", compacted: true } });
15551
15836
  return;
15552
15837
  }
15553
15838
  if (session === this.sessionId && this.taskTurn) this.followTaskTurn(frame, this.taskTurn, onEvent);
@@ -20570,6 +20855,7 @@ var PlannerConversation = class {
20570
20855
  const request = summaryRequest(this.currentPlanLines());
20571
20856
  const onProgress = (p) => {
20572
20857
  if (p.type === "liveness") this.host.onProgress(p);
20858
+ else if (p.type === "usage" && p.record) this.host.onProgress({ ...p, record: { ...p.record, aggregate: true } });
20573
20859
  };
20574
20860
  let turn;
20575
20861
  if (this.canContinueLive()) {
@@ -24260,6 +24546,9 @@ function nonPromptingFs(fs18) {
24260
24546
  }
24261
24547
  };
24262
24548
  }
24549
+ function withoutContext({ contextTokens: _context, ...record }) {
24550
+ return record;
24551
+ }
24263
24552
  async function runLoop(prompt, deps) {
24264
24553
  if (deps.signal?.aborted) return "[research agent aborted before starting]";
24265
24554
  const fs18 = nonPromptingFs(deps.fs);
@@ -24267,7 +24556,8 @@ async function runLoop(prompt, deps) {
24267
24556
  (delta) => deps.onProgress?.({ type: "thinking", text: delta, subagentId: deps.subagentId }),
24268
24557
  // The record says who made the call: unstamped, the ledger would read the
24269
24558
  // subagent's prompt as the planner's own and measure its context by it.
24270
- (record) => deps.onProgress?.({ type: "usage", record: deps.subagentId ? { ...record, subagentId: deps.subagentId } : record })
24559
+ // The chat states the context its call leaves; a subagent's is no meter's.
24560
+ (record) => deps.onProgress?.({ type: "usage", record: deps.subagentId ? { ...withoutContext(record), subagentId: deps.subagentId } : record })
24271
24561
  );
24272
24562
  let turn = await chat.sendMessage(prompt, deps.signal);
24273
24563
  for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
@@ -24725,9 +25015,11 @@ function plannerNotReadyFor(config) {
24725
25015
  var TERMINAL_UI_COMMANDS = {
24726
25016
  pickPlanner: "/planner",
24727
25017
  setKey: () => "/key",
24728
- setModel: "/model"
25018
+ // `/planner` walks harness, model and effort in one flow; there is no
25019
+ // separate `/model` (or `/planner-effort`) to point at.
25020
+ setModel: "/planner"
24729
25021
  };
24730
- var CHAT_COMMANDS = { ...TERMINAL_UI_COMMANDS, setModel: "/model set" };
25022
+ var CHAT_COMMANDS = { ...TERMINAL_UI_COMMANDS };
24731
25023
  var HEADLESS_CLI_COMMANDS = {
24732
25024
  pickPlanner: "ordewell planner <id>",
24733
25025
  setKey: (provider) => `ordewell key set ${provider} <key>`,
@@ -24741,7 +25033,7 @@ function harnessPlannerNames() {
24741
25033
  var PLANNER_ESCAPE_HINT = `or pick a coding agent with ${TERMINAL_UI_COMMANDS.pickPlanner}: it plans with no API key`;
24742
25034
  function describePlannerNotReady(reason, commands) {
24743
25035
  if (reason.code === "no_model") {
24744
- return `No planner model selected for ${reason.label}. Pick one with ${commands.setModel}, or switch planner with ${commands.pickPlanner}.`;
25036
+ return commands.setModel === commands.pickPlanner ? `No planner model selected for ${reason.label}. Pick one with ${commands.setModel}.` : `No planner model selected for ${reason.label}. Pick one with ${commands.setModel}, or switch planner with ${commands.pickPlanner}.`;
24745
25037
  }
24746
25038
  const lead = `No planner is ready. Pick one with ${commands.pickPlanner}: ${harnessPlannerNames()} plan with no API key.`;
24747
25039
  if (reason.code === "no_base_url") {
@@ -24775,7 +25067,8 @@ function usageRecordFrom(usage, model) {
24775
25067
  model,
24776
25068
  ...inputTokens !== void 0 ? { inputTokens } : {},
24777
25069
  ...reportedOutput.length > 0 ? { outputTokens: reportedOutput.reduce((a, b) => a + b, 0) } : {},
24778
- ...tokenCount(usage.cachedContentTokenCount) !== void 0 ? { cachedInputTokens: tokenCount(usage.cachedContentTokenCount) } : {}
25070
+ ...tokenCount(usage.cachedContentTokenCount) !== void 0 ? { cachedInputTokens: tokenCount(usage.cachedContentTokenCount) } : {},
25071
+ ...inputTokens !== void 0 ? { contextTokens: inputTokens + (answerTokens ?? 0) } : {}
24779
25072
  };
24780
25073
  const anyReported = record.inputTokens !== void 0 || record.outputTokens !== void 0 || record.cachedInputTokens !== void 0;
24781
25074
  return anyReported ? record : void 0;
@@ -25178,7 +25471,9 @@ var OpenAiResearchChat = class {
25178
25471
  /**
25179
25472
  * The one record for this API call, built only from what the provider
25180
25473
  * reported. OpenRouter's `cost` is in USD (its credits are dollar-pegged);
25181
- * nothing is estimated. A field the provider left out stays absent.
25474
+ * nothing is estimated. A field the provider left out stays absent. The
25475
+ * context the call leaves is its prompt and its completion: the API's own
25476
+ * `total_tokens`, and the reply the next call's prompt carries.
25182
25477
  */
25183
25478
  usageRecord(reported) {
25184
25479
  if (!reported) return void 0;
@@ -25197,9 +25492,10 @@ var OpenAiResearchChat = class {
25197
25492
  hasMeasure = true;
25198
25493
  }
25199
25494
  if (reported.cost !== void 0) {
25200
- record.reportedCost = { amount: reported.cost, currency: "USD" };
25495
+ record.reportedCost = { amount: reported.cost, currency: "USD", ...record.source === "openrouter" ? { kind: "billed" } : {} };
25201
25496
  hasMeasure = true;
25202
25497
  }
25498
+ if (record.inputTokens !== void 0) record.contextTokens = record.inputTokens + (record.outputTokens ?? 0);
25203
25499
  return hasMeasure ? record : void 0;
25204
25500
  }
25205
25501
  };
@@ -27370,6 +27666,10 @@ var TaskOrchestrator = class _TaskOrchestrator {
27370
27666
  store;
27371
27667
  output;
27372
27668
  attempts = /* @__PURE__ */ new Map();
27669
+ /** Bumped by every plan load: an ended attempt's wind-down belongs to the scope before it. */
27670
+ planScope = 0;
27671
+ /** Each task's runners still winding down once stopped: a continue waits for them (ADR-0018, L1). */
27672
+ windDowns = /* @__PURE__ */ new Map();
27373
27673
  verifier = new VerdictEngine();
27374
27674
  /** Changed only by start, stop, a plan load, a usage-limit pause, a move off the limit, and {@link settleRun}. */
27375
27675
  runState = "idle";
@@ -27918,8 +28218,9 @@ var TaskOrchestrator = class _TaskOrchestrator {
27918
28218
  return blockingFailures(this.store.planTasks).get(taskId) ?? [];
27919
28219
  }
27920
28220
  loadPlan(tasks, planRunners = [...DEFAULT_RUNNERS]) {
27921
- this.store.load(tasks, planRunners);
27922
28221
  const repairs = this.endAllAttempts("load").filter((a) => a.kind.kind === "repair" && a.worktree);
28222
+ this.planScope += 1;
28223
+ this.store.load(tasks, planRunners);
27923
28224
  for (const a of repairs) this.unawaited(this.runs.release(a.taskId, { keep: true }, a.integration), `Could not keep the worktree of task "${this.store.get(a.taskId)?.title ?? a.taskId}"`);
27924
28225
  this.runs.interrupt({ keepOpen: this.isScheduling });
27925
28226
  if (this.runState !== "scheduling") this.runState = "idle";
@@ -28005,6 +28306,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
28005
28306
  if (reason === "user") this.stopGeneration++;
28006
28307
  this.runState = "idle";
28007
28308
  this.verifier.reset();
28309
+ for (const attempt of this.attempts.values()) this.stopRunner(attempt);
28008
28310
  this.runner.stopAll();
28009
28311
  const stopped = [];
28010
28312
  for (const a of this.endAllAttempts("stop")) {
@@ -28118,6 +28420,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
28118
28420
  if (!task || !attempt) return;
28119
28421
  attempt.decided = true;
28120
28422
  const stoppedMidTurn = endedByExit(verdict) ? saidInFlight(attempt) : null;
28423
+ this.stopRunner(attempt);
28121
28424
  console.error(`[TaskOrchestrator] Task ${taskRefOf(this.store.planTasks, task.id) ?? `#${task.order}`} "${task.title}" verdict=${verdict.outcome}`);
28122
28425
  console.error(`[TaskOrchestrator] Runner: ${task.assignedRunner}, Model: ${task.assignedModel?.modelId ?? "default"}`);
28123
28426
  console.error(`[TaskOrchestrator] Prompt preview: ${(task.prompt ?? "").slice(0, 200)}`);
@@ -29029,7 +29332,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
29029
29332
  autoResumed: opts.autoResumed === true,
29030
29333
  turnError: null,
29031
29334
  turnOutputFrom: 0,
29032
- reminded: false
29335
+ reminded: false,
29336
+ runnerStopped: false,
29337
+ planScope: this.planScope
29033
29338
  };
29034
29339
  this.spawnCounts.set(task.id, attempt.attempt);
29035
29340
  this.attempts.set(task.id, attempt);
@@ -29066,6 +29371,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
29066
29371
  const manifest = this.registry?.get(attempt.runner)?.manifest;
29067
29372
  const unavailable = manifest && unavailableTaskModeMessage(manifest, task.taskMode);
29068
29373
  if (unavailable) throw new Error(unavailable);
29374
+ if (kind.kind === "continuation") await this.windDowns.get(task.id);
29069
29375
  const { cwd, worktree, previousWork } = await attemptCwd(kind, task, this.runs);
29070
29376
  attempt.cwd = cwd;
29071
29377
  attempt.worktree = worktree;
@@ -29121,10 +29427,11 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
29121
29427
  }
29122
29428
  if (takesSkills(kind)) this.store.setTaskAttemptSkills(task.id, [...attempt.skills]);
29123
29429
  session.onEvent((event) => {
29124
- if (event.type !== "usage" || this.attempts.get(task.id) !== attempt) return;
29430
+ if (event.type !== "usage" || attempt.planScope !== this.planScope || this.attempts.get(task.id) !== attempt && !attempt.runnerStopped) return;
29125
29431
  this.store.recordAttemptUsage(task.id, attempt.attempt, event.record, (/* @__PURE__ */ new Date()).toISOString());
29126
29432
  this.emit("onUsageChanged");
29127
29433
  });
29434
+ session.onExit(() => this.woundDown(attempt, session));
29128
29435
  this.output.attach(task.id, session);
29129
29436
  this.verifier.watch(task, session);
29130
29437
  session.onEvent((event) => {
@@ -29293,10 +29600,35 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
29293
29600
  if (stopsRunner(reason)) {
29294
29601
  const task = this.store.get(taskId);
29295
29602
  if (task) this.verifier.clear(task);
29296
- if (session) this.runner.stop(session.id);
29603
+ if (attempt) this.stopRunner(attempt);
29297
29604
  }
29298
29605
  return attempt;
29299
29606
  }
29607
+ /**
29608
+ * Let the attempt's runner go the way it ends a turn (ADR-0018, L1): it
29609
+ * winds down on its own time, bounded by its interrupt grace, and nothing
29610
+ * here waits on it but a continue of the same task.
29611
+ */
29612
+ stopRunner(attempt) {
29613
+ const session = attempt.session;
29614
+ if (!session || attempt.runnerStopped) return;
29615
+ attempt.runnerStopped = true;
29616
+ const { taskId } = attempt;
29617
+ const windDown = Promise.all([this.windDowns.get(taskId), this.runner.stop(session.id)]).catch(() => void 0).then(() => {
29618
+ if (this.windDowns.get(taskId) === windDown) this.windDowns.delete(taskId);
29619
+ });
29620
+ this.windDowns.set(taskId, windDown);
29621
+ }
29622
+ /**
29623
+ * An ended attempt's runner is gone: the count its session ended on is the
29624
+ * one a continue of that session counts from. One still current is ended
29625
+ * with that count instead.
29626
+ */
29627
+ woundDown(attempt, session) {
29628
+ if (attempt.planScope !== this.planScope || !attempt.runnerStopped || this.attempts.get(attempt.taskId) === attempt) return;
29629
+ this.store.endAttemptUsage(attempt.taskId, attempt.attempt, (/* @__PURE__ */ new Date()).toISOString(), session.sessionCost?.() ?? void 0, session.nativeSessionId() ?? void 0);
29630
+ this.emit("onUsageChanged");
29631
+ }
29300
29632
  /** Kept on the task once the attempt ends, so a continue can resume it after a reload (ADR-0018, K1). */
29301
29633
  saveNativeSession(taskId, nativeSessionId, runner) {
29302
29634
  if (nativeSessionId) this.store.setTaskRunnerSessionId(taskId, nativeSessionId, runner);
@@ -29654,7 +29986,7 @@ function taskReadPage(text3, { lines: lines2, since }) {
29654
29986
 
29655
29987
  // src/services/plannerTools.ts
29656
29988
  var PLANNER_TURN_ENDED = "The planning turn this call was made in has ended, so nothing was submitted.";
29657
- function runnersOf(tasks) {
29989
+ function runnersOf2(tasks) {
29658
29990
  return [...new Set(flattenTasks(tasks).filter((t) => t.type !== "user").map((t) => t.assignedRunner))];
29659
29991
  }
29660
29992
  function plannerToolHandler(host) {
@@ -32204,19 +32536,50 @@ var TaskLogRecorder = class {
32204
32536
  };
32205
32537
 
32206
32538
  // src/services/ContextWindowFill.ts
32207
- var ContextWindowFill = class {
32208
- constructor(catalog, last = {}) {
32539
+ var SessionWindows = class {
32540
+ constructor(catalog = () => void 0) {
32209
32541
  this.catalog = catalog;
32210
- if (last.contextWindow !== void 0) this.known.set(modelKey(last.source, last.model), last.contextWindow);
32211
32542
  }
32212
32543
  catalog;
32213
- /** The window known for each model of the stream, by reporter and model. */
32544
+ reported = /* @__PURE__ */ new Map();
32545
+ report(source, model, contextWindow) {
32546
+ if (contextWindow > 0) this.reported.set(modelKey(source, model), { source, model, contextWindow });
32547
+ }
32548
+ get(source, model) {
32549
+ return this.reported.get(modelKey(source, model))?.contextWindow;
32550
+ }
32551
+ snapshot() {
32552
+ return [...this.reported.values()];
32553
+ }
32554
+ restore(saved) {
32555
+ this.reported.clear();
32556
+ for (const { source, model, contextWindow } of saved ?? []) this.report(source, model, contextWindow);
32557
+ }
32558
+ clear() {
32559
+ this.reported.clear();
32560
+ }
32561
+ };
32562
+ var ContextWindowFill = class {
32563
+ constructor(windows, last = {}) {
32564
+ this.windows = windows;
32565
+ if (last.contextWindow !== void 0) this.known.set(modelKey(last.source, last.model), last.contextWindow);
32566
+ }
32567
+ windows;
32568
+ /** The window this stream used for each of its models, by reporter and model. */
32214
32569
  known = /* @__PURE__ */ new Map();
32215
32570
  fill(record) {
32216
32571
  if (record.subagentId || record.aggregate) return record;
32217
32572
  const key = modelKey(record.source, record.model);
32573
+ if (record.compacted) {
32574
+ if (!record.model) return record;
32575
+ const known = this.windows.get(record.source, record.model) ?? this.known.get(key) ?? positive(this.windows.catalog(record.source, record.model));
32576
+ if (known === void 0) return record;
32577
+ this.known.set(key, known);
32578
+ return { ...record, contextWindow: known };
32579
+ }
32218
32580
  const reported = record.contextWindow !== void 0 && record.contextWindow > 0 ? record.contextWindow : void 0;
32219
- const window = reported ?? this.known.get(key) ?? (record.model ? positive(this.catalog(record.source, record.model)) : void 0);
32581
+ if (reported !== void 0 && record.model) this.windows.report(record.source, record.model, reported);
32582
+ const window = reported ?? (record.model ? this.windows.get(record.source, record.model) : void 0) ?? this.known.get(key) ?? (record.model ? positive(this.windows.catalog(record.source, record.model)) : void 0);
32220
32583
  if (window !== void 0) this.known.set(key, window);
32221
32584
  return window === void 0 || window === record.contextWindow ? record : { ...record, contextWindow: window };
32222
32585
  }
@@ -32224,9 +32587,9 @@ var ContextWindowFill = class {
32224
32587
  function modelKey(source, model) {
32225
32588
  return JSON.stringify([source ?? null, model ?? null]);
32226
32589
  }
32227
- function withContextWindows(runner, catalog) {
32590
+ function withContextWindows(runner, windows) {
32228
32591
  return {
32229
- spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(catalog, opts.resumeWindow)),
32592
+ spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(windows, opts.resumeWindow)),
32230
32593
  stop: (sessionId) => runner.stop(sessionId),
32231
32594
  stopAll: () => runner.stopAll(),
32232
32595
  get activeCount() {
@@ -32257,12 +32620,12 @@ function positive(n) {
32257
32620
 
32258
32621
  // src/services/PlannerUsage.ts
32259
32622
  var PlannerUsageLedger = class {
32260
- /** `catalogWindow` serves the window of a planner model whose backend reports none. */
32261
- constructor(catalogWindow = () => void 0) {
32262
- this.catalogWindow = catalogWindow;
32263
- this.windows = new ContextWindowFill(catalogWindow);
32623
+ /** `sessionWindows` serves the window of a planner model whose backend reports none. */
32624
+ constructor(sessionWindows = new SessionWindows()) {
32625
+ this.sessionWindows = sessionWindows;
32626
+ this.windows = new ContextWindowFill(sessionWindows);
32264
32627
  }
32265
- catalogWindow;
32628
+ sessionWindows;
32266
32629
  usage = { totals: {} };
32267
32630
  windows;
32268
32631
  /** Fold one model call's record into the totals; returns the running ledger. */
@@ -32273,16 +32636,16 @@ var PlannerUsageLedger = class {
32273
32636
  /** Adopt a persisted ledger (a reopened session) or start from zero. */
32274
32637
  restore(usage) {
32275
32638
  this.usage = usage ?? { totals: {} };
32276
- this.windows = new ContextWindowFill(this.catalogWindow, this.usage);
32639
+ this.windows = new ContextWindowFill(this.sessionWindows, this.usage);
32277
32640
  }
32278
32641
  /** Start a fresh session's ledger from zero. */
32279
32642
  clear() {
32280
32643
  this.usage = { totals: {} };
32281
- this.windows = new ContextWindowFill(this.catalogWindow);
32644
+ this.windows = new ContextWindowFill(this.sessionWindows);
32282
32645
  }
32283
- /** Whether anything has been recorded — a plan with no usage says nothing. */
32646
+ /** Whether the ledger has measures or session billing facts to broadcast. */
32284
32647
  get hasUsage() {
32285
- return isMeasured(this.usage.totals);
32648
+ return isMeasured(this.usage.totals) || this.usage.sessionBilling !== void 0;
32286
32649
  }
32287
32650
  /** The value persisted onto the plan state. */
32288
32651
  snapshot() {
@@ -33731,7 +34094,7 @@ function createSession(deps) {
33731
34094
  const backend = new PlannerBackend(deps.aiService ? { pinned: deps.aiService, busy } : { provider: () => deps.config.aiProvider, create: () => createAiService(deps.config, { workspaceRoot: deps.workspaceRoot, mcpServer }), busy });
33732
34095
  const aiService = () => backend.current();
33733
34096
  const store = new PlanStore();
33734
- const catalogWindow = (source, model) => deps.modelResolver.contextWindowFor?.(source, model);
34097
+ const windows = new SessionWindows((source, model) => deps.modelResolver.contextWindowFor?.(source, model));
33735
34098
  const taskLogs = new TaskLogRecorder({
33736
34099
  broadcast: deps.broadcast,
33737
34100
  // Read through the Session, whose id and workspace are the ones it saves
@@ -33764,7 +34127,7 @@ function createSession(deps) {
33764
34127
  const orchestrator = TaskOrchestrator.compose({
33765
34128
  config: deps.config,
33766
34129
  notifications: deps.notifications,
33767
- runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner, catalogWindow))),
34130
+ runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner, windows))),
33768
34131
  store,
33769
34132
  output: deps.taskOutput,
33770
34133
  isolation: deps.isolation,
@@ -33776,7 +34139,7 @@ function createSession(deps) {
33776
34139
  runSettings: () => session.planState?.runSettings,
33777
34140
  isApproved: (taskId) => session.isApproved(taskId)
33778
34141
  });
33779
- const usage = new PlannerUsageLedger(catalogWindow);
34142
+ const usage = new PlannerUsageLedger(windows);
33780
34143
  const events = new SessionEventRelay({
33781
34144
  broadcast: deps.broadcast,
33782
34145
  onNotice: deps.onNotice,
@@ -33846,6 +34209,7 @@ function createSession(deps) {
33846
34209
  runs: orchestrator.runs,
33847
34210
  events,
33848
34211
  usage,
34212
+ windows,
33849
34213
  approvals,
33850
34214
  approvalPolicy,
33851
34215
  taskLogs,
@@ -33873,6 +34237,7 @@ function configSecrets(config) {
33873
34237
  var Session = class {
33874
34238
  backend;
33875
34239
  usage;
34240
+ windows;
33876
34241
  events;
33877
34242
  planner;
33878
34243
  orchestrator;
@@ -33978,6 +34343,7 @@ var Session = class {
33978
34343
  this.runs = parts.runs;
33979
34344
  this.events = parts.events;
33980
34345
  this.usage = parts.usage;
34346
+ this.windows = parts.windows;
33981
34347
  this.approvals = parts.approvals;
33982
34348
  this.approvalPolicy = parts.approvalPolicy;
33983
34349
  this.taskLogs = parts.taskLogs;
@@ -34456,6 +34822,7 @@ var Session = class {
34456
34822
  this.syncPlanTasks();
34457
34823
  this.plan.isolation = this.runs.planIsolation ?? void 0;
34458
34824
  this.plan.plannerUsage = this.usage.snapshot();
34825
+ this.plan.contextWindows = this.windows.snapshot();
34459
34826
  this.plan.supervision = this.supervision.snapshot();
34460
34827
  this.plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
34461
34828
  this.save(this.plan, this.goal, this.workspace, this.currentSessionId);
@@ -34485,6 +34852,7 @@ var Session = class {
34485
34852
  this.owingEffects.clear();
34486
34853
  this.conversation.reset();
34487
34854
  this.usage.clear();
34855
+ this.windows.clear();
34488
34856
  this.approvals.clear(isPlannerApproval);
34489
34857
  if (!keepGrants) this.approvalPolicy.reset();
34490
34858
  this.store.clearLog();
@@ -34825,7 +35193,18 @@ var Session = class {
34825
35193
  if (!this.plan) throw new ConversationEditError("No planning conversation to condense");
34826
35194
  const opening = this.plannerTurnMayOpen(signal);
34827
35195
  if (opening) await opening;
34828
- return this.conversation.compact(signal);
35196
+ const compaction = await this.conversation.compact(signal);
35197
+ const usage = this.usage.snapshot();
35198
+ if (usage.lastPromptTokens !== void 0) {
35199
+ this.usage.record({
35200
+ source: usage.source ?? this.config.aiProvider,
35201
+ ...usage.model ? { model: usage.model } : {},
35202
+ compacted: true
35203
+ });
35204
+ this.persist();
35205
+ this.broadcast(this.usage.message());
35206
+ }
35207
+ return compaction;
34829
35208
  }
34830
35209
  rewindTargets() {
34831
35210
  return this.conversation.rewindTargets();
@@ -34912,7 +35291,7 @@ var Session = class {
34912
35291
  */
34913
35292
  adoptPlannerTasks(tasks, how) {
34914
35293
  this.turnEffects.landed(how);
34915
- for (const runner of runnersOf(tasks)) this.editor.admitRunner(runner, this.catalog.known(runner));
35294
+ for (const runner of runnersOf2(tasks)) this.editor.admitRunner(runner, this.catalog.known(runner));
34916
35295
  const runners = this.plan.runners;
34917
35296
  const before = this.store.planTasks;
34918
35297
  let coerced = resolveAssignments(tasks, before, this.catalog.edit(runners)).tasks;
@@ -35313,7 +35692,10 @@ var Session = class {
35313
35692
  if (!plan.approvedTasks && plan.approved) {
35314
35693
  plan.approvedTasks = flattenTasks(plan.tasks).map((t) => t.id);
35315
35694
  }
35316
- if (adopting) this.usage.restore(plan.plannerUsage);
35695
+ if (adopting) {
35696
+ this.usage.restore(plan.plannerUsage);
35697
+ this.windows.restore(plan.contextWindows);
35698
+ }
35317
35699
  if (opts?.sessionId) this.currentSessionId = opts.sessionId;
35318
35700
  if (adopting) {
35319
35701
  for (const task of flattenTasks(plan.tasks)) delete task.resumeAt;
@@ -35395,7 +35777,7 @@ function openTurn(view, { turnId, prompt = "", skills = [], wakeUps = [], forced
35395
35777
  wakeUps = wakeUps.slice(adopted.wakeUps);
35396
35778
  if (wakeUps.length === 0) return adopted.prompt || prompt === "" && adopted.wakeUps > 0 ? view : promptLine(view, turnId, prompt, skills, forced);
35397
35779
  const said2 = prompt === "" || adopted.prompt ? view : promptLine(view, turnId, prompt, skills, forced);
35398
- const at = prompt === "" ? said2.blocks.length : findLastIndex(said2.blocks, (b) => b.type === "message" && b.role === "user" && b.turnId === turnId);
35780
+ const at = prompt === "" ? said2.blocks.length - (said2.blocks.at(-1)?.type === "usage" ? 1 : 0) : findLastIndex(said2.blocks, (b) => b.type === "message" && b.role === "user" && b.turnId === turnId);
35399
35781
  const updates = wakeUps.map((text3, n) => settledMessage(`b${said2.nextId + n}`, "wake_up", text3, turnId));
35400
35782
  return { ...said2, blocks: [...said2.blocks.slice(0, at), ...updates, ...said2.blocks.slice(at)], nextId: said2.nextId + updates.length };
35401
35783
  }
@@ -36082,17 +36464,12 @@ var AbstractRunner = class {
36082
36464
  get activeCount() {
36083
36465
  return this.sessions.size;
36084
36466
  }
36467
+ /** A session winding down stays registered until it exits, so a second stop gets the same wind-down. */
36085
36468
  stop(sessionId) {
36086
- const session = this.sessions.get(sessionId);
36087
- if (!session) return;
36088
- session.kill();
36089
- this.sessions.delete(sessionId);
36469
+ return this.sessions.get(sessionId)?.stop() ?? Promise.resolve();
36090
36470
  }
36091
36471
  stopAll() {
36092
- for (const [, session] of this.sessions) {
36093
- session.kill();
36094
- }
36095
- this.sessions.clear();
36472
+ for (const session of this.sessions.values()) void session.stop();
36096
36473
  }
36097
36474
  registerSession(id, session) {
36098
36475
  this.sessions.set(id, session);
@@ -36244,12 +36621,17 @@ var StructuredSession = class extends AbstractRunnerSession {
36244
36621
  text;
36245
36622
  state = "idle";
36246
36623
  turn = null;
36624
+ /** A `task_complete` report waiting for its turn to close; the verdict is published then. */
36625
+ completionReport = null;
36626
+ completionTimer = null;
36247
36627
  /** A terminal event ends the turn before the adapter releases its send. */
36248
36628
  sending = null;
36249
36629
  turnCount = 0;
36250
36630
  queue = [];
36251
36631
  messageCount = 0;
36252
36632
  interrupting = null;
36633
+ /** The wind-down once {@link stop} was asked for: the same for every caller, and nothing new is sent meanwhile. */
36634
+ stopping = null;
36253
36635
  lastSessionId = null;
36254
36636
  lastSessionCost = null;
36255
36637
  structuredEmitter = new import_events2.EventEmitter();
@@ -36275,7 +36657,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36275
36657
  await this.startAttached(this.launch.startOptions);
36276
36658
  this.state = "working";
36277
36659
  setImmediate(() => {
36278
- if (!this.exited) this.deliver(prompt);
36660
+ if (!this.exited && !this.stopping) this.deliver(prompt);
36279
36661
  });
36280
36662
  }
36281
36663
  getOutput() {
@@ -36300,7 +36682,9 @@ var StructuredSession = class extends AbstractRunnerSession {
36300
36682
  create: () => this.launch.createAdapter(this.launch.runner, this.launch.deps),
36301
36683
  issueToken: () => server4.issueTaskToken(scope, {
36302
36684
  taskComplete: async (report) => {
36303
- this.structuredEmitter.emit("taskComplete", report);
36685
+ this.completionReport = report;
36686
+ if (this.completionTimer) clearTimeout(this.completionTimer);
36687
+ this.completionTimer = setTimeout(() => this.flushCompletion(), this.launch.interruptGraceMs);
36304
36688
  return { text: "Recorded. End your turn now." };
36305
36689
  },
36306
36690
  checkpoint: async ({ question }, { signal }) => {
@@ -36332,6 +36716,8 @@ var StructuredSession = class extends AbstractRunnerSession {
36332
36716
  this.toolToken = null;
36333
36717
  }
36334
36718
  baseHandleExit(code) {
36719
+ this.undeliverQueued();
36720
+ this.flushCompletion();
36335
36721
  this.ended.abort();
36336
36722
  this.revokeTools();
36337
36723
  this.undeliverQueued();
@@ -36355,6 +36741,53 @@ var StructuredSession = class extends AbstractRunnerSession {
36355
36741
  this.withdrawPermissions();
36356
36742
  this.baseHandleExit(-1);
36357
36743
  }
36744
+ stop() {
36745
+ if (this.stopping) return this.stopping;
36746
+ if (this.exited) return Promise.resolve();
36747
+ let woundDown;
36748
+ this.stopping = new Promise((resolve9) => {
36749
+ woundDown = resolve9;
36750
+ });
36751
+ void this.windDown().catch(() => this.kill()).then(woundDown);
36752
+ return this.stopping;
36753
+ }
36754
+ /**
36755
+ * The attempt is over (ADR-0018, L1), so what is still owed goes undelivered
36756
+ * and every tool request unanswered at once. A turn still open is
36757
+ * interrupted, and its close — where Claude Code reports the turn's cost and
36758
+ * its model's window — is waited for; then the runner is told nothing more
36759
+ * is coming and left to exit by itself. All of it within the interrupt
36760
+ * grace: a runner still there once it runs out is killed.
36761
+ */
36762
+ async windDown() {
36763
+ const adapter = this.adapterAttached ? this.adapter : null;
36764
+ const deadline = Date.now() + this.launch.interruptGraceMs;
36765
+ this.revokeTools();
36766
+ this.undeliverQueued();
36767
+ this.withdrawPermissions();
36768
+ const open = this.turn && !this.turn.ended ? this.turn : null;
36769
+ if (adapter && (!open || await this.turnClosed(open, adapter, deadline)) && adapter.endInput && !this.exited) {
36770
+ adapter.endInput();
36771
+ await this.within(this.gone(), deadline);
36772
+ }
36773
+ if (this.exited) adapter?.dispose();
36774
+ else this.kill();
36775
+ }
36776
+ /** Whether `turn` closed within the deadline once interrupted. */
36777
+ async turnClosed(turn, adapter, deadline) {
36778
+ const closed = new Promise((resolve9) => this.structuredEmitter.once("turnEnd", () => resolve9()));
36779
+ adapter.interrupt(Math.max(0, deadline - Date.now())).catch(() => false);
36780
+ return await this.within(closed, deadline) && turn.ended;
36781
+ }
36782
+ /** Whether `done` settled before the deadline, or the session ended meanwhile. */
36783
+ within(done, deadline) {
36784
+ return settleWithin(done.then(() => true), { timeoutMs: Math.max(0, deadline - Date.now()), onTimeout: () => false, ended: this.gone(), onEnded: () => true });
36785
+ }
36786
+ gone() {
36787
+ const { signal } = this.ended;
36788
+ if (signal.aborted) return Promise.resolve();
36789
+ return new Promise((resolve9) => signal.addEventListener("abort", () => resolve9(), { once: true }));
36790
+ }
36358
36791
  turnState() {
36359
36792
  return this.state;
36360
36793
  }
@@ -36380,7 +36813,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36380
36813
  this.messageCount += 1;
36381
36814
  const id = `msg-${this.messageCount}`;
36382
36815
  const sender2 = opening.from ? { from: opening.from } : {};
36383
- if (this.exited) this.emitEvent({ type: "message_undelivered", messageId: id, text: text3, ...sender2 });
36816
+ if (this.exited || this.stopping) this.emitEvent({ type: "message_undelivered", messageId: id, text: text3, ...sender2 });
36384
36817
  else if (this.state === "idle" && this.adapterStarted && !this.sending) this.deliver(text3, opening);
36385
36818
  else {
36386
36819
  this.state = "working";
@@ -36425,6 +36858,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36425
36858
  * "waiting for input" between the two (F3).
36426
36859
  */
36427
36860
  force(message) {
36861
+ this.discardCompletion();
36428
36862
  message.forced = true;
36429
36863
  const at = this.queue.findIndex((m) => !m.forced);
36430
36864
  this.queue.splice(at < 0 ? this.queue.length : at, 0, message);
@@ -36435,7 +36869,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36435
36869
  });
36436
36870
  }
36437
36871
  runningTurn() {
36438
- return !this.exited && this.adapter !== null && this.turn !== null && !this.turn.ended;
36872
+ return !this.exited && !this.stopping && this.adapter !== null && this.turn !== null && !this.turn.ended;
36439
36873
  }
36440
36874
  /** No runner can recall a message once offered to it, so only a queued one comes back. */
36441
36875
  removeQueued(id) {
@@ -36496,7 +36930,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36496
36930
  if (this.interrupting) return this.interrupting;
36497
36931
  const turn = this.turn;
36498
36932
  const adapter = this.adapter;
36499
- if (!turn || turn.ended || !adapter) return Promise.resolve();
36933
+ if (!turn || turn.ended || !adapter || this.stopping) return Promise.resolve();
36500
36934
  this.interrupting = this.interruptTurn(turn, adapter).finally(() => {
36501
36935
  this.interrupting = null;
36502
36936
  this.offerNext();
@@ -36510,7 +36944,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36510
36944
  const settled = await settleWithin(ended.then(() => true), { timeoutMs: grace, onTimeout: () => false });
36511
36945
  if (settled) return;
36512
36946
  }
36513
- if (turn.ended || this.exited) return;
36947
+ if (turn.ended || this.exited || this.stopping) return;
36514
36948
  await this.restartInterrupted(turn);
36515
36949
  }
36516
36950
  /**
@@ -36566,6 +37000,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36566
37000
  /** `message` is what opened the turn: a queued one has an id, one delivered straight from idle only its sender. */
36567
37001
  openTurn(text3, message) {
36568
37002
  this.turnCount += 1;
37003
+ this.discardCompletion();
36569
37004
  const turn = { id: this.turnCount, abort: new AbortController(), ended: false, reason: "completed", steerRefused: false };
36570
37005
  this.turn = turn;
36571
37006
  this.state = "working";
@@ -36614,7 +37049,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36614
37049
  offerNext() {
36615
37050
  const adapter = this.adapter;
36616
37051
  const turn = this.turn;
36617
- if (!adapter?.steer || !turn || turn.ended || turn.steerRefused || this.interrupting || this.exited) return;
37052
+ if (!adapter?.steer || !turn || turn.ended || turn.steerRefused || this.interrupting || this.exited || this.stopping) return;
36618
37053
  if (this.queue.some((m) => m.stage === "steering")) return;
36619
37054
  const next = this.queue.find((m) => m.stage === "queued");
36620
37055
  if (!next || next.command) return;
@@ -36658,6 +37093,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36658
37093
  } else if (event.type === "permission_request") this.openPermission(adapter, event);
36659
37094
  else if (event.type === "permission_cancelled") this.cancelPermission(adapter, event.id);
36660
37095
  else if (event.type === "message_delivered") {
37096
+ this.discardCompletion();
36661
37097
  const message = this.delivered(event.id);
36662
37098
  if (message) this.emitEvent({ type: "message_delivered", messageId: message.id, text: message.text, ...message.from ? { from: message.from } : {} });
36663
37099
  } else if (event.type === "message_dropped") {
@@ -36689,7 +37125,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36689
37125
  }
36690
37126
  return;
36691
37127
  }
36692
- if (!turn && TURN_OPENING_EVENTS.has(event.type) && !("subagentId" in event && event.subagentId)) {
37128
+ if (!turn && !this.stopping && TURN_OPENING_EVENTS.has(event.type) && !("subagentId" in event && event.subagentId)) {
36693
37129
  turn = this.openTurn("");
36694
37130
  this.offerNext();
36695
37131
  }
@@ -36738,6 +37174,24 @@ var StructuredSession = class extends AbstractRunnerSession {
36738
37174
  }
36739
37175
  this.emitEvent(event);
36740
37176
  }
37177
+ /** Publish a `task_complete` report whose turn has closed, or that waited out the grace. */
37178
+ flushCompletion() {
37179
+ if (this.completionTimer) {
37180
+ clearTimeout(this.completionTimer);
37181
+ this.completionTimer = null;
37182
+ }
37183
+ const report = this.completionReport;
37184
+ this.completionReport = null;
37185
+ if (report) this.structuredEmitter.emit("taskComplete", report);
37186
+ }
37187
+ /** A message the runner reads, or a turn it opens, voids a report made before it (ADR-0023, D6). */
37188
+ discardCompletion() {
37189
+ if (this.completionTimer) {
37190
+ clearTimeout(this.completionTimer);
37191
+ this.completionTimer = null;
37192
+ }
37193
+ this.completionReport = null;
37194
+ }
36741
37195
  /**
36742
37196
  * A queued message goes out as the turn closes, and the state never passes
36743
37197
  * through `idle` on the way, so a listener told the turn ended can already
@@ -36750,6 +37204,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36750
37204
  turn.ended = true;
36751
37205
  this.text.endTurn();
36752
37206
  this.emitEvent({ type: "turn_end", reason });
37207
+ this.flushCompletion();
36753
37208
  if (reason === "failed") this.undeliverQueued();
36754
37209
  this.state = !this.exited && this.queue.length ? "working" : "idle";
36755
37210
  this.structuredEmitter.emit("turnEnd", reason);
@@ -36760,7 +37215,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36760
37215
  * back in Ordewell's hands: while it is on offer or owed, nothing overtakes it.
36761
37216
  */
36762
37217
  deliverNext() {
36763
- if (this.exited || this.sending || this.turn && !this.turn.ended) return;
37218
+ if (this.exited || this.stopping || this.sending || this.turn && !this.turn.ended) return;
36764
37219
  const head = this.queue[0];
36765
37220
  if (!head || this.queue.some((message) => message.stage !== "queued")) return;
36766
37221
  this.queue.shift();
@@ -37210,7 +37665,10 @@ var PlannerModelMemory = class {
37210
37665
  collectProviderCredentials,
37211
37666
  commandAllowedBy,
37212
37667
  configuredProviders,
37668
+ contextMeterText,
37669
+ contextMeterTitle,
37213
37670
  continuability,
37671
+ costText,
37214
37672
  countedSessionCost,
37215
37673
  createAiService,
37216
37674
  createEmptyPlan,
@@ -37253,7 +37711,9 @@ var PlannerModelMemory = class {
37253
37711
  flattenTasksWithParents,
37254
37712
  followTaskLog,
37255
37713
  followTurn,
37714
+ formatCost,
37256
37715
  formatSearchOutput,
37716
+ formatTokenCount,
37257
37717
  fromTranscript,
37258
37718
  getLatestSession,
37259
37719
  getProviderMeta,
@@ -37305,6 +37765,7 @@ var PlannerModelMemory = class {
37305
37765
  migratePlanState,
37306
37766
  mintDaemonToken,
37307
37767
  mintSessionId,
37768
+ modelCostTitle,
37308
37769
  namedRepos,
37309
37770
  newTaskFields,
37310
37771
  normalizeAgentArgs,
@@ -37390,6 +37851,7 @@ var PlannerModelMemory = class {
37390
37851
  stopTurn,
37391
37852
  stripAnsi,
37392
37853
  stripModelPrefix,
37854
+ subagentCostTitle,
37393
37855
  subtreeIds,
37394
37856
  summarizeToolCall,
37395
37857
  supervisorSummary,
@@ -37408,6 +37870,7 @@ var PlannerModelMemory = class {
37408
37870
  taskStartedNotice,
37409
37871
  taskStatusKind,
37410
37872
  taskUsageSummary,
37873
+ tasksTotalText,
37411
37874
  textHasTaskOps,
37412
37875
  textHasTaskQuery,
37413
37876
  titledRefs,
@@ -37425,6 +37888,7 @@ var PlannerModelMemory = class {
37425
37888
  unsendAll,
37426
37889
  unsendLatest,
37427
37890
  usageLine,
37891
+ usageText,
37428
37892
  waitsForApproval,
37429
37893
  walkPlan,
37430
37894
  wellKnownBinDirs,