@letrquan/book 0.3.0 → 0.4.0-beta.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 +1024 -0
- package/README.md +6 -3
- package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
- package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
- package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
- package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
- package/dist/chunk-AJZWLKLQ.js.map +1 -0
- package/dist/chunk-B5FC3MYZ.js +395 -0
- package/dist/chunk-B5FC3MYZ.js.map +1 -0
- package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
- package/dist/chunk-E5WLQSKA.js.map +1 -0
- package/dist/chunk-ECLC4CIJ.js +1181 -0
- package/dist/chunk-ECLC4CIJ.js.map +1 -0
- package/dist/chunk-EEX6QNXV.js +127 -0
- package/dist/chunk-EEX6QNXV.js.map +1 -0
- package/dist/chunk-EFXVSQ5J.js +931 -0
- package/dist/chunk-EFXVSQ5J.js.map +1 -0
- package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
- package/dist/chunk-HFB74CLM.js.map +1 -0
- package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
- package/dist/chunk-HOZXKOE3.js.map +1 -0
- package/dist/chunk-IDYWN37T.js +36 -0
- package/dist/chunk-IDYWN37T.js.map +1 -0
- package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
- package/dist/chunk-REASC2QG.js.map +1 -0
- package/dist/chunk-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
- package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
- package/dist/chunk-YIL6HTJX.js.map +1 -0
- package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
- package/dist/index.js +657 -754
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
- package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
- package/dist/job-runner.js +10 -3
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.js +13 -4
- package/dist/job-supervisor.js.map +1 -1
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
- package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
- package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
- package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
- package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
- package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +876 -64
- package/dist/sdk.js +57 -37
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +5 -5
- package/dist/app-GE4OHJOC.js.map +0 -1
- package/dist/chunk-224FJZLH.js.map +0 -1
- package/dist/chunk-5RDRAO4B.js.map +0 -1
- package/dist/chunk-5YHTKWJN.js.map +0 -1
- package/dist/chunk-62QL5QUP.js.map +0 -1
- package/dist/chunk-67UZEWWF.js.map +0 -1
- package/dist/chunk-6OQQOMZR.js +0 -785
- package/dist/chunk-6OQQOMZR.js.map +0 -1
- package/dist/chunk-AQEQX4FF.js.map +0 -1
- package/dist/chunk-C7ADUNPR.js +0 -273
- package/dist/chunk-C7ADUNPR.js.map +0 -1
- package/dist/chunk-EFJ2NH6E.js.map +0 -1
- package/dist/chunk-IME7R2UI.js.map +0 -1
- package/dist/chunk-J62VZYOB.js +0 -1339
- package/dist/chunk-J62VZYOB.js.map +0 -1
- package/dist/chunk-JTPOI3KU.js.map +0 -1
- package/dist/chunk-QYBD22H5.js.map +0 -1
- package/dist/chunk-RFJ6Q6LE.js.map +0 -1
- package/dist/chunk-S4JMID4G.js +0 -259
- package/dist/chunk-S4JMID4G.js.map +0 -1
- package/dist/chunk-TLX3H2CL.js.map +0 -1
- package/dist/chunk-VPNLCMVR.js.map +0 -1
- package/dist/chunk-W3XFHEF4.js.map +0 -1
- package/dist/chunk-X4NJT436.js.map +0 -1
- package/dist/chunk-X66I2NQO.js +0 -137
- package/dist/chunk-X66I2NQO.js.map +0 -1
- package/dist/chunk-Y2VHN7TC.js.map +0 -1
- package/dist/chunk-YC6W4EJH.js.map +0 -1
- package/dist/context-CBOBDY5Q.js +0 -38
- package/dist/loader-S77OVTHH.js +0 -18
- package/dist/memory-store-DZUT5HUH.js +0 -75
- package/dist/scrollback-35ME2GFX.js.map +0 -1
- package/dist/settings-loader-JUNPGAAM.js +0 -35
- package/dist/shell-selection-CDHGJ4GC.js +0 -17
- /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
- /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
- /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
- /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
- /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
- /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
- /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
- /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
- /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
isSpendlessUsage,
|
|
9
9
|
isTerminalStatus,
|
|
10
10
|
loadGitignore,
|
|
11
|
-
runAgentLoop
|
|
12
|
-
|
|
11
|
+
runAgentLoop,
|
|
12
|
+
seedShellIds
|
|
13
|
+
} from "./chunk-KDNWMKXV.js";
|
|
13
14
|
import {
|
|
14
15
|
DEFAULT_CONFIDENCE_THRESHOLD,
|
|
15
16
|
REVIEW_LENSES,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
buildReviewerPrompt,
|
|
19
20
|
buildSingleReviewPrompt,
|
|
20
21
|
clampConfidence,
|
|
22
|
+
createGoal,
|
|
21
23
|
isReviewCategory,
|
|
22
24
|
isReviewSeverity,
|
|
23
25
|
judgeCompaction,
|
|
@@ -27,30 +29,56 @@ import {
|
|
|
27
29
|
renderReviewTarget,
|
|
28
30
|
resolveCompactBudgets,
|
|
29
31
|
resolveReviewTarget,
|
|
32
|
+
restoreGoal,
|
|
30
33
|
runCompact,
|
|
31
34
|
runHooks,
|
|
32
35
|
runPostCompactHooks
|
|
33
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-FPR7SKIE.js";
|
|
34
37
|
import {
|
|
35
38
|
observationKey,
|
|
39
|
+
observationPathFor,
|
|
36
40
|
onAbort,
|
|
37
41
|
throwIfAborted,
|
|
38
42
|
workspaceIdentity,
|
|
39
43
|
yieldToEventLoop
|
|
40
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-N2G4Q7ME.js";
|
|
41
45
|
import {
|
|
42
46
|
globWalkIgnore
|
|
43
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-XJRJVXJR.js";
|
|
48
|
+
import {
|
|
49
|
+
defaultGitRunner,
|
|
50
|
+
describeMissingObjects,
|
|
51
|
+
hardenedGitArgs,
|
|
52
|
+
noFetchGitEnv,
|
|
53
|
+
readRepositoryGitConfig
|
|
54
|
+
} from "./chunk-32WBCUAN.js";
|
|
44
55
|
import {
|
|
45
56
|
buildChildEnv
|
|
46
57
|
} from "./chunk-XHAO3K7I.js";
|
|
58
|
+
import {
|
|
59
|
+
findRepositoryRoot,
|
|
60
|
+
tryRealPath
|
|
61
|
+
} from "./chunk-SBTU5BEC.js";
|
|
62
|
+
import {
|
|
63
|
+
resolveAdditionalRoots
|
|
64
|
+
} from "./chunk-4DKHLZRI.js";
|
|
47
65
|
import {
|
|
48
66
|
createDebugLogger,
|
|
49
67
|
createDebugLoggerWithCounter
|
|
50
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-OJBMDM5H.js";
|
|
69
|
+
import {
|
|
70
|
+
isOutside,
|
|
71
|
+
normalizeToolResult,
|
|
72
|
+
resolveMentionReadablePath
|
|
73
|
+
} from "./chunk-YIL6HTJX.js";
|
|
74
|
+
import {
|
|
75
|
+
createUncGuard,
|
|
76
|
+
reachesUncPath,
|
|
77
|
+
uncGuardedWalkFs
|
|
78
|
+
} from "./chunk-EEX6QNXV.js";
|
|
51
79
|
import {
|
|
52
|
-
|
|
53
|
-
} from "./chunk-
|
|
80
|
+
renderUntrustedText
|
|
81
|
+
} from "./chunk-WV3O5PGX.js";
|
|
54
82
|
|
|
55
83
|
// src/session-driver.ts
|
|
56
84
|
var SESSION_DRIVERS = ["human", "agent"];
|
|
@@ -77,7 +105,7 @@ function resolveSessionDriver(input) {
|
|
|
77
105
|
var UNTITLED_SESSION_NAME = "Untitled session";
|
|
78
106
|
var MAX_SESSION_NAME_LENGTH = 56;
|
|
79
107
|
function deriveSessionName(prompt) {
|
|
80
|
-
const normalized = prompt.replace(/\r?\n/g, " ").replace(/\s+/g, " ").trim().replace(/^[/!#>*_
|
|
108
|
+
const normalized = prompt.replace(/^\s*`{3,}[^\n`]*\r?\n/, "").replace(/^[ \t]*`{3,}[\w+-]*[ \t\r]*$/gm, "").replace(/\r?\n/g, " ").replace(/\s+/g, " ").trim().replace(/^[/!#>*_~-]+\s*/, "").replace(/[.!?,;:]+$/, "").trim();
|
|
81
109
|
if (!normalized) return UNTITLED_SESSION_NAME;
|
|
82
110
|
const titled = normalized.replace(/^[a-z]/, (letter) => letter.toUpperCase());
|
|
83
111
|
if (Array.from(titled).length <= MAX_SESSION_NAME_LENGTH) return titled;
|
|
@@ -118,6 +146,7 @@ var READ_REF_LIMIT = 8;
|
|
|
118
146
|
var READ_TOTAL_CHARS = 16e3;
|
|
119
147
|
var TOOL_RESULT_PREVIEW_CHARS = 4e3;
|
|
120
148
|
var SESSION_INDEX_FILE = "session-index.json";
|
|
149
|
+
var HEADER_PROBE_BYTES = 16 * 1024;
|
|
121
150
|
var JSONL_READ_BUFFER_BYTES = 64 * 1024;
|
|
122
151
|
var IMAGE_STORAGE_KEY = /^[a-f0-9]{64}\.(?:png|jpg|gif|webp)$/;
|
|
123
152
|
function imageSessionDirectory(root, sessionId) {
|
|
@@ -206,30 +235,37 @@ var SessionStore = class {
|
|
|
206
235
|
}
|
|
207
236
|
persistIndex(deletedIds = [], changedIds) {
|
|
208
237
|
if (this.buildingIndex) return;
|
|
238
|
+
this.withIndexLock(() => this.writeIndexLocked(deletedIds, changedIds));
|
|
239
|
+
}
|
|
240
|
+
withIndexLock(action) {
|
|
209
241
|
const lock = this.acquireIndexLock();
|
|
210
242
|
try {
|
|
211
|
-
|
|
212
|
-
const ids = changedIds ?? Array.from(this.metadataIndex.keys());
|
|
213
|
-
for (const id of ids) {
|
|
214
|
-
const entry = this.metadataIndex.get(id);
|
|
215
|
-
if (entry) sessions[id] = entry;
|
|
216
|
-
}
|
|
217
|
-
for (const id of deletedIds) delete sessions[id];
|
|
218
|
-
const target = this.indexPath();
|
|
219
|
-
const temporary = `${target}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
220
|
-
try {
|
|
221
|
-
writeFileSync(temporary, JSON.stringify({ version: 1, sessions }), "utf-8");
|
|
222
|
-
renameSync(temporary, target);
|
|
223
|
-
this.indexMtimeMs = statSync(target).mtimeMs;
|
|
224
|
-
this.metadataIndex.clear();
|
|
225
|
-
for (const [id, entry] of Object.entries(sessions)) this.metadataIndex.set(id, entry);
|
|
226
|
-
} finally {
|
|
227
|
-
if (existsSync(temporary)) unlinkSync(temporary);
|
|
228
|
-
}
|
|
243
|
+
return action();
|
|
229
244
|
} finally {
|
|
230
245
|
this.releaseIndexLock(lock);
|
|
231
246
|
}
|
|
232
247
|
}
|
|
248
|
+
/** The index write itself; the caller holds the index lock. */
|
|
249
|
+
writeIndexLocked(deletedIds, changedIds) {
|
|
250
|
+
const sessions = this.readPersistedIndexEntries();
|
|
251
|
+
const ids = changedIds ?? Array.from(this.metadataIndex.keys());
|
|
252
|
+
for (const id of ids) {
|
|
253
|
+
const entry = this.metadataIndex.get(id);
|
|
254
|
+
if (entry) sessions[id] = entry;
|
|
255
|
+
}
|
|
256
|
+
for (const id of deletedIds) delete sessions[id];
|
|
257
|
+
const target = this.indexPath();
|
|
258
|
+
const temporary = `${target}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
259
|
+
try {
|
|
260
|
+
writeFileSync(temporary, JSON.stringify({ version: 1, sessions }), "utf-8");
|
|
261
|
+
renameSync(temporary, target);
|
|
262
|
+
this.indexMtimeMs = statSync(target).mtimeMs;
|
|
263
|
+
this.metadataIndex.clear();
|
|
264
|
+
for (const [id, entry] of Object.entries(sessions)) this.metadataIndex.set(id, entry);
|
|
265
|
+
} finally {
|
|
266
|
+
if (existsSync(temporary)) unlinkSync(temporary);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
233
269
|
acquireIndexLock() {
|
|
234
270
|
const path = `${this.indexPath()}.lock`;
|
|
235
271
|
for (let attempt = 0; attempt < 50; attempt++) {
|
|
@@ -339,7 +375,7 @@ var SessionStore = class {
|
|
|
339
375
|
if (cached && cached.size === stat2.size && cached.mtimeMs === stat2.mtimeMs) {
|
|
340
376
|
return cached.records;
|
|
341
377
|
}
|
|
342
|
-
const
|
|
378
|
+
const records2 = [];
|
|
343
379
|
const descriptor = openSync(p, "r");
|
|
344
380
|
const buffer = Buffer.allocUnsafe(JSONL_READ_BUFFER_BYTES);
|
|
345
381
|
const decoder = new StringDecoder("utf8");
|
|
@@ -349,7 +385,7 @@ var SessionStore = class {
|
|
|
349
385
|
lineNumber++;
|
|
350
386
|
if (!line) return;
|
|
351
387
|
try {
|
|
352
|
-
|
|
388
|
+
records2.push(JSON.parse(line));
|
|
353
389
|
} catch {
|
|
354
390
|
log.warn("ignoring corrupt session record", { id, line: lineNumber });
|
|
355
391
|
}
|
|
@@ -373,9 +409,9 @@ var SessionStore = class {
|
|
|
373
409
|
this.parsedSessions.set(id, {
|
|
374
410
|
size: stat2.size,
|
|
375
411
|
mtimeMs: stat2.mtimeMs,
|
|
376
|
-
records
|
|
412
|
+
records: records2
|
|
377
413
|
});
|
|
378
|
-
return
|
|
414
|
+
return records2;
|
|
379
415
|
}
|
|
380
416
|
updateIndexAfterAppend(id, record) {
|
|
381
417
|
const entry = this.metadataIndex.get(id);
|
|
@@ -396,7 +432,9 @@ var SessionStore = class {
|
|
|
396
432
|
} else if (record.type === "user") {
|
|
397
433
|
entry.meta.messageCount++;
|
|
398
434
|
entry.lastMessageRole = "user";
|
|
399
|
-
if (!entry.meta.name && data.content
|
|
435
|
+
if (!entry.meta.name && data.content && data.kind !== "agent-notification") {
|
|
436
|
+
entry.meta.name = deriveSessionName(data.content);
|
|
437
|
+
}
|
|
400
438
|
} else if (record.type === "local") {
|
|
401
439
|
entry.meta.messageCount++;
|
|
402
440
|
entry.lastMessageRole = "assistant";
|
|
@@ -430,10 +468,10 @@ var SessionStore = class {
|
|
|
430
468
|
});
|
|
431
469
|
}
|
|
432
470
|
load(id) {
|
|
433
|
-
const
|
|
471
|
+
const records2 = this.readRecords(id);
|
|
434
472
|
const parsedCache = this.parsedSessions.get(id);
|
|
435
473
|
if (parsedCache?.loaded) return cloneLoadedSession(parsedCache.loaded);
|
|
436
|
-
const metaRec =
|
|
474
|
+
const metaRec = records2.find(
|
|
437
475
|
(record) => record.data?.kind === "session_meta"
|
|
438
476
|
);
|
|
439
477
|
const storedMeta = metaRec?.data;
|
|
@@ -456,11 +494,12 @@ var SessionStore = class {
|
|
|
456
494
|
const checkpointByUserEventId = /* @__PURE__ */ new Map();
|
|
457
495
|
const snapshotIds = /* @__PURE__ */ new Set();
|
|
458
496
|
let latestPlan;
|
|
497
|
+
let latestGoal;
|
|
459
498
|
let carriedUsage;
|
|
460
499
|
const carriedModels = /* @__PURE__ */ new Set();
|
|
461
500
|
let count = 0;
|
|
462
|
-
for (let lineIndex = 0; lineIndex <
|
|
463
|
-
const record =
|
|
501
|
+
for (let lineIndex = 0; lineIndex < records2.length; lineIndex++) {
|
|
502
|
+
const record = records2[lineIndex];
|
|
464
503
|
const eventId = record.eventId ?? `legacy-line-${lineIndex + 1}`;
|
|
465
504
|
const data = record.data;
|
|
466
505
|
if (data.kind === "session_meta_patch") {
|
|
@@ -485,6 +524,11 @@ var SessionStore = class {
|
|
|
485
524
|
if (plan?.version === 1) latestPlan = plan;
|
|
486
525
|
continue;
|
|
487
526
|
}
|
|
527
|
+
if (record.type === "goal") {
|
|
528
|
+
const goal = record.data;
|
|
529
|
+
if (goal?.version === 1) latestGoal = goal;
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
488
532
|
if (record.type === "turn_checkpoint") {
|
|
489
533
|
const checkpoint = record.data;
|
|
490
534
|
if (checkpoint?.version !== 1 || !checkpoint.checkpointId || !checkpoint.userEventId || typeof checkpoint.prompt !== "string") {
|
|
@@ -495,6 +539,7 @@ var SessionStore = class {
|
|
|
495
539
|
id: checkpoint.checkpointId,
|
|
496
540
|
userEventId: checkpoint.userEventId,
|
|
497
541
|
prompt: checkpoint.prompt,
|
|
542
|
+
...checkpoint.promptTyped === true ? { promptTyped: true } : {},
|
|
498
543
|
attachments: checkpoint.attachments,
|
|
499
544
|
timestamp: record.timestamp,
|
|
500
545
|
...checkpoint.checkpoint,
|
|
@@ -675,11 +720,11 @@ var SessionStore = class {
|
|
|
675
720
|
}
|
|
676
721
|
}
|
|
677
722
|
}
|
|
678
|
-
if (
|
|
723
|
+
if (records2.length) meta.updatedAt = records2[records2.length - 1].timestamp;
|
|
679
724
|
meta.messageCount = count;
|
|
680
725
|
if (!meta.name) {
|
|
681
726
|
const firstUserPrompt = transcript.find(
|
|
682
|
-
(message) => message.role === "user" && message.kind !== "local"
|
|
727
|
+
(message) => message.role === "user" && message.kind !== "local" && message.kind !== "agent-notification"
|
|
683
728
|
)?.content;
|
|
684
729
|
if (firstUserPrompt) meta.name = deriveSessionName(firstUserPrompt);
|
|
685
730
|
}
|
|
@@ -694,6 +739,7 @@ var SessionStore = class {
|
|
|
694
739
|
activeEventIds,
|
|
695
740
|
meta,
|
|
696
741
|
plan: latestPlan,
|
|
742
|
+
goal: latestGoal,
|
|
697
743
|
carriedUsage,
|
|
698
744
|
carriedModels: [...carriedModels],
|
|
699
745
|
history: contextHistory
|
|
@@ -816,9 +862,9 @@ var SessionStore = class {
|
|
|
816
862
|
activeRecordMap(id) {
|
|
817
863
|
const cache = this.parsedSessions.get(id);
|
|
818
864
|
if (cache?.recordsByActiveId) return cache.recordsByActiveId;
|
|
819
|
-
const
|
|
865
|
+
const records2 = this.activeRecordIndex(id);
|
|
820
866
|
const refreshed = this.parsedSessions.get(id);
|
|
821
|
-
const byId = new Map(
|
|
867
|
+
const byId = new Map(records2.map((record) => [record.eventId, record]));
|
|
822
868
|
if (refreshed) refreshed.recordsByActiveId = byId;
|
|
823
869
|
return byId;
|
|
824
870
|
}
|
|
@@ -841,9 +887,15 @@ var SessionStore = class {
|
|
|
841
887
|
}
|
|
842
888
|
return references;
|
|
843
889
|
}
|
|
844
|
-
|
|
890
|
+
/**
|
|
891
|
+
* The newest session in `cwd` that holds a conversation. A launch that ends
|
|
892
|
+
* before its first prompt leaves a session with no messages, and that is never
|
|
893
|
+
* the conversation `--continue` means (#428). `accept` replaces that default test
|
|
894
|
+
* for a caller with a wider idea of a live session, such as `book status`.
|
|
895
|
+
*/
|
|
896
|
+
mostRecentInCwd(cwd, accept = (meta) => meta.messageCount > 0) {
|
|
845
897
|
const normalized = normalizeWorkspace(cwd);
|
|
846
|
-
return this.list().find((meta) => meta.cwd === normalized);
|
|
898
|
+
return this.list().find((meta) => meta.cwd === normalized && accept(meta));
|
|
847
899
|
}
|
|
848
900
|
findByName(name) {
|
|
849
901
|
return this.list().find((meta) => meta.name === name);
|
|
@@ -860,19 +912,13 @@ var SessionStore = class {
|
|
|
860
912
|
if (preserveIds.has(id)) continue;
|
|
861
913
|
try {
|
|
862
914
|
if (entry.meta.updatedAt < cutoff) {
|
|
863
|
-
|
|
864
|
-
rmSync(imageSessionDirectory(this.root, id), { recursive: true, force: true });
|
|
865
|
-
this.metadataIndex.delete(id);
|
|
866
|
-
this.parsedSessions.delete(id);
|
|
915
|
+
this.discardSession(id);
|
|
867
916
|
removedIds.push(id);
|
|
868
917
|
removed++;
|
|
869
918
|
}
|
|
870
919
|
} catch {
|
|
871
920
|
if (existsSync(this.path(id)) && statSync(this.path(id)).mtimeMs < cutoff) {
|
|
872
|
-
|
|
873
|
-
rmSync(imageSessionDirectory(this.root, id), { recursive: true, force: true });
|
|
874
|
-
this.metadataIndex.delete(id);
|
|
875
|
-
this.parsedSessions.delete(id);
|
|
921
|
+
this.discardSession(id);
|
|
876
922
|
removedIds.push(id);
|
|
877
923
|
removed++;
|
|
878
924
|
}
|
|
@@ -881,7 +927,85 @@ var SessionStore = class {
|
|
|
881
927
|
if (removedIds.length > 0) this.persistIndex(removedIds, []);
|
|
882
928
|
return removed;
|
|
883
929
|
}
|
|
930
|
+
/**
|
|
931
|
+
* Remove one session this process created if nothing was ever recorded in it: its file
|
|
932
|
+
* holds nothing but the bare header `create` wrote (no turn, no run, no patch, no name).
|
|
933
|
+
* A name is something a user typed (`--name`), so a named session is kept. The decision
|
|
934
|
+
* is made from the stored file, under the index lock, and the file and its index entry go
|
|
935
|
+
* together. A file another process still holds (EBUSY, EPERM on Windows) is left in place.
|
|
936
|
+
* Once the file is gone the session is gone: an attachment directory that will not go, or an
|
|
937
|
+
* index file that cannot be rewritten, no longer undoes that (a later index rebuild drops the
|
|
938
|
+
* stale entry). Returns whether the session was removed.
|
|
939
|
+
*/
|
|
940
|
+
removeUntouched(id) {
|
|
941
|
+
if (!existsSync(this.path(id))) return false;
|
|
942
|
+
this.ensureIndex();
|
|
943
|
+
const entry = this.metadataIndex.get(id);
|
|
944
|
+
if (entry && (entry.meta.messageCount > 0 || entry.meta.name)) return false;
|
|
945
|
+
try {
|
|
946
|
+
return this.withIndexLock(() => {
|
|
947
|
+
if (!this.isUntouched(id)) return false;
|
|
948
|
+
try {
|
|
949
|
+
this.discardSession(id);
|
|
950
|
+
} catch (error) {
|
|
951
|
+
if (!isErrno(error, ["ENOENT"])) throw error;
|
|
952
|
+
this.dropSession(id);
|
|
953
|
+
}
|
|
954
|
+
try {
|
|
955
|
+
this.writeIndexLocked([id], []);
|
|
956
|
+
} catch (error) {
|
|
957
|
+
if (!isErrno(error, ["ENOENT", "EBUSY", "EPERM"])) throw error;
|
|
958
|
+
log.warn("could not rewrite the session index after removing a session", { id });
|
|
959
|
+
}
|
|
960
|
+
return true;
|
|
961
|
+
});
|
|
962
|
+
} catch (error) {
|
|
963
|
+
if (isErrno(error, ["ENOENT", "EBUSY", "EPERM"])) return false;
|
|
964
|
+
throw error;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
isUntouched(id) {
|
|
968
|
+
if (statSync(this.path(id)).size > HEADER_PROBE_BYTES) return false;
|
|
969
|
+
const lines = readFileSync(this.path(id), "utf-8").split("\n").filter(Boolean);
|
|
970
|
+
if (lines.length === 0) return true;
|
|
971
|
+
if (lines.length > 1) return false;
|
|
972
|
+
try {
|
|
973
|
+
const header = JSON.parse(lines[0]);
|
|
974
|
+
const data = header.data ?? {};
|
|
975
|
+
return header.type === "session_meta" && data.kind === "session_meta" && !data.name;
|
|
976
|
+
} catch {
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/** Remove one session's file, attachments and cached state. The index file is not rewritten. */
|
|
981
|
+
discardSession(id) {
|
|
982
|
+
this.unlinkSessionFile(id);
|
|
983
|
+
this.dropSession(id);
|
|
984
|
+
}
|
|
985
|
+
unlinkSessionFile(id) {
|
|
986
|
+
unlinkSync(this.path(id));
|
|
987
|
+
}
|
|
988
|
+
/** What remains once the session file is gone. Attachments are best-effort. */
|
|
989
|
+
dropSession(id) {
|
|
990
|
+
try {
|
|
991
|
+
this.removeAttachments(id);
|
|
992
|
+
} catch (error) {
|
|
993
|
+
log.warn("could not remove a session attachment directory", {
|
|
994
|
+
id,
|
|
995
|
+
error: error instanceof Error ? error.message : String(error)
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
this.metadataIndex.delete(id);
|
|
999
|
+
this.parsedSessions.delete(id);
|
|
1000
|
+
}
|
|
1001
|
+
removeAttachments(id) {
|
|
1002
|
+
rmSync(imageSessionDirectory(this.root, id), { recursive: true, force: true });
|
|
1003
|
+
}
|
|
884
1004
|
};
|
|
1005
|
+
function isErrno(error, codes) {
|
|
1006
|
+
const code = error?.code;
|
|
1007
|
+
return code !== void 0 && codes.includes(code);
|
|
1008
|
+
}
|
|
885
1009
|
function cloneLoadedSession(session) {
|
|
886
1010
|
const contextHistory = [...session.contextHistory];
|
|
887
1011
|
return {
|
|
@@ -896,6 +1020,7 @@ function cloneLoadedSession(session) {
|
|
|
896
1020
|
todos: session.plan.todos?.map((todo) => ({ ...todo })),
|
|
897
1021
|
tasks: session.plan.tasks?.map((task) => ({ ...task }))
|
|
898
1022
|
} : void 0,
|
|
1023
|
+
goal: session.goal ? { version: 1, goal: session.goal.goal ? { ...session.goal.goal } : null } : void 0,
|
|
899
1024
|
carriedUsage: session.carriedUsage ? { ...session.carriedUsage } : void 0,
|
|
900
1025
|
carriedModels: session.carriedModels ? [...session.carriedModels] : void 0,
|
|
901
1026
|
history: contextHistory
|
|
@@ -1019,8 +1144,8 @@ function fixRunnerFor(manager, attribution = {}) {
|
|
|
1019
1144
|
const evidence = (await manager.listEvidence({ ids: [evidenceId] }))[0];
|
|
1020
1145
|
return evidence ? evidenceProjection(evidence) : void 0;
|
|
1021
1146
|
},
|
|
1022
|
-
async apply(agentId, evidenceId) {
|
|
1023
|
-
const result = await manager.apply(agentId, evidenceId);
|
|
1147
|
+
async apply(agentId, evidenceId, options) {
|
|
1148
|
+
const result = await manager.apply(agentId, evidenceId, options);
|
|
1024
1149
|
return { status: result.status, commit: result.commit, error: result.error };
|
|
1025
1150
|
}
|
|
1026
1151
|
};
|
|
@@ -1815,7 +1940,15 @@ async function applyReviewFixes(runner, confirmed, options = {}) {
|
|
|
1815
1940
|
);
|
|
1816
1941
|
continue;
|
|
1817
1942
|
}
|
|
1818
|
-
|
|
1943
|
+
let applied;
|
|
1944
|
+
try {
|
|
1945
|
+
applied = await runner.apply(patcher.id, candidate.id, { signal: options.signal });
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
if (!options.signal?.aborted) throw error;
|
|
1948
|
+
result.cancelled = true;
|
|
1949
|
+
result.messages.push(`Stopped while applying ${finding.file}: ${REVIEW_CANCELLED}.`);
|
|
1950
|
+
break;
|
|
1951
|
+
}
|
|
1819
1952
|
if (applied.status === "applied") {
|
|
1820
1953
|
result.applied++;
|
|
1821
1954
|
result.messages.push(
|
|
@@ -1989,6 +2122,7 @@ function reduceAgentSessionSnapshot(snapshot, event) {
|
|
|
1989
2122
|
case "agent_persistence":
|
|
1990
2123
|
case "skill_lifecycle":
|
|
1991
2124
|
case "notice":
|
|
2125
|
+
case "goal_status":
|
|
1992
2126
|
case "background_job_start":
|
|
1993
2127
|
case "background_job_update":
|
|
1994
2128
|
case "background_job_output":
|
|
@@ -2037,6 +2171,7 @@ function reduceAgentSessionSnapshot(snapshot, event) {
|
|
|
2037
2171
|
}
|
|
2038
2172
|
|
|
2039
2173
|
// src/session/resolve.ts
|
|
2174
|
+
var log2 = createDebugLogger("session:resolve");
|
|
2040
2175
|
function recordResumeDriver(store, sessionId, recorded, driver) {
|
|
2041
2176
|
if (driver === "agent" && recorded !== "agent") store.patchMeta(sessionId, { driver: "agent" });
|
|
2042
2177
|
}
|
|
@@ -2080,6 +2215,25 @@ function persistHistory(store, sessionId, history) {
|
|
|
2080
2215
|
});
|
|
2081
2216
|
}
|
|
2082
2217
|
}
|
|
2218
|
+
function discardUntouchedSession(store, launch) {
|
|
2219
|
+
if (!store || !launch.created) return false;
|
|
2220
|
+
try {
|
|
2221
|
+
return store.removeUntouched?.(launch.sessionId) ?? false;
|
|
2222
|
+
} catch (error) {
|
|
2223
|
+
log2.warn("could not remove the untouched session", {
|
|
2224
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2225
|
+
});
|
|
2226
|
+
return false;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
function discardUntouchedSessions(store, launch, createdSessionIds) {
|
|
2230
|
+
let removed = 0;
|
|
2231
|
+
if (discardUntouchedSession(store, launch)) removed++;
|
|
2232
|
+
for (const sessionId of createdSessionIds) {
|
|
2233
|
+
if (discardUntouchedSession(store, { sessionId, created: true })) removed++;
|
|
2234
|
+
}
|
|
2235
|
+
return removed;
|
|
2236
|
+
}
|
|
2083
2237
|
function resolveSessionBootstrap(store, options) {
|
|
2084
2238
|
if (!store) {
|
|
2085
2239
|
if (options.resume || options.continue) {
|
|
@@ -2146,6 +2300,7 @@ function resolveSessionBootstrap(store, options) {
|
|
|
2146
2300
|
rewindTargets: loaded.rewindTargets,
|
|
2147
2301
|
activeEventIds: loaded.activeEventIds,
|
|
2148
2302
|
plan: loaded.plan,
|
|
2303
|
+
goal: loaded.goal,
|
|
2149
2304
|
source: "resume",
|
|
2150
2305
|
persisted: true,
|
|
2151
2306
|
created: true
|
|
@@ -2163,6 +2318,7 @@ function resolveSessionBootstrap(store, options) {
|
|
|
2163
2318
|
rewindTargets: loaded.rewindTargets,
|
|
2164
2319
|
activeEventIds: loaded.activeEventIds,
|
|
2165
2320
|
plan: loaded.plan,
|
|
2321
|
+
goal: loaded.goal,
|
|
2166
2322
|
carriedUsage: loaded.carriedUsage,
|
|
2167
2323
|
carriedModels: loaded.carriedModels,
|
|
2168
2324
|
source: "resume",
|
|
@@ -2214,9 +2370,8 @@ function resolveWorkspaceMentionPath(workspace, mentionPath) {
|
|
|
2214
2370
|
const normalized = mentionPath.replace(/\\/g, "/");
|
|
2215
2371
|
const candidate = isAbsolute(normalized) ? normalized : resolve2(root, normalized);
|
|
2216
2372
|
const filePath = resolve2(candidate);
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
return { filePath, relativePath: normalizeMentionPath(rel) };
|
|
2373
|
+
if (isOutside(root, filePath)) return null;
|
|
2374
|
+
return { filePath, relativePath: normalizeMentionPath(relative(root, filePath)) };
|
|
2220
2375
|
}
|
|
2221
2376
|
function findActiveFileMention(input) {
|
|
2222
2377
|
for (let i = input.length - 1; i >= 0; i--) {
|
|
@@ -2260,6 +2415,8 @@ function withMissingAncestors(entries) {
|
|
|
2260
2415
|
return missing.length > 0 ? [...entries, ...missing] : entries;
|
|
2261
2416
|
}
|
|
2262
2417
|
async function getFileMentionCandidates(workspace, query, limit = 50, signal) {
|
|
2418
|
+
const reachesUnc = createUncGuard();
|
|
2419
|
+
if (reachesUnc(workspace)) return [];
|
|
2263
2420
|
const normalizedQuery = normalizeMentionPath(query).toLowerCase();
|
|
2264
2421
|
const gitignore = loadGitignore(workspace).patterns;
|
|
2265
2422
|
const ignore = globWalkIgnore([...DEFAULT_IGNORE, ...gitignore]);
|
|
@@ -2274,6 +2431,9 @@ async function getFileMentionCandidates(workspace, query, limit = 50, signal) {
|
|
|
2274
2431
|
// A pattern naming a directory returns that directory, not everything inside it — which is
|
|
2275
2432
|
// the whole point of walking for mentions.
|
|
2276
2433
|
expandDirectories: false,
|
|
2434
|
+
// The walk follows links: on Windows each one is asked about first, so none to a share is
|
|
2435
|
+
// resolved.
|
|
2436
|
+
fs: uncGuardedWalkFs(workspace, reachesUnc),
|
|
2277
2437
|
signal
|
|
2278
2438
|
})
|
|
2279
2439
|
);
|
|
@@ -2350,9 +2510,83 @@ async function runSessionEnd(config, sessionId, reason, options) {
|
|
|
2350
2510
|
}
|
|
2351
2511
|
|
|
2352
2512
|
// src/input/input-expansion.ts
|
|
2353
|
-
import {
|
|
2513
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
2354
2514
|
import { exec } from "child_process";
|
|
2355
2515
|
import { createHash as createHash2 } from "crypto";
|
|
2516
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "path";
|
|
2517
|
+
|
|
2518
|
+
// src/input/committed-files.ts
|
|
2519
|
+
var REPOSITORY_SELECTION = {
|
|
2520
|
+
GIT_DIR: void 0,
|
|
2521
|
+
GIT_WORK_TREE: void 0,
|
|
2522
|
+
GIT_INDEX_FILE: void 0,
|
|
2523
|
+
GIT_OBJECT_DIRECTORY: void 0,
|
|
2524
|
+
GIT_ALTERNATE_OBJECT_DIRECTORIES: void 0,
|
|
2525
|
+
GIT_COMMON_DIR: void 0,
|
|
2526
|
+
GIT_NAMESPACE: void 0,
|
|
2527
|
+
GIT_PREFIX: void 0
|
|
2528
|
+
};
|
|
2529
|
+
var GIT_ENV = { ...REPOSITORY_SELECTION, ...noFetchGitEnv() };
|
|
2530
|
+
var GIT_TIMEOUT_MS = 1e4;
|
|
2531
|
+
function records(stdout) {
|
|
2532
|
+
return stdout.split("\0").filter(Boolean).flatMap((record) => {
|
|
2533
|
+
const tab = record.indexOf(" ");
|
|
2534
|
+
if (tab === -1) return [];
|
|
2535
|
+
return [{ fields: record.slice(0, tab).split(" "), path: record.slice(tab + 1) }];
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
async function committedFileStates(repoRoot, paths, signal) {
|
|
2539
|
+
const states = /* @__PURE__ */ new Map();
|
|
2540
|
+
if (paths.length === 0) return states;
|
|
2541
|
+
const { pins, partialClone } = await readRepositoryGitConfig(repoRoot, void 0, {
|
|
2542
|
+
signal,
|
|
2543
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
2544
|
+
env: GIT_ENV
|
|
2545
|
+
});
|
|
2546
|
+
const git = async (args) => {
|
|
2547
|
+
try {
|
|
2548
|
+
return (await defaultGitRunner(hardenedGitArgs([...pins, "--literal-pathspecs", ...args]), {
|
|
2549
|
+
cwd: repoRoot,
|
|
2550
|
+
signal,
|
|
2551
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
2552
|
+
env: GIT_ENV
|
|
2553
|
+
})).stdout;
|
|
2554
|
+
} catch (error) {
|
|
2555
|
+
if (!(error instanceof Error)) throw error;
|
|
2556
|
+
const message = describeMissingObjects(error.message, partialClone);
|
|
2557
|
+
throw message === error.message ? error : new Error(message, { cause: error });
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
const settled = await Promise.allSettled([
|
|
2561
|
+
git(["ls-tree", "-r", "-z", "--full-tree", "HEAD", "--", ...paths]),
|
|
2562
|
+
git(["ls-files", "-z", "--stage", "--full-name", "--", ...paths]),
|
|
2563
|
+
git(["hash-object", "--", ...paths])
|
|
2564
|
+
]);
|
|
2565
|
+
const [tree, index, hashes] = settled.map((result) => {
|
|
2566
|
+
if (result.status === "rejected") throw result.reason;
|
|
2567
|
+
return result.value;
|
|
2568
|
+
});
|
|
2569
|
+
const committed = /* @__PURE__ */ new Map();
|
|
2570
|
+
for (const { fields, path } of records(tree)) {
|
|
2571
|
+
if ((fields[0] === "100644" || fields[0] === "100755") && fields[1] === "blob") {
|
|
2572
|
+
committed.set(path, fields[2]);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
const staged = /* @__PURE__ */ new Map();
|
|
2576
|
+
for (const { fields, path } of records(index)) {
|
|
2577
|
+
if (fields[2] === "0") staged.set(path, fields[1]);
|
|
2578
|
+
}
|
|
2579
|
+
const worktree = hashes.split(/\r?\n/);
|
|
2580
|
+
paths.forEach((path, position) => {
|
|
2581
|
+
const blob = committed.get(path);
|
|
2582
|
+
if (blob === void 0) states.set(path, "untracked");
|
|
2583
|
+
else if (staged.get(path) !== blob || worktree[position] !== blob) states.set(path, "changed");
|
|
2584
|
+
else states.set(path, "unchanged");
|
|
2585
|
+
});
|
|
2586
|
+
return states;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
// src/input/input-expansion.ts
|
|
2356
2590
|
var AT_MENTION_CHAR_LIMIT = 2e4;
|
|
2357
2591
|
function isMentionBoundary2(input, index) {
|
|
2358
2592
|
if (index === 0) return true;
|
|
@@ -2488,63 +2722,134 @@ function formatMentionError(filePath, reason) {
|
|
|
2488
2722
|
function looksBinary(content) {
|
|
2489
2723
|
return content.includes("\0");
|
|
2490
2724
|
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2725
|
+
var REFUSAL_TEXT = {
|
|
2726
|
+
outside: "it is outside the workspace and additionalDirectories",
|
|
2727
|
+
unc: "it is, or links to, a network (UNC) path",
|
|
2728
|
+
"no-repository": "a project command inlines only files committed to git, and there is no repository",
|
|
2729
|
+
untracked: "a project command inlines only files committed to git, and git does not track it",
|
|
2730
|
+
changed: "a project command inlines only committed files without local changes, and it has some",
|
|
2731
|
+
"git-failed": "a project command inlines only committed files, and git could not check it"
|
|
2732
|
+
};
|
|
2733
|
+
function formatRefusedMentionNotice(refused) {
|
|
2734
|
+
return `\u26A0 Left @${renderUntrustedText(refused.path, 400)} as written: ${REFUSAL_TEXT[refused.reason]}.`;
|
|
2735
|
+
}
|
|
2736
|
+
function mentionTarget(workspace, mentionPath, additionalRoots) {
|
|
2737
|
+
const inWorkspace = resolveWorkspaceMentionPath(workspace, mentionPath);
|
|
2738
|
+
if (inWorkspace) return inWorkspace;
|
|
2739
|
+
const normalized = mentionPath.replace(/\\/g, "/");
|
|
2740
|
+
if (!isAbsolute2(normalized)) return null;
|
|
2741
|
+
const filePath = resolve3(normalized);
|
|
2742
|
+
if (!additionalRoots.some((root) => !isOutside(root, filePath))) return null;
|
|
2743
|
+
return { filePath, relativePath: normalizeMentionPath(relative2(resolve3(workspace), filePath)) };
|
|
2744
|
+
}
|
|
2745
|
+
function decideMention(workspace, mentionPath, additionalRoots) {
|
|
2746
|
+
const target = mentionTarget(workspace, mentionPath, additionalRoots);
|
|
2747
|
+
if (!target) return null;
|
|
2748
|
+
if (reachesUncPath(target.filePath)) return { kind: "refused", reason: "unc" };
|
|
2749
|
+
const realPath = tryRealPath(target.filePath);
|
|
2750
|
+
if (realPath === void 0) return null;
|
|
2751
|
+
let regular;
|
|
2495
2752
|
try {
|
|
2496
|
-
const stat2 = statSync2(
|
|
2753
|
+
const stat2 = statSync2(realPath);
|
|
2497
2754
|
if (stat2.isDirectory()) return null;
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2755
|
+
regular = stat2.isFile();
|
|
2756
|
+
} catch {
|
|
2757
|
+
return null;
|
|
2758
|
+
}
|
|
2759
|
+
const readable = resolveMentionReadablePath(
|
|
2760
|
+
{ workspaceRoot: workspace, additionalRoots },
|
|
2761
|
+
realPath
|
|
2762
|
+
);
|
|
2763
|
+
if (!("path" in readable)) {
|
|
2764
|
+
return { kind: "refused", reason: readable.reason === "unc" ? "unc" : "outside" };
|
|
2765
|
+
}
|
|
2766
|
+
return { kind: "file", realPath, relativePath: target.relativePath, regular };
|
|
2767
|
+
}
|
|
2768
|
+
async function applyProjectCommandRule(workspace, decisions, signal) {
|
|
2769
|
+
const files = decisions.filter(
|
|
2770
|
+
(decision) => decision?.kind === "file"
|
|
2771
|
+
);
|
|
2772
|
+
if (files.length === 0) return decisions;
|
|
2773
|
+
const refuse = (reason) => decisions.map(
|
|
2774
|
+
(decision) => decision?.kind === "file" ? { kind: "refused", reason } : decision
|
|
2775
|
+
);
|
|
2776
|
+
const repoRoot = findRepositoryRoot(workspace);
|
|
2777
|
+
if (repoRoot === void 0) return refuse("no-repository");
|
|
2778
|
+
const inRepository = (realPath) => isOutside(repoRoot, realPath) ? void 0 : relative2(repoRoot, realPath).replace(/\\/g, "/");
|
|
2779
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2780
|
+
for (const file of files) {
|
|
2781
|
+
const path = file.regular ? inRepository(file.realPath) : void 0;
|
|
2782
|
+
if (path !== void 0) paths.add(path);
|
|
2783
|
+
}
|
|
2784
|
+
let states;
|
|
2785
|
+
try {
|
|
2786
|
+
states = await committedFileStates(repoRoot, [...paths], signal);
|
|
2787
|
+
} catch {
|
|
2788
|
+
states = /* @__PURE__ */ new Map();
|
|
2789
|
+
for (const path of paths) {
|
|
2790
|
+
try {
|
|
2791
|
+
states.set(path, (await committedFileStates(repoRoot, [path], signal)).get(path));
|
|
2792
|
+
} catch {
|
|
2793
|
+
states.set(path, "git-failed");
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
return decisions.map((decision) => {
|
|
2798
|
+
if (decision?.kind !== "file") return decision;
|
|
2799
|
+
const path = inRepository(decision.realPath);
|
|
2800
|
+
const state = path === void 0 ? "untracked" : states.get(path) ?? "untracked";
|
|
2801
|
+
return state === "unchanged" ? decision : { kind: "refused", reason: state };
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
function inlineMention(token, decision, scope) {
|
|
2805
|
+
if (decision?.kind !== "file") return { text: null };
|
|
2806
|
+
if (!decision.regular) {
|
|
2807
|
+
return { text: formatMentionError(decision.relativePath, "path is not a regular file") };
|
|
2808
|
+
}
|
|
2809
|
+
let bytes;
|
|
2810
|
+
try {
|
|
2811
|
+
bytes = readFileSync2(decision.realPath);
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2814
|
+
return { text: formatMentionError(token.path, message.slice(0, 200) || "unable to read file") };
|
|
2815
|
+
}
|
|
2816
|
+
const observation = {
|
|
2817
|
+
// Filed the way Read and Edit file and look a file up (`observationPathFor`): canonical, so a
|
|
2818
|
+
// mention through a link, or of a file in an `additionalDirectories` entry, counts as seen.
|
|
2819
|
+
path: observationPathFor(scope.workspace, decision.realPath),
|
|
2820
|
+
workspaceId: scope.workspaceId,
|
|
2821
|
+
sha256: createHash2("sha256").update(bytes).digest("hex"),
|
|
2822
|
+
byteSize: bytes.byteLength,
|
|
2823
|
+
operation: "mention",
|
|
2824
|
+
sourceRef: scope.sourceRef,
|
|
2825
|
+
timestamp: Date.now()
|
|
2826
|
+
};
|
|
2827
|
+
const content = bytes.toString("utf-8");
|
|
2828
|
+
if (looksBinary(content)) {
|
|
2829
|
+
return {
|
|
2830
|
+
text: formatMentionError(decision.relativePath, "file appears to be binary"),
|
|
2831
|
+
observation
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
const truncated = content.length > AT_MENTION_CHAR_LIMIT;
|
|
2835
|
+
const body = truncated ? content.slice(0, AT_MENTION_CHAR_LIMIT) : content;
|
|
2836
|
+
const suffix = truncated ? `
|
|
2506
2837
|
|
|
2507
2838
|
[File truncated at ${AT_MENTION_CHAR_LIMIT} characters; use the Read tool for more.]` : "";
|
|
2508
|
-
|
|
2509
|
-
|
|
2839
|
+
return {
|
|
2840
|
+
text: `
|
|
2841
|
+
Contents of ${decision.relativePath}:
|
|
2510
2842
|
|
|
2511
2843
|
\`\`\`
|
|
2512
2844
|
${body}${suffix}
|
|
2513
2845
|
\`\`\`
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
return formatMentionError(filePath, message.slice(0, 200) || "unable to read file");
|
|
2518
|
-
}
|
|
2846
|
+
`,
|
|
2847
|
+
observation
|
|
2848
|
+
};
|
|
2519
2849
|
}
|
|
2520
2850
|
function mentionTokenRanges(input) {
|
|
2521
2851
|
return findMentionTokens(input).map((token) => [token.start, token.end]);
|
|
2522
2852
|
}
|
|
2523
|
-
function collectAtMentionObservations(input, workspace, sourceRef, expandShellInput = false) {
|
|
2524
|
-
const workspaceId = workspaceIdentity(workspace);
|
|
2525
|
-
const observations = [];
|
|
2526
|
-
const shellLines = expandShellInput ? findShellLines(input) : [];
|
|
2527
|
-
for (const token of findMentionTokens(input, shellLineRanges(shellLines))) {
|
|
2528
|
-
const resolved = resolveWorkspaceMentionPath(workspace, token.path);
|
|
2529
|
-
if (!resolved || !existsSync2(resolved.filePath)) continue;
|
|
2530
|
-
try {
|
|
2531
|
-
const info = statSync2(resolved.filePath);
|
|
2532
|
-
if (!info.isFile()) continue;
|
|
2533
|
-
const bytes = readFileSync2(resolved.filePath);
|
|
2534
|
-
observations.push({
|
|
2535
|
-
path: resolved.relativePath,
|
|
2536
|
-
workspaceId,
|
|
2537
|
-
sha256: createHash2("sha256").update(bytes).digest("hex"),
|
|
2538
|
-
byteSize: bytes.byteLength,
|
|
2539
|
-
operation: "mention",
|
|
2540
|
-
sourceRef,
|
|
2541
|
-
timestamp: Date.now()
|
|
2542
|
-
});
|
|
2543
|
-
} catch {
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
return observations;
|
|
2547
|
-
}
|
|
2548
2853
|
function findShellLines(input) {
|
|
2549
2854
|
const fenced = fencedCodeRanges(input);
|
|
2550
2855
|
return [...input.matchAll(/^!(\S.*)$/gm)].filter((match) => !isInsideRanges(fenced, match.index ?? 0)).map((match) => {
|
|
@@ -2555,20 +2860,39 @@ function findShellLines(input) {
|
|
|
2555
2860
|
function shellLineRanges(lines) {
|
|
2556
2861
|
return lines.map((line) => [line.start, line.end]);
|
|
2557
2862
|
}
|
|
2558
|
-
async function
|
|
2559
|
-
const shellLines = options.expandShell ? findShellLines(input) : [];
|
|
2863
|
+
async function expandUserInputDetailed(input, workspace, options = {}) {
|
|
2864
|
+
const shellLines = options.expandShell && !options.projectCommand ? findShellLines(input) : [];
|
|
2560
2865
|
const tokens = findMentionTokens(input, shellLineRanges(shellLines));
|
|
2561
|
-
if (shellLines.length === 0 && tokens.length === 0)
|
|
2866
|
+
if (shellLines.length === 0 && tokens.length === 0) {
|
|
2867
|
+
return { text: input, observations: [], refused: [] };
|
|
2868
|
+
}
|
|
2869
|
+
const additionalRoots = options.additionalRoots ?? [];
|
|
2870
|
+
const decide = (token) => decideMention(workspace, token.path, additionalRoots);
|
|
2871
|
+
const decided = options.projectCommand ? await applyProjectCommandRule(workspace, tokens.map(decide), options.signal) : void 0;
|
|
2872
|
+
const scope = {
|
|
2873
|
+
workspace,
|
|
2874
|
+
workspaceId: workspaceIdentity(workspace),
|
|
2875
|
+
sourceRef: options.sourceRef ?? ""
|
|
2876
|
+
};
|
|
2877
|
+
const observations = [];
|
|
2878
|
+
const refused = [];
|
|
2562
2879
|
const edits = [
|
|
2563
2880
|
...shellLines.map((line) => ({
|
|
2564
2881
|
start: line.start,
|
|
2565
2882
|
end: line.end,
|
|
2566
2883
|
apply: () => executeShellExpansion(line.command, workspace, options.signal)
|
|
2567
2884
|
})),
|
|
2568
|
-
...tokens.map((token) => ({
|
|
2885
|
+
...tokens.map((token, index) => ({
|
|
2569
2886
|
start: token.start,
|
|
2570
2887
|
end: token.end,
|
|
2571
|
-
apply: () =>
|
|
2888
|
+
apply: () => {
|
|
2889
|
+
const decision = decided ? decided[index] : decide(token);
|
|
2890
|
+
if (decision?.kind === "refused")
|
|
2891
|
+
refused.push({ path: token.path, reason: decision.reason });
|
|
2892
|
+
const inlined = inlineMention(token, decision, scope);
|
|
2893
|
+
if (inlined.observation) observations.push(inlined.observation);
|
|
2894
|
+
return inlined.text ?? token.raw;
|
|
2895
|
+
}
|
|
2572
2896
|
}))
|
|
2573
2897
|
].sort((left, right) => left.start - right.start);
|
|
2574
2898
|
let output = "";
|
|
@@ -2578,10 +2902,10 @@ async function expandUserInput(input, workspace, options = {}) {
|
|
|
2578
2902
|
output += await edit.apply();
|
|
2579
2903
|
cursor = edit.end;
|
|
2580
2904
|
}
|
|
2581
|
-
return output + input.slice(cursor);
|
|
2905
|
+
return { text: output + input.slice(cursor), observations, refused };
|
|
2582
2906
|
}
|
|
2583
2907
|
function executeShellExpansion(command, workspace, signal) {
|
|
2584
|
-
return new Promise((
|
|
2908
|
+
return new Promise((resolve4) => {
|
|
2585
2909
|
exec(
|
|
2586
2910
|
command,
|
|
2587
2911
|
{
|
|
@@ -2598,18 +2922,18 @@ function executeShellExpansion(command, workspace, signal) {
|
|
|
2598
2922
|
(error, stdout) => {
|
|
2599
2923
|
if (error) {
|
|
2600
2924
|
const message = error.message.slice(0, 200) || "unknown error";
|
|
2601
|
-
|
|
2925
|
+
resolve4(`(command '${command}' failed: ${message})`);
|
|
2602
2926
|
return;
|
|
2603
2927
|
}
|
|
2604
2928
|
const value = stdout.trim();
|
|
2605
|
-
|
|
2929
|
+
resolve4(value || `(command '${command}' produced no output)`);
|
|
2606
2930
|
}
|
|
2607
2931
|
);
|
|
2608
2932
|
});
|
|
2609
2933
|
}
|
|
2610
2934
|
|
|
2611
2935
|
// src/session/agent-interactions.ts
|
|
2612
|
-
var
|
|
2936
|
+
var log3 = createDebugLoggerWithCounter("session:interactions");
|
|
2613
2937
|
function snapshotOf(permission, planApproval, userQuestions, elicitations) {
|
|
2614
2938
|
return Object.freeze({
|
|
2615
2939
|
pendingPermission: permission ? Object.freeze({ toolCall: permission.toolCall }) : null,
|
|
@@ -2638,11 +2962,11 @@ var AgentInteractionController = class {
|
|
|
2638
2962
|
return () => this.listeners.delete(listener);
|
|
2639
2963
|
}
|
|
2640
2964
|
requestPermission(toolCall) {
|
|
2641
|
-
return new Promise((
|
|
2642
|
-
const request = { toolCall, resolve:
|
|
2965
|
+
return new Promise((resolve4) => {
|
|
2966
|
+
const request = { toolCall, resolve: resolve4 };
|
|
2643
2967
|
if (this.pendingPermission) {
|
|
2644
2968
|
this.pendingPermissionQueue.push(request);
|
|
2645
|
-
|
|
2969
|
+
log3.event("permission:queued", {
|
|
2646
2970
|
tool: toolCall.name,
|
|
2647
2971
|
id: toolCall.id,
|
|
2648
2972
|
queueLength: this.pendingPermissionQueue.length
|
|
@@ -2650,19 +2974,19 @@ var AgentInteractionController = class {
|
|
|
2650
2974
|
} else {
|
|
2651
2975
|
this.pendingPermission = request;
|
|
2652
2976
|
this.publish();
|
|
2653
|
-
|
|
2977
|
+
log3.event("permission:pending", { tool: toolCall.name, id: toolCall.id });
|
|
2654
2978
|
}
|
|
2655
2979
|
});
|
|
2656
2980
|
}
|
|
2657
2981
|
settlePermission(result, via) {
|
|
2658
2982
|
const pending = this.pendingPermission;
|
|
2659
2983
|
if (!pending) {
|
|
2660
|
-
|
|
2984
|
+
log3.event("permission:settled:noop", { reason: "no-pending", result, via });
|
|
2661
2985
|
return false;
|
|
2662
2986
|
}
|
|
2663
2987
|
this.pendingPermission = this.pendingPermissionQueue.shift() ?? null;
|
|
2664
2988
|
this.publish();
|
|
2665
|
-
|
|
2989
|
+
log3.event("permission:settled", {
|
|
2666
2990
|
tool: pending.toolCall.name,
|
|
2667
2991
|
id: pending.toolCall.id,
|
|
2668
2992
|
result,
|
|
@@ -2681,26 +3005,26 @@ var AgentInteractionController = class {
|
|
|
2681
3005
|
this.pendingPermissionQueue = [];
|
|
2682
3006
|
this.publish();
|
|
2683
3007
|
for (const request of pending) request.resolve({ result: "deny", reason: "dismissed" });
|
|
2684
|
-
|
|
3008
|
+
log3.event("permission:cancelled-all", { via, count: pending.length });
|
|
2685
3009
|
return true;
|
|
2686
3010
|
}
|
|
2687
3011
|
requestPlanApproval(plan) {
|
|
2688
3012
|
if (this.pendingPlanApproval) this.settlePlanApproval("reject", "superseded");
|
|
2689
|
-
return new Promise((
|
|
2690
|
-
this.pendingPlanApproval = { plan, resolve:
|
|
3013
|
+
return new Promise((resolve4) => {
|
|
3014
|
+
this.pendingPlanApproval = { plan, resolve: resolve4 };
|
|
2691
3015
|
this.publish();
|
|
2692
|
-
|
|
3016
|
+
log3.event("plan-approval:pending", { len: plan.length });
|
|
2693
3017
|
});
|
|
2694
3018
|
}
|
|
2695
3019
|
settlePlanApproval(result, via) {
|
|
2696
3020
|
const pending = this.pendingPlanApproval;
|
|
2697
3021
|
if (!pending) {
|
|
2698
|
-
|
|
3022
|
+
log3.event("plan-approval:settled:noop", { reason: "no-pending", result, via });
|
|
2699
3023
|
return false;
|
|
2700
3024
|
}
|
|
2701
3025
|
this.pendingPlanApproval = null;
|
|
2702
3026
|
this.publish();
|
|
2703
|
-
|
|
3027
|
+
log3.event("plan-approval:settled", { result, via, len: pending.plan.length });
|
|
2704
3028
|
pending.resolve(result);
|
|
2705
3029
|
return true;
|
|
2706
3030
|
}
|
|
@@ -2711,10 +3035,10 @@ var AgentInteractionController = class {
|
|
|
2711
3035
|
message: `Duplicate user question request: ${request.id}`
|
|
2712
3036
|
});
|
|
2713
3037
|
}
|
|
2714
|
-
return new Promise((
|
|
2715
|
-
this.pendingUserQuestions = [...this.pendingUserQuestions, { request, resolve:
|
|
3038
|
+
return new Promise((resolve4) => {
|
|
3039
|
+
this.pendingUserQuestions = [...this.pendingUserQuestions, { request, resolve: resolve4 }];
|
|
2716
3040
|
this.publish();
|
|
2717
|
-
|
|
3041
|
+
log3.event("user-question:pending", {
|
|
2718
3042
|
id: request.id,
|
|
2719
3043
|
count: request.questions.length,
|
|
2720
3044
|
queueLength: this.pendingUserQuestions.length,
|
|
@@ -2725,14 +3049,14 @@ var AgentInteractionController = class {
|
|
|
2725
3049
|
settleUserQuestion(result, via, requestId) {
|
|
2726
3050
|
const index = requestId ? this.pendingUserQuestions.findIndex((entry) => entry.request.id === requestId) : 0;
|
|
2727
3051
|
if (index < 0 || this.pendingUserQuestions.length === 0) {
|
|
2728
|
-
|
|
3052
|
+
log3.event("user-question:settled:noop", { reason: "no-pending", via, requestId });
|
|
2729
3053
|
return false;
|
|
2730
3054
|
}
|
|
2731
3055
|
const next = [...this.pendingUserQuestions];
|
|
2732
3056
|
const [pending] = next.splice(index, 1);
|
|
2733
3057
|
this.pendingUserQuestions = next;
|
|
2734
3058
|
this.publish();
|
|
2735
|
-
|
|
3059
|
+
log3.event("user-question:settled", {
|
|
2736
3060
|
id: pending.request.id,
|
|
2737
3061
|
action: result.action,
|
|
2738
3062
|
via,
|
|
@@ -2749,7 +3073,7 @@ var AgentInteractionController = class {
|
|
|
2749
3073
|
for (const entry of pending) {
|
|
2750
3074
|
entry.resolve({ action: "cancel", message: `Question cancelled via ${via}.` });
|
|
2751
3075
|
}
|
|
2752
|
-
|
|
3076
|
+
log3.event("user-question:cancelled-all", { via, count: pending.length });
|
|
2753
3077
|
return pending.length;
|
|
2754
3078
|
}
|
|
2755
3079
|
/**
|
|
@@ -2760,10 +3084,10 @@ var AgentInteractionController = class {
|
|
|
2760
3084
|
if (this.pendingElicitations.some((entry) => entry.request.id === request.id)) {
|
|
2761
3085
|
return Promise.resolve({ action: "cancel" });
|
|
2762
3086
|
}
|
|
2763
|
-
return new Promise((
|
|
2764
|
-
this.pendingElicitations = [...this.pendingElicitations, { request, resolve:
|
|
3087
|
+
return new Promise((resolve4) => {
|
|
3088
|
+
this.pendingElicitations = [...this.pendingElicitations, { request, resolve: resolve4 }];
|
|
2765
3089
|
this.publish();
|
|
2766
|
-
|
|
3090
|
+
log3.event("elicitation:pending", {
|
|
2767
3091
|
id: request.id,
|
|
2768
3092
|
server: request.server,
|
|
2769
3093
|
fields: request.fields.length,
|
|
@@ -2774,14 +3098,14 @@ var AgentInteractionController = class {
|
|
|
2774
3098
|
settleElicitation(result, via, requestId) {
|
|
2775
3099
|
const index = requestId ? this.pendingElicitations.findIndex((entry) => entry.request.id === requestId) : 0;
|
|
2776
3100
|
if (index < 0 || this.pendingElicitations.length === 0) {
|
|
2777
|
-
|
|
3101
|
+
log3.event("elicitation:settled:noop", { reason: "no-pending", via, requestId });
|
|
2778
3102
|
return false;
|
|
2779
3103
|
}
|
|
2780
3104
|
const next = [...this.pendingElicitations];
|
|
2781
3105
|
const [pending] = next.splice(index, 1);
|
|
2782
3106
|
this.pendingElicitations = next;
|
|
2783
3107
|
this.publish();
|
|
2784
|
-
|
|
3108
|
+
log3.event("elicitation:settled", {
|
|
2785
3109
|
id: pending.request.id,
|
|
2786
3110
|
action: result.action,
|
|
2787
3111
|
via,
|
|
@@ -2796,7 +3120,7 @@ var AgentInteractionController = class {
|
|
|
2796
3120
|
this.pendingElicitations = [];
|
|
2797
3121
|
this.publish();
|
|
2798
3122
|
for (const entry of pending) entry.resolve({ action: "cancel" });
|
|
2799
|
-
|
|
3123
|
+
log3.event("elicitation:cancelled-all", { via, count: pending.length });
|
|
2800
3124
|
return pending.length;
|
|
2801
3125
|
}
|
|
2802
3126
|
cancelAll(via) {
|
|
@@ -2863,7 +3187,16 @@ var AgentSessionOperations = class {
|
|
|
2863
3187
|
};
|
|
2864
3188
|
|
|
2865
3189
|
// src/session/agent-session.ts
|
|
2866
|
-
var
|
|
3190
|
+
var log4 = createDebugLogger("session:usage");
|
|
3191
|
+
function persistGoal(target, goal) {
|
|
3192
|
+
if (!target.timelineStore || !target.sessionId) return;
|
|
3193
|
+
const data = { version: 1, goal: goal ? { ...goal } : null };
|
|
3194
|
+
target.timelineStore.append(target.sessionId, {
|
|
3195
|
+
type: "goal",
|
|
3196
|
+
timestamp: Date.now(),
|
|
3197
|
+
data
|
|
3198
|
+
});
|
|
3199
|
+
}
|
|
2867
3200
|
var AgentSession = class {
|
|
2868
3201
|
interactions = new AgentInteractionController();
|
|
2869
3202
|
operations = new AgentSessionOperations();
|
|
@@ -2950,6 +3283,11 @@ var AgentSession = class {
|
|
|
2950
3283
|
contextMessage: request.contextMessage,
|
|
2951
3284
|
userMessage,
|
|
2952
3285
|
sessionName: request.sessionName,
|
|
3286
|
+
sessionNameSource: request.sessionNameSource,
|
|
3287
|
+
sessionNameExact: request.sessionNameExact,
|
|
3288
|
+
expandShellInput: request.expandShellInput,
|
|
3289
|
+
projectCommand: request.projectCommand,
|
|
3290
|
+
typedText: request.typedText,
|
|
2953
3291
|
snapshotStore: request.snapshotStore,
|
|
2954
3292
|
timelineStore: request.timelineStore,
|
|
2955
3293
|
signal: control.signal,
|
|
@@ -2995,6 +3333,7 @@ var AgentSession = class {
|
|
|
2995
3333
|
...request.options,
|
|
2996
3334
|
runtime,
|
|
2997
3335
|
displayMessage: request.displayMessage,
|
|
3336
|
+
typedText: request.typedText,
|
|
2998
3337
|
userMessageId: userMessage.id,
|
|
2999
3338
|
userMessageTimestamp: userMessage.timestamp,
|
|
3000
3339
|
userFileObservations: userMessage.fileObservations,
|
|
@@ -3042,6 +3381,41 @@ var AgentSession = class {
|
|
|
3042
3381
|
getRuntime() {
|
|
3043
3382
|
return this.runtime;
|
|
3044
3383
|
}
|
|
3384
|
+
/** The session's `/goal`, if one is set. */
|
|
3385
|
+
getGoal() {
|
|
3386
|
+
return this.runtime.activeGoal;
|
|
3387
|
+
}
|
|
3388
|
+
/**
|
|
3389
|
+
* Set the session's goal, replacing any earlier one. The next time the agent
|
|
3390
|
+
* stops without calling a tool, the loop checks the condition (`agent/goal.ts`).
|
|
3391
|
+
* Taking effect on the runtime means a goal set mid-run applies to that run.
|
|
3392
|
+
*/
|
|
3393
|
+
setGoal(condition, target = {}) {
|
|
3394
|
+
const goal = createGoal(condition);
|
|
3395
|
+
this.runtime.activeGoal = goal;
|
|
3396
|
+
persistGoal(target, goal);
|
|
3397
|
+
return goal;
|
|
3398
|
+
}
|
|
3399
|
+
/**
|
|
3400
|
+
* Carry a goal into a conversation that started fresh -- a plan handoff reseeds a new
|
|
3401
|
+
* session to implement the plan the goal was guiding -- keeping its progress.
|
|
3402
|
+
*/
|
|
3403
|
+
adoptGoal(goal, target = {}) {
|
|
3404
|
+
this.runtime.activeGoal = { ...goal };
|
|
3405
|
+
persistGoal(target, this.runtime.activeGoal);
|
|
3406
|
+
}
|
|
3407
|
+
/** Clear the session's goal. Returns the goal that was cleared, if there was one. */
|
|
3408
|
+
clearGoal(target = {}) {
|
|
3409
|
+
const goal = this.runtime.activeGoal;
|
|
3410
|
+
if (!goal) return void 0;
|
|
3411
|
+
this.runtime.activeGoal = void 0;
|
|
3412
|
+
persistGoal(target, null);
|
|
3413
|
+
return goal;
|
|
3414
|
+
}
|
|
3415
|
+
/** Put back an unmet goal a resumed session recorded. Call after the runtime it belongs on is installed. */
|
|
3416
|
+
restoreGoal(record) {
|
|
3417
|
+
this.runtime.activeGoal = restoreGoal(record);
|
|
3418
|
+
}
|
|
3045
3419
|
/**
|
|
3046
3420
|
* Stop the outgoing runtime, write what it spent, and install its replacement.
|
|
3047
3421
|
*
|
|
@@ -3054,9 +3428,14 @@ var AgentSession = class {
|
|
|
3054
3428
|
*/
|
|
3055
3429
|
replaceRuntime(options = {}, via = "session_transition") {
|
|
3056
3430
|
const outgoing = this.runtime;
|
|
3431
|
+
outgoing.agentManager?.handOffLateApplications(() => this.runtime.agentManager);
|
|
3057
3432
|
outgoing.dispose(via);
|
|
3058
3433
|
this.releaseRunUsage(outgoing);
|
|
3059
|
-
this.runtime = new SessionRuntime(
|
|
3434
|
+
this.runtime = new SessionRuntime({
|
|
3435
|
+
...options,
|
|
3436
|
+
// The managers this session disposed, the outgoing one included, own nothing (#568).
|
|
3437
|
+
disposedAgentInstanceIds: options.disposedAgentInstanceIds ?? outgoing.disposedAgentInstanceIds
|
|
3438
|
+
});
|
|
3060
3439
|
return this.runtime;
|
|
3061
3440
|
}
|
|
3062
3441
|
subscribe(listener) {
|
|
@@ -3196,7 +3575,7 @@ var AgentSession = class {
|
|
|
3196
3575
|
});
|
|
3197
3576
|
runtime.runAccounting.commitPersistedUsage(rootRunId);
|
|
3198
3577
|
} catch (error) {
|
|
3199
|
-
|
|
3578
|
+
log4.warn("usage record could not be appended; spend left unpersisted", {
|
|
3200
3579
|
rootRunId,
|
|
3201
3580
|
sessionId: target.sessionId,
|
|
3202
3581
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -3255,6 +3634,7 @@ var AgentSession = class {
|
|
|
3255
3634
|
cwd: request.config.workspace,
|
|
3256
3635
|
driver: request.config.sessionDriver
|
|
3257
3636
|
}) ?? crypto.randomUUID();
|
|
3637
|
+
if (request.store) request.createdSessions?.add(sessionId);
|
|
3258
3638
|
if (request.store && request.timelineStore && request.timelineStore !== request.store) {
|
|
3259
3639
|
request.timelineStore.create({
|
|
3260
3640
|
id: sessionId,
|
|
@@ -3305,12 +3685,14 @@ var AgentSession = class {
|
|
|
3305
3685
|
// user who wrote a plan, switched away and came back resumed a half-finished
|
|
3306
3686
|
// objective with an empty task list and no notice that it had been dropped.
|
|
3307
3687
|
plan: loaded.plan,
|
|
3688
|
+
goal: loaded.goal,
|
|
3308
3689
|
source: "resume",
|
|
3309
3690
|
persisted: true,
|
|
3310
3691
|
created: false
|
|
3311
3692
|
};
|
|
3312
3693
|
this.reset("session-resume");
|
|
3313
|
-
this.replaceRuntime({
|
|
3694
|
+
const resumed = this.replaceRuntime({}, "session-resume");
|
|
3695
|
+
seedShellIds(resumed.backgroundShells, loaded.transcript, loaded.contextHistory);
|
|
3314
3696
|
request.onTransition?.(bootstrap);
|
|
3315
3697
|
await this.startLifecycle(request.config, selected.id, "resume");
|
|
3316
3698
|
return { status: "transitioned", bootstrap };
|
|
@@ -3331,10 +3713,13 @@ var AgentSession = class {
|
|
|
3331
3713
|
gitHead: capture.gitHead,
|
|
3332
3714
|
codeUnavailableReason: capture.reason
|
|
3333
3715
|
};
|
|
3716
|
+
const rewindPrompt = request.typedText ?? request.displayMessage;
|
|
3717
|
+
const promptTyped = request.typedText !== void 0;
|
|
3334
3718
|
const rewindTarget = {
|
|
3335
3719
|
id: checkpointId,
|
|
3336
3720
|
userEventId: request.userMessage.id,
|
|
3337
|
-
prompt:
|
|
3721
|
+
prompt: rewindPrompt,
|
|
3722
|
+
...promptTyped ? { promptTyped } : {},
|
|
3338
3723
|
attachments: request.userMessage.attachments,
|
|
3339
3724
|
timestamp: checkpointTimestamp,
|
|
3340
3725
|
...checkpoint,
|
|
@@ -3348,27 +3733,32 @@ var AgentSession = class {
|
|
|
3348
3733
|
version: 1,
|
|
3349
3734
|
checkpointId,
|
|
3350
3735
|
userEventId: request.userMessage.id,
|
|
3351
|
-
prompt:
|
|
3736
|
+
prompt: rewindPrompt,
|
|
3737
|
+
...promptTyped ? { promptTyped } : {},
|
|
3352
3738
|
attachments: request.userMessage.attachments,
|
|
3353
3739
|
checkpoint
|
|
3354
3740
|
}
|
|
3355
3741
|
});
|
|
3356
|
-
const { contextMessage, sessionName } = await this.recordUserMessage(request);
|
|
3357
|
-
return { status: "prepared", contextMessage, rewindTarget, sessionName };
|
|
3742
|
+
const { contextMessage, sessionName, mentionNotices } = await this.recordUserMessage(request);
|
|
3743
|
+
return { status: "prepared", contextMessage, rewindTarget, sessionName, mentionNotices };
|
|
3358
3744
|
}
|
|
3359
3745
|
async recordUserMessage(request) {
|
|
3360
|
-
const
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3746
|
+
const expansion = request.contextMessage !== void 0 ? void 0 : await expandUserInputDetailed(request.displayMessage, request.config.workspace, {
|
|
3747
|
+
expandShell: request.expandShellInput === true,
|
|
3748
|
+
signal: request.signal,
|
|
3749
|
+
sourceRef: request.userMessage.id,
|
|
3750
|
+
// The roots Read serves besides the workspace, resolved as the agent loop does, and
|
|
3751
|
+
// only for text that can hold a mention at all.
|
|
3752
|
+
additionalRoots: request.displayMessage.includes("@") ? resolveAdditionalRoots(
|
|
3753
|
+
request.config.workspace,
|
|
3754
|
+
request.config.settings.additionalDirectories
|
|
3755
|
+
) : [],
|
|
3756
|
+
projectCommand: request.projectCommand
|
|
3364
3757
|
});
|
|
3758
|
+
const contextMessage = expansion?.text ?? request.contextMessage ?? request.displayMessage;
|
|
3759
|
+
const mentionNotices = (expansion?.refused ?? []).map(formatRefusedMentionNotice);
|
|
3365
3760
|
request.userMessage.contextContent = contextMessage === request.displayMessage ? void 0 : contextMessage;
|
|
3366
|
-
request.userMessage.fileObservations =
|
|
3367
|
-
request.displayMessage,
|
|
3368
|
-
request.config.workspace,
|
|
3369
|
-
request.userMessage.id,
|
|
3370
|
-
expandShellInput
|
|
3371
|
-
) : [];
|
|
3761
|
+
request.userMessage.fileObservations = expansion?.observations ?? [];
|
|
3372
3762
|
const observationLedger = (request.runtime ?? this.runtime).fileObservationLedger;
|
|
3373
3763
|
for (const observation of request.userMessage.fileObservations) {
|
|
3374
3764
|
observationLedger.set(observationKey(observation.workspaceId, observation.path), observation);
|
|
@@ -3392,11 +3782,14 @@ var AgentSession = class {
|
|
|
3392
3782
|
}
|
|
3393
3783
|
});
|
|
3394
3784
|
if (request.timelineStore) request.onUserMessagePersisted?.();
|
|
3395
|
-
|
|
3785
|
+
if (request.userMessage.kind === "agent-notification") {
|
|
3786
|
+
return { contextMessage, sessionName: request.sessionName, mentionNotices };
|
|
3787
|
+
}
|
|
3788
|
+
const sessionName = request.sessionName?.trim() || request.sessionNameExact?.trim() || deriveSessionName(request.sessionNameSource ?? request.displayMessage);
|
|
3396
3789
|
if (!request.sessionName?.trim()) {
|
|
3397
3790
|
request.timelineStore?.patchMeta?.(request.sessionId, { name: sessionName });
|
|
3398
3791
|
}
|
|
3399
|
-
return { contextMessage, sessionName };
|
|
3792
|
+
return { contextMessage, sessionName, mentionNotices };
|
|
3400
3793
|
}
|
|
3401
3794
|
async compact(request) {
|
|
3402
3795
|
const runtime = request.runtime ?? this.runtime;
|
|
@@ -3521,7 +3914,7 @@ var AgentSession = class {
|
|
|
3521
3914
|
/** The record, the boundary, `onCommitted` and the PostCompact hooks for a compacted result. */
|
|
3522
3915
|
async commitCompactResult(result, request) {
|
|
3523
3916
|
const timestamp = Date.now();
|
|
3524
|
-
const
|
|
3917
|
+
const recorded = {
|
|
3525
3918
|
id: result.compactId,
|
|
3526
3919
|
trigger: result.trigger,
|
|
3527
3920
|
transcriptOrdinal: request.transcriptOrdinal,
|
|
@@ -3534,6 +3927,11 @@ var AgentSession = class {
|
|
|
3534
3927
|
timestamp,
|
|
3535
3928
|
carriedCount: result.carriedCount
|
|
3536
3929
|
};
|
|
3930
|
+
const boundary = {
|
|
3931
|
+
...recorded,
|
|
3932
|
+
...result.degraded ? { degraded: true } : {},
|
|
3933
|
+
...result.warning ? { warning: result.warning } : {}
|
|
3934
|
+
};
|
|
3537
3935
|
const data = {
|
|
3538
3936
|
version: 2,
|
|
3539
3937
|
compactId: result.compactId,
|
|
@@ -3545,7 +3943,7 @@ var AgentSession = class {
|
|
|
3545
3943
|
preContextTokens: result.preContextTokens,
|
|
3546
3944
|
postContextTokens: result.postContextTokens,
|
|
3547
3945
|
replacementHistory: result.replacementHistory,
|
|
3548
|
-
boundary,
|
|
3946
|
+
boundary: recorded,
|
|
3549
3947
|
throughEventRef: result.throughEventRef,
|
|
3550
3948
|
summarizedCount: result.summarizedCount,
|
|
3551
3949
|
retainedCount: result.retainedCount,
|
|
@@ -3651,6 +4049,15 @@ var AgentSession = class {
|
|
|
3651
4049
|
data
|
|
3652
4050
|
});
|
|
3653
4051
|
};
|
|
4052
|
+
let lastGoalSignature = "";
|
|
4053
|
+
const persistRunGoal = () => {
|
|
4054
|
+
if (request.isCurrent?.() === false) return;
|
|
4055
|
+
const goal = runtime.activeGoal ?? null;
|
|
4056
|
+
const signature = JSON.stringify(goal);
|
|
4057
|
+
if (signature === lastGoalSignature) return;
|
|
4058
|
+
lastGoalSignature = signature;
|
|
4059
|
+
persistGoal({ sessionId: request.sessionId, timelineStore: request.timelineStore }, goal);
|
|
4060
|
+
};
|
|
3654
4061
|
const baseLoopCallbacks = {
|
|
3655
4062
|
onText: (content) => {
|
|
3656
4063
|
streamedAssistantText += content;
|
|
@@ -3664,6 +4071,11 @@ var AgentSession = class {
|
|
|
3664
4071
|
onToolCall: (toolCall) => emit({ type: "tool_use", toolCall }),
|
|
3665
4072
|
onToolResult: (toolResult) => emit({ type: "tool_result", toolResult }),
|
|
3666
4073
|
onNotice: (message) => emit({ type: "notice", message }),
|
|
4074
|
+
onGoalStatus: (event) => {
|
|
4075
|
+
persistRunGoal();
|
|
4076
|
+
emit({ type: "goal_status", event });
|
|
4077
|
+
},
|
|
4078
|
+
onAuxiliaryUsage: callbacks.onAuxiliaryUsage,
|
|
3667
4079
|
onError: (error) => {
|
|
3668
4080
|
emittedError = error;
|
|
3669
4081
|
emit({ type: "error", error });
|
|
@@ -3767,7 +4179,7 @@ var AgentSession = class {
|
|
|
3767
4179
|
kind: message.kind ?? "conversation",
|
|
3768
4180
|
includeInContext: message.includeInContext ?? true,
|
|
3769
4181
|
// A host-appended prompt stays host-written across a reload: the ledger,
|
|
3770
|
-
// Carried Turns
|
|
4182
|
+
// Carried Turns key on it.
|
|
3771
4183
|
derivedContent: message.derivedContent
|
|
3772
4184
|
}
|
|
3773
4185
|
});
|
|
@@ -3798,6 +4210,7 @@ var AgentSession = class {
|
|
|
3798
4210
|
);
|
|
3799
4211
|
} finally {
|
|
3800
4212
|
this.flushRunUsage(runContext.rootRunId, true);
|
|
4213
|
+
if (runtime.activeGoal) persistRunGoal();
|
|
3801
4214
|
}
|
|
3802
4215
|
const partialOutput = streamedAssistantText.length > 0 || messages.slice(effectiveHistory.length).some((message) => message.role === "assistant");
|
|
3803
4216
|
const outcome = finalizeOutcome(
|
|
@@ -3847,10 +4260,10 @@ async function captureSnapshot(snapshotStore) {
|
|
|
3847
4260
|
return { ok: false, reason: "Filesystem checkpoint storage is unavailable." };
|
|
3848
4261
|
}
|
|
3849
4262
|
if (snapshotStore.captureAsync) return snapshotStore.captureAsync();
|
|
3850
|
-
return new Promise((
|
|
4263
|
+
return new Promise((resolve4, reject) => {
|
|
3851
4264
|
setTimeout(() => {
|
|
3852
4265
|
try {
|
|
3853
|
-
|
|
4266
|
+
resolve4(snapshotStore.capture());
|
|
3854
4267
|
} catch (error) {
|
|
3855
4268
|
reject(error);
|
|
3856
4269
|
}
|
|
@@ -4048,8 +4461,6 @@ export {
|
|
|
4048
4461
|
SessionStore,
|
|
4049
4462
|
reviewRunnerFor,
|
|
4050
4463
|
fixRunnerFor,
|
|
4051
|
-
extractJsonObject,
|
|
4052
|
-
parseJsonObject,
|
|
4053
4464
|
runHostReview,
|
|
4054
4465
|
reviewReportJson,
|
|
4055
4466
|
findActiveFileMention,
|
|
@@ -4058,6 +4469,8 @@ export {
|
|
|
4058
4469
|
mentionTokenRanges,
|
|
4059
4470
|
createAgentSessionSnapshot,
|
|
4060
4471
|
reduceAgentSessionSnapshot,
|
|
4472
|
+
discardUntouchedSession,
|
|
4473
|
+
discardUntouchedSessions,
|
|
4061
4474
|
resolveSessionBootstrap,
|
|
4062
4475
|
AgentSession,
|
|
4063
4476
|
toElicitationFields,
|
|
@@ -4066,4 +4479,4 @@ export {
|
|
|
4066
4479
|
coerceElicitationValue,
|
|
4067
4480
|
validateElicitationContent
|
|
4068
4481
|
};
|
|
4069
|
-
//# sourceMappingURL=chunk-
|
|
4482
|
+
//# sourceMappingURL=chunk-ENLB7NYV.js.map
|