@ordewell/core 0.10.9 → 0.10.10

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 +119 -17
  14. package/dist/index.d.ts +119 -17
  15. package/dist/index.js +521 -88
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +397 -72
  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-rLoYQlUH.d.mts → runSettingsText-C1i9wBG1.d.mts} +26 -15
  29. package/dist/{runSettingsText-CGKX3aeV.d.ts → runSettingsText-CWNrmF5U.d.ts} +26 -15
  30. package/dist/testing.d.mts +7 -2
  31. package/dist/testing.d.ts +7 -2
  32. package/dist/testing.js +11 -0
  33. package/dist/testing.js.map +1 -1
  34. package/dist/testing.mjs +12 -1
  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++) {
@@ -24775,7 +25065,8 @@ function usageRecordFrom(usage, model) {
24775
25065
  model,
24776
25066
  ...inputTokens !== void 0 ? { inputTokens } : {},
24777
25067
  ...reportedOutput.length > 0 ? { outputTokens: reportedOutput.reduce((a, b) => a + b, 0) } : {},
24778
- ...tokenCount(usage.cachedContentTokenCount) !== void 0 ? { cachedInputTokens: tokenCount(usage.cachedContentTokenCount) } : {}
25068
+ ...tokenCount(usage.cachedContentTokenCount) !== void 0 ? { cachedInputTokens: tokenCount(usage.cachedContentTokenCount) } : {},
25069
+ ...inputTokens !== void 0 ? { contextTokens: inputTokens + (answerTokens ?? 0) } : {}
24779
25070
  };
24780
25071
  const anyReported = record.inputTokens !== void 0 || record.outputTokens !== void 0 || record.cachedInputTokens !== void 0;
24781
25072
  return anyReported ? record : void 0;
@@ -25178,7 +25469,9 @@ var OpenAiResearchChat = class {
25178
25469
  /**
25179
25470
  * The one record for this API call, built only from what the provider
25180
25471
  * reported. OpenRouter's `cost` is in USD (its credits are dollar-pegged);
25181
- * nothing is estimated. A field the provider left out stays absent.
25472
+ * nothing is estimated. A field the provider left out stays absent. The
25473
+ * context the call leaves is its prompt and its completion: the API's own
25474
+ * `total_tokens`, and the reply the next call's prompt carries.
25182
25475
  */
25183
25476
  usageRecord(reported) {
25184
25477
  if (!reported) return void 0;
@@ -25197,9 +25490,10 @@ var OpenAiResearchChat = class {
25197
25490
  hasMeasure = true;
25198
25491
  }
25199
25492
  if (reported.cost !== void 0) {
25200
- record.reportedCost = { amount: reported.cost, currency: "USD" };
25493
+ record.reportedCost = { amount: reported.cost, currency: "USD", ...record.source === "openrouter" ? { kind: "billed" } : {} };
25201
25494
  hasMeasure = true;
25202
25495
  }
25496
+ if (record.inputTokens !== void 0) record.contextTokens = record.inputTokens + (record.outputTokens ?? 0);
25203
25497
  return hasMeasure ? record : void 0;
25204
25498
  }
25205
25499
  };
@@ -27370,6 +27664,10 @@ var TaskOrchestrator = class _TaskOrchestrator {
27370
27664
  store;
27371
27665
  output;
27372
27666
  attempts = /* @__PURE__ */ new Map();
27667
+ /** Bumped by every plan load: an ended attempt's wind-down belongs to the scope before it. */
27668
+ planScope = 0;
27669
+ /** Each task's runners still winding down once stopped: a continue waits for them (ADR-0018, L1). */
27670
+ windDowns = /* @__PURE__ */ new Map();
27373
27671
  verifier = new VerdictEngine();
27374
27672
  /** Changed only by start, stop, a plan load, a usage-limit pause, a move off the limit, and {@link settleRun}. */
27375
27673
  runState = "idle";
@@ -27918,8 +28216,9 @@ var TaskOrchestrator = class _TaskOrchestrator {
27918
28216
  return blockingFailures(this.store.planTasks).get(taskId) ?? [];
27919
28217
  }
27920
28218
  loadPlan(tasks, planRunners = [...DEFAULT_RUNNERS]) {
27921
- this.store.load(tasks, planRunners);
27922
28219
  const repairs = this.endAllAttempts("load").filter((a) => a.kind.kind === "repair" && a.worktree);
28220
+ this.planScope += 1;
28221
+ this.store.load(tasks, planRunners);
27923
28222
  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
28223
  this.runs.interrupt({ keepOpen: this.isScheduling });
27925
28224
  if (this.runState !== "scheduling") this.runState = "idle";
@@ -28005,6 +28304,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
28005
28304
  if (reason === "user") this.stopGeneration++;
28006
28305
  this.runState = "idle";
28007
28306
  this.verifier.reset();
28307
+ for (const attempt of this.attempts.values()) this.stopRunner(attempt);
28008
28308
  this.runner.stopAll();
28009
28309
  const stopped = [];
28010
28310
  for (const a of this.endAllAttempts("stop")) {
@@ -28118,6 +28418,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
28118
28418
  if (!task || !attempt) return;
28119
28419
  attempt.decided = true;
28120
28420
  const stoppedMidTurn = endedByExit(verdict) ? saidInFlight(attempt) : null;
28421
+ this.stopRunner(attempt);
28121
28422
  console.error(`[TaskOrchestrator] Task ${taskRefOf(this.store.planTasks, task.id) ?? `#${task.order}`} "${task.title}" verdict=${verdict.outcome}`);
28122
28423
  console.error(`[TaskOrchestrator] Runner: ${task.assignedRunner}, Model: ${task.assignedModel?.modelId ?? "default"}`);
28123
28424
  console.error(`[TaskOrchestrator] Prompt preview: ${(task.prompt ?? "").slice(0, 200)}`);
@@ -29029,7 +29330,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
29029
29330
  autoResumed: opts.autoResumed === true,
29030
29331
  turnError: null,
29031
29332
  turnOutputFrom: 0,
29032
- reminded: false
29333
+ reminded: false,
29334
+ runnerStopped: false,
29335
+ planScope: this.planScope
29033
29336
  };
29034
29337
  this.spawnCounts.set(task.id, attempt.attempt);
29035
29338
  this.attempts.set(task.id, attempt);
@@ -29066,6 +29369,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
29066
29369
  const manifest = this.registry?.get(attempt.runner)?.manifest;
29067
29370
  const unavailable = manifest && unavailableTaskModeMessage(manifest, task.taskMode);
29068
29371
  if (unavailable) throw new Error(unavailable);
29372
+ if (kind.kind === "continuation") await this.windDowns.get(task.id);
29069
29373
  const { cwd, worktree, previousWork } = await attemptCwd(kind, task, this.runs);
29070
29374
  attempt.cwd = cwd;
29071
29375
  attempt.worktree = worktree;
@@ -29121,10 +29425,11 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
29121
29425
  }
29122
29426
  if (takesSkills(kind)) this.store.setTaskAttemptSkills(task.id, [...attempt.skills]);
29123
29427
  session.onEvent((event) => {
29124
- if (event.type !== "usage" || this.attempts.get(task.id) !== attempt) return;
29428
+ if (event.type !== "usage" || attempt.planScope !== this.planScope || this.attempts.get(task.id) !== attempt && !attempt.runnerStopped) return;
29125
29429
  this.store.recordAttemptUsage(task.id, attempt.attempt, event.record, (/* @__PURE__ */ new Date()).toISOString());
29126
29430
  this.emit("onUsageChanged");
29127
29431
  });
29432
+ session.onExit(() => this.woundDown(attempt, session));
29128
29433
  this.output.attach(task.id, session);
29129
29434
  this.verifier.watch(task, session);
29130
29435
  session.onEvent((event) => {
@@ -29293,10 +29598,35 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
29293
29598
  if (stopsRunner(reason)) {
29294
29599
  const task = this.store.get(taskId);
29295
29600
  if (task) this.verifier.clear(task);
29296
- if (session) this.runner.stop(session.id);
29601
+ if (attempt) this.stopRunner(attempt);
29297
29602
  }
29298
29603
  return attempt;
29299
29604
  }
29605
+ /**
29606
+ * Let the attempt's runner go the way it ends a turn (ADR-0018, L1): it
29607
+ * winds down on its own time, bounded by its interrupt grace, and nothing
29608
+ * here waits on it but a continue of the same task.
29609
+ */
29610
+ stopRunner(attempt) {
29611
+ const session = attempt.session;
29612
+ if (!session || attempt.runnerStopped) return;
29613
+ attempt.runnerStopped = true;
29614
+ const { taskId } = attempt;
29615
+ const windDown = Promise.all([this.windDowns.get(taskId), this.runner.stop(session.id)]).catch(() => void 0).then(() => {
29616
+ if (this.windDowns.get(taskId) === windDown) this.windDowns.delete(taskId);
29617
+ });
29618
+ this.windDowns.set(taskId, windDown);
29619
+ }
29620
+ /**
29621
+ * An ended attempt's runner is gone: the count its session ended on is the
29622
+ * one a continue of that session counts from. One still current is ended
29623
+ * with that count instead.
29624
+ */
29625
+ woundDown(attempt, session) {
29626
+ if (attempt.planScope !== this.planScope || !attempt.runnerStopped || this.attempts.get(attempt.taskId) === attempt) return;
29627
+ this.store.endAttemptUsage(attempt.taskId, attempt.attempt, (/* @__PURE__ */ new Date()).toISOString(), session.sessionCost?.() ?? void 0, session.nativeSessionId() ?? void 0);
29628
+ this.emit("onUsageChanged");
29629
+ }
29300
29630
  /** Kept on the task once the attempt ends, so a continue can resume it after a reload (ADR-0018, K1). */
29301
29631
  saveNativeSession(taskId, nativeSessionId, runner) {
29302
29632
  if (nativeSessionId) this.store.setTaskRunnerSessionId(taskId, nativeSessionId, runner);
@@ -29654,7 +29984,7 @@ function taskReadPage(text3, { lines: lines2, since }) {
29654
29984
 
29655
29985
  // src/services/plannerTools.ts
29656
29986
  var PLANNER_TURN_ENDED = "The planning turn this call was made in has ended, so nothing was submitted.";
29657
- function runnersOf(tasks) {
29987
+ function runnersOf2(tasks) {
29658
29988
  return [...new Set(flattenTasks(tasks).filter((t) => t.type !== "user").map((t) => t.assignedRunner))];
29659
29989
  }
29660
29990
  function plannerToolHandler(host) {
@@ -32204,19 +32534,50 @@ var TaskLogRecorder = class {
32204
32534
  };
32205
32535
 
32206
32536
  // src/services/ContextWindowFill.ts
32207
- var ContextWindowFill = class {
32208
- constructor(catalog, last = {}) {
32537
+ var SessionWindows = class {
32538
+ constructor(catalog = () => void 0) {
32209
32539
  this.catalog = catalog;
32210
- if (last.contextWindow !== void 0) this.known.set(modelKey(last.source, last.model), last.contextWindow);
32211
32540
  }
32212
32541
  catalog;
32213
- /** The window known for each model of the stream, by reporter and model. */
32542
+ reported = /* @__PURE__ */ new Map();
32543
+ report(source, model, contextWindow) {
32544
+ if (contextWindow > 0) this.reported.set(modelKey(source, model), { source, model, contextWindow });
32545
+ }
32546
+ get(source, model) {
32547
+ return this.reported.get(modelKey(source, model))?.contextWindow;
32548
+ }
32549
+ snapshot() {
32550
+ return [...this.reported.values()];
32551
+ }
32552
+ restore(saved) {
32553
+ this.reported.clear();
32554
+ for (const { source, model, contextWindow } of saved ?? []) this.report(source, model, contextWindow);
32555
+ }
32556
+ clear() {
32557
+ this.reported.clear();
32558
+ }
32559
+ };
32560
+ var ContextWindowFill = class {
32561
+ constructor(windows, last = {}) {
32562
+ this.windows = windows;
32563
+ if (last.contextWindow !== void 0) this.known.set(modelKey(last.source, last.model), last.contextWindow);
32564
+ }
32565
+ windows;
32566
+ /** The window this stream used for each of its models, by reporter and model. */
32214
32567
  known = /* @__PURE__ */ new Map();
32215
32568
  fill(record) {
32216
32569
  if (record.subagentId || record.aggregate) return record;
32217
32570
  const key = modelKey(record.source, record.model);
32571
+ if (record.compacted) {
32572
+ if (!record.model) return record;
32573
+ const known = this.windows.get(record.source, record.model) ?? this.known.get(key) ?? positive(this.windows.catalog(record.source, record.model));
32574
+ if (known === void 0) return record;
32575
+ this.known.set(key, known);
32576
+ return { ...record, contextWindow: known };
32577
+ }
32218
32578
  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);
32579
+ if (reported !== void 0 && record.model) this.windows.report(record.source, record.model, reported);
32580
+ 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
32581
  if (window !== void 0) this.known.set(key, window);
32221
32582
  return window === void 0 || window === record.contextWindow ? record : { ...record, contextWindow: window };
32222
32583
  }
@@ -32224,9 +32585,9 @@ var ContextWindowFill = class {
32224
32585
  function modelKey(source, model) {
32225
32586
  return JSON.stringify([source ?? null, model ?? null]);
32226
32587
  }
32227
- function withContextWindows(runner, catalog) {
32588
+ function withContextWindows(runner, windows) {
32228
32589
  return {
32229
- spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(catalog, opts.resumeWindow)),
32590
+ spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(windows, opts.resumeWindow)),
32230
32591
  stop: (sessionId) => runner.stop(sessionId),
32231
32592
  stopAll: () => runner.stopAll(),
32232
32593
  get activeCount() {
@@ -32257,12 +32618,12 @@ function positive(n) {
32257
32618
 
32258
32619
  // src/services/PlannerUsage.ts
32259
32620
  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);
32621
+ /** `sessionWindows` serves the window of a planner model whose backend reports none. */
32622
+ constructor(sessionWindows = new SessionWindows()) {
32623
+ this.sessionWindows = sessionWindows;
32624
+ this.windows = new ContextWindowFill(sessionWindows);
32264
32625
  }
32265
- catalogWindow;
32626
+ sessionWindows;
32266
32627
  usage = { totals: {} };
32267
32628
  windows;
32268
32629
  /** Fold one model call's record into the totals; returns the running ledger. */
@@ -32273,16 +32634,16 @@ var PlannerUsageLedger = class {
32273
32634
  /** Adopt a persisted ledger (a reopened session) or start from zero. */
32274
32635
  restore(usage) {
32275
32636
  this.usage = usage ?? { totals: {} };
32276
- this.windows = new ContextWindowFill(this.catalogWindow, this.usage);
32637
+ this.windows = new ContextWindowFill(this.sessionWindows, this.usage);
32277
32638
  }
32278
32639
  /** Start a fresh session's ledger from zero. */
32279
32640
  clear() {
32280
32641
  this.usage = { totals: {} };
32281
- this.windows = new ContextWindowFill(this.catalogWindow);
32642
+ this.windows = new ContextWindowFill(this.sessionWindows);
32282
32643
  }
32283
- /** Whether anything has been recorded — a plan with no usage says nothing. */
32644
+ /** Whether the ledger has measures or session billing facts to broadcast. */
32284
32645
  get hasUsage() {
32285
- return isMeasured(this.usage.totals);
32646
+ return isMeasured(this.usage.totals) || this.usage.sessionBilling !== void 0;
32286
32647
  }
32287
32648
  /** The value persisted onto the plan state. */
32288
32649
  snapshot() {
@@ -33731,7 +34092,7 @@ function createSession(deps) {
33731
34092
  const backend = new PlannerBackend(deps.aiService ? { pinned: deps.aiService, busy } : { provider: () => deps.config.aiProvider, create: () => createAiService(deps.config, { workspaceRoot: deps.workspaceRoot, mcpServer }), busy });
33732
34093
  const aiService = () => backend.current();
33733
34094
  const store = new PlanStore();
33734
- const catalogWindow = (source, model) => deps.modelResolver.contextWindowFor?.(source, model);
34095
+ const windows = new SessionWindows((source, model) => deps.modelResolver.contextWindowFor?.(source, model));
33735
34096
  const taskLogs = new TaskLogRecorder({
33736
34097
  broadcast: deps.broadcast,
33737
34098
  // Read through the Session, whose id and workspace are the ones it saves
@@ -33764,7 +34125,7 @@ function createSession(deps) {
33764
34125
  const orchestrator = TaskOrchestrator.compose({
33765
34126
  config: deps.config,
33766
34127
  notifications: deps.notifications,
33767
- runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner, catalogWindow))),
34128
+ runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner, windows))),
33768
34129
  store,
33769
34130
  output: deps.taskOutput,
33770
34131
  isolation: deps.isolation,
@@ -33776,7 +34137,7 @@ function createSession(deps) {
33776
34137
  runSettings: () => session.planState?.runSettings,
33777
34138
  isApproved: (taskId) => session.isApproved(taskId)
33778
34139
  });
33779
- const usage = new PlannerUsageLedger(catalogWindow);
34140
+ const usage = new PlannerUsageLedger(windows);
33780
34141
  const events = new SessionEventRelay({
33781
34142
  broadcast: deps.broadcast,
33782
34143
  onNotice: deps.onNotice,
@@ -33846,6 +34207,7 @@ function createSession(deps) {
33846
34207
  runs: orchestrator.runs,
33847
34208
  events,
33848
34209
  usage,
34210
+ windows,
33849
34211
  approvals,
33850
34212
  approvalPolicy,
33851
34213
  taskLogs,
@@ -33873,6 +34235,7 @@ function configSecrets(config) {
33873
34235
  var Session = class {
33874
34236
  backend;
33875
34237
  usage;
34238
+ windows;
33876
34239
  events;
33877
34240
  planner;
33878
34241
  orchestrator;
@@ -33978,6 +34341,7 @@ var Session = class {
33978
34341
  this.runs = parts.runs;
33979
34342
  this.events = parts.events;
33980
34343
  this.usage = parts.usage;
34344
+ this.windows = parts.windows;
33981
34345
  this.approvals = parts.approvals;
33982
34346
  this.approvalPolicy = parts.approvalPolicy;
33983
34347
  this.taskLogs = parts.taskLogs;
@@ -34456,6 +34820,7 @@ var Session = class {
34456
34820
  this.syncPlanTasks();
34457
34821
  this.plan.isolation = this.runs.planIsolation ?? void 0;
34458
34822
  this.plan.plannerUsage = this.usage.snapshot();
34823
+ this.plan.contextWindows = this.windows.snapshot();
34459
34824
  this.plan.supervision = this.supervision.snapshot();
34460
34825
  this.plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
34461
34826
  this.save(this.plan, this.goal, this.workspace, this.currentSessionId);
@@ -34485,6 +34850,7 @@ var Session = class {
34485
34850
  this.owingEffects.clear();
34486
34851
  this.conversation.reset();
34487
34852
  this.usage.clear();
34853
+ this.windows.clear();
34488
34854
  this.approvals.clear(isPlannerApproval);
34489
34855
  if (!keepGrants) this.approvalPolicy.reset();
34490
34856
  this.store.clearLog();
@@ -34825,7 +35191,18 @@ var Session = class {
34825
35191
  if (!this.plan) throw new ConversationEditError("No planning conversation to condense");
34826
35192
  const opening = this.plannerTurnMayOpen(signal);
34827
35193
  if (opening) await opening;
34828
- return this.conversation.compact(signal);
35194
+ const compaction = await this.conversation.compact(signal);
35195
+ const usage = this.usage.snapshot();
35196
+ if (usage.lastPromptTokens !== void 0) {
35197
+ this.usage.record({
35198
+ source: usage.source ?? this.config.aiProvider,
35199
+ ...usage.model ? { model: usage.model } : {},
35200
+ compacted: true
35201
+ });
35202
+ this.persist();
35203
+ this.broadcast(this.usage.message());
35204
+ }
35205
+ return compaction;
34829
35206
  }
34830
35207
  rewindTargets() {
34831
35208
  return this.conversation.rewindTargets();
@@ -34912,7 +35289,7 @@ var Session = class {
34912
35289
  */
34913
35290
  adoptPlannerTasks(tasks, how) {
34914
35291
  this.turnEffects.landed(how);
34915
- for (const runner of runnersOf(tasks)) this.editor.admitRunner(runner, this.catalog.known(runner));
35292
+ for (const runner of runnersOf2(tasks)) this.editor.admitRunner(runner, this.catalog.known(runner));
34916
35293
  const runners = this.plan.runners;
34917
35294
  const before = this.store.planTasks;
34918
35295
  let coerced = resolveAssignments(tasks, before, this.catalog.edit(runners)).tasks;
@@ -35313,7 +35690,10 @@ var Session = class {
35313
35690
  if (!plan.approvedTasks && plan.approved) {
35314
35691
  plan.approvedTasks = flattenTasks(plan.tasks).map((t) => t.id);
35315
35692
  }
35316
- if (adopting) this.usage.restore(plan.plannerUsage);
35693
+ if (adopting) {
35694
+ this.usage.restore(plan.plannerUsage);
35695
+ this.windows.restore(plan.contextWindows);
35696
+ }
35317
35697
  if (opts?.sessionId) this.currentSessionId = opts.sessionId;
35318
35698
  if (adopting) {
35319
35699
  for (const task of flattenTasks(plan.tasks)) delete task.resumeAt;
@@ -35395,7 +35775,7 @@ function openTurn(view, { turnId, prompt = "", skills = [], wakeUps = [], forced
35395
35775
  wakeUps = wakeUps.slice(adopted.wakeUps);
35396
35776
  if (wakeUps.length === 0) return adopted.prompt || prompt === "" && adopted.wakeUps > 0 ? view : promptLine(view, turnId, prompt, skills, forced);
35397
35777
  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);
35778
+ 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
35779
  const updates = wakeUps.map((text3, n) => settledMessage(`b${said2.nextId + n}`, "wake_up", text3, turnId));
35400
35780
  return { ...said2, blocks: [...said2.blocks.slice(0, at), ...updates, ...said2.blocks.slice(at)], nextId: said2.nextId + updates.length };
35401
35781
  }
@@ -36082,17 +36462,12 @@ var AbstractRunner = class {
36082
36462
  get activeCount() {
36083
36463
  return this.sessions.size;
36084
36464
  }
36465
+ /** A session winding down stays registered until it exits, so a second stop gets the same wind-down. */
36085
36466
  stop(sessionId) {
36086
- const session = this.sessions.get(sessionId);
36087
- if (!session) return;
36088
- session.kill();
36089
- this.sessions.delete(sessionId);
36467
+ return this.sessions.get(sessionId)?.stop() ?? Promise.resolve();
36090
36468
  }
36091
36469
  stopAll() {
36092
- for (const [, session] of this.sessions) {
36093
- session.kill();
36094
- }
36095
- this.sessions.clear();
36470
+ for (const session of this.sessions.values()) void session.stop();
36096
36471
  }
36097
36472
  registerSession(id, session) {
36098
36473
  this.sessions.set(id, session);
@@ -36250,6 +36625,8 @@ var StructuredSession = class extends AbstractRunnerSession {
36250
36625
  queue = [];
36251
36626
  messageCount = 0;
36252
36627
  interrupting = null;
36628
+ /** The wind-down once {@link stop} was asked for: the same for every caller, and nothing new is sent meanwhile. */
36629
+ stopping = null;
36253
36630
  lastSessionId = null;
36254
36631
  lastSessionCost = null;
36255
36632
  structuredEmitter = new import_events2.EventEmitter();
@@ -36275,7 +36652,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36275
36652
  await this.startAttached(this.launch.startOptions);
36276
36653
  this.state = "working";
36277
36654
  setImmediate(() => {
36278
- if (!this.exited) this.deliver(prompt);
36655
+ if (!this.exited && !this.stopping) this.deliver(prompt);
36279
36656
  });
36280
36657
  }
36281
36658
  getOutput() {
@@ -36355,6 +36732,53 @@ var StructuredSession = class extends AbstractRunnerSession {
36355
36732
  this.withdrawPermissions();
36356
36733
  this.baseHandleExit(-1);
36357
36734
  }
36735
+ stop() {
36736
+ if (this.stopping) return this.stopping;
36737
+ if (this.exited) return Promise.resolve();
36738
+ let woundDown;
36739
+ this.stopping = new Promise((resolve9) => {
36740
+ woundDown = resolve9;
36741
+ });
36742
+ void this.windDown().catch(() => this.kill()).then(woundDown);
36743
+ return this.stopping;
36744
+ }
36745
+ /**
36746
+ * The attempt is over (ADR-0018, L1), so what is still owed goes undelivered
36747
+ * and every tool request unanswered at once. A turn still open is
36748
+ * interrupted, and its close — where Claude Code reports the turn's cost and
36749
+ * its model's window — is waited for; then the runner is told nothing more
36750
+ * is coming and left to exit by itself. All of it within the interrupt
36751
+ * grace: a runner still there once it runs out is killed.
36752
+ */
36753
+ async windDown() {
36754
+ const adapter = this.adapterAttached ? this.adapter : null;
36755
+ const deadline = Date.now() + this.launch.interruptGraceMs;
36756
+ this.revokeTools();
36757
+ this.undeliverQueued();
36758
+ this.withdrawPermissions();
36759
+ const open = this.turn && !this.turn.ended ? this.turn : null;
36760
+ if (adapter && (!open || await this.turnClosed(open, adapter, deadline)) && adapter.endInput && !this.exited) {
36761
+ adapter.endInput();
36762
+ await this.within(this.gone(), deadline);
36763
+ }
36764
+ if (this.exited) adapter?.dispose();
36765
+ else this.kill();
36766
+ }
36767
+ /** Whether `turn` closed within the deadline once interrupted. */
36768
+ async turnClosed(turn, adapter, deadline) {
36769
+ const closed = new Promise((resolve9) => this.structuredEmitter.once("turnEnd", () => resolve9()));
36770
+ adapter.interrupt(Math.max(0, deadline - Date.now())).catch(() => false);
36771
+ return await this.within(closed, deadline) && turn.ended;
36772
+ }
36773
+ /** Whether `done` settled before the deadline, or the session ended meanwhile. */
36774
+ within(done, deadline) {
36775
+ return settleWithin(done.then(() => true), { timeoutMs: Math.max(0, deadline - Date.now()), onTimeout: () => false, ended: this.gone(), onEnded: () => true });
36776
+ }
36777
+ gone() {
36778
+ const { signal } = this.ended;
36779
+ if (signal.aborted) return Promise.resolve();
36780
+ return new Promise((resolve9) => signal.addEventListener("abort", () => resolve9(), { once: true }));
36781
+ }
36358
36782
  turnState() {
36359
36783
  return this.state;
36360
36784
  }
@@ -36380,7 +36804,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36380
36804
  this.messageCount += 1;
36381
36805
  const id = `msg-${this.messageCount}`;
36382
36806
  const sender2 = opening.from ? { from: opening.from } : {};
36383
- if (this.exited) this.emitEvent({ type: "message_undelivered", messageId: id, text: text3, ...sender2 });
36807
+ if (this.exited || this.stopping) this.emitEvent({ type: "message_undelivered", messageId: id, text: text3, ...sender2 });
36384
36808
  else if (this.state === "idle" && this.adapterStarted && !this.sending) this.deliver(text3, opening);
36385
36809
  else {
36386
36810
  this.state = "working";
@@ -36435,7 +36859,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36435
36859
  });
36436
36860
  }
36437
36861
  runningTurn() {
36438
- return !this.exited && this.adapter !== null && this.turn !== null && !this.turn.ended;
36862
+ return !this.exited && !this.stopping && this.adapter !== null && this.turn !== null && !this.turn.ended;
36439
36863
  }
36440
36864
  /** No runner can recall a message once offered to it, so only a queued one comes back. */
36441
36865
  removeQueued(id) {
@@ -36496,7 +36920,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36496
36920
  if (this.interrupting) return this.interrupting;
36497
36921
  const turn = this.turn;
36498
36922
  const adapter = this.adapter;
36499
- if (!turn || turn.ended || !adapter) return Promise.resolve();
36923
+ if (!turn || turn.ended || !adapter || this.stopping) return Promise.resolve();
36500
36924
  this.interrupting = this.interruptTurn(turn, adapter).finally(() => {
36501
36925
  this.interrupting = null;
36502
36926
  this.offerNext();
@@ -36510,7 +36934,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36510
36934
  const settled = await settleWithin(ended.then(() => true), { timeoutMs: grace, onTimeout: () => false });
36511
36935
  if (settled) return;
36512
36936
  }
36513
- if (turn.ended || this.exited) return;
36937
+ if (turn.ended || this.exited || this.stopping) return;
36514
36938
  await this.restartInterrupted(turn);
36515
36939
  }
36516
36940
  /**
@@ -36614,7 +37038,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36614
37038
  offerNext() {
36615
37039
  const adapter = this.adapter;
36616
37040
  const turn = this.turn;
36617
- if (!adapter?.steer || !turn || turn.ended || turn.steerRefused || this.interrupting || this.exited) return;
37041
+ if (!adapter?.steer || !turn || turn.ended || turn.steerRefused || this.interrupting || this.exited || this.stopping) return;
36618
37042
  if (this.queue.some((m) => m.stage === "steering")) return;
36619
37043
  const next = this.queue.find((m) => m.stage === "queued");
36620
37044
  if (!next || next.command) return;
@@ -36689,7 +37113,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36689
37113
  }
36690
37114
  return;
36691
37115
  }
36692
- if (!turn && TURN_OPENING_EVENTS.has(event.type) && !("subagentId" in event && event.subagentId)) {
37116
+ if (!turn && !this.stopping && TURN_OPENING_EVENTS.has(event.type) && !("subagentId" in event && event.subagentId)) {
36693
37117
  turn = this.openTurn("");
36694
37118
  this.offerNext();
36695
37119
  }
@@ -36760,7 +37184,7 @@ var StructuredSession = class extends AbstractRunnerSession {
36760
37184
  * back in Ordewell's hands: while it is on offer or owed, nothing overtakes it.
36761
37185
  */
36762
37186
  deliverNext() {
36763
- if (this.exited || this.sending || this.turn && !this.turn.ended) return;
37187
+ if (this.exited || this.stopping || this.sending || this.turn && !this.turn.ended) return;
36764
37188
  const head = this.queue[0];
36765
37189
  if (!head || this.queue.some((message) => message.stage !== "queued")) return;
36766
37190
  this.queue.shift();
@@ -37210,7 +37634,10 @@ var PlannerModelMemory = class {
37210
37634
  collectProviderCredentials,
37211
37635
  commandAllowedBy,
37212
37636
  configuredProviders,
37637
+ contextMeterText,
37638
+ contextMeterTitle,
37213
37639
  continuability,
37640
+ costText,
37214
37641
  countedSessionCost,
37215
37642
  createAiService,
37216
37643
  createEmptyPlan,
@@ -37253,7 +37680,9 @@ var PlannerModelMemory = class {
37253
37680
  flattenTasksWithParents,
37254
37681
  followTaskLog,
37255
37682
  followTurn,
37683
+ formatCost,
37256
37684
  formatSearchOutput,
37685
+ formatTokenCount,
37257
37686
  fromTranscript,
37258
37687
  getLatestSession,
37259
37688
  getProviderMeta,
@@ -37305,6 +37734,7 @@ var PlannerModelMemory = class {
37305
37734
  migratePlanState,
37306
37735
  mintDaemonToken,
37307
37736
  mintSessionId,
37737
+ modelCostTitle,
37308
37738
  namedRepos,
37309
37739
  newTaskFields,
37310
37740
  normalizeAgentArgs,
@@ -37390,6 +37820,7 @@ var PlannerModelMemory = class {
37390
37820
  stopTurn,
37391
37821
  stripAnsi,
37392
37822
  stripModelPrefix,
37823
+ subagentCostTitle,
37393
37824
  subtreeIds,
37394
37825
  summarizeToolCall,
37395
37826
  supervisorSummary,
@@ -37408,6 +37839,7 @@ var PlannerModelMemory = class {
37408
37839
  taskStartedNotice,
37409
37840
  taskStatusKind,
37410
37841
  taskUsageSummary,
37842
+ tasksTotalText,
37411
37843
  textHasTaskOps,
37412
37844
  textHasTaskQuery,
37413
37845
  titledRefs,
@@ -37425,6 +37857,7 @@ var PlannerModelMemory = class {
37425
37857
  unsendAll,
37426
37858
  unsendLatest,
37427
37859
  usageLine,
37860
+ usageText,
37428
37861
  waitsForApproval,
37429
37862
  walkPlan,
37430
37863
  wellKnownBinDirs,