@ordewell/core 0.10.8 → 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.
- package/dist/{IFileSystem-C4_mpfze.d.ts → IFileSystem-1PqRCjhu.d.mts} +23 -3
- package/dist/{IFileSystem-DUgeWWQN.d.mts → IFileSystem-lTRL_G9Z.d.ts} +23 -3
- package/dist/{ModeResolver-4qNjxcDs.d.mts → ModeResolver-6TQXVsde.d.mts} +1 -1
- package/dist/{ModeResolver-DGK2rWHE.d.ts → ModeResolver-CoJAJD-f.d.ts} +1 -1
- package/dist/{Task-C0o4i3Nn.d.mts → Task-CfBM0wkV.d.mts} +100 -24
- package/dist/{Task-C0o4i3Nn.d.ts → Task-CfBM0wkV.d.ts} +100 -24
- package/dist/{chunk-H7QX32BH.mjs → chunk-24EL4Z2W.mjs} +2 -2
- package/dist/{chunk-DMG4ISUG.mjs → chunk-ESKK5I4F.mjs} +145 -21
- package/dist/chunk-ESKK5I4F.mjs.map +1 -0
- package/dist/{chunk-LTHKFBKM.mjs → chunk-KWS3VEER.mjs} +3 -3
- package/dist/{chunk-EEE2GBLG.mjs → chunk-PFH7WFIB.mjs} +1 -1
- package/dist/chunk-PFH7WFIB.mjs.map +1 -0
- package/dist/index.d.mts +145 -20
- package/dist/index.d.ts +145 -20
- package/dist/index.js +668 -112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +537 -96
- package/dist/index.mjs.map +1 -1
- package/dist/parsing.d.mts +2 -2
- package/dist/parsing.d.ts +2 -2
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -3
- package/dist/plan-utils.d.mts +4 -4
- package/dist/plan-utils.d.ts +4 -4
- package/dist/plan-utils.js +144 -18
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +22 -4
- package/dist/{runSettingsText-BgOmLxdw.d.mts → runSettingsText-C1i9wBG1.d.mts} +61 -21
- package/dist/{runSettingsText-BgXvajj2.d.ts → runSettingsText-CWNrmF5U.d.ts} +61 -21
- package/dist/testing.d.mts +7 -2
- package/dist/testing.d.ts +7 -2
- package/dist/testing.js +11 -0
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +12 -1
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-DMG4ISUG.mjs.map +0 -1
- package/dist/chunk-EEE2GBLG.mjs.map +0 -1
- /package/dist/{chunk-H7QX32BH.mjs.map → chunk-24EL4Z2W.mjs.map} +0 -0
- /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
|
-
|
|
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
|
-
|
|
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 (
|
|
894
|
-
if (record.source !== usage.source
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
next.
|
|
899
|
-
next.
|
|
939
|
+
if (record.aggregate) {
|
|
940
|
+
if (record.source !== usage.source && !record.sessionBilling) delete next.sessionBilling;
|
|
941
|
+
return next;
|
|
942
|
+
}
|
|
943
|
+
if (record.source !== usage.source || record.model !== usage.model) {
|
|
944
|
+
delete next.lastPromptTokens;
|
|
945
|
+
delete next.contextWindow;
|
|
900
946
|
}
|
|
901
|
-
if (record.
|
|
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
|
|
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
|
|
938
|
-
wallTimeMs: Math.max(0, Date.parse(
|
|
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
|
-
|
|
952
|
-
for (const [currency, amount] of Object.entries(addition.reportedCost ?? {}))
|
|
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);
|
|
@@ -10811,6 +10928,8 @@ var RunnerProcess = class {
|
|
|
10811
10928
|
markEnded;
|
|
10812
10929
|
child = null;
|
|
10813
10930
|
exitStatus = null;
|
|
10931
|
+
/** The process itself reported its end, before its pipes drained; see {@link hasExited}. */
|
|
10932
|
+
exitReported = false;
|
|
10814
10933
|
stderrTail = "";
|
|
10815
10934
|
spawnEnv = {};
|
|
10816
10935
|
/** Stdout readers' end-of-stream flushes, for an end the pipe itself never reports. */
|
|
@@ -10861,6 +10980,7 @@ var RunnerProcess = class {
|
|
|
10861
10980
|
});
|
|
10862
10981
|
child.on("close", (code, signal) => this.end({ code, signal }));
|
|
10863
10982
|
child.once("exit", (code, signal) => {
|
|
10983
|
+
this.exitReported = true;
|
|
10864
10984
|
const grace = setTimeout(() => {
|
|
10865
10985
|
for (const flush of [...this.stdoutEnds]) flush();
|
|
10866
10986
|
this.end({ code, signal });
|
|
@@ -10915,6 +11035,15 @@ ${err.message}`);
|
|
|
10915
11035
|
get exit() {
|
|
10916
11036
|
return this.exitStatus;
|
|
10917
11037
|
}
|
|
11038
|
+
/**
|
|
11039
|
+
* Whether the process has ended, for any reason. Unlike {@link exit} this is
|
|
11040
|
+
* true from its own 'exit', not only once 'close' has drained the pipes: a
|
|
11041
|
+
* runner that has exited can take no further turn even while a descendant
|
|
11042
|
+
* holds its stdout or stderr.
|
|
11043
|
+
*/
|
|
11044
|
+
get hasExited() {
|
|
11045
|
+
return this.exitStatus !== null || this.exitReported;
|
|
11046
|
+
}
|
|
10918
11047
|
/** The exit code a listener is told: -1 for a signal or a spawn failure. */
|
|
10919
11048
|
get exitCode() {
|
|
10920
11049
|
return this.exitStatus?.code ?? -1;
|
|
@@ -11113,6 +11242,9 @@ var StdioAgentAdapter = class {
|
|
|
11113
11242
|
nativeSessionId() {
|
|
11114
11243
|
return this.sessionId;
|
|
11115
11244
|
}
|
|
11245
|
+
get exited() {
|
|
11246
|
+
return this.runner.hasExited;
|
|
11247
|
+
}
|
|
11116
11248
|
onProcessExit(listener) {
|
|
11117
11249
|
const fire = () => listener(this.runner.exitCode, this.exitMessage());
|
|
11118
11250
|
if (this.runner.exit) queueMicrotask(fire);
|
|
@@ -11613,6 +11745,14 @@ function canUseToolAnswer({ input, suggestions }, decision) {
|
|
|
11613
11745
|
}
|
|
11614
11746
|
var MODEL_COMMAND = "model";
|
|
11615
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
|
+
}
|
|
11616
11756
|
function usageRecord(usage, model, subagentId) {
|
|
11617
11757
|
const record = {
|
|
11618
11758
|
source: "claude-code",
|
|
@@ -11621,6 +11761,7 @@ function usageRecord(usage, model, subagentId) {
|
|
|
11621
11761
|
if (model) record.model = model;
|
|
11622
11762
|
if (usage.output_tokens !== void 0) record.outputTokens = usage.output_tokens;
|
|
11623
11763
|
if (subagentId) record.subagentId = subagentId;
|
|
11764
|
+
else if (record.inputTokens !== void 0) record.contextTokens = record.inputTokens + (record.outputTokens ?? 0);
|
|
11624
11765
|
return record;
|
|
11625
11766
|
}
|
|
11626
11767
|
function subagentFinalCall(result) {
|
|
@@ -11670,16 +11811,27 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11670
11811
|
replyRuns = new ReplyRuns();
|
|
11671
11812
|
/** The model answering the planner's current message, and the message's id, as its `message_start` named them. */
|
|
11672
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;
|
|
11673
11820
|
streamingMessage;
|
|
11674
11821
|
/** The message the last `assistant` line was part of, and how many of its blocks the lines have carried. */
|
|
11675
11822
|
lineMessage = null;
|
|
11676
11823
|
/**
|
|
11677
|
-
*
|
|
11678
|
-
*
|
|
11679
|
-
*
|
|
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.
|
|
11680
11830
|
*/
|
|
11681
11831
|
reportedCostUsd = 0;
|
|
11682
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 };
|
|
11683
11835
|
/**
|
|
11684
11836
|
* Subagents started and not yet finished, keyed by the `Agent` call's id.
|
|
11685
11837
|
* Kept across turns: a backgrounded one reports after its turn has ended.
|
|
@@ -11766,6 +11918,14 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11766
11918
|
if (!sessionId || this.reportedCostUsd === void 0) return null;
|
|
11767
11919
|
return { sessionId, usd: this.reportedCostUsd, modelUsd: Object.fromEntries(this.reportedModelCostUsd) };
|
|
11768
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
|
+
}
|
|
11769
11929
|
/** A count kept for another session says nothing of this one's. */
|
|
11770
11930
|
countFrom(sessionId, counted) {
|
|
11771
11931
|
const known = counted?.sessionId === sessionId ? counted : void 0;
|
|
@@ -11839,11 +11999,10 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11839
11999
|
* the alias resolves to now; that id is pinned before the first turn.
|
|
11840
12000
|
*/
|
|
11841
12001
|
async handshake(opts) {
|
|
11842
|
-
if (opts.kind !== "task") return;
|
|
11843
12002
|
const initialized = (await this.control({ subtype: "initialize" }, MODEL_PIN_TIMEOUT_MS))?.response;
|
|
11844
12003
|
this.listedCommands = initialized?.commands ?? [];
|
|
11845
12004
|
this.listedModels = initialized?.models ?? [];
|
|
11846
|
-
if (opts.flags.permissionMode !== "plan" || !opts.model) return;
|
|
12005
|
+
if (opts.kind !== "task" || opts.flags.permissionMode !== "plan" || !opts.model) return;
|
|
11847
12006
|
const resolved = initialized?.models?.find((m) => m.value === opts.model)?.resolvedModel;
|
|
11848
12007
|
if (resolved && resolved !== opts.model) await this.control({ subtype: "set_model", model: resolved }, MODEL_PIN_TIMEOUT_MS);
|
|
11849
12008
|
}
|
|
@@ -11884,6 +12043,15 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11884
12043
|
this.interruptRequested = true;
|
|
11885
12044
|
return (await this.control({ subtype: "interrupt" }, timeoutMs))?.subtype === "success";
|
|
11886
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
|
+
}
|
|
11887
12055
|
/**
|
|
11888
12056
|
* A `user` line written mid-turn is Claude Code's own queue (ADR-0023): the
|
|
11889
12057
|
* CLI attaches it to the next tool result, or runs it as a turn of its own
|
|
@@ -11971,6 +12139,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
11971
12139
|
this.resultHeld = false;
|
|
11972
12140
|
this.turnClosed = false;
|
|
11973
12141
|
this.clearFollowOnTimer();
|
|
12142
|
+
this.plannerModel = void 0;
|
|
11974
12143
|
}
|
|
11975
12144
|
/**
|
|
11976
12145
|
* The echo of a steered message: the model has it. After a closed turn it
|
|
@@ -12025,6 +12194,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12025
12194
|
this.permissions.withdraw(msg.request_id, emit);
|
|
12026
12195
|
return;
|
|
12027
12196
|
case "control_response": {
|
|
12197
|
+
const account = msg.response?.response?.account;
|
|
12198
|
+
if (account) this.reportBilling(account.apiKeySource, account.subscriptionType, emit);
|
|
12028
12199
|
const requestId = msg.response?.request_id;
|
|
12029
12200
|
if (requestId) this.pendingControl.get(requestId)?.(msg.response ?? null);
|
|
12030
12201
|
return;
|
|
@@ -12068,12 +12239,24 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12068
12239
|
emit({ type: "tool_result", id, name: "", output: diff || output, success: block.is_error !== true, subagentId });
|
|
12069
12240
|
}
|
|
12070
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
|
+
}
|
|
12071
12251
|
case "system":
|
|
12252
|
+
if (subagentId && (msg.subtype === "init" || msg.subtype === "compact_boundary")) return;
|
|
12072
12253
|
if (msg.subtype === "init") {
|
|
12073
12254
|
this.clearFollowOnTimer();
|
|
12074
12255
|
if (this.refuseOtherMode(msg, emit)) return;
|
|
12256
|
+
if (msg.model && msg.model !== "<synthetic>") this.sessionModel = msg.model;
|
|
12075
12257
|
this.readLoaded(msg, emit);
|
|
12076
12258
|
this.followModel(msg.model, emit);
|
|
12259
|
+
this.reportBilling(msg.apiKeySource, void 0, emit);
|
|
12077
12260
|
}
|
|
12078
12261
|
if (msg.subtype === "commands_changed" && Array.isArray(msg.commands)) this.listedCommands = msg.commands;
|
|
12079
12262
|
if (msg.subtype === "compact_boundary") {
|
|
@@ -12084,6 +12267,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12084
12267
|
...typeof meta?.pre_tokens === "number" ? { preTokens: meta.pre_tokens } : {},
|
|
12085
12268
|
...typeof meta?.post_tokens === "number" ? { postTokens: meta.post_tokens } : {}
|
|
12086
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 } : {} } });
|
|
12087
12272
|
}
|
|
12088
12273
|
if (msg.subtype === "background_tasks_changed" && Array.isArray(msg.tasks)) {
|
|
12089
12274
|
this.backgroundTaskCount = msg.tasks.length;
|
|
@@ -12095,6 +12280,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12095
12280
|
}
|
|
12096
12281
|
return;
|
|
12097
12282
|
case "result":
|
|
12283
|
+
if (subagentId) return;
|
|
12098
12284
|
this.reportSessionCost(msg, emit);
|
|
12099
12285
|
for (const command of this.modelCommands) command.turnsLeft = Math.max(0, command.turnsLeft - 1);
|
|
12100
12286
|
if (this.interruptRequested && (msg.is_error || msg.subtype !== "success")) {
|
|
@@ -12259,16 +12445,69 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12259
12445
|
*/
|
|
12260
12446
|
handleStreamEvent(event, emit) {
|
|
12261
12447
|
if (event.type === "message_start") {
|
|
12262
|
-
this.plannerModel = event.message?.model;
|
|
12448
|
+
this.plannerModel = this.modelId(event.message?.model);
|
|
12263
12449
|
this.streamingMessage = event.message?.id;
|
|
12264
12450
|
this.followModel(this.plannerModel, emit);
|
|
12265
|
-
} else if (event.type === "message_delta" && event.usage)
|
|
12266
|
-
|
|
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) {
|
|
12267
12458
|
this.replyRuns.delta(`${this.streamingMessage ?? ""}:${event.index ?? 0}`, event.delta.text, emit);
|
|
12268
12459
|
} else if (event.type === "content_block_delta" && event.delta?.type === "thinking_delta" && event.delta.thinking) {
|
|
12269
12460
|
emit({ type: "thinking_delta", text: event.delta.thinking });
|
|
12270
12461
|
}
|
|
12271
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
|
+
}
|
|
12272
12511
|
/**
|
|
12273
12512
|
* What only the result line knows: the cost, which covers every call the
|
|
12274
12513
|
* session made — subagents included, since none of their lines carries one —
|
|
@@ -12277,24 +12516,54 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12277
12516
|
* the session before; with no count kept, the first turn only sets the
|
|
12278
12517
|
* baseline: its total includes turns counted before, and a turn's own share
|
|
12279
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.
|
|
12280
12532
|
*/
|
|
12281
12533
|
reportSessionCost(msg, emit) {
|
|
12534
|
+
this.reportUnstreamedUsage(msg, emit);
|
|
12282
12535
|
const total = msg.total_cost_usd;
|
|
12283
12536
|
const canReport = this.reportedCostUsd !== void 0;
|
|
12284
|
-
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
|
+
};
|
|
12285
12545
|
if (modelCosts.length > 0) {
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
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
|
+
}
|
|
12291
12553
|
}
|
|
12292
|
-
|
|
12293
|
-
|
|
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 });
|
|
12294
12557
|
}
|
|
12295
|
-
if (
|
|
12296
|
-
|
|
12297
|
-
|
|
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;
|
|
12563
|
+
}
|
|
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 } });
|
|
12298
12567
|
}
|
|
12299
12568
|
};
|
|
12300
12569
|
|
|
@@ -12929,9 +13198,13 @@ function mergeCodexRateLimits(known, params) {
|
|
|
12929
13198
|
const id = snapshot.limitId ?? "";
|
|
12930
13199
|
const before = known.get(id);
|
|
12931
13200
|
const next = new Map(known);
|
|
12932
|
-
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 });
|
|
12933
13202
|
return next;
|
|
12934
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
|
+
}
|
|
12935
13208
|
function isCodexUsageLimit(error) {
|
|
12936
13209
|
const info = error?.codexErrorInfo;
|
|
12937
13210
|
if (info === void 0 || info === null || info === "other") return void 0;
|
|
@@ -13490,8 +13763,21 @@ ${this.exitMessage()}`);
|
|
|
13490
13763
|
case "thread/tokenUsage/updated":
|
|
13491
13764
|
this.emitUsage(msg.params, emit);
|
|
13492
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
|
+
}
|
|
13493
13775
|
case "account/rateLimits/updated":
|
|
13494
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
|
+
}
|
|
13495
13781
|
return;
|
|
13496
13782
|
case "turn/started": {
|
|
13497
13783
|
if (this.subagentOf(msg.params?.threadId)) return;
|
|
@@ -13620,6 +13906,13 @@ ${this.exitMessage()}`);
|
|
|
13620
13906
|
* `last.outputTokens` already includes `reasoningOutputTokens` — the thread
|
|
13621
13907
|
* total is `inputTokens + outputTokens`, not a sum of three — so reasoning is
|
|
13622
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`).
|
|
13623
13916
|
*/
|
|
13624
13917
|
emitUsage(params, emit) {
|
|
13625
13918
|
const last = params?.tokenUsage?.last;
|
|
@@ -13647,6 +13940,8 @@ ${this.exitMessage()}`);
|
|
|
13647
13940
|
} else {
|
|
13648
13941
|
const window = params?.tokenUsage?.modelContextWindow;
|
|
13649
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);
|
|
13650
13945
|
}
|
|
13651
13946
|
emit({ type: "usage", record });
|
|
13652
13947
|
}
|
|
@@ -13808,7 +14103,9 @@ ${this.exitMessage()}`);
|
|
|
13808
14103
|
}
|
|
13809
14104
|
switch (item.type) {
|
|
13810
14105
|
case "contextCompaction":
|
|
13811
|
-
if (
|
|
14106
|
+
if (subagentId) return;
|
|
14107
|
+
emit({ type: "compaction", trigger: this.command === "compact" ? "manual" : "auto" });
|
|
14108
|
+
emit({ type: "usage", record: { source: this.agentId, compacted: true } });
|
|
13812
14109
|
return;
|
|
13813
14110
|
// Codex announces the item started and completed; whichever lands first delivers.
|
|
13814
14111
|
case "userMessage":
|
|
@@ -14104,8 +14401,9 @@ function usageRecord2(tokens, call) {
|
|
|
14104
14401
|
if ((prompt.inputTokens ?? 0) + outputTokens === 0) return null;
|
|
14105
14402
|
const record = { source: "opencode", ...prompt, outputTokens };
|
|
14106
14403
|
if (call.model) record.model = call.model;
|
|
14107
|
-
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" };
|
|
14108
14405
|
if (call.subagentId) record.subagentId = call.subagentId;
|
|
14406
|
+
else record.contextTokens = (prompt.inputTokens ?? 0) + outputTokens;
|
|
14109
14407
|
return record;
|
|
14110
14408
|
}
|
|
14111
14409
|
function permissionReply(decision, key) {
|
|
@@ -14668,6 +14966,7 @@ var OpenCodeV2 = class {
|
|
|
14668
14966
|
}
|
|
14669
14967
|
if (frame.type === "session.compaction.ended") {
|
|
14670
14968
|
onEvent({ type: "compaction", trigger: data.reason === "manual" ? "manual" : "auto" });
|
|
14969
|
+
onEvent({ type: "usage", record: { source: "opencode", compacted: true } });
|
|
14671
14970
|
return;
|
|
14672
14971
|
}
|
|
14673
14972
|
if (frame.type === "session.execution.started") {
|
|
@@ -14926,7 +15225,7 @@ var OpenCodeAdapter = class {
|
|
|
14926
15225
|
return this.runner.ended;
|
|
14927
15226
|
}
|
|
14928
15227
|
get exited() {
|
|
14929
|
-
return this.runner.
|
|
15228
|
+
return this.runner.hasExited;
|
|
14930
15229
|
}
|
|
14931
15230
|
async start(opts) {
|
|
14932
15231
|
try {
|
|
@@ -15533,6 +15832,7 @@ var OpenCodeAdapter = class {
|
|
|
15533
15832
|
if (session && session !== this.sessionId && !turn.children.follows(session)) return;
|
|
15534
15833
|
if (frame.type === "session.compacted" && session === this.sessionId) {
|
|
15535
15834
|
onEvent({ type: "compaction", trigger: this.manualCompaction ? "manual" : "auto" });
|
|
15835
|
+
onEvent({ type: "usage", record: { source: "opencode", compacted: true } });
|
|
15536
15836
|
return;
|
|
15537
15837
|
}
|
|
15538
15838
|
if (session === this.sessionId && this.taskTurn) this.followTaskTurn(frame, this.taskTurn, onEvent);
|
|
@@ -20555,6 +20855,7 @@ var PlannerConversation = class {
|
|
|
20555
20855
|
const request = summaryRequest(this.currentPlanLines());
|
|
20556
20856
|
const onProgress = (p) => {
|
|
20557
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 } });
|
|
20558
20859
|
};
|
|
20559
20860
|
let turn;
|
|
20560
20861
|
if (this.canContinueLive()) {
|
|
@@ -23103,10 +23404,21 @@ var GitWorktreeIsolation = class {
|
|
|
23103
23404
|
const entry = record.repos[repo.path];
|
|
23104
23405
|
if (!entry?.changed) continue;
|
|
23105
23406
|
const merged = await this.tryGit(repo.root, ["merge-base", "--is-ancestor", entry.landedTip ?? repo.integrationBranch, "HEAD"]);
|
|
23106
|
-
if (!merged.ok) return false;
|
|
23407
|
+
if (!merged.ok && !await this.integrationGone(repo)) return false;
|
|
23107
23408
|
}
|
|
23108
23409
|
return true;
|
|
23109
23410
|
}
|
|
23411
|
+
/**
|
|
23412
|
+
* The repo's integration branch is gone — deleted by hand, or by a squash
|
|
23413
|
+
* merge's clean-up — in a repo git can read. Merge all merges that branch by
|
|
23414
|
+
* name, so no merge can bring the work in again, and it is not offered as
|
|
23415
|
+
* work to merge. A repo git cannot read at all (moved, or git failing)
|
|
23416
|
+
* proves nothing either way and counts as not merged.
|
|
23417
|
+
*/
|
|
23418
|
+
async integrationGone(repo) {
|
|
23419
|
+
if (!(await this.tryGit(repo.root, ["rev-parse", "--verify", "-q", "HEAD"])).ok) return false;
|
|
23420
|
+
return !(await this.tryGit(repo.root, ["rev-parse", "--verify", "-q", `refs/heads/${repo.integrationBranch}`])).ok;
|
|
23421
|
+
}
|
|
23110
23422
|
async snapshotTree(workspaceRoot, exclude) {
|
|
23111
23423
|
const scan = await this.scanGroup(workspaceRoot);
|
|
23112
23424
|
if ("refused" in scan) return null;
|
|
@@ -24234,6 +24546,9 @@ function nonPromptingFs(fs18) {
|
|
|
24234
24546
|
}
|
|
24235
24547
|
};
|
|
24236
24548
|
}
|
|
24549
|
+
function withoutContext({ contextTokens: _context, ...record }) {
|
|
24550
|
+
return record;
|
|
24551
|
+
}
|
|
24237
24552
|
async function runLoop(prompt, deps) {
|
|
24238
24553
|
if (deps.signal?.aborted) return "[research agent aborted before starting]";
|
|
24239
24554
|
const fs18 = nonPromptingFs(deps.fs);
|
|
@@ -24241,7 +24556,8 @@ async function runLoop(prompt, deps) {
|
|
|
24241
24556
|
(delta) => deps.onProgress?.({ type: "thinking", text: delta, subagentId: deps.subagentId }),
|
|
24242
24557
|
// The record says who made the call: unstamped, the ledger would read the
|
|
24243
24558
|
// subagent's prompt as the planner's own and measure its context by it.
|
|
24244
|
-
|
|
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 })
|
|
24245
24561
|
);
|
|
24246
24562
|
let turn = await chat.sendMessage(prompt, deps.signal);
|
|
24247
24563
|
for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
|
|
@@ -24749,7 +25065,8 @@ function usageRecordFrom(usage, model) {
|
|
|
24749
25065
|
model,
|
|
24750
25066
|
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
24751
25067
|
...reportedOutput.length > 0 ? { outputTokens: reportedOutput.reduce((a, b) => a + b, 0) } : {},
|
|
24752
|
-
...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) } : {}
|
|
24753
25070
|
};
|
|
24754
25071
|
const anyReported = record.inputTokens !== void 0 || record.outputTokens !== void 0 || record.cachedInputTokens !== void 0;
|
|
24755
25072
|
return anyReported ? record : void 0;
|
|
@@ -25152,7 +25469,9 @@ var OpenAiResearchChat = class {
|
|
|
25152
25469
|
/**
|
|
25153
25470
|
* The one record for this API call, built only from what the provider
|
|
25154
25471
|
* reported. OpenRouter's `cost` is in USD (its credits are dollar-pegged);
|
|
25155
|
-
* 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.
|
|
25156
25475
|
*/
|
|
25157
25476
|
usageRecord(reported) {
|
|
25158
25477
|
if (!reported) return void 0;
|
|
@@ -25171,9 +25490,10 @@ var OpenAiResearchChat = class {
|
|
|
25171
25490
|
hasMeasure = true;
|
|
25172
25491
|
}
|
|
25173
25492
|
if (reported.cost !== void 0) {
|
|
25174
|
-
record.reportedCost = { amount: reported.cost, currency: "USD" };
|
|
25493
|
+
record.reportedCost = { amount: reported.cost, currency: "USD", ...record.source === "openrouter" ? { kind: "billed" } : {} };
|
|
25175
25494
|
hasMeasure = true;
|
|
25176
25495
|
}
|
|
25496
|
+
if (record.inputTokens !== void 0) record.contextTokens = record.inputTokens + (record.outputTokens ?? 0);
|
|
25177
25497
|
return hasMeasure ? record : void 0;
|
|
25178
25498
|
}
|
|
25179
25499
|
};
|
|
@@ -26321,6 +26641,8 @@ var IsolationRunController = class {
|
|
|
26321
26641
|
listener;
|
|
26322
26642
|
liveTasks;
|
|
26323
26643
|
run = null;
|
|
26644
|
+
/** An adoption stays scoped even while cleanup temporarily leaves no run record. */
|
|
26645
|
+
adoptions = 0;
|
|
26324
26646
|
/** Copied paths already reported for the current run: every task gets the same copies. */
|
|
26325
26647
|
reportedCopies = /* @__PURE__ */ new Set();
|
|
26326
26648
|
reportedSetupFailures = /* @__PURE__ */ new Set();
|
|
@@ -26396,18 +26718,27 @@ var IsolationRunController = class {
|
|
|
26396
26718
|
* Take over a plan's persisted isolation, or none for a plan that has not
|
|
26397
26719
|
* isolated yet. Whatever a crashed process left behind for the run — a
|
|
26398
26720
|
* worktree still marked active, a directory no record owns — is pruned,
|
|
26399
|
-
* while kept, failed and conflicted worktrees stay for the user.
|
|
26721
|
+
* while kept, failed and conflicted worktrees stay for the user. Then git is
|
|
26722
|
+
* asked which landed work the user has merged since the record was saved:
|
|
26723
|
+
* a settled run whose landed work is all in HEAD has nothing left to hand
|
|
26724
|
+
* over, so it is cleared up as Merge all would have, unless it still holds
|
|
26725
|
+
* work that never landed (ADR-0013).
|
|
26400
26726
|
*
|
|
26401
|
-
* Deliberately not reported
|
|
26402
|
-
*
|
|
26403
|
-
* would otherwise write a new session file on every reload.
|
|
26727
|
+
* Deliberately not reported to the listener: adopting is not a change of the
|
|
26728
|
+
* session's own, and a host that adopts without persisting (VS Code's
|
|
26729
|
+
* restore) would otherwise write a new session file on every reload. What
|
|
26730
|
+
* git changed is returned instead, for the caller to save where it can and
|
|
26731
|
+
* announce. A plan loaded meanwhile supersedes this adoption, which then
|
|
26732
|
+
* touches nothing and resolves `unchanged`.
|
|
26404
26733
|
*/
|
|
26405
26734
|
async adopt(state) {
|
|
26735
|
+
const adoption = ++this.adoptions;
|
|
26406
26736
|
this.run = state?.run ?? null;
|
|
26407
26737
|
this.resolvers = { ...state?.resolvers ?? {} };
|
|
26408
|
-
|
|
26738
|
+
const run = this.run;
|
|
26739
|
+
if (!run) return "unchanged";
|
|
26409
26740
|
try {
|
|
26410
|
-
const pruned = await this.isolation.pruneOrphans(
|
|
26741
|
+
const pruned = await this.isolation.pruneOrphans(run);
|
|
26411
26742
|
for (const task of pruned.kept) {
|
|
26412
26743
|
this.tell("warn", `Task "${task.title}" was still holding unlanded work when this plan was re-opened, so its worktree and branch were kept. Retry it to land the work, or review it by hand.`);
|
|
26413
26744
|
}
|
|
@@ -26415,6 +26746,24 @@ var IsolationRunController = class {
|
|
|
26415
26746
|
} catch (err) {
|
|
26416
26747
|
this.tell("warn", `Could not prune leftover worktrees: ${err instanceof Error ? err.message : String(err)}`);
|
|
26417
26748
|
}
|
|
26749
|
+
if (this.run !== run) return "unchanged";
|
|
26750
|
+
const found = await this.findLanded(run);
|
|
26751
|
+
if (this.run !== run) return "unchanged";
|
|
26752
|
+
const outcome = found ? "changed" : "unchanged";
|
|
26753
|
+
if (this.isOpen || !this.allMerged(run) || this.holdouts(run).length > 0) return outcome;
|
|
26754
|
+
const resolvers = this.resolvers;
|
|
26755
|
+
this.run = null;
|
|
26756
|
+
this.resolvers = {};
|
|
26757
|
+
const discarded = await this.discardMerged(run);
|
|
26758
|
+
if (this.adoptions !== adoption) return "unchanged";
|
|
26759
|
+
if (!discarded) {
|
|
26760
|
+
if (this.run === null) {
|
|
26761
|
+
this.run = run;
|
|
26762
|
+
this.resolvers = resolvers;
|
|
26763
|
+
}
|
|
26764
|
+
return outcome;
|
|
26765
|
+
}
|
|
26766
|
+
return this.isOpen ? "changed" : "cleared";
|
|
26418
26767
|
}
|
|
26419
26768
|
/** Open a run if none is, leaving whether it isolates to its first change task. */
|
|
26420
26769
|
open() {
|
|
@@ -26571,8 +26920,7 @@ var IsolationRunController = class {
|
|
|
26571
26920
|
const run = this.run;
|
|
26572
26921
|
if (mode !== "isolated" || !run) return;
|
|
26573
26922
|
await this.refreshInHead();
|
|
26574
|
-
|
|
26575
|
-
if (records.some((r) => r.status === "merged") && handoffOf(run).landed.length === 0) {
|
|
26923
|
+
if (this.allMerged(run)) {
|
|
26576
26924
|
this.tell("info", "Everything this run landed is merged into your branch already.");
|
|
26577
26925
|
await this.clearMerged(run);
|
|
26578
26926
|
return;
|
|
@@ -26599,11 +26947,21 @@ var IsolationRunController = class {
|
|
|
26599
26947
|
*/
|
|
26600
26948
|
async refreshInHead() {
|
|
26601
26949
|
const run = this.run;
|
|
26602
|
-
if (!run
|
|
26950
|
+
if (!run) return false;
|
|
26951
|
+
const found = await this.findLanded(run);
|
|
26952
|
+
if (found) this.listener.changed();
|
|
26953
|
+
return found;
|
|
26954
|
+
}
|
|
26955
|
+
/** Mark the run's landed work git finds in HEAD; true when some was found. */
|
|
26956
|
+
async findLanded(run) {
|
|
26957
|
+
if (!Object.values(run.tasks).some((r) => r.status === "merged" && !r.inHead)) return false;
|
|
26603
26958
|
const found = await this.isolation.findInHead(run).catch(() => []);
|
|
26604
|
-
if (found.length > 0) this.listener.changed();
|
|
26605
26959
|
return found.length > 0;
|
|
26606
26960
|
}
|
|
26961
|
+
/** The run landed something and every landing is in the user's HEAD. */
|
|
26962
|
+
allMerged(run) {
|
|
26963
|
+
return Object.values(run.tasks).some((r) => r.status === "merged") && handoffOf(run).landed.length === 0;
|
|
26964
|
+
}
|
|
26607
26965
|
/**
|
|
26608
26966
|
* The tracked state of the workspace as an attempt whose tree is checked
|
|
26609
26967
|
* starts (see `attemptKind.checksTree`), for {@link filesChangedSince}
|
|
@@ -26725,7 +27083,7 @@ var IsolationRunController = class {
|
|
|
26725
27083
|
*/
|
|
26726
27084
|
async clearMerged(run) {
|
|
26727
27085
|
const live2 = this.liveRecords(run);
|
|
26728
|
-
const unlanded =
|
|
27086
|
+
const unlanded = this.holdouts(run).filter((r) => !live2.includes(r));
|
|
26729
27087
|
if (live2.length > 0 || unlanded.length > 0) {
|
|
26730
27088
|
const why = [
|
|
26731
27089
|
...live2.length > 0 ? [`${tasksNamed(live2)} ${live2.length === 1 ? "is" : "are"} still running or waiting on you`] : [],
|
|
@@ -26735,13 +27093,22 @@ var IsolationRunController = class {
|
|
|
26735
27093
|
this.tell("info", `The run is not cleared up: ${why.join(", and ")}, so ${one ? "its worktree stays" : "their worktrees stay"}, and so do the run's branches.`);
|
|
26736
27094
|
return;
|
|
26737
27095
|
}
|
|
27096
|
+
if (await this.discardMerged(run)) this.forget();
|
|
27097
|
+
}
|
|
27098
|
+
/** The records that keep a settled run from being cleared up: live, or holding work that never landed. */
|
|
27099
|
+
holdouts(run) {
|
|
27100
|
+
const live2 = this.liveTasks();
|
|
27101
|
+
return Object.values(run.tasks).filter((r) => live2.has(r.taskId) || r.status !== "merged");
|
|
27102
|
+
}
|
|
27103
|
+
/** The run's worktrees and merged branches go; false, said, when git would not let them. */
|
|
27104
|
+
async discardMerged(run) {
|
|
26738
27105
|
try {
|
|
26739
27106
|
this.report(await this.isolation.discard(run, { integration: "delete-merged" }));
|
|
27107
|
+
return true;
|
|
26740
27108
|
} catch (err) {
|
|
26741
27109
|
this.tell("warn", `Merged, but could not clean up the run's worktrees and branches: ${err instanceof Error ? err.message : String(err)}`);
|
|
26742
|
-
return;
|
|
27110
|
+
return false;
|
|
26743
27111
|
}
|
|
26744
|
-
this.forget();
|
|
26745
27112
|
}
|
|
26746
27113
|
/** Say what a removal kept back instead of deleting: where the work went, or which worktree stayed. */
|
|
26747
27114
|
report(removal) {
|
|
@@ -27297,6 +27664,10 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
27297
27664
|
store;
|
|
27298
27665
|
output;
|
|
27299
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();
|
|
27300
27671
|
verifier = new VerdictEngine();
|
|
27301
27672
|
/** Changed only by start, stop, a plan load, a usage-limit pause, a move off the limit, and {@link settleRun}. */
|
|
27302
27673
|
runState = "idle";
|
|
@@ -27845,8 +28216,9 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
27845
28216
|
return blockingFailures(this.store.planTasks).get(taskId) ?? [];
|
|
27846
28217
|
}
|
|
27847
28218
|
loadPlan(tasks, planRunners = [...DEFAULT_RUNNERS]) {
|
|
27848
|
-
this.store.load(tasks, planRunners);
|
|
27849
28219
|
const repairs = this.endAllAttempts("load").filter((a) => a.kind.kind === "repair" && a.worktree);
|
|
28220
|
+
this.planScope += 1;
|
|
28221
|
+
this.store.load(tasks, planRunners);
|
|
27850
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}"`);
|
|
27851
28223
|
this.runs.interrupt({ keepOpen: this.isScheduling });
|
|
27852
28224
|
if (this.runState !== "scheduling") this.runState = "idle";
|
|
@@ -27932,6 +28304,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
27932
28304
|
if (reason === "user") this.stopGeneration++;
|
|
27933
28305
|
this.runState = "idle";
|
|
27934
28306
|
this.verifier.reset();
|
|
28307
|
+
for (const attempt of this.attempts.values()) this.stopRunner(attempt);
|
|
27935
28308
|
this.runner.stopAll();
|
|
27936
28309
|
const stopped = [];
|
|
27937
28310
|
for (const a of this.endAllAttempts("stop")) {
|
|
@@ -28045,6 +28418,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
28045
28418
|
if (!task || !attempt) return;
|
|
28046
28419
|
attempt.decided = true;
|
|
28047
28420
|
const stoppedMidTurn = endedByExit(verdict) ? saidInFlight(attempt) : null;
|
|
28421
|
+
this.stopRunner(attempt);
|
|
28048
28422
|
console.error(`[TaskOrchestrator] Task ${taskRefOf(this.store.planTasks, task.id) ?? `#${task.order}`} "${task.title}" verdict=${verdict.outcome}`);
|
|
28049
28423
|
console.error(`[TaskOrchestrator] Runner: ${task.assignedRunner}, Model: ${task.assignedModel?.modelId ?? "default"}`);
|
|
28050
28424
|
console.error(`[TaskOrchestrator] Prompt preview: ${(task.prompt ?? "").slice(0, 200)}`);
|
|
@@ -28956,7 +29330,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
28956
29330
|
autoResumed: opts.autoResumed === true,
|
|
28957
29331
|
turnError: null,
|
|
28958
29332
|
turnOutputFrom: 0,
|
|
28959
|
-
reminded: false
|
|
29333
|
+
reminded: false,
|
|
29334
|
+
runnerStopped: false,
|
|
29335
|
+
planScope: this.planScope
|
|
28960
29336
|
};
|
|
28961
29337
|
this.spawnCounts.set(task.id, attempt.attempt);
|
|
28962
29338
|
this.attempts.set(task.id, attempt);
|
|
@@ -28993,6 +29369,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
28993
29369
|
const manifest = this.registry?.get(attempt.runner)?.manifest;
|
|
28994
29370
|
const unavailable = manifest && unavailableTaskModeMessage(manifest, task.taskMode);
|
|
28995
29371
|
if (unavailable) throw new Error(unavailable);
|
|
29372
|
+
if (kind.kind === "continuation") await this.windDowns.get(task.id);
|
|
28996
29373
|
const { cwd, worktree, previousWork } = await attemptCwd(kind, task, this.runs);
|
|
28997
29374
|
attempt.cwd = cwd;
|
|
28998
29375
|
attempt.worktree = worktree;
|
|
@@ -29048,10 +29425,11 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
29048
29425
|
}
|
|
29049
29426
|
if (takesSkills(kind)) this.store.setTaskAttemptSkills(task.id, [...attempt.skills]);
|
|
29050
29427
|
session.onEvent((event) => {
|
|
29051
|
-
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;
|
|
29052
29429
|
this.store.recordAttemptUsage(task.id, attempt.attempt, event.record, (/* @__PURE__ */ new Date()).toISOString());
|
|
29053
29430
|
this.emit("onUsageChanged");
|
|
29054
29431
|
});
|
|
29432
|
+
session.onExit(() => this.woundDown(attempt, session));
|
|
29055
29433
|
this.output.attach(task.id, session);
|
|
29056
29434
|
this.verifier.watch(task, session);
|
|
29057
29435
|
session.onEvent((event) => {
|
|
@@ -29220,10 +29598,35 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
29220
29598
|
if (stopsRunner(reason)) {
|
|
29221
29599
|
const task = this.store.get(taskId);
|
|
29222
29600
|
if (task) this.verifier.clear(task);
|
|
29223
|
-
if (
|
|
29601
|
+
if (attempt) this.stopRunner(attempt);
|
|
29224
29602
|
}
|
|
29225
29603
|
return attempt;
|
|
29226
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
|
+
}
|
|
29227
29630
|
/** Kept on the task once the attempt ends, so a continue can resume it after a reload (ADR-0018, K1). */
|
|
29228
29631
|
saveNativeSession(taskId, nativeSessionId, runner) {
|
|
29229
29632
|
if (nativeSessionId) this.store.setTaskRunnerSessionId(taskId, nativeSessionId, runner);
|
|
@@ -29581,7 +29984,7 @@ function taskReadPage(text3, { lines: lines2, since }) {
|
|
|
29581
29984
|
|
|
29582
29985
|
// src/services/plannerTools.ts
|
|
29583
29986
|
var PLANNER_TURN_ENDED = "The planning turn this call was made in has ended, so nothing was submitted.";
|
|
29584
|
-
function
|
|
29987
|
+
function runnersOf2(tasks) {
|
|
29585
29988
|
return [...new Set(flattenTasks(tasks).filter((t) => t.type !== "user").map((t) => t.assignedRunner))];
|
|
29586
29989
|
}
|
|
29587
29990
|
function plannerToolHandler(host) {
|
|
@@ -30396,17 +30799,37 @@ var CliAgentAiService = class {
|
|
|
30396
30799
|
* `startConversation` reseeds from Ordewell's transcript, which is the same
|
|
30397
30800
|
* degradation `restoreChat` already performs on every surface.
|
|
30398
30801
|
*
|
|
30802
|
+
* A process can end while idle without Ordewell stopping it — the OS, the
|
|
30803
|
+
* CLI itself, a sleeping machine, a signal sent to its group — and every
|
|
30804
|
+
* path of ours that kills one also drops its adapter. So an adapter still
|
|
30805
|
+
* held whose process has ended died on its own, and is restarted here
|
|
30806
|
+
* rather than answering the next message with its exit code.
|
|
30807
|
+
*
|
|
30399
30808
|
* Also restarted when the policy's decision on the runner's own search has
|
|
30400
30809
|
* changed since the process was spawned: the running thread would otherwise
|
|
30401
30810
|
* keep searching unasked after Full turned Guarded. Codex 0.162 applies
|
|
30402
30811
|
* `thread/resume`'s `config.web_search` either way (checked live).
|
|
30403
30812
|
*/
|
|
30404
30813
|
async ensureAdapter(session) {
|
|
30405
|
-
|
|
30814
|
+
const live2 = session.adapter;
|
|
30815
|
+
if (live2 && !live2.exited && session.nativeSearch === this.nativeSearch(session)) return live2;
|
|
30406
30816
|
session.stop();
|
|
30407
30817
|
const start = session.start;
|
|
30408
30818
|
if (!start) throw new Error("No active planner conversation.");
|
|
30409
|
-
|
|
30819
|
+
const resumeSessionId = session.nativeSessionId ?? void 0;
|
|
30820
|
+
try {
|
|
30821
|
+
const adapter = await this.startAdapter({ ...start.startOptions, resumeSessionId, resumeCost: session.sessionCost ?? void 0 }, start.tools, session);
|
|
30822
|
+
const resumed = adapter.nativeSessionId();
|
|
30823
|
+
if (resumeSessionId && resumed && resumed !== resumeSessionId) {
|
|
30824
|
+
throw new Error("The runner started a different session instead.");
|
|
30825
|
+
}
|
|
30826
|
+
return adapter;
|
|
30827
|
+
} catch (err) {
|
|
30828
|
+
if (!resumeSessionId || session.discarded.aborted) throw err;
|
|
30829
|
+
if (this.session === session) this.session = null;
|
|
30830
|
+
session.discard();
|
|
30831
|
+
throw new Error(`The ${this.runner} planner's session could not be resumed (${err instanceof Error ? err.message : String(err)}). Send your message again: the planner picks the conversation up from its transcript.`);
|
|
30832
|
+
}
|
|
30410
30833
|
}
|
|
30411
30834
|
/**
|
|
30412
30835
|
* What the planner gets of the repository's config, its runner being
|
|
@@ -31532,6 +31955,12 @@ function withoutIsolation(tasks) {
|
|
|
31532
31955
|
function handoffOf2({ type: _type, ...handoff }) {
|
|
31533
31956
|
return handoff;
|
|
31534
31957
|
}
|
|
31958
|
+
function stillToMerge(handoff, runMerge) {
|
|
31959
|
+
if (!handoff?.landed.length) return handoff;
|
|
31960
|
+
if (!runMerge) return null;
|
|
31961
|
+
const same = runMerge.landed.length === handoff.landed.length && runMerge.landed.every((t, i) => t.taskId === handoff.landed[i].taskId);
|
|
31962
|
+
return same ? handoff : { ...handoff, repos: runMerge.repos, landed: runMerge.landed };
|
|
31963
|
+
}
|
|
31535
31964
|
function planTasks(plan) {
|
|
31536
31965
|
if ("tasks" in plan) return plan.tasks;
|
|
31537
31966
|
return rejoinPlanTree(plan.pendingTasks, plan.phase === "executing" ? plan.executionLog : []);
|
|
@@ -31552,6 +31981,7 @@ function reduceRun(view, input) {
|
|
|
31552
31981
|
...view,
|
|
31553
31982
|
tasks: applyStatus(view.tasks, new Map(input.tasks.map((t) => [t.id, t]))),
|
|
31554
31983
|
runMerge: input.runMerge ?? null,
|
|
31984
|
+
handoff: stillToMerge(view.handoff, input.runMerge),
|
|
31555
31985
|
usage: input.usage
|
|
31556
31986
|
};
|
|
31557
31987
|
case "task_started":
|
|
@@ -32104,19 +32534,50 @@ var TaskLogRecorder = class {
|
|
|
32104
32534
|
};
|
|
32105
32535
|
|
|
32106
32536
|
// src/services/ContextWindowFill.ts
|
|
32107
|
-
var
|
|
32108
|
-
constructor(catalog
|
|
32537
|
+
var SessionWindows = class {
|
|
32538
|
+
constructor(catalog = () => void 0) {
|
|
32109
32539
|
this.catalog = catalog;
|
|
32110
|
-
if (last.contextWindow !== void 0) this.known.set(modelKey(last.source, last.model), last.contextWindow);
|
|
32111
32540
|
}
|
|
32112
32541
|
catalog;
|
|
32113
|
-
|
|
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. */
|
|
32114
32567
|
known = /* @__PURE__ */ new Map();
|
|
32115
32568
|
fill(record) {
|
|
32116
32569
|
if (record.subagentId || record.aggregate) return record;
|
|
32117
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
|
+
}
|
|
32118
32578
|
const reported = record.contextWindow !== void 0 && record.contextWindow > 0 ? record.contextWindow : void 0;
|
|
32119
|
-
|
|
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);
|
|
32120
32581
|
if (window !== void 0) this.known.set(key, window);
|
|
32121
32582
|
return window === void 0 || window === record.contextWindow ? record : { ...record, contextWindow: window };
|
|
32122
32583
|
}
|
|
@@ -32124,9 +32585,9 @@ var ContextWindowFill = class {
|
|
|
32124
32585
|
function modelKey(source, model) {
|
|
32125
32586
|
return JSON.stringify([source ?? null, model ?? null]);
|
|
32126
32587
|
}
|
|
32127
|
-
function withContextWindows(runner,
|
|
32588
|
+
function withContextWindows(runner, windows) {
|
|
32128
32589
|
return {
|
|
32129
|
-
spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(
|
|
32590
|
+
spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(windows, opts.resumeWindow)),
|
|
32130
32591
|
stop: (sessionId) => runner.stop(sessionId),
|
|
32131
32592
|
stopAll: () => runner.stopAll(),
|
|
32132
32593
|
get activeCount() {
|
|
@@ -32157,12 +32618,12 @@ function positive(n) {
|
|
|
32157
32618
|
|
|
32158
32619
|
// src/services/PlannerUsage.ts
|
|
32159
32620
|
var PlannerUsageLedger = class {
|
|
32160
|
-
/** `
|
|
32161
|
-
constructor(
|
|
32162
|
-
this.
|
|
32163
|
-
this.windows = new ContextWindowFill(
|
|
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);
|
|
32164
32625
|
}
|
|
32165
|
-
|
|
32626
|
+
sessionWindows;
|
|
32166
32627
|
usage = { totals: {} };
|
|
32167
32628
|
windows;
|
|
32168
32629
|
/** Fold one model call's record into the totals; returns the running ledger. */
|
|
@@ -32173,16 +32634,16 @@ var PlannerUsageLedger = class {
|
|
|
32173
32634
|
/** Adopt a persisted ledger (a reopened session) or start from zero. */
|
|
32174
32635
|
restore(usage) {
|
|
32175
32636
|
this.usage = usage ?? { totals: {} };
|
|
32176
|
-
this.windows = new ContextWindowFill(this.
|
|
32637
|
+
this.windows = new ContextWindowFill(this.sessionWindows, this.usage);
|
|
32177
32638
|
}
|
|
32178
32639
|
/** Start a fresh session's ledger from zero. */
|
|
32179
32640
|
clear() {
|
|
32180
32641
|
this.usage = { totals: {} };
|
|
32181
|
-
this.windows = new ContextWindowFill(this.
|
|
32642
|
+
this.windows = new ContextWindowFill(this.sessionWindows);
|
|
32182
32643
|
}
|
|
32183
|
-
/** Whether
|
|
32644
|
+
/** Whether the ledger has measures or session billing facts to broadcast. */
|
|
32184
32645
|
get hasUsage() {
|
|
32185
|
-
return isMeasured(this.usage.totals);
|
|
32646
|
+
return isMeasured(this.usage.totals) || this.usage.sessionBilling !== void 0;
|
|
32186
32647
|
}
|
|
32187
32648
|
/** The value persisted onto the plan state. */
|
|
32188
32649
|
snapshot() {
|
|
@@ -33631,7 +34092,7 @@ function createSession(deps) {
|
|
|
33631
34092
|
const backend = new PlannerBackend(deps.aiService ? { pinned: deps.aiService, busy } : { provider: () => deps.config.aiProvider, create: () => createAiService(deps.config, { workspaceRoot: deps.workspaceRoot, mcpServer }), busy });
|
|
33632
34093
|
const aiService = () => backend.current();
|
|
33633
34094
|
const store = new PlanStore();
|
|
33634
|
-
const
|
|
34095
|
+
const windows = new SessionWindows((source, model) => deps.modelResolver.contextWindowFor?.(source, model));
|
|
33635
34096
|
const taskLogs = new TaskLogRecorder({
|
|
33636
34097
|
broadcast: deps.broadcast,
|
|
33637
34098
|
// Read through the Session, whose id and workspace are the ones it saves
|
|
@@ -33664,7 +34125,7 @@ function createSession(deps) {
|
|
|
33664
34125
|
const orchestrator = TaskOrchestrator.compose({
|
|
33665
34126
|
config: deps.config,
|
|
33666
34127
|
notifications: deps.notifications,
|
|
33667
|
-
runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner,
|
|
34128
|
+
runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner, windows))),
|
|
33668
34129
|
store,
|
|
33669
34130
|
output: deps.taskOutput,
|
|
33670
34131
|
isolation: deps.isolation,
|
|
@@ -33676,7 +34137,7 @@ function createSession(deps) {
|
|
|
33676
34137
|
runSettings: () => session.planState?.runSettings,
|
|
33677
34138
|
isApproved: (taskId) => session.isApproved(taskId)
|
|
33678
34139
|
});
|
|
33679
|
-
const usage = new PlannerUsageLedger(
|
|
34140
|
+
const usage = new PlannerUsageLedger(windows);
|
|
33680
34141
|
const events = new SessionEventRelay({
|
|
33681
34142
|
broadcast: deps.broadcast,
|
|
33682
34143
|
onNotice: deps.onNotice,
|
|
@@ -33746,6 +34207,7 @@ function createSession(deps) {
|
|
|
33746
34207
|
runs: orchestrator.runs,
|
|
33747
34208
|
events,
|
|
33748
34209
|
usage,
|
|
34210
|
+
windows,
|
|
33749
34211
|
approvals,
|
|
33750
34212
|
approvalPolicy,
|
|
33751
34213
|
taskLogs,
|
|
@@ -33773,6 +34235,7 @@ function configSecrets(config) {
|
|
|
33773
34235
|
var Session = class {
|
|
33774
34236
|
backend;
|
|
33775
34237
|
usage;
|
|
34238
|
+
windows;
|
|
33776
34239
|
events;
|
|
33777
34240
|
planner;
|
|
33778
34241
|
orchestrator;
|
|
@@ -33878,6 +34341,7 @@ var Session = class {
|
|
|
33878
34341
|
this.runs = parts.runs;
|
|
33879
34342
|
this.events = parts.events;
|
|
33880
34343
|
this.usage = parts.usage;
|
|
34344
|
+
this.windows = parts.windows;
|
|
33881
34345
|
this.approvals = parts.approvals;
|
|
33882
34346
|
this.approvalPolicy = parts.approvalPolicy;
|
|
33883
34347
|
this.taskLogs = parts.taskLogs;
|
|
@@ -34356,6 +34820,7 @@ var Session = class {
|
|
|
34356
34820
|
this.syncPlanTasks();
|
|
34357
34821
|
this.plan.isolation = this.runs.planIsolation ?? void 0;
|
|
34358
34822
|
this.plan.plannerUsage = this.usage.snapshot();
|
|
34823
|
+
this.plan.contextWindows = this.windows.snapshot();
|
|
34359
34824
|
this.plan.supervision = this.supervision.snapshot();
|
|
34360
34825
|
this.plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
34361
34826
|
this.save(this.plan, this.goal, this.workspace, this.currentSessionId);
|
|
@@ -34385,6 +34850,7 @@ var Session = class {
|
|
|
34385
34850
|
this.owingEffects.clear();
|
|
34386
34851
|
this.conversation.reset();
|
|
34387
34852
|
this.usage.clear();
|
|
34853
|
+
this.windows.clear();
|
|
34388
34854
|
this.approvals.clear(isPlannerApproval);
|
|
34389
34855
|
if (!keepGrants) this.approvalPolicy.reset();
|
|
34390
34856
|
this.store.clearLog();
|
|
@@ -34725,7 +35191,18 @@ var Session = class {
|
|
|
34725
35191
|
if (!this.plan) throw new ConversationEditError("No planning conversation to condense");
|
|
34726
35192
|
const opening = this.plannerTurnMayOpen(signal);
|
|
34727
35193
|
if (opening) await opening;
|
|
34728
|
-
|
|
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;
|
|
34729
35206
|
}
|
|
34730
35207
|
rewindTargets() {
|
|
34731
35208
|
return this.conversation.rewindTargets();
|
|
@@ -34812,7 +35289,7 @@ var Session = class {
|
|
|
34812
35289
|
*/
|
|
34813
35290
|
adoptPlannerTasks(tasks, how) {
|
|
34814
35291
|
this.turnEffects.landed(how);
|
|
34815
|
-
for (const runner of
|
|
35292
|
+
for (const runner of runnersOf2(tasks)) this.editor.admitRunner(runner, this.catalog.known(runner));
|
|
34816
35293
|
const runners = this.plan.runners;
|
|
34817
35294
|
const before = this.store.planTasks;
|
|
34818
35295
|
let coerced = resolveAssignments(tasks, before, this.catalog.edit(runners)).tasks;
|
|
@@ -35035,9 +35512,9 @@ var Session = class {
|
|
|
35035
35512
|
runSnapshot() {
|
|
35036
35513
|
if (!this.plan) return EMPTY_RUN;
|
|
35037
35514
|
const planned = reduceRun(EMPTY_RUN, { type: "plan_snapshot", plan: surfacePlan({ ...this.plan, tasks: this.store.snapshot() }) });
|
|
35038
|
-
const
|
|
35039
|
-
const
|
|
35040
|
-
return this.
|
|
35515
|
+
const handed = this.sentHandoff ? reduceRun(planned, { type: "isolation_handoff", ...this.sentHandoff }) : planned;
|
|
35516
|
+
const view = reduceRun(handed, this.events.statusMessage());
|
|
35517
|
+
return this.sentHeld ? reduceRun(view, { type: "plan_edit_held", held: this.sentHeld }) : view;
|
|
35041
35518
|
}
|
|
35042
35519
|
/** The ancestors a task waits on at its merge gate (ADR-0020); empty when it waits for no Merge all. */
|
|
35043
35520
|
mergeGate(taskId) {
|
|
@@ -35213,7 +35690,10 @@ var Session = class {
|
|
|
35213
35690
|
if (!plan.approvedTasks && plan.approved) {
|
|
35214
35691
|
plan.approvedTasks = flattenTasks(plan.tasks).map((t) => t.id);
|
|
35215
35692
|
}
|
|
35216
|
-
if (adopting)
|
|
35693
|
+
if (adopting) {
|
|
35694
|
+
this.usage.restore(plan.plannerUsage);
|
|
35695
|
+
this.windows.restore(plan.contextWindows);
|
|
35696
|
+
}
|
|
35217
35697
|
if (opts?.sessionId) this.currentSessionId = opts.sessionId;
|
|
35218
35698
|
if (adopting) {
|
|
35219
35699
|
for (const task of flattenTasks(plan.tasks)) delete task.resumeAt;
|
|
@@ -35227,12 +35707,31 @@ var Session = class {
|
|
|
35227
35707
|
}
|
|
35228
35708
|
carryOverQueuedEdits(plan);
|
|
35229
35709
|
if (adopting) {
|
|
35230
|
-
void this.
|
|
35710
|
+
void this.adoptRun(plan, opts?.persist !== false || opts?.sessionId !== void 0);
|
|
35231
35711
|
this.sentHandoff = this.owedHandoff();
|
|
35232
35712
|
}
|
|
35233
35713
|
if (opts?.persist !== false) this.persist();
|
|
35234
35714
|
if (this.usage.hasUsage) this.broadcast(this.usage.message());
|
|
35235
35715
|
}
|
|
35716
|
+
/**
|
|
35717
|
+
* Take the loaded plan's run over, then correct what the surfaces were told
|
|
35718
|
+
* by what git found: the saved record cannot know what the user merged
|
|
35719
|
+
* since, and its handoff would offer Merge all for work already in their
|
|
35720
|
+
* branch. The status update says what landed work is still unmerged, which
|
|
35721
|
+
* a handoff folds to (see `reduceRun`), and `run_closed` that the run was
|
|
35722
|
+
* cleared up. Saved only where the load itself may save (`persists`), so a
|
|
35723
|
+
* restore with no session file to write keeps the correction in memory for
|
|
35724
|
+
* its next save. A plan loaded meanwhile is not this one's to correct.
|
|
35725
|
+
* Starts nothing (ADR-0044).
|
|
35726
|
+
*/
|
|
35727
|
+
async adoptRun(plan, persists) {
|
|
35728
|
+
const outcome = await this.runs.adopt(plan.isolation ?? null);
|
|
35729
|
+
if (this.closed || this.plan !== plan || outcome === "unchanged") return;
|
|
35730
|
+
plan.isolation = this.runs.planIsolation ?? void 0;
|
|
35731
|
+
if (persists) this.persist();
|
|
35732
|
+
this.events.status(plan);
|
|
35733
|
+
if (outcome === "cleared") this.broadcast({ type: "run_closed" });
|
|
35734
|
+
}
|
|
35236
35735
|
destroy() {
|
|
35237
35736
|
this.closed = true;
|
|
35238
35737
|
this.dropPlanScope();
|
|
@@ -35276,7 +35775,7 @@ function openTurn(view, { turnId, prompt = "", skills = [], wakeUps = [], forced
|
|
|
35276
35775
|
wakeUps = wakeUps.slice(adopted.wakeUps);
|
|
35277
35776
|
if (wakeUps.length === 0) return adopted.prompt || prompt === "" && adopted.wakeUps > 0 ? view : promptLine(view, turnId, prompt, skills, forced);
|
|
35278
35777
|
const said2 = prompt === "" || adopted.prompt ? view : promptLine(view, turnId, prompt, skills, forced);
|
|
35279
|
-
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);
|
|
35280
35779
|
const updates = wakeUps.map((text3, n) => settledMessage(`b${said2.nextId + n}`, "wake_up", text3, turnId));
|
|
35281
35780
|
return { ...said2, blocks: [...said2.blocks.slice(0, at), ...updates, ...said2.blocks.slice(at)], nextId: said2.nextId + updates.length };
|
|
35282
35781
|
}
|
|
@@ -35963,17 +36462,12 @@ var AbstractRunner = class {
|
|
|
35963
36462
|
get activeCount() {
|
|
35964
36463
|
return this.sessions.size;
|
|
35965
36464
|
}
|
|
36465
|
+
/** A session winding down stays registered until it exits, so a second stop gets the same wind-down. */
|
|
35966
36466
|
stop(sessionId) {
|
|
35967
|
-
|
|
35968
|
-
if (!session) return;
|
|
35969
|
-
session.kill();
|
|
35970
|
-
this.sessions.delete(sessionId);
|
|
36467
|
+
return this.sessions.get(sessionId)?.stop() ?? Promise.resolve();
|
|
35971
36468
|
}
|
|
35972
36469
|
stopAll() {
|
|
35973
|
-
for (const
|
|
35974
|
-
session.kill();
|
|
35975
|
-
}
|
|
35976
|
-
this.sessions.clear();
|
|
36470
|
+
for (const session of this.sessions.values()) void session.stop();
|
|
35977
36471
|
}
|
|
35978
36472
|
registerSession(id, session) {
|
|
35979
36473
|
this.sessions.set(id, session);
|
|
@@ -36131,6 +36625,8 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36131
36625
|
queue = [];
|
|
36132
36626
|
messageCount = 0;
|
|
36133
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;
|
|
36134
36630
|
lastSessionId = null;
|
|
36135
36631
|
lastSessionCost = null;
|
|
36136
36632
|
structuredEmitter = new import_events2.EventEmitter();
|
|
@@ -36156,7 +36652,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36156
36652
|
await this.startAttached(this.launch.startOptions);
|
|
36157
36653
|
this.state = "working";
|
|
36158
36654
|
setImmediate(() => {
|
|
36159
|
-
if (!this.exited) this.deliver(prompt);
|
|
36655
|
+
if (!this.exited && !this.stopping) this.deliver(prompt);
|
|
36160
36656
|
});
|
|
36161
36657
|
}
|
|
36162
36658
|
getOutput() {
|
|
@@ -36236,6 +36732,53 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36236
36732
|
this.withdrawPermissions();
|
|
36237
36733
|
this.baseHandleExit(-1);
|
|
36238
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
|
+
}
|
|
36239
36782
|
turnState() {
|
|
36240
36783
|
return this.state;
|
|
36241
36784
|
}
|
|
@@ -36261,7 +36804,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36261
36804
|
this.messageCount += 1;
|
|
36262
36805
|
const id = `msg-${this.messageCount}`;
|
|
36263
36806
|
const sender2 = opening.from ? { from: opening.from } : {};
|
|
36264
|
-
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 });
|
|
36265
36808
|
else if (this.state === "idle" && this.adapterStarted && !this.sending) this.deliver(text3, opening);
|
|
36266
36809
|
else {
|
|
36267
36810
|
this.state = "working";
|
|
@@ -36316,7 +36859,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36316
36859
|
});
|
|
36317
36860
|
}
|
|
36318
36861
|
runningTurn() {
|
|
36319
|
-
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;
|
|
36320
36863
|
}
|
|
36321
36864
|
/** No runner can recall a message once offered to it, so only a queued one comes back. */
|
|
36322
36865
|
removeQueued(id) {
|
|
@@ -36377,7 +36920,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36377
36920
|
if (this.interrupting) return this.interrupting;
|
|
36378
36921
|
const turn = this.turn;
|
|
36379
36922
|
const adapter = this.adapter;
|
|
36380
|
-
if (!turn || turn.ended || !adapter) return Promise.resolve();
|
|
36923
|
+
if (!turn || turn.ended || !adapter || this.stopping) return Promise.resolve();
|
|
36381
36924
|
this.interrupting = this.interruptTurn(turn, adapter).finally(() => {
|
|
36382
36925
|
this.interrupting = null;
|
|
36383
36926
|
this.offerNext();
|
|
@@ -36391,7 +36934,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36391
36934
|
const settled = await settleWithin(ended.then(() => true), { timeoutMs: grace, onTimeout: () => false });
|
|
36392
36935
|
if (settled) return;
|
|
36393
36936
|
}
|
|
36394
|
-
if (turn.ended || this.exited) return;
|
|
36937
|
+
if (turn.ended || this.exited || this.stopping) return;
|
|
36395
36938
|
await this.restartInterrupted(turn);
|
|
36396
36939
|
}
|
|
36397
36940
|
/**
|
|
@@ -36495,7 +37038,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36495
37038
|
offerNext() {
|
|
36496
37039
|
const adapter = this.adapter;
|
|
36497
37040
|
const turn = this.turn;
|
|
36498
|
-
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;
|
|
36499
37042
|
if (this.queue.some((m) => m.stage === "steering")) return;
|
|
36500
37043
|
const next = this.queue.find((m) => m.stage === "queued");
|
|
36501
37044
|
if (!next || next.command) return;
|
|
@@ -36570,7 +37113,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36570
37113
|
}
|
|
36571
37114
|
return;
|
|
36572
37115
|
}
|
|
36573
|
-
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)) {
|
|
36574
37117
|
turn = this.openTurn("");
|
|
36575
37118
|
this.offerNext();
|
|
36576
37119
|
}
|
|
@@ -36641,7 +37184,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
36641
37184
|
* back in Ordewell's hands: while it is on offer or owed, nothing overtakes it.
|
|
36642
37185
|
*/
|
|
36643
37186
|
deliverNext() {
|
|
36644
|
-
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;
|
|
36645
37188
|
const head = this.queue[0];
|
|
36646
37189
|
if (!head || this.queue.some((message) => message.stage !== "queued")) return;
|
|
36647
37190
|
this.queue.shift();
|
|
@@ -36810,11 +37353,13 @@ var STATE_FILE = "state.json";
|
|
|
36810
37353
|
function getStatePath(baseDir) {
|
|
36811
37354
|
return path34.join(getStateDir(baseDir), STATE_FILE);
|
|
36812
37355
|
}
|
|
36813
|
-
|
|
37356
|
+
var SESSION_ID_KEY = "sessionId";
|
|
37357
|
+
function saveState(plan, baseDir, sessionId) {
|
|
36814
37358
|
const statePath = getStatePath(baseDir ?? process.cwd());
|
|
36815
37359
|
ensureDir(path34.dirname(statePath));
|
|
36816
37360
|
plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
36817
|
-
|
|
37361
|
+
const file = sessionId ? { ...plan, [SESSION_ID_KEY]: sessionId } : plan;
|
|
37362
|
+
fs17.writeFileSync(statePath, JSON.stringify(file, null, 2));
|
|
36818
37363
|
}
|
|
36819
37364
|
function loadState(baseDir, logger = defaultLogger) {
|
|
36820
37365
|
const dir = baseDir ?? process.cwd();
|
|
@@ -36827,10 +37372,12 @@ function loadState(baseDir, logger = defaultLogger) {
|
|
|
36827
37372
|
logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
|
|
36828
37373
|
return null;
|
|
36829
37374
|
}
|
|
37375
|
+
const sessionId = parsed[SESSION_ID_KEY];
|
|
37376
|
+
delete parsed[SESSION_ID_KEY];
|
|
36830
37377
|
const plan = parsed;
|
|
36831
37378
|
migratePlanStateIsolation(plan);
|
|
36832
37379
|
migrateLoadedTasks(plan.tasks);
|
|
36833
|
-
return plan;
|
|
37380
|
+
return typeof sessionId === "string" && sessionId ? { plan, sessionId } : { plan };
|
|
36834
37381
|
} catch (err) {
|
|
36835
37382
|
logger.warn("stateStore", `failed to load state from ${statePath}; falling back to empty state`, err);
|
|
36836
37383
|
return null;
|
|
@@ -37087,7 +37634,10 @@ var PlannerModelMemory = class {
|
|
|
37087
37634
|
collectProviderCredentials,
|
|
37088
37635
|
commandAllowedBy,
|
|
37089
37636
|
configuredProviders,
|
|
37637
|
+
contextMeterText,
|
|
37638
|
+
contextMeterTitle,
|
|
37090
37639
|
continuability,
|
|
37640
|
+
costText,
|
|
37091
37641
|
countedSessionCost,
|
|
37092
37642
|
createAiService,
|
|
37093
37643
|
createEmptyPlan,
|
|
@@ -37130,7 +37680,9 @@ var PlannerModelMemory = class {
|
|
|
37130
37680
|
flattenTasksWithParents,
|
|
37131
37681
|
followTaskLog,
|
|
37132
37682
|
followTurn,
|
|
37683
|
+
formatCost,
|
|
37133
37684
|
formatSearchOutput,
|
|
37685
|
+
formatTokenCount,
|
|
37134
37686
|
fromTranscript,
|
|
37135
37687
|
getLatestSession,
|
|
37136
37688
|
getProviderMeta,
|
|
@@ -37182,6 +37734,7 @@ var PlannerModelMemory = class {
|
|
|
37182
37734
|
migratePlanState,
|
|
37183
37735
|
mintDaemonToken,
|
|
37184
37736
|
mintSessionId,
|
|
37737
|
+
modelCostTitle,
|
|
37185
37738
|
namedRepos,
|
|
37186
37739
|
newTaskFields,
|
|
37187
37740
|
normalizeAgentArgs,
|
|
@@ -37267,6 +37820,7 @@ var PlannerModelMemory = class {
|
|
|
37267
37820
|
stopTurn,
|
|
37268
37821
|
stripAnsi,
|
|
37269
37822
|
stripModelPrefix,
|
|
37823
|
+
subagentCostTitle,
|
|
37270
37824
|
subtreeIds,
|
|
37271
37825
|
summarizeToolCall,
|
|
37272
37826
|
supervisorSummary,
|
|
@@ -37285,6 +37839,7 @@ var PlannerModelMemory = class {
|
|
|
37285
37839
|
taskStartedNotice,
|
|
37286
37840
|
taskStatusKind,
|
|
37287
37841
|
taskUsageSummary,
|
|
37842
|
+
tasksTotalText,
|
|
37288
37843
|
textHasTaskOps,
|
|
37289
37844
|
textHasTaskQuery,
|
|
37290
37845
|
titledRefs,
|
|
@@ -37302,6 +37857,7 @@ var PlannerModelMemory = class {
|
|
|
37302
37857
|
unsendAll,
|
|
37303
37858
|
unsendLatest,
|
|
37304
37859
|
usageLine,
|
|
37860
|
+
usageText,
|
|
37305
37861
|
waitsForApproval,
|
|
37306
37862
|
walkPlan,
|
|
37307
37863
|
wellKnownBinDirs,
|