@ordewell/core 0.10.9 → 0.10.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{IFileSystem-C9W5mRYA.d.mts → IFileSystem-1PqRCjhu.d.mts} +21 -3
- package/dist/{IFileSystem-B8qRNC_6.d.ts → IFileSystem-lTRL_G9Z.d.ts} +21 -3
- package/dist/{ModeResolver-C1PN5FYG.d.mts → ModeResolver-6TQXVsde.d.mts} +1 -1
- package/dist/{ModeResolver-CaPnoU8N.d.ts → ModeResolver-CoJAJD-f.d.ts} +1 -1
- package/dist/{Task-hFfRS9rN.d.mts → Task-CfBM0wkV.d.mts} +94 -21
- package/dist/{Task-hFfRS9rN.d.ts → Task-CfBM0wkV.d.ts} +94 -21
- package/dist/{chunk-H7QX32BH.mjs → chunk-24EL4Z2W.mjs} +2 -2
- package/dist/{chunk-O6LQU3W6.mjs → chunk-ESKK5I4F.mjs} +138 -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 +129 -20
- package/dist/index.d.ts +129 -20
- package/dist/index.js +556 -92
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +432 -76
- 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-CGKX3aeV.d.ts → runSettingsText-C5uv2axI.d.ts} +29 -18
- package/dist/{runSettingsText-rLoYQlUH.d.mts → runSettingsText-Jb_efnI4.d.mts} +29 -18
- package/dist/testing.d.mts +12 -3
- package/dist/testing.d.ts +12 -3
- package/dist/testing.js +16 -1
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +17 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-EEE2GBLG.mjs.map +0 -1
- package/dist/chunk-O6LQU3W6.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.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
looksLikePlanAttempt,
|
|
3
3
|
parsePlanJson,
|
|
4
4
|
validatePlanTasks
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KWS3VEER.mjs";
|
|
6
6
|
import {
|
|
7
7
|
ACT_RISK,
|
|
8
8
|
AUTORUN_RISK,
|
|
@@ -38,7 +38,10 @@ import {
|
|
|
38
38
|
canSplitTask,
|
|
39
39
|
capConflictFiles,
|
|
40
40
|
classifyOutcome,
|
|
41
|
+
contextMeterText,
|
|
42
|
+
contextMeterTitle,
|
|
41
43
|
continuability,
|
|
44
|
+
costText,
|
|
42
45
|
countedSessionCost,
|
|
43
46
|
dependentsNotice,
|
|
44
47
|
diffRows,
|
|
@@ -49,6 +52,8 @@ import {
|
|
|
49
52
|
executionSummary,
|
|
50
53
|
followTaskLog,
|
|
51
54
|
followTurn,
|
|
55
|
+
formatCost,
|
|
56
|
+
formatTokenCount,
|
|
52
57
|
fromTranscript,
|
|
53
58
|
hasHiddenDetail,
|
|
54
59
|
holdPrompt,
|
|
@@ -59,6 +64,7 @@ import {
|
|
|
59
64
|
markRequestFor,
|
|
60
65
|
markdownHtml,
|
|
61
66
|
markdownTokens,
|
|
67
|
+
modelCostTitle,
|
|
62
68
|
namedRepos,
|
|
63
69
|
newTaskFields,
|
|
64
70
|
normalizeAgentArgs,
|
|
@@ -90,6 +96,7 @@ import {
|
|
|
90
96
|
skippedLabel,
|
|
91
97
|
startAttemptUsage,
|
|
92
98
|
stopTurn,
|
|
99
|
+
subagentCostTitle,
|
|
93
100
|
summarizeToolCall,
|
|
94
101
|
supervisorSummary,
|
|
95
102
|
surfacePlan,
|
|
@@ -103,6 +110,7 @@ import {
|
|
|
103
110
|
taskStartedNotice,
|
|
104
111
|
taskStatusKind,
|
|
105
112
|
taskUsageSummary,
|
|
113
|
+
tasksTotalText,
|
|
106
114
|
textHasTaskOps,
|
|
107
115
|
titledRefs,
|
|
108
116
|
toolHeadline,
|
|
@@ -110,8 +118,9 @@ import {
|
|
|
110
118
|
unsendAll,
|
|
111
119
|
unsendLatest,
|
|
112
120
|
usageLine,
|
|
121
|
+
usageText,
|
|
113
122
|
validateTaskEdit
|
|
114
|
-
} from "./chunk-
|
|
123
|
+
} from "./chunk-ESKK5I4F.mjs";
|
|
115
124
|
import {
|
|
116
125
|
PLAN_ENVELOPE_KEY,
|
|
117
126
|
PlanParseError,
|
|
@@ -137,7 +146,7 @@ import {
|
|
|
137
146
|
runnerModesFrom,
|
|
138
147
|
stripTrailingCommas,
|
|
139
148
|
unavailableTaskModeMessage
|
|
140
|
-
} from "./chunk-
|
|
149
|
+
} from "./chunk-24EL4Z2W.mjs";
|
|
141
150
|
import {
|
|
142
151
|
SELF_REPO,
|
|
143
152
|
absorbRemoval,
|
|
@@ -190,7 +199,7 @@ import {
|
|
|
190
199
|
waitsForApproval,
|
|
191
200
|
waitsForReset,
|
|
192
201
|
walkPlan
|
|
193
|
-
} from "./chunk-
|
|
202
|
+
} from "./chunk-PFH7WFIB.mjs";
|
|
194
203
|
|
|
195
204
|
// src/models/TaskLog.ts
|
|
196
205
|
var HEAD_LINES = 60;
|
|
@@ -6028,7 +6037,7 @@ var RunnerApprovals = class {
|
|
|
6028
6037
|
},
|
|
6029
6038
|
stop: (sessionId) => {
|
|
6030
6039
|
this.denySession(sessionId);
|
|
6031
|
-
runner.stop(sessionId);
|
|
6040
|
+
return runner.stop(sessionId);
|
|
6032
6041
|
},
|
|
6033
6042
|
stopAll: () => {
|
|
6034
6043
|
this.approvals.clear(isRunnerApproval);
|
|
@@ -9889,6 +9898,14 @@ function canUseToolAnswer({ input, suggestions }, decision) {
|
|
|
9889
9898
|
}
|
|
9890
9899
|
var MODEL_COMMAND = "model";
|
|
9891
9900
|
var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
|
|
9901
|
+
var VARIANT_SUFFIX = /^(.*?)(\[[^\]]*\])?$/;
|
|
9902
|
+
function sameModel(a, b) {
|
|
9903
|
+
if (a === void 0 || b === void 0) return false;
|
|
9904
|
+
if (a === b) return true;
|
|
9905
|
+
const [, aBase, aVariant] = VARIANT_SUFFIX.exec(a) ?? [];
|
|
9906
|
+
const [, bBase, bVariant] = VARIANT_SUFFIX.exec(b) ?? [];
|
|
9907
|
+
return aBase === bBase && (aVariant === void 0 || bVariant === void 0 || aVariant === bVariant);
|
|
9908
|
+
}
|
|
9892
9909
|
function usageRecord(usage, model, subagentId) {
|
|
9893
9910
|
const record = {
|
|
9894
9911
|
source: "claude-code",
|
|
@@ -9897,6 +9914,7 @@ function usageRecord(usage, model, subagentId) {
|
|
|
9897
9914
|
if (model) record.model = model;
|
|
9898
9915
|
if (usage.output_tokens !== void 0) record.outputTokens = usage.output_tokens;
|
|
9899
9916
|
if (subagentId) record.subagentId = subagentId;
|
|
9917
|
+
else if (record.inputTokens !== void 0) record.contextTokens = record.inputTokens + (record.outputTokens ?? 0);
|
|
9900
9918
|
return record;
|
|
9901
9919
|
}
|
|
9902
9920
|
function subagentFinalCall(result) {
|
|
@@ -9946,16 +9964,27 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
9946
9964
|
replyRuns = new ReplyRuns();
|
|
9947
9965
|
/** The model answering the planner's current message, and the message's id, as its `message_start` named them. */
|
|
9948
9966
|
plannerModel;
|
|
9967
|
+
/**
|
|
9968
|
+
* The id the turn runs as, as `init` names it. A `[1m]` variant is the
|
|
9969
|
+
* selected id, while a message names the API's canonical id without it.
|
|
9970
|
+
*/
|
|
9971
|
+
sessionModel;
|
|
9972
|
+
billing;
|
|
9949
9973
|
streamingMessage;
|
|
9950
9974
|
/** The message the last `assistant` line was part of, and how many of its blocks the lines have carried. */
|
|
9951
9975
|
lineMessage = null;
|
|
9952
9976
|
/**
|
|
9953
|
-
*
|
|
9954
|
-
*
|
|
9955
|
-
*
|
|
9977
|
+
* What has been counted of the session's running cost: a resume starts from
|
|
9978
|
+
* it, and it is undefined without a kept count — the next result then only
|
|
9979
|
+
* sets the baseline, since the CLI restores spend from turns before it. It
|
|
9980
|
+
* grows by what each result is counted for, never by a total that includes
|
|
9981
|
+
* cost the runner marked unknown, so that spend can be counted when its basis
|
|
9982
|
+
* is reported; and it never lowers, since counted dollars are not un-spent.
|
|
9956
9983
|
*/
|
|
9957
9984
|
reportedCostUsd = 0;
|
|
9958
9985
|
reportedModelCostUsd = /* @__PURE__ */ new Map();
|
|
9986
|
+
/** What this turn's streamed calls already reported, so its result only adds what did not stream. */
|
|
9987
|
+
turnCallUsage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
|
9959
9988
|
/**
|
|
9960
9989
|
* Subagents started and not yet finished, keyed by the `Agent` call's id.
|
|
9961
9990
|
* Kept across turns: a backgrounded one reports after its turn has ended.
|
|
@@ -10042,6 +10071,14 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10042
10071
|
if (!sessionId || this.reportedCostUsd === void 0) return null;
|
|
10043
10072
|
return { sessionId, usd: this.reportedCostUsd, modelUsd: Object.fromEntries(this.reportedModelCostUsd) };
|
|
10044
10073
|
}
|
|
10074
|
+
/**
|
|
10075
|
+
* A cleared conversation's totals start again from zero, so what follows is
|
|
10076
|
+
* all new spend, even for a resume that had no count kept.
|
|
10077
|
+
*/
|
|
10078
|
+
restartCount() {
|
|
10079
|
+
this.reportedCostUsd = 0;
|
|
10080
|
+
this.reportedModelCostUsd = /* @__PURE__ */ new Map();
|
|
10081
|
+
}
|
|
10045
10082
|
/** A count kept for another session says nothing of this one's. */
|
|
10046
10083
|
countFrom(sessionId, counted) {
|
|
10047
10084
|
const known = counted?.sessionId === sessionId ? counted : void 0;
|
|
@@ -10115,11 +10152,10 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10115
10152
|
* the alias resolves to now; that id is pinned before the first turn.
|
|
10116
10153
|
*/
|
|
10117
10154
|
async handshake(opts) {
|
|
10118
|
-
if (opts.kind !== "task") return;
|
|
10119
10155
|
const initialized = (await this.control({ subtype: "initialize" }, MODEL_PIN_TIMEOUT_MS))?.response;
|
|
10120
10156
|
this.listedCommands = initialized?.commands ?? [];
|
|
10121
10157
|
this.listedModels = initialized?.models ?? [];
|
|
10122
|
-
if (opts.flags.permissionMode !== "plan" || !opts.model) return;
|
|
10158
|
+
if (opts.kind !== "task" || opts.flags.permissionMode !== "plan" || !opts.model) return;
|
|
10123
10159
|
const resolved = initialized?.models?.find((m) => m.value === opts.model)?.resolvedModel;
|
|
10124
10160
|
if (resolved && resolved !== opts.model) await this.control({ subtype: "set_model", model: resolved }, MODEL_PIN_TIMEOUT_MS);
|
|
10125
10161
|
}
|
|
@@ -10160,6 +10196,15 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10160
10196
|
this.interruptRequested = true;
|
|
10161
10197
|
return (await this.control({ subtype: "interrupt" }, timeoutMs))?.subtype === "success";
|
|
10162
10198
|
}
|
|
10199
|
+
/**
|
|
10200
|
+
* On stdin's end the CLI exits by itself once its turn has closed, and only
|
|
10201
|
+
* an exit of its own saves the session's running totals for a `--resume` to
|
|
10202
|
+
* restore (checked on 2.1.303: about a second later, code 0, or 1 after an
|
|
10203
|
+
* interrupted turn).
|
|
10204
|
+
*/
|
|
10205
|
+
endInput() {
|
|
10206
|
+
this.process?.stdin?.end();
|
|
10207
|
+
}
|
|
10163
10208
|
/**
|
|
10164
10209
|
* A `user` line written mid-turn is Claude Code's own queue (ADR-0023): the
|
|
10165
10210
|
* CLI attaches it to the next tool result, or runs it as a turn of its own
|
|
@@ -10247,6 +10292,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10247
10292
|
this.resultHeld = false;
|
|
10248
10293
|
this.turnClosed = false;
|
|
10249
10294
|
this.clearFollowOnTimer();
|
|
10295
|
+
this.plannerModel = void 0;
|
|
10250
10296
|
}
|
|
10251
10297
|
/**
|
|
10252
10298
|
* The echo of a steered message: the model has it. After a closed turn it
|
|
@@ -10301,6 +10347,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10301
10347
|
this.permissions.withdraw(msg.request_id, emit);
|
|
10302
10348
|
return;
|
|
10303
10349
|
case "control_response": {
|
|
10350
|
+
const account = msg.response?.response?.account;
|
|
10351
|
+
if (account) this.reportBilling(account.apiKeySource, account.subscriptionType, emit);
|
|
10304
10352
|
const requestId = msg.response?.request_id;
|
|
10305
10353
|
if (requestId) this.pendingControl.get(requestId)?.(msg.response ?? null);
|
|
10306
10354
|
return;
|
|
@@ -10344,12 +10392,24 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10344
10392
|
emit({ type: "tool_result", id, name: "", output: diff || output, success: block.is_error !== true, subagentId });
|
|
10345
10393
|
}
|
|
10346
10394
|
return;
|
|
10395
|
+
// `/clear` (and `/reset`, `/new`) emit this top-level line before their
|
|
10396
|
+
// own result, and a task's chat can send them. The CLI's totals restart.
|
|
10397
|
+
case "conversation_reset": {
|
|
10398
|
+
if (subagentId) return;
|
|
10399
|
+
this.restartCount();
|
|
10400
|
+
const resetModel = this.sessionModel ?? (this.plannerModel !== "<synthetic>" ? this.plannerModel : void 0);
|
|
10401
|
+
emit({ type: "usage", record: { source: "claude-code", ...resetModel ? { model: resetModel } : {}, compacted: true } });
|
|
10402
|
+
return;
|
|
10403
|
+
}
|
|
10347
10404
|
case "system":
|
|
10405
|
+
if (subagentId && (msg.subtype === "init" || msg.subtype === "compact_boundary")) return;
|
|
10348
10406
|
if (msg.subtype === "init") {
|
|
10349
10407
|
this.clearFollowOnTimer();
|
|
10350
10408
|
if (this.refuseOtherMode(msg, emit)) return;
|
|
10409
|
+
if (msg.model && msg.model !== "<synthetic>") this.sessionModel = msg.model;
|
|
10351
10410
|
this.readLoaded(msg, emit);
|
|
10352
10411
|
this.followModel(msg.model, emit);
|
|
10412
|
+
this.reportBilling(msg.apiKeySource, void 0, emit);
|
|
10353
10413
|
}
|
|
10354
10414
|
if (msg.subtype === "commands_changed" && Array.isArray(msg.commands)) this.listedCommands = msg.commands;
|
|
10355
10415
|
if (msg.subtype === "compact_boundary") {
|
|
@@ -10360,6 +10420,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10360
10420
|
...typeof meta?.pre_tokens === "number" ? { preTokens: meta.pre_tokens } : {},
|
|
10361
10421
|
...typeof meta?.post_tokens === "number" ? { postTokens: meta.post_tokens } : {}
|
|
10362
10422
|
});
|
|
10423
|
+
const model = this.sessionModel ?? (this.plannerModel !== "<synthetic>" ? this.plannerModel : void 0);
|
|
10424
|
+
emit({ type: "usage", record: { source: "claude-code", ...model ? { model } : {}, compacted: true, ...typeof meta?.post_tokens === "number" ? { contextTokens: meta.post_tokens } : {} } });
|
|
10363
10425
|
}
|
|
10364
10426
|
if (msg.subtype === "background_tasks_changed" && Array.isArray(msg.tasks)) {
|
|
10365
10427
|
this.backgroundTaskCount = msg.tasks.length;
|
|
@@ -10371,6 +10433,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10371
10433
|
}
|
|
10372
10434
|
return;
|
|
10373
10435
|
case "result":
|
|
10436
|
+
if (subagentId) return;
|
|
10374
10437
|
this.reportSessionCost(msg, emit);
|
|
10375
10438
|
for (const command of this.modelCommands) command.turnsLeft = Math.max(0, command.turnsLeft - 1);
|
|
10376
10439
|
if (this.interruptRequested && (msg.is_error || msg.subtype !== "success")) {
|
|
@@ -10535,16 +10598,69 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10535
10598
|
*/
|
|
10536
10599
|
handleStreamEvent(event, emit) {
|
|
10537
10600
|
if (event.type === "message_start") {
|
|
10538
|
-
this.plannerModel = event.message?.model;
|
|
10601
|
+
this.plannerModel = this.modelId(event.message?.model);
|
|
10539
10602
|
this.streamingMessage = event.message?.id;
|
|
10540
10603
|
this.followModel(this.plannerModel, emit);
|
|
10541
|
-
} else if (event.type === "message_delta" && event.usage)
|
|
10542
|
-
|
|
10604
|
+
} else if (event.type === "message_delta" && event.usage) {
|
|
10605
|
+
const record = usageRecord(event.usage, this.plannerModel);
|
|
10606
|
+
this.turnCallUsage.inputTokens += record.inputTokens ?? 0;
|
|
10607
|
+
this.turnCallUsage.cachedInputTokens += record.cachedInputTokens ?? 0;
|
|
10608
|
+
this.turnCallUsage.outputTokens += record.outputTokens ?? 0;
|
|
10609
|
+
emit({ type: "usage", record });
|
|
10610
|
+
} else if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta.text) {
|
|
10543
10611
|
this.replyRuns.delta(`${this.streamingMessage ?? ""}:${event.index ?? 0}`, event.delta.text, emit);
|
|
10544
10612
|
} else if (event.type === "content_block_delta" && event.delta?.type === "thinking_delta" && event.delta.thinking) {
|
|
10545
10613
|
emit({ type: "thinking_delta", text: event.delta.thinking });
|
|
10546
10614
|
}
|
|
10547
10615
|
}
|
|
10616
|
+
/**
|
|
10617
|
+
* The id a reported model runs as. `init` names the selected value — a
|
|
10618
|
+
* `[1m]` variant included — while a message names the API's canonical id
|
|
10619
|
+
* without it. Keeping the variant is what lets the meter find the window
|
|
10620
|
+
* `modelUsage` keys it by, and keeps that window off the base model.
|
|
10621
|
+
*/
|
|
10622
|
+
modelId(reported) {
|
|
10623
|
+
if (!reported) return void 0;
|
|
10624
|
+
if (!sameModel(this.sessionModel, reported)) return reported;
|
|
10625
|
+
return this.sessionModel?.includes("[") ? this.sessionModel : reported;
|
|
10626
|
+
}
|
|
10627
|
+
reportBilling(apiKeySource, plan, emit) {
|
|
10628
|
+
const type = apiKeySource ? apiKeySource === "none" ? "subscription" : "api-key" : plan ? "subscription" : void 0;
|
|
10629
|
+
if (!type) return;
|
|
10630
|
+
const knownPlan = plan ?? (this.billing?.type === type ? this.billing.plan : void 0);
|
|
10631
|
+
this.billing = { type, ...knownPlan ? { plan: knownPlan } : {} };
|
|
10632
|
+
emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling: this.billing } });
|
|
10633
|
+
}
|
|
10634
|
+
/**
|
|
10635
|
+
* A turn's `result` states its own token sum. Every call that streamed its
|
|
10636
|
+
* `message_delta` usage has already reported, so only the remainder is added:
|
|
10637
|
+
* all of it when nothing streamed (a recording without partial messages), or
|
|
10638
|
+
* a lost call's share. It is a sum across calls, marked `aggregate` so it
|
|
10639
|
+
* never moves the meter — only a call says what the context was left at.
|
|
10640
|
+
*/
|
|
10641
|
+
reportUnstreamedUsage(msg, emit) {
|
|
10642
|
+
const { inputTokens: streamedInput, cachedInputTokens: streamedCached, outputTokens: streamedOutput } = this.turnCallUsage;
|
|
10643
|
+
this.turnCallUsage = { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0 };
|
|
10644
|
+
if (!msg.usage) return;
|
|
10645
|
+
const models = Object.keys(msg.modelUsage ?? {});
|
|
10646
|
+
const model = this.plannerModel ?? this.mainModel ?? (models.length === 1 ? models[0] : void 0);
|
|
10647
|
+
const total = usageRecord(msg.usage, model);
|
|
10648
|
+
const inputTokens = (total.inputTokens ?? 0) - streamedInput;
|
|
10649
|
+
const cachedInputTokens = (total.cachedInputTokens ?? 0) - streamedCached;
|
|
10650
|
+
const outputTokens = (total.outputTokens ?? 0) - streamedOutput;
|
|
10651
|
+
if (inputTokens <= 0 && outputTokens <= 0) return;
|
|
10652
|
+
emit({
|
|
10653
|
+
type: "usage",
|
|
10654
|
+
record: {
|
|
10655
|
+
source: "claude-code",
|
|
10656
|
+
...model ? { model } : {},
|
|
10657
|
+
aggregate: true,
|
|
10658
|
+
...inputTokens > 0 ? { inputTokens } : {},
|
|
10659
|
+
...inputTokens > 0 && cachedInputTokens > 0 ? { cachedInputTokens } : {},
|
|
10660
|
+
...outputTokens > 0 ? { outputTokens } : {}
|
|
10661
|
+
}
|
|
10662
|
+
});
|
|
10663
|
+
}
|
|
10548
10664
|
/**
|
|
10549
10665
|
* What only the result line knows: the cost, which covers every call the
|
|
10550
10666
|
* session made — subagents included, since none of their lines carries one —
|
|
@@ -10553,24 +10669,54 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
10553
10669
|
* the session before; with no count kept, the first turn only sets the
|
|
10554
10670
|
* baseline: its total includes turns counted before, and a turn's own share
|
|
10555
10671
|
* cannot be told apart from them.
|
|
10672
|
+
*
|
|
10673
|
+
* The running total never lowers: money already reported and counted is not
|
|
10674
|
+
* un-spent because a later or resumed process remembers less of it, and
|
|
10675
|
+
* counting its return would count the same dollars twice. A zero is not a
|
|
10676
|
+
* measurement at all — a crash can end with one — and only `/clear` restarts
|
|
10677
|
+
* the count, through {@link restartCount}. Per-model baselines never lower
|
|
10678
|
+
* either, for the same reason.
|
|
10679
|
+
*
|
|
10680
|
+
* A report without a model breakdown is counted as a session total; one with
|
|
10681
|
+
* a breakdown is counted per model. A breakdown that would count more than
|
|
10682
|
+
* the session total's own growth (the count before it had no model split, or
|
|
10683
|
+
* a lossy report) is counted as that growth alone, so the two shapes
|
|
10684
|
+
* together still add each dollar once.
|
|
10556
10685
|
*/
|
|
10557
10686
|
reportSessionCost(msg, emit) {
|
|
10687
|
+
this.reportUnstreamedUsage(msg, emit);
|
|
10558
10688
|
const total = msg.total_cost_usd;
|
|
10559
10689
|
const canReport = this.reportedCostUsd !== void 0;
|
|
10560
|
-
const modelCosts = Object.entries(msg.modelUsage ?? {}).filter((entry) => typeof entry[1].costUSD === "number");
|
|
10690
|
+
const modelCosts = Object.entries(msg.modelUsage ?? {}).filter((entry) => typeof entry[1].costUSD === "number" && entry[1].costUSD > 0 && entry[1].costBasis !== "unknown");
|
|
10691
|
+
const growth = canReport && typeof total === "number" && total > 0 ? total - this.reportedCostUsd : void 0;
|
|
10692
|
+
let emitted = 0;
|
|
10693
|
+
const emitCost = (record) => {
|
|
10694
|
+
if (!(record.amount > 0)) return;
|
|
10695
|
+
emitted += record.amount;
|
|
10696
|
+
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 } : {} } } });
|
|
10697
|
+
};
|
|
10561
10698
|
if (modelCosts.length > 0) {
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10699
|
+
const modelGrowth = modelCosts.reduce((sum, [model2, usage]) => sum + Math.max(0, usage.costUSD - (this.reportedModelCostUsd.get(model2) ?? 0)), 0);
|
|
10700
|
+
if (canReport) {
|
|
10701
|
+
if (growth !== void 0 && modelGrowth > growth + 1e-9) {
|
|
10702
|
+
emitCost({ amount: growth });
|
|
10703
|
+
} else {
|
|
10704
|
+
for (const [model2, usage] of modelCosts) emitCost({ model: model2, amount: usage.costUSD - (this.reportedModelCostUsd.get(model2) ?? 0), ...usage.costBasis ? { basis: usage.costBasis } : {} });
|
|
10705
|
+
}
|
|
10567
10706
|
}
|
|
10568
|
-
|
|
10569
|
-
|
|
10707
|
+
for (const [model2, usage] of modelCosts) this.reportedModelCostUsd.set(model2, Math.max(this.reportedModelCostUsd.get(model2) ?? 0, usage.costUSD));
|
|
10708
|
+
} else if (!Object.values(msg.modelUsage ?? {}).some((usage) => usage.costBasis === "unknown") && typeof total === "number" && total > 0 && canReport && total >= this.reportedCostUsd) {
|
|
10709
|
+
emitCost({ amount: total - this.reportedCostUsd });
|
|
10710
|
+
}
|
|
10711
|
+
if (!canReport) {
|
|
10712
|
+
if (typeof total === "number" && total > 0) this.reportedCostUsd = total;
|
|
10713
|
+
else if (modelCosts.length > 0) this.reportedCostUsd = modelCosts.reduce((sum, [, usage]) => sum + usage.costUSD, 0);
|
|
10714
|
+
} else {
|
|
10715
|
+
this.reportedCostUsd = (this.reportedCostUsd ?? 0) + emitted;
|
|
10570
10716
|
}
|
|
10571
|
-
|
|
10572
|
-
const contextWindow =
|
|
10573
|
-
if (contextWindow
|
|
10717
|
+
const model = this.plannerModel && this.plannerModel !== "<synthetic>" ? this.plannerModel : this.sessionModel;
|
|
10718
|
+
const contextWindow = model ? msg.modelUsage?.[model]?.contextWindow : void 0;
|
|
10719
|
+
if (typeof contextWindow === "number" && contextWindow > 0) emit({ type: "usage", record: { source: "claude-code", model, contextWindow } });
|
|
10574
10720
|
}
|
|
10575
10721
|
};
|
|
10576
10722
|
|
|
@@ -11205,9 +11351,13 @@ function mergeCodexRateLimits(known, params) {
|
|
|
11205
11351
|
const id = snapshot.limitId ?? "";
|
|
11206
11352
|
const before = known.get(id);
|
|
11207
11353
|
const next = new Map(known);
|
|
11208
|
-
next.set(id, { limitId: id, primary: snapshot.primary ?? before?.primary, secondary: snapshot.secondary ?? before?.secondary });
|
|
11354
|
+
next.set(id, { limitId: id, planType: snapshot.planType ?? before?.planType, primary: snapshot.primary ?? before?.primary, secondary: snapshot.secondary ?? before?.secondary });
|
|
11209
11355
|
return next;
|
|
11210
11356
|
}
|
|
11357
|
+
function codexSessionBilling(limits) {
|
|
11358
|
+
const plan = [...limits.values()].find((snapshot) => snapshot.planType && snapshot.planType !== "unknown")?.planType;
|
|
11359
|
+
return plan ? { type: "subscription", plan } : void 0;
|
|
11360
|
+
}
|
|
11211
11361
|
function isCodexUsageLimit(error) {
|
|
11212
11362
|
const info = error?.codexErrorInfo;
|
|
11213
11363
|
if (info === void 0 || info === null || info === "other") return void 0;
|
|
@@ -11766,8 +11916,21 @@ ${this.exitMessage()}`);
|
|
|
11766
11916
|
case "thread/tokenUsage/updated":
|
|
11767
11917
|
this.emitUsage(msg.params, emit);
|
|
11768
11918
|
return;
|
|
11919
|
+
case "account/updated": {
|
|
11920
|
+
const account = msg.params;
|
|
11921
|
+
if (account?.authMode === "apikey") emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling: { type: "api-key" } } });
|
|
11922
|
+
else if (account?.authMode === "chatgpt" || account?.authMode === "chatgptAuthTokens") {
|
|
11923
|
+
const plan = account.planType;
|
|
11924
|
+
emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling: { type: "subscription", ...plan && plan !== "unknown" ? { plan } : {} } } });
|
|
11925
|
+
}
|
|
11926
|
+
return;
|
|
11927
|
+
}
|
|
11769
11928
|
case "account/rateLimits/updated":
|
|
11770
11929
|
this.rateLimits = mergeCodexRateLimits(this.rateLimits, msg.params);
|
|
11930
|
+
{
|
|
11931
|
+
const sessionBilling = codexSessionBilling(this.rateLimits);
|
|
11932
|
+
if (sessionBilling) emit({ type: "usage", record: { source: this.agentId, aggregate: true, sessionBilling } });
|
|
11933
|
+
}
|
|
11771
11934
|
return;
|
|
11772
11935
|
case "turn/started": {
|
|
11773
11936
|
if (this.subagentOf(msg.params?.threadId)) return;
|
|
@@ -11896,6 +12059,13 @@ ${this.exitMessage()}`);
|
|
|
11896
12059
|
* `last.outputTokens` already includes `reasoningOutputTokens` — the thread
|
|
11897
12060
|
* total is `inputTokens + outputTokens`, not a sum of three — so reasoning is
|
|
11898
12061
|
* not added a second time. Codex reports no price.
|
|
12062
|
+
*
|
|
12063
|
+
* The main thread's context is what Codex itself shows as in its window:
|
|
12064
|
+
* `TokenUsage::tokens_in_context_window`, which is `total_tokens` over
|
|
12065
|
+
* `last` as codex-rs defines it. `total_tokens` is input plus output, and
|
|
12066
|
+
* reasoning is part of output, so this is not `input + output − reasoning`
|
|
12067
|
+
* (checked against codex-rs and codex-cli 0.163.0's token_count events,
|
|
12068
|
+
* where `total_tokens` is `input_tokens + output_tokens`).
|
|
11899
12069
|
*/
|
|
11900
12070
|
emitUsage(params, emit) {
|
|
11901
12071
|
const last = params?.tokenUsage?.last;
|
|
@@ -11923,6 +12093,8 @@ ${this.exitMessage()}`);
|
|
|
11923
12093
|
} else {
|
|
11924
12094
|
const window = params?.tokenUsage?.modelContextWindow;
|
|
11925
12095
|
if (typeof window === "number" && window > 0) record.contextWindow = window;
|
|
12096
|
+
const inContext = last.totalTokens !== void 0 && last.totalTokens > 0 ? last.totalTokens : last.inputTokens !== void 0 ? last.inputTokens + (last.outputTokens ?? 0) : void 0;
|
|
12097
|
+
if (inContext !== void 0) record.contextTokens = Math.max(0, inContext);
|
|
11926
12098
|
}
|
|
11927
12099
|
emit({ type: "usage", record });
|
|
11928
12100
|
}
|
|
@@ -12084,7 +12256,9 @@ ${this.exitMessage()}`);
|
|
|
12084
12256
|
}
|
|
12085
12257
|
switch (item.type) {
|
|
12086
12258
|
case "contextCompaction":
|
|
12087
|
-
if (
|
|
12259
|
+
if (subagentId) return;
|
|
12260
|
+
emit({ type: "compaction", trigger: this.command === "compact" ? "manual" : "auto" });
|
|
12261
|
+
emit({ type: "usage", record: { source: this.agentId, compacted: true } });
|
|
12088
12262
|
return;
|
|
12089
12263
|
// Codex announces the item started and completed; whichever lands first delivers.
|
|
12090
12264
|
case "userMessage":
|
|
@@ -12380,8 +12554,9 @@ function usageRecord2(tokens, call) {
|
|
|
12380
12554
|
if ((prompt.inputTokens ?? 0) + outputTokens === 0) return null;
|
|
12381
12555
|
const record = { source: "opencode", ...prompt, outputTokens };
|
|
12382
12556
|
if (call.model) record.model = call.model;
|
|
12383
|
-
if (typeof call.cost === "number" && call.cost > 0) record.reportedCost = { amount: call.cost, currency: "USD" };
|
|
12557
|
+
if (typeof call.cost === "number" && call.cost > 0) record.reportedCost = { amount: call.cost, currency: "USD", kind: "estimate" };
|
|
12384
12558
|
if (call.subagentId) record.subagentId = call.subagentId;
|
|
12559
|
+
else record.contextTokens = (prompt.inputTokens ?? 0) + outputTokens;
|
|
12385
12560
|
return record;
|
|
12386
12561
|
}
|
|
12387
12562
|
function permissionReply(decision, key) {
|
|
@@ -12944,6 +13119,7 @@ var OpenCodeV2 = class {
|
|
|
12944
13119
|
}
|
|
12945
13120
|
if (frame.type === "session.compaction.ended") {
|
|
12946
13121
|
onEvent({ type: "compaction", trigger: data.reason === "manual" ? "manual" : "auto" });
|
|
13122
|
+
onEvent({ type: "usage", record: { source: "opencode", compacted: true } });
|
|
12947
13123
|
return;
|
|
12948
13124
|
}
|
|
12949
13125
|
if (frame.type === "session.execution.started") {
|
|
@@ -13809,6 +13985,7 @@ var OpenCodeAdapter = class {
|
|
|
13809
13985
|
if (session && session !== this.sessionId && !turn.children.follows(session)) return;
|
|
13810
13986
|
if (frame.type === "session.compacted" && session === this.sessionId) {
|
|
13811
13987
|
onEvent({ type: "compaction", trigger: this.manualCompaction ? "manual" : "auto" });
|
|
13988
|
+
onEvent({ type: "usage", record: { source: "opencode", compacted: true } });
|
|
13812
13989
|
return;
|
|
13813
13990
|
}
|
|
13814
13991
|
if (session === this.sessionId && this.taskTurn) this.followTaskTurn(frame, this.taskTurn, onEvent);
|
|
@@ -17363,6 +17540,7 @@ var PlannerConversation = class {
|
|
|
17363
17540
|
const request = summaryRequest(this.currentPlanLines());
|
|
17364
17541
|
const onProgress = (p) => {
|
|
17365
17542
|
if (p.type === "liveness") this.host.onProgress(p);
|
|
17543
|
+
else if (p.type === "usage" && p.record) this.host.onProgress({ ...p, record: { ...p.record, aggregate: true } });
|
|
17366
17544
|
};
|
|
17367
17545
|
let turn;
|
|
17368
17546
|
if (this.canContinueLive()) {
|
|
@@ -20979,6 +21157,9 @@ function nonPromptingFs(fs18) {
|
|
|
20979
21157
|
}
|
|
20980
21158
|
};
|
|
20981
21159
|
}
|
|
21160
|
+
function withoutContext({ contextTokens: _context, ...record }) {
|
|
21161
|
+
return record;
|
|
21162
|
+
}
|
|
20982
21163
|
async function runLoop(prompt, deps) {
|
|
20983
21164
|
if (deps.signal?.aborted) return "[research agent aborted before starting]";
|
|
20984
21165
|
const fs18 = nonPromptingFs(deps.fs);
|
|
@@ -20986,7 +21167,8 @@ async function runLoop(prompt, deps) {
|
|
|
20986
21167
|
(delta) => deps.onProgress?.({ type: "thinking", text: delta, subagentId: deps.subagentId }),
|
|
20987
21168
|
// The record says who made the call: unstamped, the ledger would read the
|
|
20988
21169
|
// subagent's prompt as the planner's own and measure its context by it.
|
|
20989
|
-
|
|
21170
|
+
// The chat states the context its call leaves; a subagent's is no meter's.
|
|
21171
|
+
(record) => deps.onProgress?.({ type: "usage", record: deps.subagentId ? { ...withoutContext(record), subagentId: deps.subagentId } : record })
|
|
20990
21172
|
);
|
|
20991
21173
|
let turn = await chat.sendMessage(prompt, deps.signal);
|
|
20992
21174
|
for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
|
|
@@ -21446,9 +21628,11 @@ function plannerNotReadyFor(config) {
|
|
|
21446
21628
|
var TERMINAL_UI_COMMANDS = {
|
|
21447
21629
|
pickPlanner: "/planner",
|
|
21448
21630
|
setKey: () => "/key",
|
|
21449
|
-
|
|
21631
|
+
// `/planner` walks harness, model and effort in one flow; there is no
|
|
21632
|
+
// separate `/model` (or `/planner-effort`) to point at.
|
|
21633
|
+
setModel: "/planner"
|
|
21450
21634
|
};
|
|
21451
|
-
var CHAT_COMMANDS = { ...TERMINAL_UI_COMMANDS
|
|
21635
|
+
var CHAT_COMMANDS = { ...TERMINAL_UI_COMMANDS };
|
|
21452
21636
|
var HEADLESS_CLI_COMMANDS = {
|
|
21453
21637
|
pickPlanner: "ordewell planner <id>",
|
|
21454
21638
|
setKey: (provider) => `ordewell key set ${provider} <key>`,
|
|
@@ -21462,7 +21646,7 @@ function harnessPlannerNames() {
|
|
|
21462
21646
|
var PLANNER_ESCAPE_HINT = `or pick a coding agent with ${TERMINAL_UI_COMMANDS.pickPlanner}: it plans with no API key`;
|
|
21463
21647
|
function describePlannerNotReady(reason, commands) {
|
|
21464
21648
|
if (reason.code === "no_model") {
|
|
21465
|
-
return `No planner model selected for ${reason.label}. Pick one with ${commands.setModel}, or switch planner with ${commands.pickPlanner}.`;
|
|
21649
|
+
return commands.setModel === commands.pickPlanner ? `No planner model selected for ${reason.label}. Pick one with ${commands.setModel}.` : `No planner model selected for ${reason.label}. Pick one with ${commands.setModel}, or switch planner with ${commands.pickPlanner}.`;
|
|
21466
21650
|
}
|
|
21467
21651
|
const lead = `No planner is ready. Pick one with ${commands.pickPlanner}: ${harnessPlannerNames()} plan with no API key.`;
|
|
21468
21652
|
if (reason.code === "no_base_url") {
|
|
@@ -21496,7 +21680,8 @@ function usageRecordFrom(usage, model) {
|
|
|
21496
21680
|
model,
|
|
21497
21681
|
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
21498
21682
|
...reportedOutput.length > 0 ? { outputTokens: reportedOutput.reduce((a, b) => a + b, 0) } : {},
|
|
21499
|
-
...tokenCount(usage.cachedContentTokenCount) !== void 0 ? { cachedInputTokens: tokenCount(usage.cachedContentTokenCount) } : {}
|
|
21683
|
+
...tokenCount(usage.cachedContentTokenCount) !== void 0 ? { cachedInputTokens: tokenCount(usage.cachedContentTokenCount) } : {},
|
|
21684
|
+
...inputTokens !== void 0 ? { contextTokens: inputTokens + (answerTokens ?? 0) } : {}
|
|
21500
21685
|
};
|
|
21501
21686
|
const anyReported = record.inputTokens !== void 0 || record.outputTokens !== void 0 || record.cachedInputTokens !== void 0;
|
|
21502
21687
|
return anyReported ? record : void 0;
|
|
@@ -21899,7 +22084,9 @@ var OpenAiResearchChat = class {
|
|
|
21899
22084
|
/**
|
|
21900
22085
|
* The one record for this API call, built only from what the provider
|
|
21901
22086
|
* reported. OpenRouter's `cost` is in USD (its credits are dollar-pegged);
|
|
21902
|
-
* nothing is estimated. A field the provider left out stays absent.
|
|
22087
|
+
* nothing is estimated. A field the provider left out stays absent. The
|
|
22088
|
+
* context the call leaves is its prompt and its completion: the API's own
|
|
22089
|
+
* `total_tokens`, and the reply the next call's prompt carries.
|
|
21903
22090
|
*/
|
|
21904
22091
|
usageRecord(reported) {
|
|
21905
22092
|
if (!reported) return void 0;
|
|
@@ -21918,9 +22105,10 @@ var OpenAiResearchChat = class {
|
|
|
21918
22105
|
hasMeasure = true;
|
|
21919
22106
|
}
|
|
21920
22107
|
if (reported.cost !== void 0) {
|
|
21921
|
-
record.reportedCost = { amount: reported.cost, currency: "USD" };
|
|
22108
|
+
record.reportedCost = { amount: reported.cost, currency: "USD", ...record.source === "openrouter" ? { kind: "billed" } : {} };
|
|
21922
22109
|
hasMeasure = true;
|
|
21923
22110
|
}
|
|
22111
|
+
if (record.inputTokens !== void 0) record.contextTokens = record.inputTokens + (record.outputTokens ?? 0);
|
|
21924
22112
|
return hasMeasure ? record : void 0;
|
|
21925
22113
|
}
|
|
21926
22114
|
};
|
|
@@ -24091,6 +24279,10 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
24091
24279
|
store;
|
|
24092
24280
|
output;
|
|
24093
24281
|
attempts = /* @__PURE__ */ new Map();
|
|
24282
|
+
/** Bumped by every plan load: an ended attempt's wind-down belongs to the scope before it. */
|
|
24283
|
+
planScope = 0;
|
|
24284
|
+
/** Each task's runners still winding down once stopped: a continue waits for them (ADR-0018, L1). */
|
|
24285
|
+
windDowns = /* @__PURE__ */ new Map();
|
|
24094
24286
|
verifier = new VerdictEngine();
|
|
24095
24287
|
/** Changed only by start, stop, a plan load, a usage-limit pause, a move off the limit, and {@link settleRun}. */
|
|
24096
24288
|
runState = "idle";
|
|
@@ -24639,8 +24831,9 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
24639
24831
|
return blockingFailures(this.store.planTasks).get(taskId) ?? [];
|
|
24640
24832
|
}
|
|
24641
24833
|
loadPlan(tasks, planRunners = [...DEFAULT_RUNNERS]) {
|
|
24642
|
-
this.store.load(tasks, planRunners);
|
|
24643
24834
|
const repairs = this.endAllAttempts("load").filter((a) => a.kind.kind === "repair" && a.worktree);
|
|
24835
|
+
this.planScope += 1;
|
|
24836
|
+
this.store.load(tasks, planRunners);
|
|
24644
24837
|
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}"`);
|
|
24645
24838
|
this.runs.interrupt({ keepOpen: this.isScheduling });
|
|
24646
24839
|
if (this.runState !== "scheduling") this.runState = "idle";
|
|
@@ -24726,6 +24919,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
24726
24919
|
if (reason === "user") this.stopGeneration++;
|
|
24727
24920
|
this.runState = "idle";
|
|
24728
24921
|
this.verifier.reset();
|
|
24922
|
+
for (const attempt of this.attempts.values()) this.stopRunner(attempt);
|
|
24729
24923
|
this.runner.stopAll();
|
|
24730
24924
|
const stopped = [];
|
|
24731
24925
|
for (const a of this.endAllAttempts("stop")) {
|
|
@@ -24839,6 +25033,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
24839
25033
|
if (!task || !attempt) return;
|
|
24840
25034
|
attempt.decided = true;
|
|
24841
25035
|
const stoppedMidTurn = endedByExit(verdict) ? saidInFlight(attempt) : null;
|
|
25036
|
+
this.stopRunner(attempt);
|
|
24842
25037
|
console.error(`[TaskOrchestrator] Task ${taskRefOf(this.store.planTasks, task.id) ?? `#${task.order}`} "${task.title}" verdict=${verdict.outcome}`);
|
|
24843
25038
|
console.error(`[TaskOrchestrator] Runner: ${task.assignedRunner}, Model: ${task.assignedModel?.modelId ?? "default"}`);
|
|
24844
25039
|
console.error(`[TaskOrchestrator] Prompt preview: ${(task.prompt ?? "").slice(0, 200)}`);
|
|
@@ -25750,7 +25945,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
25750
25945
|
autoResumed: opts.autoResumed === true,
|
|
25751
25946
|
turnError: null,
|
|
25752
25947
|
turnOutputFrom: 0,
|
|
25753
|
-
reminded: false
|
|
25948
|
+
reminded: false,
|
|
25949
|
+
runnerStopped: false,
|
|
25950
|
+
planScope: this.planScope
|
|
25754
25951
|
};
|
|
25755
25952
|
this.spawnCounts.set(task.id, attempt.attempt);
|
|
25756
25953
|
this.attempts.set(task.id, attempt);
|
|
@@ -25787,6 +25984,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
25787
25984
|
const manifest = this.registry?.get(attempt.runner)?.manifest;
|
|
25788
25985
|
const unavailable = manifest && unavailableTaskModeMessage(manifest, task.taskMode);
|
|
25789
25986
|
if (unavailable) throw new Error(unavailable);
|
|
25987
|
+
if (kind.kind === "continuation") await this.windDowns.get(task.id);
|
|
25790
25988
|
const { cwd, worktree, previousWork } = await attemptCwd(kind, task, this.runs);
|
|
25791
25989
|
attempt.cwd = cwd;
|
|
25792
25990
|
attempt.worktree = worktree;
|
|
@@ -25842,10 +26040,11 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
25842
26040
|
}
|
|
25843
26041
|
if (takesSkills(kind)) this.store.setTaskAttemptSkills(task.id, [...attempt.skills]);
|
|
25844
26042
|
session.onEvent((event) => {
|
|
25845
|
-
if (event.type !== "usage" || this.attempts.get(task.id) !== attempt) return;
|
|
26043
|
+
if (event.type !== "usage" || attempt.planScope !== this.planScope || this.attempts.get(task.id) !== attempt && !attempt.runnerStopped) return;
|
|
25846
26044
|
this.store.recordAttemptUsage(task.id, attempt.attempt, event.record, (/* @__PURE__ */ new Date()).toISOString());
|
|
25847
26045
|
this.emit("onUsageChanged");
|
|
25848
26046
|
});
|
|
26047
|
+
session.onExit(() => this.woundDown(attempt, session));
|
|
25849
26048
|
this.output.attach(task.id, session);
|
|
25850
26049
|
this.verifier.watch(task, session);
|
|
25851
26050
|
session.onEvent((event) => {
|
|
@@ -26014,10 +26213,35 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
26014
26213
|
if (stopsRunner(reason)) {
|
|
26015
26214
|
const task = this.store.get(taskId);
|
|
26016
26215
|
if (task) this.verifier.clear(task);
|
|
26017
|
-
if (
|
|
26216
|
+
if (attempt) this.stopRunner(attempt);
|
|
26018
26217
|
}
|
|
26019
26218
|
return attempt;
|
|
26020
26219
|
}
|
|
26220
|
+
/**
|
|
26221
|
+
* Let the attempt's runner go the way it ends a turn (ADR-0018, L1): it
|
|
26222
|
+
* winds down on its own time, bounded by its interrupt grace, and nothing
|
|
26223
|
+
* here waits on it but a continue of the same task.
|
|
26224
|
+
*/
|
|
26225
|
+
stopRunner(attempt) {
|
|
26226
|
+
const session = attempt.session;
|
|
26227
|
+
if (!session || attempt.runnerStopped) return;
|
|
26228
|
+
attempt.runnerStopped = true;
|
|
26229
|
+
const { taskId } = attempt;
|
|
26230
|
+
const windDown = Promise.all([this.windDowns.get(taskId), this.runner.stop(session.id)]).catch(() => void 0).then(() => {
|
|
26231
|
+
if (this.windDowns.get(taskId) === windDown) this.windDowns.delete(taskId);
|
|
26232
|
+
});
|
|
26233
|
+
this.windDowns.set(taskId, windDown);
|
|
26234
|
+
}
|
|
26235
|
+
/**
|
|
26236
|
+
* An ended attempt's runner is gone: the count its session ended on is the
|
|
26237
|
+
* one a continue of that session counts from. One still current is ended
|
|
26238
|
+
* with that count instead.
|
|
26239
|
+
*/
|
|
26240
|
+
woundDown(attempt, session) {
|
|
26241
|
+
if (attempt.planScope !== this.planScope || !attempt.runnerStopped || this.attempts.get(attempt.taskId) === attempt) return;
|
|
26242
|
+
this.store.endAttemptUsage(attempt.taskId, attempt.attempt, (/* @__PURE__ */ new Date()).toISOString(), session.sessionCost?.() ?? void 0, session.nativeSessionId() ?? void 0);
|
|
26243
|
+
this.emit("onUsageChanged");
|
|
26244
|
+
}
|
|
26021
26245
|
/** Kept on the task once the attempt ends, so a continue can resume it after a reload (ADR-0018, K1). */
|
|
26022
26246
|
saveNativeSession(taskId, nativeSessionId, runner) {
|
|
26023
26247
|
if (nativeSessionId) this.store.setTaskRunnerSessionId(taskId, nativeSessionId, runner);
|
|
@@ -28752,19 +28976,50 @@ var TaskLogRecorder = class {
|
|
|
28752
28976
|
};
|
|
28753
28977
|
|
|
28754
28978
|
// src/services/ContextWindowFill.ts
|
|
28755
|
-
var
|
|
28756
|
-
constructor(catalog
|
|
28979
|
+
var SessionWindows = class {
|
|
28980
|
+
constructor(catalog = () => void 0) {
|
|
28757
28981
|
this.catalog = catalog;
|
|
28758
|
-
if (last.contextWindow !== void 0) this.known.set(modelKey(last.source, last.model), last.contextWindow);
|
|
28759
28982
|
}
|
|
28760
28983
|
catalog;
|
|
28761
|
-
|
|
28984
|
+
reported = /* @__PURE__ */ new Map();
|
|
28985
|
+
report(source, model, contextWindow) {
|
|
28986
|
+
if (contextWindow > 0) this.reported.set(modelKey(source, model), { source, model, contextWindow });
|
|
28987
|
+
}
|
|
28988
|
+
get(source, model) {
|
|
28989
|
+
return this.reported.get(modelKey(source, model))?.contextWindow;
|
|
28990
|
+
}
|
|
28991
|
+
snapshot() {
|
|
28992
|
+
return [...this.reported.values()];
|
|
28993
|
+
}
|
|
28994
|
+
restore(saved) {
|
|
28995
|
+
this.reported.clear();
|
|
28996
|
+
for (const { source, model, contextWindow } of saved ?? []) this.report(source, model, contextWindow);
|
|
28997
|
+
}
|
|
28998
|
+
clear() {
|
|
28999
|
+
this.reported.clear();
|
|
29000
|
+
}
|
|
29001
|
+
};
|
|
29002
|
+
var ContextWindowFill = class {
|
|
29003
|
+
constructor(windows, last = {}) {
|
|
29004
|
+
this.windows = windows;
|
|
29005
|
+
if (last.contextWindow !== void 0) this.known.set(modelKey(last.source, last.model), last.contextWindow);
|
|
29006
|
+
}
|
|
29007
|
+
windows;
|
|
29008
|
+
/** The window this stream used for each of its models, by reporter and model. */
|
|
28762
29009
|
known = /* @__PURE__ */ new Map();
|
|
28763
29010
|
fill(record) {
|
|
28764
29011
|
if (record.subagentId || record.aggregate) return record;
|
|
28765
29012
|
const key = modelKey(record.source, record.model);
|
|
29013
|
+
if (record.compacted) {
|
|
29014
|
+
if (!record.model) return record;
|
|
29015
|
+
const known = this.windows.get(record.source, record.model) ?? this.known.get(key) ?? positive(this.windows.catalog(record.source, record.model));
|
|
29016
|
+
if (known === void 0) return record;
|
|
29017
|
+
this.known.set(key, known);
|
|
29018
|
+
return { ...record, contextWindow: known };
|
|
29019
|
+
}
|
|
28766
29020
|
const reported = record.contextWindow !== void 0 && record.contextWindow > 0 ? record.contextWindow : void 0;
|
|
28767
|
-
|
|
29021
|
+
if (reported !== void 0 && record.model) this.windows.report(record.source, record.model, reported);
|
|
29022
|
+
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);
|
|
28768
29023
|
if (window !== void 0) this.known.set(key, window);
|
|
28769
29024
|
return window === void 0 || window === record.contextWindow ? record : { ...record, contextWindow: window };
|
|
28770
29025
|
}
|
|
@@ -28772,9 +29027,9 @@ var ContextWindowFill = class {
|
|
|
28772
29027
|
function modelKey(source, model) {
|
|
28773
29028
|
return JSON.stringify([source ?? null, model ?? null]);
|
|
28774
29029
|
}
|
|
28775
|
-
function withContextWindows(runner,
|
|
29030
|
+
function withContextWindows(runner, windows) {
|
|
28776
29031
|
return {
|
|
28777
|
-
spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(
|
|
29032
|
+
spawn: async (opts) => windowedSession(await runner.spawn(opts), new ContextWindowFill(windows, opts.resumeWindow)),
|
|
28778
29033
|
stop: (sessionId) => runner.stop(sessionId),
|
|
28779
29034
|
stopAll: () => runner.stopAll(),
|
|
28780
29035
|
get activeCount() {
|
|
@@ -28805,12 +29060,12 @@ function positive(n) {
|
|
|
28805
29060
|
|
|
28806
29061
|
// src/services/PlannerUsage.ts
|
|
28807
29062
|
var PlannerUsageLedger = class {
|
|
28808
|
-
/** `
|
|
28809
|
-
constructor(
|
|
28810
|
-
this.
|
|
28811
|
-
this.windows = new ContextWindowFill(
|
|
29063
|
+
/** `sessionWindows` serves the window of a planner model whose backend reports none. */
|
|
29064
|
+
constructor(sessionWindows = new SessionWindows()) {
|
|
29065
|
+
this.sessionWindows = sessionWindows;
|
|
29066
|
+
this.windows = new ContextWindowFill(sessionWindows);
|
|
28812
29067
|
}
|
|
28813
|
-
|
|
29068
|
+
sessionWindows;
|
|
28814
29069
|
usage = { totals: {} };
|
|
28815
29070
|
windows;
|
|
28816
29071
|
/** Fold one model call's record into the totals; returns the running ledger. */
|
|
@@ -28821,16 +29076,16 @@ var PlannerUsageLedger = class {
|
|
|
28821
29076
|
/** Adopt a persisted ledger (a reopened session) or start from zero. */
|
|
28822
29077
|
restore(usage) {
|
|
28823
29078
|
this.usage = usage ?? { totals: {} };
|
|
28824
|
-
this.windows = new ContextWindowFill(this.
|
|
29079
|
+
this.windows = new ContextWindowFill(this.sessionWindows, this.usage);
|
|
28825
29080
|
}
|
|
28826
29081
|
/** Start a fresh session's ledger from zero. */
|
|
28827
29082
|
clear() {
|
|
28828
29083
|
this.usage = { totals: {} };
|
|
28829
|
-
this.windows = new ContextWindowFill(this.
|
|
29084
|
+
this.windows = new ContextWindowFill(this.sessionWindows);
|
|
28830
29085
|
}
|
|
28831
|
-
/** Whether
|
|
29086
|
+
/** Whether the ledger has measures or session billing facts to broadcast. */
|
|
28832
29087
|
get hasUsage() {
|
|
28833
|
-
return isMeasured(this.usage.totals);
|
|
29088
|
+
return isMeasured(this.usage.totals) || this.usage.sessionBilling !== void 0;
|
|
28834
29089
|
}
|
|
28835
29090
|
/** The value persisted onto the plan state. */
|
|
28836
29091
|
snapshot() {
|
|
@@ -30279,7 +30534,7 @@ function createSession(deps) {
|
|
|
30279
30534
|
const backend = new PlannerBackend(deps.aiService ? { pinned: deps.aiService, busy } : { provider: () => deps.config.aiProvider, create: () => createAiService(deps.config, { workspaceRoot: deps.workspaceRoot, mcpServer }), busy });
|
|
30280
30535
|
const aiService = () => backend.current();
|
|
30281
30536
|
const store = new PlanStore();
|
|
30282
|
-
const
|
|
30537
|
+
const windows = new SessionWindows((source, model) => deps.modelResolver.contextWindowFor?.(source, model));
|
|
30283
30538
|
const taskLogs = new TaskLogRecorder({
|
|
30284
30539
|
broadcast: deps.broadcast,
|
|
30285
30540
|
// Read through the Session, whose id and workspace are the ones it saves
|
|
@@ -30312,7 +30567,7 @@ function createSession(deps) {
|
|
|
30312
30567
|
const orchestrator = TaskOrchestrator.compose({
|
|
30313
30568
|
config: deps.config,
|
|
30314
30569
|
notifications: deps.notifications,
|
|
30315
|
-
runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner,
|
|
30570
|
+
runner: taskLogs.wrap(runnerApprovals.wrap(withContextWindows(deps.runner, windows))),
|
|
30316
30571
|
store,
|
|
30317
30572
|
output: deps.taskOutput,
|
|
30318
30573
|
isolation: deps.isolation,
|
|
@@ -30324,7 +30579,7 @@ function createSession(deps) {
|
|
|
30324
30579
|
runSettings: () => session.planState?.runSettings,
|
|
30325
30580
|
isApproved: (taskId) => session.isApproved(taskId)
|
|
30326
30581
|
});
|
|
30327
|
-
const usage = new PlannerUsageLedger(
|
|
30582
|
+
const usage = new PlannerUsageLedger(windows);
|
|
30328
30583
|
const events = new SessionEventRelay({
|
|
30329
30584
|
broadcast: deps.broadcast,
|
|
30330
30585
|
onNotice: deps.onNotice,
|
|
@@ -30394,6 +30649,7 @@ function createSession(deps) {
|
|
|
30394
30649
|
runs: orchestrator.runs,
|
|
30395
30650
|
events,
|
|
30396
30651
|
usage,
|
|
30652
|
+
windows,
|
|
30397
30653
|
approvals,
|
|
30398
30654
|
approvalPolicy,
|
|
30399
30655
|
taskLogs,
|
|
@@ -30421,6 +30677,7 @@ function configSecrets(config) {
|
|
|
30421
30677
|
var Session = class {
|
|
30422
30678
|
backend;
|
|
30423
30679
|
usage;
|
|
30680
|
+
windows;
|
|
30424
30681
|
events;
|
|
30425
30682
|
planner;
|
|
30426
30683
|
orchestrator;
|
|
@@ -30526,6 +30783,7 @@ var Session = class {
|
|
|
30526
30783
|
this.runs = parts.runs;
|
|
30527
30784
|
this.events = parts.events;
|
|
30528
30785
|
this.usage = parts.usage;
|
|
30786
|
+
this.windows = parts.windows;
|
|
30529
30787
|
this.approvals = parts.approvals;
|
|
30530
30788
|
this.approvalPolicy = parts.approvalPolicy;
|
|
30531
30789
|
this.taskLogs = parts.taskLogs;
|
|
@@ -31004,6 +31262,7 @@ var Session = class {
|
|
|
31004
31262
|
this.syncPlanTasks();
|
|
31005
31263
|
this.plan.isolation = this.runs.planIsolation ?? void 0;
|
|
31006
31264
|
this.plan.plannerUsage = this.usage.snapshot();
|
|
31265
|
+
this.plan.contextWindows = this.windows.snapshot();
|
|
31007
31266
|
this.plan.supervision = this.supervision.snapshot();
|
|
31008
31267
|
this.plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
31009
31268
|
this.save(this.plan, this.goal, this.workspace, this.currentSessionId);
|
|
@@ -31033,6 +31292,7 @@ var Session = class {
|
|
|
31033
31292
|
this.owingEffects.clear();
|
|
31034
31293
|
this.conversation.reset();
|
|
31035
31294
|
this.usage.clear();
|
|
31295
|
+
this.windows.clear();
|
|
31036
31296
|
this.approvals.clear(isPlannerApproval);
|
|
31037
31297
|
if (!keepGrants) this.approvalPolicy.reset();
|
|
31038
31298
|
this.store.clearLog();
|
|
@@ -31373,7 +31633,18 @@ var Session = class {
|
|
|
31373
31633
|
if (!this.plan) throw new ConversationEditError("No planning conversation to condense");
|
|
31374
31634
|
const opening = this.plannerTurnMayOpen(signal);
|
|
31375
31635
|
if (opening) await opening;
|
|
31376
|
-
|
|
31636
|
+
const compaction = await this.conversation.compact(signal);
|
|
31637
|
+
const usage = this.usage.snapshot();
|
|
31638
|
+
if (usage.lastPromptTokens !== void 0) {
|
|
31639
|
+
this.usage.record({
|
|
31640
|
+
source: usage.source ?? this.config.aiProvider,
|
|
31641
|
+
...usage.model ? { model: usage.model } : {},
|
|
31642
|
+
compacted: true
|
|
31643
|
+
});
|
|
31644
|
+
this.persist();
|
|
31645
|
+
this.broadcast(this.usage.message());
|
|
31646
|
+
}
|
|
31647
|
+
return compaction;
|
|
31377
31648
|
}
|
|
31378
31649
|
rewindTargets() {
|
|
31379
31650
|
return this.conversation.rewindTargets();
|
|
@@ -31861,7 +32132,10 @@ var Session = class {
|
|
|
31861
32132
|
if (!plan.approvedTasks && plan.approved) {
|
|
31862
32133
|
plan.approvedTasks = flattenTasks(plan.tasks).map((t) => t.id);
|
|
31863
32134
|
}
|
|
31864
|
-
if (adopting)
|
|
32135
|
+
if (adopting) {
|
|
32136
|
+
this.usage.restore(plan.plannerUsage);
|
|
32137
|
+
this.windows.restore(plan.contextWindows);
|
|
32138
|
+
}
|
|
31865
32139
|
if (opts?.sessionId) this.currentSessionId = opts.sessionId;
|
|
31866
32140
|
if (adopting) {
|
|
31867
32141
|
for (const task of flattenTasks(plan.tasks)) delete task.resumeAt;
|
|
@@ -31948,17 +32222,12 @@ var AbstractRunner = class {
|
|
|
31948
32222
|
get activeCount() {
|
|
31949
32223
|
return this.sessions.size;
|
|
31950
32224
|
}
|
|
32225
|
+
/** A session winding down stays registered until it exits, so a second stop gets the same wind-down. */
|
|
31951
32226
|
stop(sessionId) {
|
|
31952
|
-
|
|
31953
|
-
if (!session) return;
|
|
31954
|
-
session.kill();
|
|
31955
|
-
this.sessions.delete(sessionId);
|
|
32227
|
+
return this.sessions.get(sessionId)?.stop() ?? Promise.resolve();
|
|
31956
32228
|
}
|
|
31957
32229
|
stopAll() {
|
|
31958
|
-
for (const
|
|
31959
|
-
session.kill();
|
|
31960
|
-
}
|
|
31961
|
-
this.sessions.clear();
|
|
32230
|
+
for (const session of this.sessions.values()) void session.stop();
|
|
31962
32231
|
}
|
|
31963
32232
|
registerSession(id, session) {
|
|
31964
32233
|
this.sessions.set(id, session);
|
|
@@ -32110,12 +32379,17 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32110
32379
|
text;
|
|
32111
32380
|
state = "idle";
|
|
32112
32381
|
turn = null;
|
|
32382
|
+
/** A `task_complete` report waiting for its turn to close; the verdict is published then. */
|
|
32383
|
+
completionReport = null;
|
|
32384
|
+
completionTimer = null;
|
|
32113
32385
|
/** A terminal event ends the turn before the adapter releases its send. */
|
|
32114
32386
|
sending = null;
|
|
32115
32387
|
turnCount = 0;
|
|
32116
32388
|
queue = [];
|
|
32117
32389
|
messageCount = 0;
|
|
32118
32390
|
interrupting = null;
|
|
32391
|
+
/** The wind-down once {@link stop} was asked for: the same for every caller, and nothing new is sent meanwhile. */
|
|
32392
|
+
stopping = null;
|
|
32119
32393
|
lastSessionId = null;
|
|
32120
32394
|
lastSessionCost = null;
|
|
32121
32395
|
structuredEmitter = new EventEmitter2();
|
|
@@ -32141,7 +32415,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32141
32415
|
await this.startAttached(this.launch.startOptions);
|
|
32142
32416
|
this.state = "working";
|
|
32143
32417
|
setImmediate(() => {
|
|
32144
|
-
if (!this.exited) this.deliver(prompt);
|
|
32418
|
+
if (!this.exited && !this.stopping) this.deliver(prompt);
|
|
32145
32419
|
});
|
|
32146
32420
|
}
|
|
32147
32421
|
getOutput() {
|
|
@@ -32166,7 +32440,9 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32166
32440
|
create: () => this.launch.createAdapter(this.launch.runner, this.launch.deps),
|
|
32167
32441
|
issueToken: () => server4.issueTaskToken(scope, {
|
|
32168
32442
|
taskComplete: async (report) => {
|
|
32169
|
-
this.
|
|
32443
|
+
this.completionReport = report;
|
|
32444
|
+
if (this.completionTimer) clearTimeout(this.completionTimer);
|
|
32445
|
+
this.completionTimer = setTimeout(() => this.flushCompletion(), this.launch.interruptGraceMs);
|
|
32170
32446
|
return { text: "Recorded. End your turn now." };
|
|
32171
32447
|
},
|
|
32172
32448
|
checkpoint: async ({ question }, { signal }) => {
|
|
@@ -32198,6 +32474,8 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32198
32474
|
this.toolToken = null;
|
|
32199
32475
|
}
|
|
32200
32476
|
baseHandleExit(code) {
|
|
32477
|
+
this.undeliverQueued();
|
|
32478
|
+
this.flushCompletion();
|
|
32201
32479
|
this.ended.abort();
|
|
32202
32480
|
this.revokeTools();
|
|
32203
32481
|
this.undeliverQueued();
|
|
@@ -32221,6 +32499,53 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32221
32499
|
this.withdrawPermissions();
|
|
32222
32500
|
this.baseHandleExit(-1);
|
|
32223
32501
|
}
|
|
32502
|
+
stop() {
|
|
32503
|
+
if (this.stopping) return this.stopping;
|
|
32504
|
+
if (this.exited) return Promise.resolve();
|
|
32505
|
+
let woundDown;
|
|
32506
|
+
this.stopping = new Promise((resolve9) => {
|
|
32507
|
+
woundDown = resolve9;
|
|
32508
|
+
});
|
|
32509
|
+
void this.windDown().catch(() => this.kill()).then(woundDown);
|
|
32510
|
+
return this.stopping;
|
|
32511
|
+
}
|
|
32512
|
+
/**
|
|
32513
|
+
* The attempt is over (ADR-0018, L1), so what is still owed goes undelivered
|
|
32514
|
+
* and every tool request unanswered at once. A turn still open is
|
|
32515
|
+
* interrupted, and its close — where Claude Code reports the turn's cost and
|
|
32516
|
+
* its model's window — is waited for; then the runner is told nothing more
|
|
32517
|
+
* is coming and left to exit by itself. All of it within the interrupt
|
|
32518
|
+
* grace: a runner still there once it runs out is killed.
|
|
32519
|
+
*/
|
|
32520
|
+
async windDown() {
|
|
32521
|
+
const adapter = this.adapterAttached ? this.adapter : null;
|
|
32522
|
+
const deadline = Date.now() + this.launch.interruptGraceMs;
|
|
32523
|
+
this.revokeTools();
|
|
32524
|
+
this.undeliverQueued();
|
|
32525
|
+
this.withdrawPermissions();
|
|
32526
|
+
const open = this.turn && !this.turn.ended ? this.turn : null;
|
|
32527
|
+
if (adapter && (!open || await this.turnClosed(open, adapter, deadline)) && adapter.endInput && !this.exited) {
|
|
32528
|
+
adapter.endInput();
|
|
32529
|
+
await this.within(this.gone(), deadline);
|
|
32530
|
+
}
|
|
32531
|
+
if (this.exited) adapter?.dispose();
|
|
32532
|
+
else this.kill();
|
|
32533
|
+
}
|
|
32534
|
+
/** Whether `turn` closed within the deadline once interrupted. */
|
|
32535
|
+
async turnClosed(turn, adapter, deadline) {
|
|
32536
|
+
const closed = new Promise((resolve9) => this.structuredEmitter.once("turnEnd", () => resolve9()));
|
|
32537
|
+
adapter.interrupt(Math.max(0, deadline - Date.now())).catch(() => false);
|
|
32538
|
+
return await this.within(closed, deadline) && turn.ended;
|
|
32539
|
+
}
|
|
32540
|
+
/** Whether `done` settled before the deadline, or the session ended meanwhile. */
|
|
32541
|
+
within(done, deadline) {
|
|
32542
|
+
return settleWithin(done.then(() => true), { timeoutMs: Math.max(0, deadline - Date.now()), onTimeout: () => false, ended: this.gone(), onEnded: () => true });
|
|
32543
|
+
}
|
|
32544
|
+
gone() {
|
|
32545
|
+
const { signal } = this.ended;
|
|
32546
|
+
if (signal.aborted) return Promise.resolve();
|
|
32547
|
+
return new Promise((resolve9) => signal.addEventListener("abort", () => resolve9(), { once: true }));
|
|
32548
|
+
}
|
|
32224
32549
|
turnState() {
|
|
32225
32550
|
return this.state;
|
|
32226
32551
|
}
|
|
@@ -32246,7 +32571,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32246
32571
|
this.messageCount += 1;
|
|
32247
32572
|
const id = `msg-${this.messageCount}`;
|
|
32248
32573
|
const sender2 = opening.from ? { from: opening.from } : {};
|
|
32249
|
-
if (this.exited) this.emitEvent({ type: "message_undelivered", messageId: id, text, ...sender2 });
|
|
32574
|
+
if (this.exited || this.stopping) this.emitEvent({ type: "message_undelivered", messageId: id, text, ...sender2 });
|
|
32250
32575
|
else if (this.state === "idle" && this.adapterStarted && !this.sending) this.deliver(text, opening);
|
|
32251
32576
|
else {
|
|
32252
32577
|
this.state = "working";
|
|
@@ -32291,6 +32616,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32291
32616
|
* "waiting for input" between the two (F3).
|
|
32292
32617
|
*/
|
|
32293
32618
|
force(message) {
|
|
32619
|
+
this.discardCompletion();
|
|
32294
32620
|
message.forced = true;
|
|
32295
32621
|
const at = this.queue.findIndex((m) => !m.forced);
|
|
32296
32622
|
this.queue.splice(at < 0 ? this.queue.length : at, 0, message);
|
|
@@ -32301,7 +32627,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32301
32627
|
});
|
|
32302
32628
|
}
|
|
32303
32629
|
runningTurn() {
|
|
32304
|
-
return !this.exited && this.adapter !== null && this.turn !== null && !this.turn.ended;
|
|
32630
|
+
return !this.exited && !this.stopping && this.adapter !== null && this.turn !== null && !this.turn.ended;
|
|
32305
32631
|
}
|
|
32306
32632
|
/** No runner can recall a message once offered to it, so only a queued one comes back. */
|
|
32307
32633
|
removeQueued(id) {
|
|
@@ -32362,7 +32688,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32362
32688
|
if (this.interrupting) return this.interrupting;
|
|
32363
32689
|
const turn = this.turn;
|
|
32364
32690
|
const adapter = this.adapter;
|
|
32365
|
-
if (!turn || turn.ended || !adapter) return Promise.resolve();
|
|
32691
|
+
if (!turn || turn.ended || !adapter || this.stopping) return Promise.resolve();
|
|
32366
32692
|
this.interrupting = this.interruptTurn(turn, adapter).finally(() => {
|
|
32367
32693
|
this.interrupting = null;
|
|
32368
32694
|
this.offerNext();
|
|
@@ -32376,7 +32702,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32376
32702
|
const settled = await settleWithin(ended.then(() => true), { timeoutMs: grace, onTimeout: () => false });
|
|
32377
32703
|
if (settled) return;
|
|
32378
32704
|
}
|
|
32379
|
-
if (turn.ended || this.exited) return;
|
|
32705
|
+
if (turn.ended || this.exited || this.stopping) return;
|
|
32380
32706
|
await this.restartInterrupted(turn);
|
|
32381
32707
|
}
|
|
32382
32708
|
/**
|
|
@@ -32432,6 +32758,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32432
32758
|
/** `message` is what opened the turn: a queued one has an id, one delivered straight from idle only its sender. */
|
|
32433
32759
|
openTurn(text, message) {
|
|
32434
32760
|
this.turnCount += 1;
|
|
32761
|
+
this.discardCompletion();
|
|
32435
32762
|
const turn = { id: this.turnCount, abort: new AbortController(), ended: false, reason: "completed", steerRefused: false };
|
|
32436
32763
|
this.turn = turn;
|
|
32437
32764
|
this.state = "working";
|
|
@@ -32480,7 +32807,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32480
32807
|
offerNext() {
|
|
32481
32808
|
const adapter = this.adapter;
|
|
32482
32809
|
const turn = this.turn;
|
|
32483
|
-
if (!adapter?.steer || !turn || turn.ended || turn.steerRefused || this.interrupting || this.exited) return;
|
|
32810
|
+
if (!adapter?.steer || !turn || turn.ended || turn.steerRefused || this.interrupting || this.exited || this.stopping) return;
|
|
32484
32811
|
if (this.queue.some((m) => m.stage === "steering")) return;
|
|
32485
32812
|
const next = this.queue.find((m) => m.stage === "queued");
|
|
32486
32813
|
if (!next || next.command) return;
|
|
@@ -32524,6 +32851,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32524
32851
|
} else if (event.type === "permission_request") this.openPermission(adapter, event);
|
|
32525
32852
|
else if (event.type === "permission_cancelled") this.cancelPermission(adapter, event.id);
|
|
32526
32853
|
else if (event.type === "message_delivered") {
|
|
32854
|
+
this.discardCompletion();
|
|
32527
32855
|
const message = this.delivered(event.id);
|
|
32528
32856
|
if (message) this.emitEvent({ type: "message_delivered", messageId: message.id, text: message.text, ...message.from ? { from: message.from } : {} });
|
|
32529
32857
|
} else if (event.type === "message_dropped") {
|
|
@@ -32555,7 +32883,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32555
32883
|
}
|
|
32556
32884
|
return;
|
|
32557
32885
|
}
|
|
32558
|
-
if (!turn && TURN_OPENING_EVENTS.has(event.type) && !("subagentId" in event && event.subagentId)) {
|
|
32886
|
+
if (!turn && !this.stopping && TURN_OPENING_EVENTS.has(event.type) && !("subagentId" in event && event.subagentId)) {
|
|
32559
32887
|
turn = this.openTurn("");
|
|
32560
32888
|
this.offerNext();
|
|
32561
32889
|
}
|
|
@@ -32604,6 +32932,24 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32604
32932
|
}
|
|
32605
32933
|
this.emitEvent(event);
|
|
32606
32934
|
}
|
|
32935
|
+
/** Publish a `task_complete` report whose turn has closed, or that waited out the grace. */
|
|
32936
|
+
flushCompletion() {
|
|
32937
|
+
if (this.completionTimer) {
|
|
32938
|
+
clearTimeout(this.completionTimer);
|
|
32939
|
+
this.completionTimer = null;
|
|
32940
|
+
}
|
|
32941
|
+
const report = this.completionReport;
|
|
32942
|
+
this.completionReport = null;
|
|
32943
|
+
if (report) this.structuredEmitter.emit("taskComplete", report);
|
|
32944
|
+
}
|
|
32945
|
+
/** A message the runner reads, or a turn it opens, voids a report made before it (ADR-0023, D6). */
|
|
32946
|
+
discardCompletion() {
|
|
32947
|
+
if (this.completionTimer) {
|
|
32948
|
+
clearTimeout(this.completionTimer);
|
|
32949
|
+
this.completionTimer = null;
|
|
32950
|
+
}
|
|
32951
|
+
this.completionReport = null;
|
|
32952
|
+
}
|
|
32607
32953
|
/**
|
|
32608
32954
|
* A queued message goes out as the turn closes, and the state never passes
|
|
32609
32955
|
* through `idle` on the way, so a listener told the turn ended can already
|
|
@@ -32616,6 +32962,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32616
32962
|
turn.ended = true;
|
|
32617
32963
|
this.text.endTurn();
|
|
32618
32964
|
this.emitEvent({ type: "turn_end", reason });
|
|
32965
|
+
this.flushCompletion();
|
|
32619
32966
|
if (reason === "failed") this.undeliverQueued();
|
|
32620
32967
|
this.state = !this.exited && this.queue.length ? "working" : "idle";
|
|
32621
32968
|
this.structuredEmitter.emit("turnEnd", reason);
|
|
@@ -32626,7 +32973,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
32626
32973
|
* back in Ordewell's hands: while it is on offer or owed, nothing overtakes it.
|
|
32627
32974
|
*/
|
|
32628
32975
|
deliverNext() {
|
|
32629
|
-
if (this.exited || this.sending || this.turn && !this.turn.ended) return;
|
|
32976
|
+
if (this.exited || this.stopping || this.sending || this.turn && !this.turn.ended) return;
|
|
32630
32977
|
const head = this.queue[0];
|
|
32631
32978
|
if (!head || this.queue.some((message) => message.stage !== "queued")) return;
|
|
32632
32979
|
this.queue.shift();
|
|
@@ -33075,7 +33422,10 @@ export {
|
|
|
33075
33422
|
collectProviderCredentials,
|
|
33076
33423
|
commandAllowedBy,
|
|
33077
33424
|
configuredProviders,
|
|
33425
|
+
contextMeterText,
|
|
33426
|
+
contextMeterTitle,
|
|
33078
33427
|
continuability,
|
|
33428
|
+
costText,
|
|
33079
33429
|
countedSessionCost,
|
|
33080
33430
|
createAiService,
|
|
33081
33431
|
createEmptyPlan,
|
|
@@ -33118,7 +33468,9 @@ export {
|
|
|
33118
33468
|
flattenTasksWithParents,
|
|
33119
33469
|
followTaskLog,
|
|
33120
33470
|
followTurn,
|
|
33471
|
+
formatCost,
|
|
33121
33472
|
formatSearchOutput,
|
|
33473
|
+
formatTokenCount,
|
|
33122
33474
|
fromTranscript,
|
|
33123
33475
|
getLatestSession,
|
|
33124
33476
|
getProviderMeta,
|
|
@@ -33170,6 +33522,7 @@ export {
|
|
|
33170
33522
|
migratePlanState,
|
|
33171
33523
|
mintDaemonToken,
|
|
33172
33524
|
mintSessionId,
|
|
33525
|
+
modelCostTitle,
|
|
33173
33526
|
namedRepos,
|
|
33174
33527
|
newTaskFields,
|
|
33175
33528
|
normalizeAgentArgs,
|
|
@@ -33255,6 +33608,7 @@ export {
|
|
|
33255
33608
|
stopTurn,
|
|
33256
33609
|
stripAnsi,
|
|
33257
33610
|
stripModelPrefix,
|
|
33611
|
+
subagentCostTitle,
|
|
33258
33612
|
subtreeIds,
|
|
33259
33613
|
summarizeToolCall,
|
|
33260
33614
|
supervisorSummary,
|
|
@@ -33273,6 +33627,7 @@ export {
|
|
|
33273
33627
|
taskStartedNotice,
|
|
33274
33628
|
taskStatusKind,
|
|
33275
33629
|
taskUsageSummary,
|
|
33630
|
+
tasksTotalText,
|
|
33276
33631
|
textHasTaskOps,
|
|
33277
33632
|
textHasTaskQuery,
|
|
33278
33633
|
titledRefs,
|
|
@@ -33290,6 +33645,7 @@ export {
|
|
|
33290
33645
|
unsendAll,
|
|
33291
33646
|
unsendLatest,
|
|
33292
33647
|
usageLine,
|
|
33648
|
+
usageText,
|
|
33293
33649
|
waitsForApproval,
|
|
33294
33650
|
walkPlan,
|
|
33295
33651
|
wellKnownBinDirs,
|