@letrquan/book 0.2.0 → 0.3.0
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/CHANGELOG.md +2747 -40
- package/README.md +132 -1311
- package/dist/additional-roots-H7UNAEHS.js +31 -0
- package/dist/{app-MOXXWEOY.js → app-GE4OHJOC.js} +5740 -4045
- package/dist/app-GE4OHJOC.js.map +1 -0
- package/dist/{chunk-5GDZ22YP.js → chunk-224FJZLH.js} +99 -108
- package/dist/chunk-224FJZLH.js.map +1 -0
- package/dist/chunk-2AAOF5B5.js +28 -0
- package/dist/chunk-2AAOF5B5.js.map +1 -0
- package/dist/{chunk-CAGOB2N4.js → chunk-4AS7LKKQ.js} +6 -4
- package/dist/chunk-4AS7LKKQ.js.map +1 -0
- package/dist/chunk-525CAZPY.js +120 -0
- package/dist/chunk-525CAZPY.js.map +1 -0
- package/dist/{chunk-URLQAXTR.js → chunk-56SIEMDX.js} +2 -2
- package/dist/chunk-56SIEMDX.js.map +1 -0
- package/dist/{chunk-XIFFTATU.js → chunk-5YHTKWJN.js} +5420 -4031
- package/dist/chunk-5YHTKWJN.js.map +1 -0
- package/dist/chunk-62QL5QUP.js +283 -0
- package/dist/chunk-62QL5QUP.js.map +1 -0
- package/dist/{chunk-KWHQ2XXL.js → chunk-67UZEWWF.js} +3 -3
- package/dist/chunk-67UZEWWF.js.map +1 -0
- package/dist/chunk-6OQQOMZR.js +785 -0
- package/dist/chunk-6OQQOMZR.js.map +1 -0
- package/dist/{chunk-GTVB4HEM.js → chunk-AQEQX4FF.js} +344 -315
- package/dist/chunk-AQEQX4FF.js.map +1 -0
- package/dist/chunk-C7ADUNPR.js +273 -0
- package/dist/chunk-C7ADUNPR.js.map +1 -0
- package/dist/{chunk-E75IPST5.js → chunk-EFJ2NH6E.js} +12 -58
- package/dist/chunk-EFJ2NH6E.js.map +1 -0
- package/dist/{chunk-5KLVH3PY.js → chunk-HQI7NYGX.js} +5 -3
- package/dist/chunk-HQI7NYGX.js.map +1 -0
- package/dist/chunk-IME7R2UI.js +6541 -0
- package/dist/chunk-IME7R2UI.js.map +1 -0
- package/dist/chunk-J4F2QLM2.js +273 -0
- package/dist/chunk-J4F2QLM2.js.map +1 -0
- package/dist/{chunk-MAQXCUR4.js → chunk-J62VZYOB.js} +444 -112
- package/dist/chunk-J62VZYOB.js.map +1 -0
- package/dist/chunk-JTPOI3KU.js +826 -0
- package/dist/chunk-JTPOI3KU.js.map +1 -0
- package/dist/{chunk-WKBO5R4O.js → chunk-KBQS35OV.js} +2 -2
- package/dist/chunk-KBQS35OV.js.map +1 -0
- package/dist/chunk-KXAYUIKP.js +90 -0
- package/dist/chunk-KXAYUIKP.js.map +1 -0
- package/dist/{chunk-VZQXX3WW.js → chunk-QYBD22H5.js} +1 -1
- package/dist/chunk-QYBD22H5.js.map +1 -0
- package/dist/{chunk-V4BOYR52.js → chunk-RFJ6Q6LE.js} +763 -198
- package/dist/chunk-RFJ6Q6LE.js.map +1 -0
- package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
- package/dist/chunk-RT5SATF7.js.map +1 -0
- package/dist/{chunk-C7D4NJ2D.js → chunk-S4JMID4G.js} +68 -26
- package/dist/chunk-S4JMID4G.js.map +1 -0
- package/dist/{chunk-ANSJPYGI.js → chunk-SOW4WRQP.js} +3 -3
- package/dist/chunk-SOW4WRQP.js.map +1 -0
- package/dist/{chunk-J7MIZ5CA.js → chunk-TLX3H2CL.js} +518 -79
- package/dist/chunk-TLX3H2CL.js.map +1 -0
- package/dist/{chunk-DGMQEI6Y.js → chunk-VPNLCMVR.js} +22 -9
- package/dist/chunk-VPNLCMVR.js.map +1 -0
- package/dist/{chunk-BTZZUCLI.js → chunk-W3XFHEF4.js} +173 -29
- package/dist/chunk-W3XFHEF4.js.map +1 -0
- package/dist/{chunk-574C6MQR.js → chunk-X4NJT436.js} +130 -60
- package/dist/chunk-X4NJT436.js.map +1 -0
- package/dist/chunk-X66I2NQO.js +137 -0
- package/dist/chunk-X66I2NQO.js.map +1 -0
- package/dist/chunk-XHAO3K7I.js +18 -0
- package/dist/chunk-XHAO3K7I.js.map +1 -0
- package/dist/{chunk-3U5IBM24.js → chunk-Y2VHN7TC.js} +2 -2
- package/dist/chunk-Y2VHN7TC.js.map +1 -0
- package/dist/chunk-YC6W4EJH.js +947 -0
- package/dist/chunk-YC6W4EJH.js.map +1 -0
- package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
- package/dist/chunk-ZPSZZUWF.js.map +1 -0
- package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-VBIU6BLM.js} +6 -5
- package/dist/context-CBOBDY5Q.js +38 -0
- package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-ZVT4V7IM.js} +5 -5
- package/dist/index.js +996 -155
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-T4I4SEHU.js} +13 -9
- package/dist/interactive-assets-T4I4SEHU.js.map +1 -0
- package/dist/job-runner.js +159 -111
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.d.ts +2 -0
- package/dist/job-supervisor.js +112 -0
- package/dist/job-supervisor.js.map +1 -0
- package/dist/loader-S77OVTHH.js +18 -0
- package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-HJ2VDYMC.js} +5 -5
- package/dist/{mcp-config-YIJIES2C.js → mcp-config-RNB5LEVE.js} +3 -3
- package/dist/memory-store-DZUT5HUH.js +75 -0
- package/dist/{model-window-store-YFVNXCIP.js → model-window-store-MTSQ5JDJ.js} +8 -4
- package/dist/openai-compatible-2OB54YST.js +14 -0
- package/dist/{permission-approvals-76PVREFD.js → permission-approvals-YLV6UC5W.js} +5 -5
- package/dist/permissions-DHOKF3W2.js +45 -0
- package/dist/sandbox-WRYTZH3D.js +41 -0
- package/dist/{scrollback-LUKLIBVI.js → scrollback-35ME2GFX.js} +32 -22
- package/dist/scrollback-35ME2GFX.js.map +1 -0
- package/dist/sdk.d.ts +2219 -2361
- package/dist/sdk.js +53 -38
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-JUNPGAAM.js +35 -0
- package/dist/settings-loader-JUNPGAAM.js.map +1 -0
- package/dist/{shell-selection-V6QAYXPX.js → shell-selection-CDHGJ4GC.js} +2 -2
- package/dist/shell-selection-CDHGJ4GC.js.map +1 -0
- package/package.json +23 -22
- package/dist/app-MOXXWEOY.js.map +0 -1
- package/dist/chunk-24AXE6SP.js.map +0 -1
- package/dist/chunk-3U5IBM24.js.map +0 -1
- package/dist/chunk-574C6MQR.js.map +0 -1
- package/dist/chunk-5GDZ22YP.js.map +0 -1
- package/dist/chunk-5KLVH3PY.js.map +0 -1
- package/dist/chunk-7N4J557C.js.map +0 -1
- package/dist/chunk-A7JLBK2Y.js +0 -3506
- package/dist/chunk-A7JLBK2Y.js.map +0 -1
- package/dist/chunk-ANSJPYGI.js.map +0 -1
- package/dist/chunk-BTZZUCLI.js.map +0 -1
- package/dist/chunk-C7D4NJ2D.js.map +0 -1
- package/dist/chunk-CAGOB2N4.js.map +0 -1
- package/dist/chunk-DGMQEI6Y.js.map +0 -1
- package/dist/chunk-E75IPST5.js.map +0 -1
- package/dist/chunk-EYGD6X2T.js +0 -180
- package/dist/chunk-EYGD6X2T.js.map +0 -1
- package/dist/chunk-GTVB4HEM.js.map +0 -1
- package/dist/chunk-J7MIZ5CA.js.map +0 -1
- package/dist/chunk-KWHQ2XXL.js.map +0 -1
- package/dist/chunk-L6LK2QIQ.js +0 -657
- package/dist/chunk-L6LK2QIQ.js.map +0 -1
- package/dist/chunk-MAQXCUR4.js.map +0 -1
- package/dist/chunk-S4XL7HOM.js +0 -402
- package/dist/chunk-S4XL7HOM.js.map +0 -1
- package/dist/chunk-URLQAXTR.js.map +0 -1
- package/dist/chunk-V4BOYR52.js.map +0 -1
- package/dist/chunk-VZQXX3WW.js.map +0 -1
- package/dist/chunk-WKBO5R4O.js.map +0 -1
- package/dist/chunk-XIFFTATU.js.map +0 -1
- package/dist/context-ZC5IFEAX.js +0 -25
- package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
- package/dist/loader-4IDS2RIE.js +0 -15
- package/dist/openai-compatible-RHZUBHV6.js +0 -10
- package/dist/permissions-CHCKTFQQ.js +0 -28
- package/dist/sandbox-CNVAQDA7.js +0 -23
- package/dist/scrollback-LUKLIBVI.js.map +0 -1
- package/dist/settings-loader-M2QP5TX2.js +0 -21
- package/patches/ink+6.8.0.patch +0 -13
- package/scripts/apply-ink-patch.mjs +0 -29
- package/scripts/ink-patch.mjs +0 -59
- package/scripts/verify-ink-patch.mjs +0 -11
- /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-H7UNAEHS.js.map} +0 -0
- /package/dist/{context-ZC5IFEAX.js.map → command-approvals-VBIU6BLM.js.map} +0 -0
- /package/dist/{hook-approvals-CTUFKEJT.js.map → context-CBOBDY5Q.js.map} +0 -0
- /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-ZVT4V7IM.js.map} +0 -0
- /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-S77OVTHH.js.map} +0 -0
- /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-HJ2VDYMC.js.map} +0 -0
- /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-RNB5LEVE.js.map} +0 -0
- /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-DZUT5HUH.js.map} +0 -0
- /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-MTSQ5JDJ.js.map} +0 -0
- /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-2OB54YST.js.map} +0 -0
- /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-YLV6UC5W.js.map} +0 -0
- /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-DHOKF3W2.js.map} +0 -0
- /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-WRYTZH3D.js.map} +0 -0
|
@@ -5,40 +5,73 @@ import {
|
|
|
5
5
|
createAgentRunContext,
|
|
6
6
|
createRunAmbientSnapshot,
|
|
7
7
|
createTerminalOutcome,
|
|
8
|
+
isSpendlessUsage,
|
|
8
9
|
isTerminalStatus,
|
|
9
10
|
loadGitignore,
|
|
10
|
-
runAgentLoop
|
|
11
|
-
|
|
12
|
-
runHooks,
|
|
13
|
-
runPostCompactHooks
|
|
14
|
-
} from "./chunk-XIFFTATU.js";
|
|
11
|
+
runAgentLoop
|
|
12
|
+
} from "./chunk-5YHTKWJN.js";
|
|
15
13
|
import {
|
|
16
14
|
DEFAULT_CONFIDENCE_THRESHOLD,
|
|
17
15
|
REVIEW_LENSES,
|
|
16
|
+
addUsage,
|
|
17
|
+
applyCompactResult,
|
|
18
18
|
buildReviewerPrompt,
|
|
19
19
|
buildSingleReviewPrompt,
|
|
20
20
|
clampConfidence,
|
|
21
21
|
isReviewCategory,
|
|
22
22
|
isReviewSeverity,
|
|
23
|
+
judgeCompaction,
|
|
24
|
+
judgedResult,
|
|
23
25
|
loadReviewConfig,
|
|
24
26
|
renderReviewConfigInstruction,
|
|
25
27
|
renderReviewTarget,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
resolveCompactBudgets,
|
|
29
|
+
resolveReviewTarget,
|
|
30
|
+
runCompact,
|
|
31
|
+
runHooks,
|
|
32
|
+
runPostCompactHooks
|
|
33
|
+
} from "./chunk-IME7R2UI.js";
|
|
28
34
|
import {
|
|
35
|
+
observationKey,
|
|
29
36
|
onAbort,
|
|
30
37
|
throwIfAborted,
|
|
38
|
+
workspaceIdentity,
|
|
31
39
|
yieldToEventLoop
|
|
32
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-J62VZYOB.js";
|
|
33
41
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
globWalkIgnore
|
|
43
|
+
} from "./chunk-JTPOI3KU.js";
|
|
44
|
+
import {
|
|
45
|
+
buildChildEnv
|
|
46
|
+
} from "./chunk-XHAO3K7I.js";
|
|
38
47
|
import {
|
|
39
48
|
createDebugLogger,
|
|
40
49
|
createDebugLoggerWithCounter
|
|
41
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-62QL5QUP.js";
|
|
51
|
+
import {
|
|
52
|
+
normalizeToolResult
|
|
53
|
+
} from "./chunk-224FJZLH.js";
|
|
54
|
+
|
|
55
|
+
// src/session-driver.ts
|
|
56
|
+
var SESSION_DRIVERS = ["human", "agent"];
|
|
57
|
+
function isSessionDriver(value) {
|
|
58
|
+
return SESSION_DRIVERS.includes(value);
|
|
59
|
+
}
|
|
60
|
+
function parseSessionDriver(value) {
|
|
61
|
+
const normalized = value?.trim().toLowerCase();
|
|
62
|
+
return isSessionDriver(normalized) ? normalized : void 0;
|
|
63
|
+
}
|
|
64
|
+
function resolveSessionDriver(input) {
|
|
65
|
+
const explicit = parseSessionDriver(input.explicit);
|
|
66
|
+
if (explicit) return { driver: explicit, signals: ["flag"] };
|
|
67
|
+
const fromEnv = parseSessionDriver(input.env.BOOK_SESSION_DRIVER);
|
|
68
|
+
if (fromEnv) return { driver: fromEnv, signals: ["BOOK_SESSION_DRIVER"] };
|
|
69
|
+
const signals = [];
|
|
70
|
+
const claudeCode = input.env.CLAUDECODE?.trim();
|
|
71
|
+
if (claudeCode && claudeCode !== "0") signals.push("CLAUDECODE");
|
|
72
|
+
if (input.env.AI_AGENT?.trim()) signals.push("AI_AGENT");
|
|
73
|
+
return { driver: signals.length > 0 ? "agent" : "human", signals };
|
|
74
|
+
}
|
|
42
75
|
|
|
43
76
|
// src/session/name.ts
|
|
44
77
|
var UNTITLED_SESSION_NAME = "Untitled session";
|
|
@@ -274,7 +307,8 @@ var SessionStore = class {
|
|
|
274
307
|
name: meta.name,
|
|
275
308
|
createdAt: now,
|
|
276
309
|
updatedAt: now,
|
|
277
|
-
messageCount: 0
|
|
310
|
+
messageCount: 0,
|
|
311
|
+
...meta.driver ? { driver: meta.driver } : {}
|
|
278
312
|
}
|
|
279
313
|
};
|
|
280
314
|
appendFileSync(this.path(id), JSON.stringify(header) + "\n", "utf-8");
|
|
@@ -286,10 +320,10 @@ var SessionStore = class {
|
|
|
286
320
|
appendFileSync(this.path(id), serialized, "utf-8");
|
|
287
321
|
const cached = this.parsedSessions.get(id);
|
|
288
322
|
if (cached) {
|
|
289
|
-
const
|
|
323
|
+
const stat2 = statSync(this.path(id));
|
|
290
324
|
cached.records.push(record);
|
|
291
|
-
cached.size =
|
|
292
|
-
cached.mtimeMs =
|
|
325
|
+
cached.size = stat2.size;
|
|
326
|
+
cached.mtimeMs = stat2.mtimeMs;
|
|
293
327
|
cached.loaded = void 0;
|
|
294
328
|
cached.activeEventIds = void 0;
|
|
295
329
|
cached.activeRecords = void 0;
|
|
@@ -300,9 +334,9 @@ var SessionStore = class {
|
|
|
300
334
|
readRecords(id) {
|
|
301
335
|
const p = this.path(id);
|
|
302
336
|
if (!existsSync(p)) throw new Error(`Session not found: ${id}`);
|
|
303
|
-
const
|
|
337
|
+
const stat2 = statSync(p);
|
|
304
338
|
const cached = this.parsedSessions.get(id);
|
|
305
|
-
if (cached && cached.size ===
|
|
339
|
+
if (cached && cached.size === stat2.size && cached.mtimeMs === stat2.mtimeMs) {
|
|
306
340
|
return cached.records;
|
|
307
341
|
}
|
|
308
342
|
const records = [];
|
|
@@ -337,8 +371,8 @@ var SessionStore = class {
|
|
|
337
371
|
closeSync(descriptor);
|
|
338
372
|
}
|
|
339
373
|
this.parsedSessions.set(id, {
|
|
340
|
-
size:
|
|
341
|
-
mtimeMs:
|
|
374
|
+
size: stat2.size,
|
|
375
|
+
mtimeMs: stat2.mtimeMs,
|
|
342
376
|
records
|
|
343
377
|
});
|
|
344
378
|
return records;
|
|
@@ -356,6 +390,9 @@ var SessionStore = class {
|
|
|
356
390
|
entry.meta.updatedAt = record.timestamp;
|
|
357
391
|
if (data.kind === "session_meta_patch") {
|
|
358
392
|
if (Object.prototype.hasOwnProperty.call(data, "name")) entry.meta.name = data.name;
|
|
393
|
+
if (isSessionDriver(data.driver)) {
|
|
394
|
+
entry.meta.driver = data.driver;
|
|
395
|
+
}
|
|
359
396
|
} else if (record.type === "user") {
|
|
360
397
|
entry.meta.messageCount++;
|
|
361
398
|
entry.lastMessageRole = "user";
|
|
@@ -406,7 +443,8 @@ var SessionStore = class {
|
|
|
406
443
|
createdAt: storedMeta?.createdAt ?? 0,
|
|
407
444
|
updatedAt: storedMeta?.updatedAt ?? 0,
|
|
408
445
|
messageCount: 0,
|
|
409
|
-
...storedMeta?.name === void 0 ? {} : { name: storedMeta.name }
|
|
446
|
+
...storedMeta?.name === void 0 ? {} : { name: storedMeta.name },
|
|
447
|
+
...isSessionDriver(storedMeta?.driver) ? { driver: storedMeta.driver } : {}
|
|
410
448
|
};
|
|
411
449
|
const transcript = [];
|
|
412
450
|
let contextHistory = [];
|
|
@@ -427,21 +465,18 @@ var SessionStore = class {
|
|
|
427
465
|
const data = record.data;
|
|
428
466
|
if (data.kind === "session_meta_patch") {
|
|
429
467
|
if (Object.prototype.hasOwnProperty.call(data, "name")) meta.name = data.name;
|
|
468
|
+
const driver = data.driver;
|
|
469
|
+
if (isSessionDriver(driver)) meta.driver = driver;
|
|
430
470
|
continue;
|
|
431
471
|
}
|
|
432
472
|
if (data.kind === "session_meta" || data.kind === "session_touch") continue;
|
|
433
473
|
if (record.type === "usage") {
|
|
434
474
|
const entry = record.data;
|
|
435
475
|
if (entry?.version === 1 && entry.usage) {
|
|
436
|
-
carriedUsage =
|
|
437
|
-
promptTokens: (carriedUsage?.promptTokens ?? 0) + (entry.usage.promptTokens ?? 0),
|
|
438
|
-
completionTokens: (carriedUsage?.completionTokens ?? 0) + (entry.usage.completionTokens ?? 0),
|
|
439
|
-
totalTokens: (carriedUsage?.totalTokens ?? 0) + (entry.usage.totalTokens ?? 0),
|
|
440
|
-
cacheCreationInputTokens: (carriedUsage?.cacheCreationInputTokens ?? 0) + (entry.usage.cacheCreationInputTokens ?? 0),
|
|
441
|
-
cacheReadInputTokens: (carriedUsage?.cacheReadInputTokens ?? 0) + (entry.usage.cacheReadInputTokens ?? 0)
|
|
442
|
-
};
|
|
476
|
+
carriedUsage = addUsage(carriedUsage ?? null, entry.usage);
|
|
443
477
|
const model = entry.responseModel ?? entry.requestedModel;
|
|
444
478
|
if (model) carriedModels.add(model);
|
|
479
|
+
for (const spent of entry.models ?? []) carriedModels.add(spent);
|
|
445
480
|
}
|
|
446
481
|
continue;
|
|
447
482
|
}
|
|
@@ -601,6 +636,7 @@ var SessionStore = class {
|
|
|
601
636
|
toolCalls: data.toolCalls,
|
|
602
637
|
toolResults: normalizePersistedToolResults(data.toolResults),
|
|
603
638
|
fileObservations: data.fileObservations,
|
|
639
|
+
hostNotice: data.hostNotice,
|
|
604
640
|
timestamp: record.timestamp
|
|
605
641
|
};
|
|
606
642
|
transcript.push(message);
|
|
@@ -942,7 +978,10 @@ function reviewRunnerFor(manager, attribution = {}) {
|
|
|
942
978
|
rootRunId: attribution.rootRunId,
|
|
943
979
|
parentRunId: attribution.parentRunId,
|
|
944
980
|
parentSessionId: attribution.parentSessionId,
|
|
945
|
-
notifyParentOnCompletion: false
|
|
981
|
+
notifyParentOnCompletion: false,
|
|
982
|
+
// The report dies with the process, so a restart must not re-run a reviewer
|
|
983
|
+
// whose result nobody would receive.
|
|
984
|
+
resumeAfterRestart: false
|
|
946
985
|
});
|
|
947
986
|
return {
|
|
948
987
|
id: record.id,
|
|
@@ -988,16 +1027,17 @@ function fixRunnerFor(manager, attribution = {}) {
|
|
|
988
1027
|
}
|
|
989
1028
|
|
|
990
1029
|
// src/review/json.ts
|
|
991
|
-
function
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1030
|
+
function firstObjectIn(text) {
|
|
1031
|
+
return objectIn(text, 0)?.slice;
|
|
1032
|
+
}
|
|
1033
|
+
function objectIn(text, from) {
|
|
1034
|
+
const start = text.indexOf("{", from);
|
|
995
1035
|
if (start === -1) return void 0;
|
|
996
1036
|
let depth = 0;
|
|
997
1037
|
let inString = false;
|
|
998
1038
|
let escaped = false;
|
|
999
|
-
for (let index = start; index <
|
|
1000
|
-
const char =
|
|
1039
|
+
for (let index = start; index < text.length; index++) {
|
|
1040
|
+
const char = text[index];
|
|
1001
1041
|
if (inString) {
|
|
1002
1042
|
if (escaped) escaped = false;
|
|
1003
1043
|
else if (char === "\\") escaped = true;
|
|
@@ -1006,10 +1046,47 @@ function extractJsonObject(text) {
|
|
|
1006
1046
|
}
|
|
1007
1047
|
if (char === '"') inString = true;
|
|
1008
1048
|
else if (char === "{") depth++;
|
|
1009
|
-
else if (char === "}" && --depth === 0)
|
|
1049
|
+
else if (char === "}" && --depth === 0) {
|
|
1050
|
+
return { start, end: index + 1, slice: text.slice(start, index + 1) };
|
|
1051
|
+
}
|
|
1010
1052
|
}
|
|
1011
1053
|
return void 0;
|
|
1012
1054
|
}
|
|
1055
|
+
function fenceRuns(text) {
|
|
1056
|
+
const runs = [];
|
|
1057
|
+
for (let index = text.indexOf("```"); index !== -1; index = text.indexOf("```", index + 3)) {
|
|
1058
|
+
runs.push(index);
|
|
1059
|
+
}
|
|
1060
|
+
return runs;
|
|
1061
|
+
}
|
|
1062
|
+
function fenceContent(text, open, close) {
|
|
1063
|
+
return text.slice(open + 3, close).replace(/^json\s*/i, "");
|
|
1064
|
+
}
|
|
1065
|
+
function isJsonObject(candidate) {
|
|
1066
|
+
try {
|
|
1067
|
+
const parsed = JSON.parse(candidate);
|
|
1068
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed);
|
|
1069
|
+
} catch {
|
|
1070
|
+
return false;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function extractJsonObject(text) {
|
|
1074
|
+
const runs = fenceRuns(text);
|
|
1075
|
+
const whole = objectIn(text, 0);
|
|
1076
|
+
if (whole !== void 0 && isJsonObject(whole.slice) && runs.every((run) => run >= whole.start && run < whole.end)) {
|
|
1077
|
+
return whole.slice;
|
|
1078
|
+
}
|
|
1079
|
+
let firstBalanced;
|
|
1080
|
+
for (let open = 0; open < runs.length; open++) {
|
|
1081
|
+
for (let close = open + 1; close < runs.length; close++) {
|
|
1082
|
+
const found = firstObjectIn(fenceContent(text, runs[open], runs[close]));
|
|
1083
|
+
if (found === void 0) continue;
|
|
1084
|
+
if (isJsonObject(found)) return found;
|
|
1085
|
+
firstBalanced ??= found;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return firstBalanced ?? firstObjectIn(text);
|
|
1089
|
+
}
|
|
1013
1090
|
function parseJsonObject(text) {
|
|
1014
1091
|
const candidate = extractJsonObject(text);
|
|
1015
1092
|
if (!candidate) return void 0;
|
|
@@ -1207,6 +1284,29 @@ function rankFindings(findings) {
|
|
|
1207
1284
|
function filterLowConfidence(findings, threshold = DEFAULT_CONFIDENCE_THRESHOLD) {
|
|
1208
1285
|
return findings.filter((finding) => finding.confidence >= threshold);
|
|
1209
1286
|
}
|
|
1287
|
+
function isStanding(finding) {
|
|
1288
|
+
return finding.verification === void 0 || finding.verification === "confirmed";
|
|
1289
|
+
}
|
|
1290
|
+
function deriveReviewVerdict(input) {
|
|
1291
|
+
const entries = [...input.coverage.reviewers, input.coverage.verifier].filter(
|
|
1292
|
+
(entry) => entry !== void 0
|
|
1293
|
+
);
|
|
1294
|
+
if (entries.some((entry) => entry.status !== "completed")) {
|
|
1295
|
+
return { verdict: "inconclusive", reason: "incomplete-coverage" };
|
|
1296
|
+
}
|
|
1297
|
+
const standing = input.findings.filter(isStanding);
|
|
1298
|
+
if (standing.some((finding) => finding.severity === "critical")) {
|
|
1299
|
+
return { verdict: "blocking", reason: "standing-findings" };
|
|
1300
|
+
}
|
|
1301
|
+
if (standing.length > 0) return { verdict: "recommend", reason: "standing-findings" };
|
|
1302
|
+
if (input.findings.length > 0) {
|
|
1303
|
+
return { verdict: "inconclusive", reason: "unverified-findings" };
|
|
1304
|
+
}
|
|
1305
|
+
if (input.reviewerVerdicts?.some((verdict) => verdict === "inconclusive")) {
|
|
1306
|
+
return { verdict: "inconclusive", reason: "reviewer-inconclusive" };
|
|
1307
|
+
}
|
|
1308
|
+
return { verdict: "clean", reason: "clean" };
|
|
1309
|
+
}
|
|
1210
1310
|
|
|
1211
1311
|
// src/review/verify-findings.ts
|
|
1212
1312
|
function buildVerificationPrompt(findings, config = {}, target) {
|
|
@@ -1370,13 +1470,23 @@ async function runSingleReview(runner, scope, workspace, options = {}) {
|
|
|
1370
1470
|
rankFindings(filterLowConfidence(dedupeFindings(parsed.report.findings)))
|
|
1371
1471
|
);
|
|
1372
1472
|
const coverage = passCoverage("single", structured, parsed.droppedFindings, findings.length);
|
|
1473
|
+
const reviewerEntries = [coverage];
|
|
1474
|
+
const reportCoverage = { reviewers: reviewerEntries };
|
|
1475
|
+
const { verdict, reason } = deriveReviewVerdict({
|
|
1476
|
+
findings,
|
|
1477
|
+
coverage: reportCoverage,
|
|
1478
|
+
// A completed, structured pass whose own verdict is `inconclusive` is the
|
|
1479
|
+
// one reviewer signal that is not already a coverage entry.
|
|
1480
|
+
reviewerVerdicts: structured ? [parsed.report.verdict] : []
|
|
1481
|
+
});
|
|
1373
1482
|
const report = {
|
|
1374
|
-
verdict
|
|
1483
|
+
verdict,
|
|
1375
1484
|
findings,
|
|
1376
|
-
coverage:
|
|
1485
|
+
coverage: reportCoverage
|
|
1377
1486
|
};
|
|
1378
1487
|
const text = [
|
|
1379
|
-
renderCoverageWarnings(
|
|
1488
|
+
renderCoverageWarnings(reviewerEntries),
|
|
1489
|
+
renderVerdictReason("Review", reason),
|
|
1380
1490
|
renderReviewReport(report),
|
|
1381
1491
|
coverage.status === "completed" ? "" : renderRawOutput("reviewer", settled.result)
|
|
1382
1492
|
].filter(Boolean).join("\n\n");
|
|
@@ -1414,6 +1524,17 @@ function renderCoverageWarnings(entries) {
|
|
|
1414
1524
|
})
|
|
1415
1525
|
].join("\n");
|
|
1416
1526
|
}
|
|
1527
|
+
function renderVerdictReason(prefix, reason) {
|
|
1528
|
+
const explanation = {
|
|
1529
|
+
"incomplete-coverage": "",
|
|
1530
|
+
"standing-findings": "",
|
|
1531
|
+
"unverified-findings": "the verifier could neither confirm nor reject the remaining findings.",
|
|
1532
|
+
"reviewer-inconclusive": "a reviewer reported its review as inconclusive.",
|
|
1533
|
+
clean: ""
|
|
1534
|
+
};
|
|
1535
|
+
const text = explanation[reason];
|
|
1536
|
+
return text ? `${prefix}: ${text}` : "";
|
|
1537
|
+
}
|
|
1417
1538
|
async function runDeepReview(runner, scope, workspace, options = {}) {
|
|
1418
1539
|
if (scope.error) throw new Error(scope.error);
|
|
1419
1540
|
const target = await resolveReviewTarget(workspace, scope);
|
|
@@ -1514,22 +1635,22 @@ async function runDeepReview(runner, scope, workspace, options = {}) {
|
|
|
1514
1635
|
reviewerCoverage.sort(
|
|
1515
1636
|
(left, right) => REVIEW_LENSES.findIndex((lens) => lens.id === left.id) - REVIEW_LENSES.findIndex((lens) => lens.id === right.id)
|
|
1516
1637
|
);
|
|
1517
|
-
const completeDiscovery = reviewerCoverage.every((entry) => entry.status === "completed");
|
|
1518
1638
|
const candidates = reindex(rankFindings(filterLowConfidence(dedupeFindings(rawFindings))));
|
|
1519
1639
|
if (candidates.length === 0) {
|
|
1640
|
+
const reportCoverage2 = { reviewers: reviewerCoverage };
|
|
1641
|
+
const { verdict: verdict2, reason: reason2 } = deriveReviewVerdict({
|
|
1642
|
+
findings: [],
|
|
1643
|
+
coverage: reportCoverage2,
|
|
1644
|
+
reviewerVerdicts
|
|
1645
|
+
});
|
|
1520
1646
|
const report2 = {
|
|
1521
|
-
verdict:
|
|
1647
|
+
verdict: verdict2,
|
|
1522
1648
|
findings: [],
|
|
1523
|
-
coverage:
|
|
1649
|
+
coverage: reportCoverage2
|
|
1524
1650
|
};
|
|
1525
1651
|
const warning2 = renderCoverageWarnings(reviewerCoverage);
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
report: report2,
|
|
1529
|
-
text: [warning2 || "Deep review complete: no confirmed findings.", ...rawOutputs].join(
|
|
1530
|
-
"\n\n"
|
|
1531
|
-
)
|
|
1532
|
-
};
|
|
1652
|
+
const summary = warning2 || (reason2 === "clean" ? "Deep review complete: no confirmed findings." : renderVerdictReason("Deep review", reason2));
|
|
1653
|
+
return { target, report: report2, text: [summary, ...rawOutputs].join("\n\n") };
|
|
1533
1654
|
}
|
|
1534
1655
|
if (options.signal?.aborted) return cancelledDeepResult(target);
|
|
1535
1656
|
const verifier = await spawnTracked(
|
|
@@ -1556,20 +1677,30 @@ async function runDeepReview(runner, scope, workspace, options = {}) {
|
|
|
1556
1677
|
}
|
|
1557
1678
|
const verified = applyVerification(candidates, verdicts);
|
|
1558
1679
|
const ranked = rankFindings(verified);
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1680
|
+
const reportCoverage = {
|
|
1681
|
+
reviewers: reviewerCoverage,
|
|
1682
|
+
verifier: verifierCoverage
|
|
1683
|
+
};
|
|
1684
|
+
const { verdict, reason } = deriveReviewVerdict({
|
|
1685
|
+
findings: ranked,
|
|
1686
|
+
coverage: reportCoverage,
|
|
1687
|
+
reviewerVerdicts
|
|
1688
|
+
});
|
|
1563
1689
|
const report = {
|
|
1564
1690
|
verdict,
|
|
1565
1691
|
findings: ranked,
|
|
1566
|
-
coverage:
|
|
1692
|
+
coverage: reportCoverage
|
|
1567
1693
|
};
|
|
1568
1694
|
const warning = renderCoverageWarnings([...reviewerCoverage, verifierCoverage]);
|
|
1569
1695
|
return {
|
|
1570
1696
|
target,
|
|
1571
1697
|
report,
|
|
1572
|
-
text: [
|
|
1698
|
+
text: [
|
|
1699
|
+
warning,
|
|
1700
|
+
renderVerdictReason("Deep review", reason),
|
|
1701
|
+
renderReviewReport(report),
|
|
1702
|
+
...rawOutputs
|
|
1703
|
+
].filter(Boolean).join("\n\n")
|
|
1573
1704
|
};
|
|
1574
1705
|
} catch (error) {
|
|
1575
1706
|
await stopNonTerminal(runner, spawned);
|
|
@@ -1857,12 +1988,18 @@ function reduceAgentSessionSnapshot(snapshot, event) {
|
|
|
1857
1988
|
case "agent_permission":
|
|
1858
1989
|
case "agent_persistence":
|
|
1859
1990
|
case "skill_lifecycle":
|
|
1991
|
+
case "notice":
|
|
1860
1992
|
case "background_job_start":
|
|
1861
1993
|
case "background_job_update":
|
|
1862
1994
|
case "background_job_output":
|
|
1863
1995
|
case "background_job_result":
|
|
1864
1996
|
case "background_job_dismiss":
|
|
1865
1997
|
return snapshot;
|
|
1998
|
+
// `agent_notice` is a child's own line for the operator (#305): it is not part of the
|
|
1999
|
+
// child's record and not part of the parent's transcript, so the snapshot drops it and
|
|
2000
|
+
// the TUI reads it from the manager subscription instead (see `useManagedAgents`).
|
|
2001
|
+
case "agent_notice":
|
|
2002
|
+
return snapshot;
|
|
1866
2003
|
case "evidence_update":
|
|
1867
2004
|
return { ...snapshot, evidence: upsertById(snapshot.evidence, event.evidence) };
|
|
1868
2005
|
case "attempt_discarded":
|
|
@@ -1900,6 +2037,9 @@ function reduceAgentSessionSnapshot(snapshot, event) {
|
|
|
1900
2037
|
}
|
|
1901
2038
|
|
|
1902
2039
|
// src/session/resolve.ts
|
|
2040
|
+
function recordResumeDriver(store, sessionId, recorded, driver) {
|
|
2041
|
+
if (driver === "agent" && recorded !== "agent") store.patchMeta(sessionId, { driver: "agent" });
|
|
2042
|
+
}
|
|
1903
2043
|
function selectSession(store, selector, cwd) {
|
|
1904
2044
|
const exact = store.findByName(selector) ?? store.findById(selector);
|
|
1905
2045
|
if (exact) return exact;
|
|
@@ -1970,7 +2110,8 @@ function resolveSessionBootstrap(store, options) {
|
|
|
1970
2110
|
const sessionId2 = store.create({
|
|
1971
2111
|
id: options.sessionId,
|
|
1972
2112
|
cwd: options.cwd,
|
|
1973
|
-
name: options.sessionName
|
|
2113
|
+
name: options.sessionName,
|
|
2114
|
+
driver: options.driver
|
|
1974
2115
|
});
|
|
1975
2116
|
return {
|
|
1976
2117
|
sessionId: sessionId2,
|
|
@@ -1992,7 +2133,8 @@ function resolveSessionBootstrap(store, options) {
|
|
|
1992
2133
|
if (selected) {
|
|
1993
2134
|
const loaded = store.load(selected.id);
|
|
1994
2135
|
if (options.forkSession) {
|
|
1995
|
-
const
|
|
2136
|
+
const driver = loaded.meta.driver === "agent" ? "agent" : options.driver;
|
|
2137
|
+
const sessionId2 = store.fork ? store.fork(selected.id, { cwd: options.cwd, name: options.sessionName, driver }) : store.create({ cwd: options.cwd, name: options.sessionName, driver });
|
|
1996
2138
|
if (!store.fork) persistHistory(store, sessionId2, loaded.transcript);
|
|
1997
2139
|
return {
|
|
1998
2140
|
sessionId: sessionId2,
|
|
@@ -2010,6 +2152,7 @@ function resolveSessionBootstrap(store, options) {
|
|
|
2010
2152
|
};
|
|
2011
2153
|
}
|
|
2012
2154
|
store.touch(selected.id);
|
|
2155
|
+
recordResumeDriver(store, selected.id, loaded.meta.driver, options.driver);
|
|
2013
2156
|
return {
|
|
2014
2157
|
sessionId: selected.id,
|
|
2015
2158
|
sessionName: loaded.meta.name,
|
|
@@ -2027,7 +2170,11 @@ function resolveSessionBootstrap(store, options) {
|
|
|
2027
2170
|
created: false
|
|
2028
2171
|
};
|
|
2029
2172
|
}
|
|
2030
|
-
const sessionId = store.create({
|
|
2173
|
+
const sessionId = store.create({
|
|
2174
|
+
cwd: options.cwd,
|
|
2175
|
+
name: options.sessionName,
|
|
2176
|
+
driver: options.driver
|
|
2177
|
+
});
|
|
2031
2178
|
return {
|
|
2032
2179
|
sessionId,
|
|
2033
2180
|
sessionName: options.sessionName,
|
|
@@ -2044,8 +2191,9 @@ function resolveSessionBootstrap(store, options) {
|
|
|
2044
2191
|
}
|
|
2045
2192
|
|
|
2046
2193
|
// src/input/file-mentions.ts
|
|
2194
|
+
import { stat } from "fs/promises";
|
|
2047
2195
|
import { isAbsolute, relative, resolve as resolve2 } from "path";
|
|
2048
|
-
import
|
|
2196
|
+
import { glob } from "tinyglobby";
|
|
2049
2197
|
var DEFAULT_IGNORE = [
|
|
2050
2198
|
"**/.git/**",
|
|
2051
2199
|
"**/node_modules/**",
|
|
@@ -2090,21 +2238,45 @@ function replaceActiveFileMention(input, mention, replacementPath) {
|
|
|
2090
2238
|
const mentionText = needsQuotes ? `@"${replacementPath}" ` : `@${replacementPath} `;
|
|
2091
2239
|
return input.slice(0, mention.start) + mentionText + input.slice(mention.end);
|
|
2092
2240
|
}
|
|
2241
|
+
async function fileSizeBytes(filePath) {
|
|
2242
|
+
try {
|
|
2243
|
+
return (await stat(filePath)).size;
|
|
2244
|
+
} catch {
|
|
2245
|
+
return 0;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
function withMissingAncestors(entries) {
|
|
2249
|
+
const reported = new Set(entries.filter((entry) => entry.endsWith("/")));
|
|
2250
|
+
const missing = [];
|
|
2251
|
+
for (const entry of entries) {
|
|
2252
|
+
const path = entry.endsWith("/") ? entry.slice(0, -1) : entry;
|
|
2253
|
+
for (let cut = path.lastIndexOf("/"); cut > 0; cut = path.lastIndexOf("/", cut - 1)) {
|
|
2254
|
+
const ancestor = path.slice(0, cut + 1);
|
|
2255
|
+
if (reported.has(ancestor)) break;
|
|
2256
|
+
reported.add(ancestor);
|
|
2257
|
+
missing.push(ancestor);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
return missing.length > 0 ? [...entries, ...missing] : entries;
|
|
2261
|
+
}
|
|
2093
2262
|
async function getFileMentionCandidates(workspace, query, limit = 50, signal) {
|
|
2094
2263
|
const normalizedQuery = normalizeMentionPath(query).toLowerCase();
|
|
2095
2264
|
const gitignore = loadGitignore(workspace).patterns;
|
|
2096
|
-
const ignore = [...DEFAULT_IGNORE, ...gitignore];
|
|
2265
|
+
const ignore = globWalkIgnore([...DEFAULT_IGNORE, ...gitignore]);
|
|
2097
2266
|
let entries;
|
|
2098
2267
|
try {
|
|
2099
|
-
entries =
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2268
|
+
entries = withMissingAncestors(
|
|
2269
|
+
await glob("**/*", {
|
|
2270
|
+
cwd: workspace,
|
|
2271
|
+
dot: true,
|
|
2272
|
+
onlyFiles: false,
|
|
2273
|
+
ignore,
|
|
2274
|
+
// A pattern naming a directory returns that directory, not everything inside it — which is
|
|
2275
|
+
// the whole point of walking for mentions.
|
|
2276
|
+
expandDirectories: false,
|
|
2277
|
+
signal
|
|
2278
|
+
})
|
|
2279
|
+
);
|
|
2108
2280
|
} catch {
|
|
2109
2281
|
return [];
|
|
2110
2282
|
}
|
|
@@ -2112,7 +2284,8 @@ async function getFileMentionCandidates(workspace, query, limit = 50, signal) {
|
|
|
2112
2284
|
const scored = [];
|
|
2113
2285
|
for (let index = 0; index < entries.length; index++) {
|
|
2114
2286
|
const entry = entries[index];
|
|
2115
|
-
const
|
|
2287
|
+
const isDirectory = entry.endsWith("/");
|
|
2288
|
+
const display = normalizeMentionPath(isDirectory ? entry.slice(0, -1) : entry);
|
|
2116
2289
|
const lower = display.toLowerCase();
|
|
2117
2290
|
const base = lower.split("/").pop() ?? lower;
|
|
2118
2291
|
let score = null;
|
|
@@ -2122,11 +2295,10 @@ async function getFileMentionCandidates(workspace, query, limit = 50, signal) {
|
|
|
2122
2295
|
else if (base.startsWith(normalizedQuery)) score = 2;
|
|
2123
2296
|
else if (lower.includes(normalizedQuery)) score = 4;
|
|
2124
2297
|
if (score !== null) {
|
|
2125
|
-
const kind = entry.dirent.isDirectory() ? "directory" : "file";
|
|
2126
2298
|
scored.push({
|
|
2127
|
-
path:
|
|
2128
|
-
kind,
|
|
2129
|
-
desc:
|
|
2299
|
+
path: isDirectory ? `${display}/` : display,
|
|
2300
|
+
kind: isDirectory ? "directory" : "file",
|
|
2301
|
+
desc: "",
|
|
2130
2302
|
score
|
|
2131
2303
|
});
|
|
2132
2304
|
}
|
|
@@ -2138,7 +2310,18 @@ async function getFileMentionCandidates(workspace, query, limit = 50, signal) {
|
|
|
2138
2310
|
return a.path.localeCompare(b.path);
|
|
2139
2311
|
});
|
|
2140
2312
|
throwIfAborted(signal);
|
|
2141
|
-
|
|
2313
|
+
const listed = scored.slice(0, limit);
|
|
2314
|
+
const sizes = await Promise.all(
|
|
2315
|
+
listed.map(
|
|
2316
|
+
(candidate) => candidate.kind === "directory" ? 0 : fileSizeBytes(resolve2(workspace, candidate.path))
|
|
2317
|
+
)
|
|
2318
|
+
);
|
|
2319
|
+
throwIfAborted(signal);
|
|
2320
|
+
return listed.map(({ score: _score, path, kind }, index) => ({
|
|
2321
|
+
path,
|
|
2322
|
+
kind,
|
|
2323
|
+
desc: kind === "directory" ? "directory" : `${sizes[index]} bytes`
|
|
2324
|
+
}));
|
|
2142
2325
|
}
|
|
2143
2326
|
|
|
2144
2327
|
// src/session/lifecycle.ts
|
|
@@ -2154,7 +2337,14 @@ async function runSessionEnd(config, sessionId, reason, options) {
|
|
|
2154
2337
|
await runHooks(
|
|
2155
2338
|
config.settings.hooks.SessionEnd,
|
|
2156
2339
|
"SessionEnd",
|
|
2157
|
-
{
|
|
2340
|
+
{
|
|
2341
|
+
workspace: config.workspace,
|
|
2342
|
+
event: "SessionEnd",
|
|
2343
|
+
sessionId,
|
|
2344
|
+
reason,
|
|
2345
|
+
status: options?.endOutcome?.status,
|
|
2346
|
+
stopReason: options?.endOutcome?.reason
|
|
2347
|
+
},
|
|
2158
2348
|
options
|
|
2159
2349
|
);
|
|
2160
2350
|
}
|
|
@@ -2173,11 +2363,76 @@ function splitTrailingPunctuation(token) {
|
|
|
2173
2363
|
while (end > 0 && /[.,;:!?)]/.test(token[end - 1])) end--;
|
|
2174
2364
|
return { path: token.slice(0, end), trailing: token.slice(end) };
|
|
2175
2365
|
}
|
|
2176
|
-
function
|
|
2366
|
+
function codeRanges(input) {
|
|
2367
|
+
const fenced = fencedCodeRanges(input);
|
|
2368
|
+
const inline = [];
|
|
2369
|
+
let cursor = 0;
|
|
2370
|
+
for (const [start, end] of fenced) {
|
|
2371
|
+
collectParagraphInlineCodeSpans(input.slice(cursor, start), cursor, inline);
|
|
2372
|
+
cursor = end;
|
|
2373
|
+
}
|
|
2374
|
+
collectParagraphInlineCodeSpans(input.slice(cursor), cursor, inline);
|
|
2375
|
+
return [...fenced, ...inline];
|
|
2376
|
+
}
|
|
2377
|
+
function collectParagraphInlineCodeSpans(segment, base, ranges) {
|
|
2378
|
+
const blankLine = /\n[ \t]*\r?\n/g;
|
|
2379
|
+
let start = 0;
|
|
2380
|
+
let separator;
|
|
2381
|
+
while ((separator = blankLine.exec(segment)) !== null) {
|
|
2382
|
+
collectInlineCodeSpans(segment.slice(start, separator.index), base + start, ranges);
|
|
2383
|
+
start = separator.index + separator[0].length;
|
|
2384
|
+
}
|
|
2385
|
+
collectInlineCodeSpans(segment.slice(start), base + start, ranges);
|
|
2386
|
+
}
|
|
2387
|
+
function fencedCodeRanges(input) {
|
|
2388
|
+
const ranges = [];
|
|
2389
|
+
let fence = null;
|
|
2390
|
+
let offset = 0;
|
|
2391
|
+
while (offset < input.length) {
|
|
2392
|
+
const newline = input.indexOf("\n", offset);
|
|
2393
|
+
const lineEnd = newline === -1 ? input.length : newline;
|
|
2394
|
+
const rangeEnd = newline === -1 ? input.length : newline + 1;
|
|
2395
|
+
const line = input.slice(offset, lineEnd).replace(/\r$/, "");
|
|
2396
|
+
if (fence) {
|
|
2397
|
+
const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line);
|
|
2398
|
+
if (close && close[1][0] === fence.char && close[1].length >= fence.length) {
|
|
2399
|
+
ranges.push([fence.start, rangeEnd]);
|
|
2400
|
+
fence = null;
|
|
2401
|
+
}
|
|
2402
|
+
} else {
|
|
2403
|
+
const open = /^ {0,3}(?:(`{3,})(?!.*`)|(~{3,}))/.exec(line);
|
|
2404
|
+
if (open) {
|
|
2405
|
+
const run = open[1] ?? open[2];
|
|
2406
|
+
fence = { start: offset, char: run[0], length: run.length };
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
offset = rangeEnd;
|
|
2410
|
+
}
|
|
2411
|
+
if (fence) ranges.push([fence.start, input.length]);
|
|
2412
|
+
return ranges;
|
|
2413
|
+
}
|
|
2414
|
+
function collectInlineCodeSpans(text, base, ranges) {
|
|
2415
|
+
const runs = [...text.matchAll(/`+/g)];
|
|
2416
|
+
for (let i = 0; i < runs.length; i++) {
|
|
2417
|
+
const length = runs[i][0].length;
|
|
2418
|
+
for (let j = i + 1; j < runs.length; j++) {
|
|
2419
|
+
if (runs[j][0].length !== length) continue;
|
|
2420
|
+
ranges.push([base + (runs[i].index ?? 0), base + (runs[j].index ?? 0) + length]);
|
|
2421
|
+
i = j;
|
|
2422
|
+
break;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
function isInsideRanges(ranges, index) {
|
|
2427
|
+
return ranges.some(([start, end]) => index >= start && index < end);
|
|
2428
|
+
}
|
|
2429
|
+
function findMentionTokens(input, excluded = []) {
|
|
2430
|
+
if (!input.includes("@")) return [];
|
|
2177
2431
|
const tokens = [];
|
|
2432
|
+
const code = codeRanges(input);
|
|
2178
2433
|
let i = 0;
|
|
2179
2434
|
while (i < input.length) {
|
|
2180
|
-
if (input[i] !== "@" || !isMentionBoundary2(input, i)) {
|
|
2435
|
+
if (input[i] !== "@" || !isMentionBoundary2(input, i) || isInsideRanges(code, i) || isInsideRanges(excluded, i)) {
|
|
2181
2436
|
i++;
|
|
2182
2437
|
continue;
|
|
2183
2438
|
}
|
|
@@ -2235,12 +2490,12 @@ function looksBinary(content) {
|
|
|
2235
2490
|
}
|
|
2236
2491
|
function expandMention(filePath, workspace) {
|
|
2237
2492
|
const resolved = resolveWorkspaceMentionPath(workspace, filePath);
|
|
2238
|
-
if (!resolved) return
|
|
2239
|
-
if (!existsSync2(resolved.filePath)) return
|
|
2493
|
+
if (!resolved) return null;
|
|
2494
|
+
if (!existsSync2(resolved.filePath)) return null;
|
|
2240
2495
|
try {
|
|
2241
|
-
const
|
|
2242
|
-
if (
|
|
2243
|
-
if (!
|
|
2496
|
+
const stat2 = statSync2(resolved.filePath);
|
|
2497
|
+
if (stat2.isDirectory()) return null;
|
|
2498
|
+
if (!stat2.isFile())
|
|
2244
2499
|
return formatMentionError(resolved.relativePath, "path is not a regular file");
|
|
2245
2500
|
const content = readFileSync2(resolved.filePath, "utf-8");
|
|
2246
2501
|
if (looksBinary(content))
|
|
@@ -2262,24 +2517,14 @@ ${body}${suffix}
|
|
|
2262
2517
|
return formatMentionError(filePath, message.slice(0, 200) || "unable to read file");
|
|
2263
2518
|
}
|
|
2264
2519
|
}
|
|
2265
|
-
function
|
|
2266
|
-
|
|
2267
|
-
if (tokens.length === 0) return input;
|
|
2268
|
-
let output = "";
|
|
2269
|
-
let cursor = 0;
|
|
2270
|
-
for (const token of tokens) {
|
|
2271
|
-
output += input.slice(cursor, token.start);
|
|
2272
|
-
output += expandMention(token.path, workspace);
|
|
2273
|
-
output += token.trailing;
|
|
2274
|
-
cursor = token.end + token.trailing.length;
|
|
2275
|
-
}
|
|
2276
|
-
output += input.slice(cursor);
|
|
2277
|
-
return output;
|
|
2520
|
+
function mentionTokenRanges(input) {
|
|
2521
|
+
return findMentionTokens(input).map((token) => [token.start, token.end]);
|
|
2278
2522
|
}
|
|
2279
|
-
function collectAtMentionObservations(input, workspace, sourceRef) {
|
|
2523
|
+
function collectAtMentionObservations(input, workspace, sourceRef, expandShellInput = false) {
|
|
2280
2524
|
const workspaceId = workspaceIdentity(workspace);
|
|
2281
2525
|
const observations = [];
|
|
2282
|
-
|
|
2526
|
+
const shellLines = expandShellInput ? findShellLines(input) : [];
|
|
2527
|
+
for (const token of findMentionTokens(input, shellLineRanges(shellLines))) {
|
|
2283
2528
|
const resolved = resolveWorkspaceMentionPath(workspace, token.path);
|
|
2284
2529
|
if (!resolved || !existsSync2(resolved.filePath)) continue;
|
|
2285
2530
|
try {
|
|
@@ -2300,17 +2545,38 @@ function collectAtMentionObservations(input, workspace, sourceRef) {
|
|
|
2300
2545
|
}
|
|
2301
2546
|
return observations;
|
|
2302
2547
|
}
|
|
2303
|
-
|
|
2304
|
-
const
|
|
2305
|
-
|
|
2548
|
+
function findShellLines(input) {
|
|
2549
|
+
const fenced = fencedCodeRanges(input);
|
|
2550
|
+
return [...input.matchAll(/^!(\S.*)$/gm)].filter((match) => !isInsideRanges(fenced, match.index ?? 0)).map((match) => {
|
|
2551
|
+
const start = match.index ?? 0;
|
|
2552
|
+
return { start, end: start + match[0].length, command: match[1] };
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
function shellLineRanges(lines) {
|
|
2556
|
+
return lines.map((line) => [line.start, line.end]);
|
|
2557
|
+
}
|
|
2558
|
+
async function expandUserInput(input, workspace, options = {}) {
|
|
2559
|
+
const shellLines = options.expandShell ? findShellLines(input) : [];
|
|
2560
|
+
const tokens = findMentionTokens(input, shellLineRanges(shellLines));
|
|
2561
|
+
if (shellLines.length === 0 && tokens.length === 0) return input;
|
|
2562
|
+
const edits = [
|
|
2563
|
+
...shellLines.map((line) => ({
|
|
2564
|
+
start: line.start,
|
|
2565
|
+
end: line.end,
|
|
2566
|
+
apply: () => executeShellExpansion(line.command, workspace, options.signal)
|
|
2567
|
+
})),
|
|
2568
|
+
...tokens.map((token) => ({
|
|
2569
|
+
start: token.start,
|
|
2570
|
+
end: token.end,
|
|
2571
|
+
apply: () => expandMention(token.path, workspace) ?? token.raw
|
|
2572
|
+
}))
|
|
2573
|
+
].sort((left, right) => left.start - right.start);
|
|
2306
2574
|
let output = "";
|
|
2307
2575
|
let cursor = 0;
|
|
2308
|
-
for (const
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
output += await executeShellExpansion(command, workspace, signal);
|
|
2313
|
-
cursor = index + match[0].length;
|
|
2576
|
+
for (const edit of edits) {
|
|
2577
|
+
output += input.slice(cursor, edit.start);
|
|
2578
|
+
output += await edit.apply();
|
|
2579
|
+
cursor = edit.end;
|
|
2314
2580
|
}
|
|
2315
2581
|
return output + input.slice(cursor);
|
|
2316
2582
|
}
|
|
@@ -2321,6 +2587,9 @@ function executeShellExpansion(command, workspace, signal) {
|
|
|
2321
2587
|
{
|
|
2322
2588
|
cwd: workspace,
|
|
2323
2589
|
encoding: "utf8",
|
|
2590
|
+
// The `!command` the user typed is theirs, so it must not inherit the NODE_ENV Book
|
|
2591
|
+
// defaulted for its own renderer.
|
|
2592
|
+
env: buildChildEnv(),
|
|
2324
2593
|
timeout: 1e4,
|
|
2325
2594
|
maxBuffer: 1024 * 1024,
|
|
2326
2595
|
signal,
|
|
@@ -2411,7 +2680,7 @@ var AgentInteractionController = class {
|
|
|
2411
2680
|
this.pendingPermission = null;
|
|
2412
2681
|
this.pendingPermissionQueue = [];
|
|
2413
2682
|
this.publish();
|
|
2414
|
-
for (const request of pending) request.resolve("deny");
|
|
2683
|
+
for (const request of pending) request.resolve({ result: "deny", reason: "dismissed" });
|
|
2415
2684
|
log2.event("permission:cancelled-all", { via, count: pending.length });
|
|
2416
2685
|
return true;
|
|
2417
2686
|
}
|
|
@@ -2594,21 +2863,13 @@ var AgentSessionOperations = class {
|
|
|
2594
2863
|
};
|
|
2595
2864
|
|
|
2596
2865
|
// src/session/agent-session.ts
|
|
2597
|
-
|
|
2598
|
-
const at = (left, right) => Math.max(0, (left ?? 0) - (right ?? 0));
|
|
2599
|
-
return {
|
|
2600
|
-
promptTokens: at(total.promptTokens, already.promptTokens),
|
|
2601
|
-
completionTokens: at(total.completionTokens, already.completionTokens),
|
|
2602
|
-
totalTokens: at(total.totalTokens, already.totalTokens),
|
|
2603
|
-
cacheReadInputTokens: at(total.cacheReadInputTokens, already.cacheReadInputTokens),
|
|
2604
|
-
cacheCreationInputTokens: at(total.cacheCreationInputTokens, already.cacheCreationInputTokens)
|
|
2605
|
-
};
|
|
2606
|
-
}
|
|
2866
|
+
var log3 = createDebugLogger("session:usage");
|
|
2607
2867
|
var AgentSession = class {
|
|
2608
2868
|
interactions = new AgentInteractionController();
|
|
2609
2869
|
operations = new AgentSessionOperations();
|
|
2610
2870
|
runLoop;
|
|
2611
2871
|
compactRunner;
|
|
2872
|
+
judgeRunner;
|
|
2612
2873
|
postCompactHooksRunner;
|
|
2613
2874
|
sessionStartRunner;
|
|
2614
2875
|
sessionEndRunner;
|
|
@@ -2616,12 +2877,27 @@ var AgentSession = class {
|
|
|
2616
2877
|
listeners = /* @__PURE__ */ new Set();
|
|
2617
2878
|
runGeneration = 0;
|
|
2618
2879
|
lifecycleStartedSessionId;
|
|
2880
|
+
lifecycleStart;
|
|
2619
2881
|
lifecycleEndedSessionId;
|
|
2882
|
+
lifecycleEnd;
|
|
2620
2883
|
runtime;
|
|
2621
2884
|
registryFactory;
|
|
2885
|
+
/**
|
|
2886
|
+
* Every root this session has run, and where its spend is written (#336).
|
|
2887
|
+
*
|
|
2888
|
+
* `onUsage` persists what a run's own responses report, which leaves two
|
|
2889
|
+
* shapes of spend with nowhere to go: whatever a managed child spent after the
|
|
2890
|
+
* root's last response, and a root whose model was never called at all — a
|
|
2891
|
+
* `/review` the host performed. Neither reaches a store on the way out, so the
|
|
2892
|
+
* process ends holding tokens no later process restores, and `--max-budget-usd`
|
|
2893
|
+
* re-authorises them. Registered by `run()` and by hosts that create a root
|
|
2894
|
+
* themselves (`trackRunUsage`), and drained by `dispose`.
|
|
2895
|
+
*/
|
|
2896
|
+
usageTargets = /* @__PURE__ */ new Map();
|
|
2622
2897
|
constructor(dependencies = {}) {
|
|
2623
2898
|
this.runLoop = dependencies.runLoop ?? runAgentLoop;
|
|
2624
2899
|
this.compactRunner = dependencies.compactRunner ?? runCompact;
|
|
2900
|
+
this.judgeRunner = dependencies.judgeRunner ?? judgeCompaction;
|
|
2625
2901
|
this.postCompactHooksRunner = dependencies.postCompactHooksRunner ?? runPostCompactHooks;
|
|
2626
2902
|
this.sessionStartRunner = dependencies.sessionStartRunner ?? runSessionStart;
|
|
2627
2903
|
this.sessionEndRunner = dependencies.sessionEndRunner ?? runSessionEnd;
|
|
@@ -2766,8 +3042,20 @@ var AgentSession = class {
|
|
|
2766
3042
|
getRuntime() {
|
|
2767
3043
|
return this.runtime;
|
|
2768
3044
|
}
|
|
3045
|
+
/**
|
|
3046
|
+
* Stop the outgoing runtime, write what it spent, and install its replacement.
|
|
3047
|
+
*
|
|
3048
|
+
* The one place a runtime goes away, so it is where its spend is written: the
|
|
3049
|
+
* children are stopped first, so a managed agent charged during the teardown is
|
|
3050
|
+
* in the figures the flush reads, and its targets are released with it rather
|
|
3051
|
+
* than walked by every later sweep. A `/resume` or `/rewind` that reached the
|
|
3052
|
+
* replacement without this left the outgoing runtime's unpersisted spend behind
|
|
3053
|
+
* with it, and the resumed session restored a carry short of what it had cost.
|
|
3054
|
+
*/
|
|
2769
3055
|
replaceRuntime(options = {}, via = "session_transition") {
|
|
2770
|
-
this.runtime
|
|
3056
|
+
const outgoing = this.runtime;
|
|
3057
|
+
outgoing.dispose(via);
|
|
3058
|
+
this.releaseRunUsage(outgoing);
|
|
2771
3059
|
this.runtime = new SessionRuntime(options);
|
|
2772
3060
|
return this.runtime;
|
|
2773
3061
|
}
|
|
@@ -2793,16 +3081,165 @@ var AgentSession = class {
|
|
|
2793
3081
|
this.operations.reset({ bookTerminalReason: "session_disposed" });
|
|
2794
3082
|
this.runGeneration++;
|
|
2795
3083
|
this.runtime.dispose(via);
|
|
3084
|
+
this.releaseRunUsage(this.runtime);
|
|
3085
|
+
}
|
|
3086
|
+
/**
|
|
3087
|
+
* Record that `rootRunId`'s spend belongs in `target`, so the flush at the end
|
|
3088
|
+
* of the session can persist what no response of that root reported.
|
|
3089
|
+
*
|
|
3090
|
+
* `run()` registers every root it runs. A host that starts a root itself and
|
|
3091
|
+
* never sends a turn — a print-mode `/review`, whose reviewer agents spend
|
|
3092
|
+
* under a root the root model is never asked for — registers it here, or its
|
|
3093
|
+
* spend leaves the process with it. Re-registering a root replaces the target,
|
|
3094
|
+
* so a run under an existing root keeps writing to where that root writes.
|
|
3095
|
+
*/
|
|
3096
|
+
trackRunUsage(rootRunId, target) {
|
|
3097
|
+
this.usageTargets.set(rootRunId, { target, runtime: this.runtime });
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Register a compaction's root the way `run` registers a turn's.
|
|
3101
|
+
*
|
|
3102
|
+
* A compactor's model calls are charged to a run context, and no turn of that
|
|
3103
|
+
* root may ever run — a manual `/compact` mints a root of its own, and a send
|
|
3104
|
+
* cancelled between the host's auto-compact in `beforePrepare` and `run()`
|
|
3105
|
+
* never reaches `run`'s own registration. Without this, that spend is charged
|
|
3106
|
+
* to a root no flush knows about, and leaves the process unwritten.
|
|
3107
|
+
*/
|
|
3108
|
+
registerUsageTarget(request, runtime) {
|
|
3109
|
+
if (!request.runContext || !request.timelineStore || !request.sessionId) return;
|
|
3110
|
+
this.usageTargets.set(request.runContext.rootRunId, {
|
|
3111
|
+
target: { sessionId: request.sessionId, timelineStore: request.timelineStore },
|
|
3112
|
+
runtime
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Write what a root has spent and no `usage` record covers, as one record
|
|
3117
|
+
* named after the response the host is being told about.
|
|
3118
|
+
*
|
|
3119
|
+
* The `onUsage` writer, and the reason it is one function rather than a
|
|
3120
|
+
* closure: every host reaches it the same way, so the watermark it moves and
|
|
3121
|
+
* the ones `flushRunUsage` moves are the same figure, written once. Cost is
|
|
3122
|
+
* not stored — pricing changes between processes, so it is re-derived from the
|
|
3123
|
+
* tokens at bootstrap, deliberately at the most expensive model involved.
|
|
3124
|
+
*
|
|
3125
|
+
* The watermark moves only where a record really lands. A turn that reaches no
|
|
3126
|
+
* store — a session that moved on, a run with nowhere to write — leaves its
|
|
3127
|
+
* spend unpersisted instead, for the next writer under this root to record;
|
|
3128
|
+
* counted as written, no record would ever hold it and no restart would
|
|
3129
|
+
* restore it.
|
|
3130
|
+
*/
|
|
3131
|
+
persistReportedUsage(rootRunId, reported, metadata) {
|
|
3132
|
+
const entry = this.usageTargets.get(rootRunId);
|
|
3133
|
+
if (!entry) return;
|
|
3134
|
+
const { target, runtime } = entry;
|
|
3135
|
+
const recordUsage = runtime.runAccounting.peekUnpersistedUsage(rootRunId) ?? reported;
|
|
3136
|
+
if (isSpendlessUsage(recordUsage)) return;
|
|
3137
|
+
const store = target.isCurrent?.() === false ? void 0 : target.timelineStore;
|
|
3138
|
+
if (!store) return;
|
|
3139
|
+
store.append(target.sessionId, {
|
|
3140
|
+
type: "usage",
|
|
3141
|
+
timestamp: Date.now(),
|
|
3142
|
+
data: {
|
|
3143
|
+
version: 1,
|
|
3144
|
+
usage: recordUsage,
|
|
3145
|
+
// The response that triggered this write, which is not the same thing as
|
|
3146
|
+
// the models the delta covers: a dearer child may have finished before it.
|
|
3147
|
+
requestedModel: metadata?.requestedModel,
|
|
3148
|
+
responseModel: metadata?.responseModel,
|
|
3149
|
+
models: runtime.runAccounting.modelsFor(rootRunId)
|
|
3150
|
+
}
|
|
3151
|
+
});
|
|
3152
|
+
runtime.runAccounting.commitPersistedUsage(rootRunId);
|
|
3153
|
+
}
|
|
3154
|
+
/**
|
|
3155
|
+
* Write what a root has spent and no `usage` record covers, if it can be
|
|
3156
|
+
* written at all.
|
|
3157
|
+
*
|
|
3158
|
+
* One record for the whole delta, so what this writes and what it then commits
|
|
3159
|
+
* are the same figure by construction. It is named after the dearest model the
|
|
3160
|
+
* root has spent on rather than after any one of them: `carriedCostUsd`
|
|
3161
|
+
* (`headless.ts`) prices a restored carry at the most expensive model in
|
|
3162
|
+
* `carriedModels` regardless of which record said what, and the records do not
|
|
3163
|
+
* attribute tokens to models, so one name is all the restored pool can be said
|
|
3164
|
+
* to have cost.
|
|
3165
|
+
*
|
|
3166
|
+
* `requireCurrent` is the end of a run, where the target's own currency still
|
|
3167
|
+
* decides whether it is the session to write to. The end-of-session sweep does
|
|
3168
|
+
* not pass it: by then every operation lease has been released, so consulting
|
|
3169
|
+
* one would skip every target the TUI ever registered — which is where a
|
|
3170
|
+
* background agent's late spend goes.
|
|
3171
|
+
*
|
|
3172
|
+
* A store that throws is contained here. This runs from a run's `finally` and
|
|
3173
|
+
* from the runtime swap, so an escaping filesystem error would replace a
|
|
3174
|
+
* completed run's outcome, abort the sweep part-way, and leave the session
|
|
3175
|
+
* installed on a disposed runtime. Nothing was written, so the watermark stays
|
|
3176
|
+
* where it was and the spend remains owed to the next writer that can.
|
|
3177
|
+
*/
|
|
3178
|
+
flushRunUsage(rootRunId, requireCurrent) {
|
|
3179
|
+
const entry = this.usageTargets.get(rootRunId);
|
|
3180
|
+
if (!entry) return;
|
|
3181
|
+
const { target, runtime } = entry;
|
|
3182
|
+
const recordUsage = runtime.runAccounting.peekUnpersistedUsage(rootRunId);
|
|
3183
|
+
if (!recordUsage || isSpendlessUsage(recordUsage)) return;
|
|
3184
|
+
const store = requireCurrent && target.isCurrent?.() === false ? void 0 : target.timelineStore;
|
|
3185
|
+
if (!store) return;
|
|
3186
|
+
try {
|
|
3187
|
+
store.append(target.sessionId, {
|
|
3188
|
+
type: "usage",
|
|
3189
|
+
timestamp: Date.now(),
|
|
3190
|
+
data: {
|
|
3191
|
+
version: 1,
|
|
3192
|
+
usage: recordUsage,
|
|
3193
|
+
requestedModel: runtime.runAccounting.dearestModel(rootRunId),
|
|
3194
|
+
models: runtime.runAccounting.modelsFor(rootRunId)
|
|
3195
|
+
}
|
|
3196
|
+
});
|
|
3197
|
+
runtime.runAccounting.commitPersistedUsage(rootRunId);
|
|
3198
|
+
} catch (error) {
|
|
3199
|
+
log3.warn("usage record could not be appended; spend left unpersisted", {
|
|
3200
|
+
rootRunId,
|
|
3201
|
+
sessionId: target.sessionId,
|
|
3202
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3203
|
+
});
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
/**
|
|
3207
|
+
* Write what the roots of a runtime that is going away spent, and drop their
|
|
3208
|
+
* targets: nothing can be charged to a root whose runtime is gone, so keeping
|
|
3209
|
+
* its target would only make every later sweep walk it.
|
|
3210
|
+
*
|
|
3211
|
+
* Whatever lease a target carries is ignored — a `send()` lease has been
|
|
3212
|
+
* released by the time any of these runs, so consulting one would skip every
|
|
3213
|
+
* target the TUI ever made.
|
|
3214
|
+
*/
|
|
3215
|
+
releaseRunUsage(runtime) {
|
|
3216
|
+
for (const [rootRunId, entry] of [...this.usageTargets.entries()]) {
|
|
3217
|
+
if (entry.runtime !== runtime) continue;
|
|
3218
|
+
this.flushRunUsage(rootRunId, false);
|
|
3219
|
+
this.usageTargets.delete(rootRunId);
|
|
3220
|
+
}
|
|
2796
3221
|
}
|
|
2797
3222
|
async startLifecycle(config, sessionId, source, options) {
|
|
2798
|
-
if (this.lifecycleStartedSessionId === sessionId)
|
|
3223
|
+
if (this.lifecycleStartedSessionId === sessionId) {
|
|
3224
|
+
await this.lifecycleStart?.catch(() => void 0);
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
2799
3227
|
this.lifecycleStartedSessionId = sessionId;
|
|
2800
|
-
|
|
3228
|
+
this.lifecycleStart = this.sessionStartRunner(config, sessionId, source, options);
|
|
3229
|
+
await this.lifecycleStart;
|
|
2801
3230
|
}
|
|
3231
|
+
// A second call for a session that is already ending waits for the SessionEnd in flight
|
|
3232
|
+
// instead of returning at once, which let a second exit or a `/clear` racing an exit move on
|
|
3233
|
+
// while the hooks were still running. The first caller reports a failed SessionEnd; a caller that
|
|
3234
|
+
// only waited for it does not report the same failure a second time.
|
|
2802
3235
|
async endLifecycle(config, sessionId, reason, options) {
|
|
2803
|
-
if (this.lifecycleEndedSessionId === sessionId)
|
|
3236
|
+
if (this.lifecycleEndedSessionId === sessionId) {
|
|
3237
|
+
await this.lifecycleEnd?.catch(() => void 0);
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
2804
3240
|
this.lifecycleEndedSessionId = sessionId;
|
|
2805
|
-
|
|
3241
|
+
this.lifecycleEnd = this.sessionEndRunner(config, sessionId, reason, options);
|
|
3242
|
+
await this.lifecycleEnd;
|
|
2806
3243
|
}
|
|
2807
3244
|
async clearSession(request) {
|
|
2808
3245
|
request.onTransitionStart?.();
|
|
@@ -2811,9 +3248,19 @@ var AgentSession = class {
|
|
|
2811
3248
|
if (request.previousName && request.store) {
|
|
2812
3249
|
request.store.patchMeta(request.currentSessionId, { name: request.previousName });
|
|
2813
3250
|
}
|
|
2814
|
-
const sessionId = request.store ? request.store.create({
|
|
3251
|
+
const sessionId = request.store ? request.store.create({
|
|
3252
|
+
cwd: request.config.workspace,
|
|
3253
|
+
driver: request.config.sessionDriver
|
|
3254
|
+
}) : request.timelineStore?.create({
|
|
3255
|
+
cwd: request.config.workspace,
|
|
3256
|
+
driver: request.config.sessionDriver
|
|
3257
|
+
}) ?? crypto.randomUUID();
|
|
2815
3258
|
if (request.store && request.timelineStore && request.timelineStore !== request.store) {
|
|
2816
|
-
request.timelineStore.create({
|
|
3259
|
+
request.timelineStore.create({
|
|
3260
|
+
id: sessionId,
|
|
3261
|
+
cwd: request.config.workspace,
|
|
3262
|
+
driver: request.config.sessionDriver
|
|
3263
|
+
});
|
|
2817
3264
|
}
|
|
2818
3265
|
const bootstrap = emptySessionBootstrap(
|
|
2819
3266
|
sessionId,
|
|
@@ -2839,6 +3286,12 @@ var AgentSession = class {
|
|
|
2839
3286
|
this.operations.cancel({ bookTerminalReason: "session_replaced" });
|
|
2840
3287
|
await this.endLifecycle(request.config, request.currentSessionId, "resume");
|
|
2841
3288
|
request.store.touch(selected.id);
|
|
3289
|
+
recordResumeDriver(
|
|
3290
|
+
request.store,
|
|
3291
|
+
selected.id,
|
|
3292
|
+
loaded.meta.driver,
|
|
3293
|
+
request.config.sessionDriver
|
|
3294
|
+
);
|
|
2842
3295
|
const bootstrap = {
|
|
2843
3296
|
sessionId: selected.id,
|
|
2844
3297
|
sessionName: loaded.meta.name,
|
|
@@ -2857,6 +3310,7 @@ var AgentSession = class {
|
|
|
2857
3310
|
created: false
|
|
2858
3311
|
};
|
|
2859
3312
|
this.reset("session-resume");
|
|
3313
|
+
this.replaceRuntime({ history: bootstrap.transcript ?? bootstrap.history }, "session-resume");
|
|
2860
3314
|
request.onTransition?.(bootstrap);
|
|
2861
3315
|
await this.startLifecycle(request.config, selected.id, "resume");
|
|
2862
3316
|
return { status: "transitioned", bootstrap };
|
|
@@ -2903,13 +3357,17 @@ var AgentSession = class {
|
|
|
2903
3357
|
return { status: "prepared", contextMessage, rewindTarget, sessionName };
|
|
2904
3358
|
}
|
|
2905
3359
|
async recordUserMessage(request) {
|
|
2906
|
-
const
|
|
2907
|
-
const contextMessage = request.contextMessage !== void 0
|
|
3360
|
+
const expandShellInput = request.contextMessage === void 0 && request.expandShellInput !== false;
|
|
3361
|
+
const contextMessage = request.contextMessage !== void 0 ? request.contextMessage : await expandUserInput(request.displayMessage, request.config.workspace, {
|
|
3362
|
+
expandShell: expandShellInput,
|
|
3363
|
+
signal: request.signal
|
|
3364
|
+
});
|
|
2908
3365
|
request.userMessage.contextContent = contextMessage === request.displayMessage ? void 0 : contextMessage;
|
|
2909
3366
|
request.userMessage.fileObservations = request.contextMessage === void 0 ? collectAtMentionObservations(
|
|
2910
3367
|
request.displayMessage,
|
|
2911
3368
|
request.config.workspace,
|
|
2912
|
-
request.userMessage.id
|
|
3369
|
+
request.userMessage.id,
|
|
3370
|
+
expandShellInput
|
|
2913
3371
|
) : [];
|
|
2914
3372
|
const observationLedger = (request.runtime ?? this.runtime).fileObservationLedger;
|
|
2915
3373
|
for (const observation of request.userMessage.fileObservations) {
|
|
@@ -2943,29 +3401,125 @@ var AgentSession = class {
|
|
|
2943
3401
|
async compact(request) {
|
|
2944
3402
|
const runtime = request.runtime ?? this.runtime;
|
|
2945
3403
|
if (request.runContext) runtime.runAccounting.startRoot(request.runContext);
|
|
3404
|
+
this.registerUsageTarget(request, runtime);
|
|
2946
3405
|
const result = await this.compactRunner(request.config, request.history, {
|
|
2947
|
-
...request.options,
|
|
2948
|
-
sessionId: request.sessionId
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
3406
|
+
...this.accountedOptions(request.options, request.runContext, runtime, request.onUsage),
|
|
3407
|
+
sessionId: request.sessionId
|
|
3408
|
+
});
|
|
3409
|
+
if (result.status !== "compacted") return { result };
|
|
3410
|
+
if (request.isCurrent?.() === false) return { result };
|
|
3411
|
+
return this.commitCompactResult(result, request);
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* Deferred compaction (`plans/async-compaction-plan.md`): the reducer half of
|
|
3415
|
+
* `compact`, on a snapshot, without the record, the boundary or the
|
|
3416
|
+
* PostCompact hooks. PreCompact hooks still run -- they are the refusal
|
|
3417
|
+
* point, and the suspect-input scan they see is the snapshot's.
|
|
3418
|
+
*/
|
|
3419
|
+
async prepareCompact(request) {
|
|
3420
|
+
const runtime = request.runtime ?? this.runtime;
|
|
3421
|
+
if (request.runContext) runtime.runAccounting.startRoot(request.runContext);
|
|
3422
|
+
this.registerUsageTarget(request, runtime);
|
|
3423
|
+
const result = await this.compactRunner(request.config, request.history, {
|
|
3424
|
+
...this.accountedOptions(request.options, request.runContext, runtime, request.onUsage),
|
|
3425
|
+
sessionId: request.sessionId
|
|
2966
3426
|
});
|
|
3427
|
+
if (result.status !== "compacted") return { status: result.status, result };
|
|
3428
|
+
return {
|
|
3429
|
+
status: "prepared",
|
|
3430
|
+
prepared: {
|
|
3431
|
+
snapshot: [...request.history],
|
|
3432
|
+
result,
|
|
3433
|
+
trigger: request.options.trigger,
|
|
3434
|
+
preContextTokens: request.options.preContextTokens
|
|
3435
|
+
}
|
|
3436
|
+
};
|
|
3437
|
+
}
|
|
3438
|
+
/**
|
|
3439
|
+
* The other half: apply a prepared compaction to the history as it stands,
|
|
3440
|
+
* ask the judge whether the checkpoint holds what the steps taken meanwhile
|
|
3441
|
+
* relied on, and only then write the record and run the hooks. A rejected
|
|
3442
|
+
* or inapplicable checkpoint is dropped, and the caller falls back to a
|
|
3443
|
+
* synchronous compaction.
|
|
3444
|
+
*/
|
|
3445
|
+
async commitCompact(request) {
|
|
3446
|
+
const runtime = request.runtime ?? this.runtime;
|
|
3447
|
+
const { prepared } = request;
|
|
3448
|
+
const applied = applyCompactResult(prepared.result, prepared.snapshot, request.history, {
|
|
3449
|
+
toolResultMaxTokens: resolveCompactBudgets(request.config).retainedToolResultMaxTokens
|
|
3450
|
+
});
|
|
3451
|
+
if (!applied) {
|
|
3452
|
+
return {
|
|
3453
|
+
result: {
|
|
3454
|
+
status: "skipped",
|
|
3455
|
+
reason: "not-applicable",
|
|
3456
|
+
message: "The history no longer extends the snapshot the reducer read."
|
|
3457
|
+
}
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
const snapshotIds = new Set(prepared.snapshot.map((message) => message.id));
|
|
3461
|
+
const delta = request.history.filter((message) => !snapshotIds.has(message.id));
|
|
3462
|
+
if (request.runContext) runtime.runAccounting.startRoot(request.runContext);
|
|
3463
|
+
this.registerUsageTarget(request, runtime);
|
|
3464
|
+
const accounted = this.accountedOptions(
|
|
3465
|
+
{ ...request.options, trigger: prepared.trigger },
|
|
3466
|
+
request.runContext,
|
|
3467
|
+
runtime,
|
|
3468
|
+
request.onUsage
|
|
3469
|
+
);
|
|
3470
|
+
const judge = await this.judgeRunner(request.config, applied, delta, {
|
|
3471
|
+
signal: accounted.signal,
|
|
3472
|
+
provider: accounted.provider,
|
|
3473
|
+
beforeModelCall: accounted.beforeModelCall,
|
|
3474
|
+
onUsage: accounted.onUsage,
|
|
3475
|
+
onUsageMissing: accounted.onUsageMissing
|
|
3476
|
+
});
|
|
3477
|
+
if (accounted.signal?.aborted || judge.note === "aborted") {
|
|
3478
|
+
return { result: { status: "failed", reason: "aborted", error: "Compaction aborted." } };
|
|
3479
|
+
}
|
|
3480
|
+
const result = judgedResult(applied, judge);
|
|
2967
3481
|
if (result.status !== "compacted") return { result };
|
|
2968
3482
|
if (request.isCurrent?.() === false) return { result };
|
|
3483
|
+
return this.commitCompactResult(result, {
|
|
3484
|
+
config: request.config,
|
|
3485
|
+
history: request.history,
|
|
3486
|
+
sessionId: request.sessionId,
|
|
3487
|
+
transcriptOrdinal: request.transcriptOrdinal,
|
|
3488
|
+
options: { ...request.options, trigger: prepared.trigger },
|
|
3489
|
+
runContext: request.runContext,
|
|
3490
|
+
runtime,
|
|
3491
|
+
timelineStore: request.timelineStore,
|
|
3492
|
+
isCurrent: request.isCurrent,
|
|
3493
|
+
onCommitted: request.onCommitted
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
/**
|
|
3497
|
+
* The compactor's model calls charged to the run, the way `compact` has always charged
|
|
3498
|
+
* them, and reported to the host's own session bill when it keeps one (#370).
|
|
3499
|
+
*/
|
|
3500
|
+
accountedOptions(options, runContext, runtime, onUsage) {
|
|
3501
|
+
return {
|
|
3502
|
+
...options,
|
|
3503
|
+
beforeModelCall: runContext ? (model) => {
|
|
3504
|
+
const requestCheck = options.beforeModelCall?.(model);
|
|
3505
|
+
if (requestCheck && !requestCheck.allowed) return requestCheck;
|
|
3506
|
+
return runtime.runAccounting.checkBeforeModelCall(runContext.rootRunId, model);
|
|
3507
|
+
} : options.beforeModelCall,
|
|
3508
|
+
// Left exactly as it was when there is nothing to chain: a compaction with
|
|
3509
|
+
// no run context and no host callback hands the runner its own options back.
|
|
3510
|
+
onUsage: runContext || onUsage ? (usage, metadata) => {
|
|
3511
|
+
if (runContext) runtime.runAccounting.record(runContext, usage, metadata);
|
|
3512
|
+
options.onUsage?.(usage, metadata);
|
|
3513
|
+
onUsage?.(usage);
|
|
3514
|
+
} : options.onUsage,
|
|
3515
|
+
onUsageMissing: runContext ? (metadata) => {
|
|
3516
|
+
runtime.runAccounting.markUsageUnknown(runContext, metadata, "compaction_usage");
|
|
3517
|
+
options.onUsageMissing?.(metadata);
|
|
3518
|
+
} : options.onUsageMissing
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
/** The record, the boundary, `onCommitted` and the PostCompact hooks for a compacted result. */
|
|
3522
|
+
async commitCompactResult(result, request) {
|
|
2969
3523
|
const timestamp = Date.now();
|
|
2970
3524
|
const boundary = {
|
|
2971
3525
|
id: result.compactId,
|
|
@@ -2977,7 +3531,8 @@ var AgentSession = class {
|
|
|
2977
3531
|
postContextTokens: result.postContextTokens,
|
|
2978
3532
|
generation: result.generation,
|
|
2979
3533
|
checkpointVersion: 2,
|
|
2980
|
-
timestamp
|
|
3534
|
+
timestamp,
|
|
3535
|
+
carriedCount: result.carriedCount
|
|
2981
3536
|
};
|
|
2982
3537
|
const data = {
|
|
2983
3538
|
version: 2,
|
|
@@ -2994,10 +3549,12 @@ var AgentSession = class {
|
|
|
2994
3549
|
throughEventRef: result.throughEventRef,
|
|
2995
3550
|
summarizedCount: result.summarizedCount,
|
|
2996
3551
|
retainedCount: result.retainedCount,
|
|
3552
|
+
carriedCount: result.carriedCount,
|
|
2997
3553
|
strategy: result.strategy,
|
|
2998
3554
|
modelCalls: result.modelCalls,
|
|
2999
3555
|
degraded: result.degraded,
|
|
3000
|
-
warning: result.warning
|
|
3556
|
+
warning: result.warning,
|
|
3557
|
+
...result.judge ? { judge: result.judge } : {}
|
|
3001
3558
|
};
|
|
3002
3559
|
if (request.timelineStore && request.sessionId) {
|
|
3003
3560
|
request.timelineStore.append(request.sessionId, {
|
|
@@ -3012,8 +3569,7 @@ var AgentSession = class {
|
|
|
3012
3569
|
trigger: result.trigger,
|
|
3013
3570
|
sessionId: request.sessionId,
|
|
3014
3571
|
focus: request.options.focus,
|
|
3015
|
-
onHookEvent: request.options.onHookEvent
|
|
3016
|
-
signal: request.options.signal
|
|
3572
|
+
onHookEvent: request.options.onHookEvent
|
|
3017
3573
|
});
|
|
3018
3574
|
return { result, boundary };
|
|
3019
3575
|
}
|
|
@@ -3049,6 +3605,14 @@ var AgentSession = class {
|
|
|
3049
3605
|
allowedTools: request.options?.allowedTools
|
|
3050
3606
|
})
|
|
3051
3607
|
);
|
|
3608
|
+
this.usageTargets.set(runContext.rootRunId, {
|
|
3609
|
+
target: {
|
|
3610
|
+
sessionId: request.sessionId,
|
|
3611
|
+
timelineStore: request.timelineStore,
|
|
3612
|
+
isCurrent: request.isCurrent
|
|
3613
|
+
},
|
|
3614
|
+
runtime
|
|
3615
|
+
});
|
|
3052
3616
|
const finalizeOutcome = (outcome) => {
|
|
3053
3617
|
if (!terminalOutcome) {
|
|
3054
3618
|
terminalOutcome = outcome;
|
|
@@ -3066,13 +3630,6 @@ var AgentSession = class {
|
|
|
3066
3630
|
emit({ type: "session", sessionId: request.sessionId });
|
|
3067
3631
|
const unsubscribeShellEvents = runtime.shellManager.subscribe((event) => emit(event));
|
|
3068
3632
|
try {
|
|
3069
|
-
let persistedUsage = {
|
|
3070
|
-
promptTokens: 0,
|
|
3071
|
-
completionTokens: 0,
|
|
3072
|
-
totalTokens: 0,
|
|
3073
|
-
cacheReadInputTokens: 0,
|
|
3074
|
-
cacheCreationInputTokens: 0
|
|
3075
|
-
};
|
|
3076
3633
|
let lastPlanSignature = "";
|
|
3077
3634
|
const persistPlan = () => {
|
|
3078
3635
|
if (!request.timelineStore) return;
|
|
@@ -3106,6 +3663,7 @@ var AgentSession = class {
|
|
|
3106
3663
|
},
|
|
3107
3664
|
onToolCall: (toolCall) => emit({ type: "tool_use", toolCall }),
|
|
3108
3665
|
onToolResult: (toolResult) => emit({ type: "tool_result", toolResult }),
|
|
3666
|
+
onNotice: (message) => emit({ type: "notice", message }),
|
|
3109
3667
|
onError: (error) => {
|
|
3110
3668
|
emittedError = error;
|
|
3111
3669
|
emit({ type: "error", error });
|
|
@@ -3117,7 +3675,9 @@ var AgentSession = class {
|
|
|
3117
3675
|
finalizeOutcome(outcome2);
|
|
3118
3676
|
},
|
|
3119
3677
|
onPermissionRequired: (toolCall) => {
|
|
3120
|
-
if (request.isCurrent?.() === false)
|
|
3678
|
+
if (request.isCurrent?.() === false) {
|
|
3679
|
+
return Promise.resolve({ result: "deny", reason: "dismissed" });
|
|
3680
|
+
}
|
|
3121
3681
|
return callbacks.onPermissionRequired ? callbacks.onPermissionRequired(toolCall) : this.interactions.requestPermission(toolCall);
|
|
3122
3682
|
},
|
|
3123
3683
|
onPlanApprovalRequired: (plan) => {
|
|
@@ -3139,32 +3699,21 @@ var AgentSession = class {
|
|
|
3139
3699
|
},
|
|
3140
3700
|
onUsage: (nextUsage, metadata) => {
|
|
3141
3701
|
usage = nextUsage;
|
|
3142
|
-
|
|
3143
|
-
const recordUsage = inclusive ? subtractUsage(inclusive, persistedUsage) : nextUsage;
|
|
3144
|
-
if (inclusive) persistedUsage = inclusive;
|
|
3145
|
-
if (recordUsage.totalTokens <= 0 && recordUsage.promptTokens <= 0) return;
|
|
3146
|
-
if (request.isCurrent?.() !== false) {
|
|
3147
|
-
request.timelineStore?.append(request.sessionId, {
|
|
3148
|
-
type: "usage",
|
|
3149
|
-
timestamp: Date.now(),
|
|
3150
|
-
data: {
|
|
3151
|
-
version: 1,
|
|
3152
|
-
usage: recordUsage,
|
|
3153
|
-
requestedModel: metadata?.requestedModel,
|
|
3154
|
-
responseModel: metadata?.responseModel
|
|
3155
|
-
}
|
|
3156
|
-
});
|
|
3157
|
-
}
|
|
3702
|
+
this.persistReportedUsage(runContext.rootRunId, nextUsage, metadata);
|
|
3158
3703
|
callbacks.onUsage?.(nextUsage, metadata);
|
|
3159
3704
|
},
|
|
3160
3705
|
getMode: callbacks.getMode,
|
|
3161
3706
|
onModeChange: callbacks.onModeChange,
|
|
3707
|
+
// The root run's only: a managed child and a Task subagent run the loop
|
|
3708
|
+
// with their own callbacks, and neither announces calls to a stream the
|
|
3709
|
+
// host is reading for them.
|
|
3710
|
+
beforeToolExecution: callbacks.beforeToolExecution,
|
|
3162
3711
|
onPlanHandoff: callbacks.onPlanHandoff,
|
|
3163
|
-
//
|
|
3164
|
-
//
|
|
3165
|
-
// context-overflow path at the bottom of the turn is deliberately not
|
|
3166
|
-
// gated by it. Nulling the callback disabled that recovery too.
|
|
3712
|
+
// Passed through as is: the loop gates every compaction site on
|
|
3713
|
+
// `autoCompactEnabled`, the context-overflow recovery included.
|
|
3167
3714
|
onCompact: callbacks.onCompact,
|
|
3715
|
+
prepareCompact: callbacks.prepareCompact,
|
|
3716
|
+
commitCompact: callbacks.commitCompact,
|
|
3168
3717
|
onAssistantMessageComplete: (message) => {
|
|
3169
3718
|
if (request.isCurrent?.() === false) return;
|
|
3170
3719
|
request.timelineStore?.append(request.sessionId, {
|
|
@@ -3180,7 +3729,10 @@ var AgentSession = class {
|
|
|
3180
3729
|
kind: message.kind ?? "conversation",
|
|
3181
3730
|
toolCalls: message.toolCalls,
|
|
3182
3731
|
toolResults: message.toolResults,
|
|
3183
|
-
fileObservations: message.fileObservations
|
|
3732
|
+
fileObservations: message.fileObservations,
|
|
3733
|
+
// Without this a resumed session reads a host notice as the model's
|
|
3734
|
+
// own reply: `finalAnswerText` and the answer walk both key on it.
|
|
3735
|
+
hostNotice: message.hostNotice
|
|
3184
3736
|
}
|
|
3185
3737
|
});
|
|
3186
3738
|
callbacks.onAssistantMessageComplete?.(message);
|
|
@@ -3213,7 +3765,10 @@ var AgentSession = class {
|
|
|
3213
3765
|
id: message.id,
|
|
3214
3766
|
content: message.content,
|
|
3215
3767
|
kind: message.kind ?? "conversation",
|
|
3216
|
-
includeInContext: message.includeInContext ?? true
|
|
3768
|
+
includeInContext: message.includeInContext ?? true,
|
|
3769
|
+
// A host-appended prompt stays host-written across a reload: the ledger,
|
|
3770
|
+
// Carried Turns and memory extraction all key on it.
|
|
3771
|
+
derivedContent: message.derivedContent
|
|
3217
3772
|
}
|
|
3218
3773
|
});
|
|
3219
3774
|
},
|
|
@@ -3224,21 +3779,26 @@ var AgentSession = class {
|
|
|
3224
3779
|
onHookEvent: callbacks.onHookEvent,
|
|
3225
3780
|
onAgentEvent: (event) => emit(event)
|
|
3226
3781
|
};
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3782
|
+
let messages;
|
|
3783
|
+
try {
|
|
3784
|
+
messages = await this.runLoop(
|
|
3785
|
+
request.config,
|
|
3786
|
+
request.registry,
|
|
3787
|
+
request.prompt,
|
|
3788
|
+
effectiveHistory,
|
|
3789
|
+
baseLoopCallbacks,
|
|
3790
|
+
request.mode,
|
|
3791
|
+
{
|
|
3792
|
+
...request.options,
|
|
3793
|
+
runtime,
|
|
3794
|
+
runContext,
|
|
3795
|
+
resolveAttachment: request.options?.resolveAttachment ?? (request.timelineStore?.readImageAttachment ? (attachment) => request.timelineStore.readImageAttachment(request.sessionId, attachment) : void 0),
|
|
3796
|
+
signal: request.signal
|
|
3797
|
+
}
|
|
3798
|
+
);
|
|
3799
|
+
} finally {
|
|
3800
|
+
this.flushRunUsage(runContext.rootRunId, true);
|
|
3801
|
+
}
|
|
3242
3802
|
const partialOutput = streamedAssistantText.length > 0 || messages.slice(effectiveHistory.length).some((message) => message.role === "assistant");
|
|
3243
3803
|
const outcome = finalizeOutcome(
|
|
3244
3804
|
terminalOutcome ?? (request.signal?.aborted ? classifyAbortReason(request.signal.reason, partialOutput) : emittedError ? createTerminalOutcome("failed", "provider_error", {
|
|
@@ -3481,16 +4041,21 @@ function validateElicitationContent(fields, content) {
|
|
|
3481
4041
|
}
|
|
3482
4042
|
|
|
3483
4043
|
export {
|
|
4044
|
+
parseSessionDriver,
|
|
4045
|
+
resolveSessionDriver,
|
|
3484
4046
|
displaySessionName,
|
|
3485
4047
|
normalizeWorkspace,
|
|
3486
4048
|
SessionStore,
|
|
3487
4049
|
reviewRunnerFor,
|
|
3488
4050
|
fixRunnerFor,
|
|
4051
|
+
extractJsonObject,
|
|
4052
|
+
parseJsonObject,
|
|
3489
4053
|
runHostReview,
|
|
3490
4054
|
reviewReportJson,
|
|
3491
4055
|
findActiveFileMention,
|
|
3492
4056
|
replaceActiveFileMention,
|
|
3493
4057
|
getFileMentionCandidates,
|
|
4058
|
+
mentionTokenRanges,
|
|
3494
4059
|
createAgentSessionSnapshot,
|
|
3495
4060
|
reduceAgentSessionSnapshot,
|
|
3496
4061
|
resolveSessionBootstrap,
|
|
@@ -3501,4 +4066,4 @@ export {
|
|
|
3501
4066
|
coerceElicitationValue,
|
|
3502
4067
|
validateElicitationContent
|
|
3503
4068
|
};
|
|
3504
|
-
//# sourceMappingURL=chunk-
|
|
4069
|
+
//# sourceMappingURL=chunk-RFJ6Q6LE.js.map
|