@letrquan/book 0.2.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 +3771 -40
- package/README.md +135 -1311
- package/dist/additional-roots-VAWRQ625.js +32 -0
- package/dist/{app-MOXXWEOY.js → app-J7BVPSUH.js} +9648 -5509
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-2AAOF5B5.js +28 -0
- package/dist/chunk-2AAOF5B5.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/chunk-4DKHLZRI.js +120 -0
- package/dist/chunk-4DKHLZRI.js.map +1 -0
- package/dist/{chunk-CAGOB2N4.js → chunk-62EEAUID.js} +6 -4
- package/dist/chunk-62EEAUID.js.map +1 -0
- package/dist/chunk-AHC7OU75.js +1124 -0
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-E75IPST5.js → chunk-AJZWLKLQ.js} +73 -70
- 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-KWHQ2XXL.js → chunk-DNFQTSMJ.js} +55 -13
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-J7MIZ5CA.js → chunk-E5WLQSKA.js} +719 -89
- 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-V4BOYR52.js → chunk-ENLB7NYV.js} +1304 -326
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/chunk-FPR7SKIE.js +6926 -0
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-DGMQEI6Y.js → chunk-HFB74CLM.js} +30 -9
- 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-J4F2QLM2.js +273 -0
- package/dist/chunk-J4F2QLM2.js.map +1 -0
- package/dist/chunk-JGF5HLSI.js +1065 -0
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-XIFFTATU.js → chunk-KDNWMKXV.js} +7166 -4237
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/chunk-KXAYUIKP.js +90 -0
- package/dist/chunk-KXAYUIKP.js.map +1 -0
- package/dist/{chunk-3U5IBM24.js → chunk-MEOB2DZA.js} +12 -2
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-WKBO5R4O.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-MV5PQNIA.js.map +1 -0
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/chunk-OJBMDM5H.js +299 -0
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-URLQAXTR.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/chunk-PTDC7ZUP.js.map +1 -0
- package/dist/{chunk-574C6MQR.js → chunk-REASC2QG.js} +173 -64
- package/dist/chunk-REASC2QG.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-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-5KLVH3PY.js → chunk-SWPDKLNM.js} +5 -3
- package/dist/chunk-SWPDKLNM.js.map +1 -0
- package/dist/chunk-UCFJ43OS.js +520 -0
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-VZQXX3WW.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-ANSJPYGI.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/chunk-WV3O5PGX.js.map +1 -0
- package/dist/chunk-XHAO3K7I.js +18 -0
- package/dist/chunk-XHAO3K7I.js.map +1 -0
- package/dist/chunk-XJRJVXJR.js +1050 -0
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/chunk-YIL6HTJX.js +663 -0
- package/dist/chunk-YIL6HTJX.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-ZRY66LCW.js} +7 -5
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-DPQD5H65.js} +6 -5
- package/dist/index.js +947 -203
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-73AQO5WP.js} +18 -9
- package/dist/interactive-assets-73AQO5WP.js.map +1 -0
- package/dist/job-runner.js +166 -111
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.d.ts +2 -0
- package/dist/job-supervisor.js +121 -0
- package/dist/job-supervisor.js.map +1 -0
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-27LJYADQ.js} +6 -5
- package/dist/{mcp-config-YIJIES2C.js → mcp-config-GXSODNCM.js} +4 -3
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-YFVNXCIP.js → model-window-store-6VKXXTPJ.js} +9 -4
- package/dist/openai-compatible-IPUNBJ53.js +16 -0
- package/dist/permission-approvals-Q5IMFEZI.js +15 -0
- package/dist/permissions-7Y35X6HZ.js +62 -0
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/sandbox-N2RY72GN.js +42 -0
- package/dist/{scrollback-LUKLIBVI.js → scrollback-GD76GNM4.js} +44 -26
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +2739 -2069
- package/dist/sdk.js +77 -42
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/settings-loader-KDBH45AQ.js.map +1 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/settings-removed-3ATWJEF3.js.map +1 -0
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +25 -24
- 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 +0 -449
- package/dist/chunk-5GDZ22YP.js.map +0 -1
- package/dist/chunk-5KLVH3PY.js.map +0 -1
- package/dist/chunk-5RDRAO4B.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 +0 -264
- package/dist/chunk-BTZZUCLI.js.map +0 -1
- package/dist/chunk-C7D4NJ2D.js +0 -217
- 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 +0 -765
- 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 +0 -1007
- 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/permission-approvals-76PVREFD.js +0 -14
- 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/dist/shell-selection-V6QAYXPX.js +0 -17
- 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-VAWRQ625.js.map} +0 -0
- /package/dist/{context-ZC5IFEAX.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{hook-approvals-CTUFKEJT.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-N2RY72GN.js.map} +0 -0
|
@@ -5,67 +5,116 @@ import {
|
|
|
5
5
|
runHostReview,
|
|
6
6
|
toElicitationFields,
|
|
7
7
|
validateElicitationContent
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ENLB7NYV.js";
|
|
9
9
|
import {
|
|
10
|
+
SessionRuntime,
|
|
10
11
|
buildAgentCompletionMessage,
|
|
11
12
|
buildPlanApprovalQuestion,
|
|
13
|
+
classifyAbortReason,
|
|
12
14
|
createAgentRunContext,
|
|
13
15
|
createSessionHistoryTools,
|
|
14
16
|
createTerminalOutcome,
|
|
17
|
+
finalAnswerText,
|
|
15
18
|
getOrCreateAgentManager,
|
|
19
|
+
maskAtGate,
|
|
16
20
|
normalizePersistedTodos,
|
|
17
21
|
planApprovalFromUserQuestionResponse,
|
|
18
22
|
planStopDecision,
|
|
19
|
-
|
|
23
|
+
seedShellIds,
|
|
24
|
+
separateInlineReasoning,
|
|
20
25
|
takeAgentCompletionBatch,
|
|
21
|
-
|
|
22
|
-
} from "./chunk-
|
|
26
|
+
uniqueAgentDisplayName
|
|
27
|
+
} from "./chunk-KDNWMKXV.js";
|
|
23
28
|
import {
|
|
29
|
+
TRUSTED_SETTINGS_LOCATION,
|
|
30
|
+
collectSkippedInstructionNotices,
|
|
31
|
+
collectSkippedReviewNotices,
|
|
32
|
+
compactionGate,
|
|
24
33
|
createBuiltinCommandRegistry,
|
|
25
34
|
estimateUsageCost,
|
|
35
|
+
formatGoalStatus,
|
|
26
36
|
getPackageVersion,
|
|
37
|
+
goalKickoffPrompt,
|
|
27
38
|
planWasLost,
|
|
28
39
|
resolveContextLimit,
|
|
29
|
-
resolvePermissionMode
|
|
30
|
-
|
|
40
|
+
resolvePermissionMode,
|
|
41
|
+
usageAtGate,
|
|
42
|
+
usagePressureTokens
|
|
43
|
+
} from "./chunk-FPR7SKIE.js";
|
|
44
|
+
import {
|
|
45
|
+
onAbort,
|
|
46
|
+
seedObservationLedger,
|
|
47
|
+
throwIfAborted
|
|
48
|
+
} from "./chunk-N2G4Q7ME.js";
|
|
49
|
+
import {
|
|
50
|
+
formatIgnoredWorkspaceSandboxKey,
|
|
51
|
+
ignoredWorkspaceSandboxKeys,
|
|
52
|
+
isUserSettingsFile,
|
|
53
|
+
loadSettingsFile,
|
|
54
|
+
settingsLayerPaths,
|
|
55
|
+
workspaceLayerThatEnabledSandbox
|
|
56
|
+
} from "./chunk-UCFJ43OS.js";
|
|
31
57
|
import {
|
|
58
|
+
collectSkippedCommandNotices,
|
|
32
59
|
commandEnforcementContext,
|
|
33
60
|
discoverCommands,
|
|
34
61
|
parseSlashInput,
|
|
35
62
|
resolveCommandBody
|
|
36
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-AJZWLKLQ.js";
|
|
37
64
|
import {
|
|
38
65
|
consoleMcpDiagnosticSink,
|
|
39
66
|
resolveMcpServerList
|
|
40
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-MEOB2DZA.js";
|
|
68
|
+
import {
|
|
69
|
+
isProcessAlive
|
|
70
|
+
} from "./chunk-32WBCUAN.js";
|
|
71
|
+
import {
|
|
72
|
+
buildChildEnv
|
|
73
|
+
} from "./chunk-XHAO3K7I.js";
|
|
74
|
+
import {
|
|
75
|
+
collectDeclaredDirectories,
|
|
76
|
+
partitionProjectDirectories
|
|
77
|
+
} from "./chunk-4DKHLZRI.js";
|
|
41
78
|
import {
|
|
42
|
-
|
|
79
|
+
getPrimaryArg,
|
|
43
80
|
toolFailure,
|
|
81
|
+
toolResultErrorMessage,
|
|
44
82
|
toolSuccess
|
|
45
|
-
} from "./chunk-
|
|
46
|
-
import {
|
|
47
|
-
loadSettingsFile
|
|
48
|
-
} from "./chunk-BTZZUCLI.js";
|
|
83
|
+
} from "./chunk-YIL6HTJX.js";
|
|
49
84
|
import {
|
|
50
85
|
partitionProjectAllowRules
|
|
51
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-MV5PQNIA.js";
|
|
87
|
+
import {
|
|
88
|
+
LEGACY_BASE_URL_ID,
|
|
89
|
+
describeApiKeyDeclaration,
|
|
90
|
+
describeDroppedOverride,
|
|
91
|
+
describeOrigin,
|
|
92
|
+
describeReservedProvider,
|
|
93
|
+
describeUnaffectedSelection,
|
|
94
|
+
describeWithheldSelection,
|
|
95
|
+
providerTrustCommand
|
|
96
|
+
} from "./chunk-B5FC3MYZ.js";
|
|
52
97
|
import {
|
|
53
98
|
collectDeclaredHooks,
|
|
54
|
-
partitionProjectHooks
|
|
55
|
-
|
|
99
|
+
partitionProjectHooks,
|
|
100
|
+
renderUntrustedText
|
|
101
|
+
} from "./chunk-WV3O5PGX.js";
|
|
56
102
|
import {
|
|
57
103
|
resolveBookHome
|
|
58
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-ZPSZZUWF.js";
|
|
59
105
|
|
|
60
106
|
// src/run-status.ts
|
|
61
107
|
import {
|
|
62
108
|
closeSync,
|
|
63
109
|
mkdirSync,
|
|
64
110
|
openSync,
|
|
111
|
+
readdirSync,
|
|
65
112
|
readFileSync,
|
|
66
113
|
renameSync,
|
|
67
114
|
rmSync,
|
|
68
115
|
statfsSync,
|
|
116
|
+
statSync,
|
|
117
|
+
unlinkSync,
|
|
69
118
|
writeSync
|
|
70
119
|
} from "fs";
|
|
71
120
|
import { join } from "path";
|
|
@@ -115,6 +164,43 @@ function readRunStatus(sessionId, home = resolveBookHome()) {
|
|
|
115
164
|
return void 0;
|
|
116
165
|
}
|
|
117
166
|
}
|
|
167
|
+
var RUN_RECORD_NAME = /\.json$/;
|
|
168
|
+
var RUN_TEMP_NAME = /\.json\..+\.tmp$/;
|
|
169
|
+
function recordsLiveRun(path) {
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
172
|
+
if (!parsed || typeof parsed !== "object") return false;
|
|
173
|
+
const record = parsed;
|
|
174
|
+
if (record.terminal || record.crash) return false;
|
|
175
|
+
const pid = record.pid;
|
|
176
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return false;
|
|
177
|
+
return isProcessAlive(pid);
|
|
178
|
+
} catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function cleanupRunStatus(days, home = resolveBookHome(), now = Date.now()) {
|
|
183
|
+
const cutoff = now - Math.max(1, days) * 864e5;
|
|
184
|
+
const directory = runStatusDir(home);
|
|
185
|
+
let removed = 0;
|
|
186
|
+
try {
|
|
187
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
188
|
+
if (!entry.isFile()) continue;
|
|
189
|
+
const isRecord = RUN_RECORD_NAME.test(entry.name);
|
|
190
|
+
if (!isRecord && !RUN_TEMP_NAME.test(entry.name)) continue;
|
|
191
|
+
const path = join(directory, entry.name);
|
|
192
|
+
try {
|
|
193
|
+
if (statSync(path).mtimeMs >= cutoff) continue;
|
|
194
|
+
if (isRecord && recordsLiveRun(path)) continue;
|
|
195
|
+
unlinkSync(path);
|
|
196
|
+
removed++;
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
return removed;
|
|
203
|
+
}
|
|
118
204
|
var RunStatusWriter = class {
|
|
119
205
|
seed;
|
|
120
206
|
last;
|
|
@@ -249,6 +335,10 @@ async function performPrintReview(effect, env, command) {
|
|
|
249
335
|
// Every reviewer, lens and verifier agent is spawned under the host's run,
|
|
250
336
|
// so the whole pipeline is billed and budgeted as one root.
|
|
251
337
|
runner: reviewRunnerFor(env.agents.manager(), {
|
|
338
|
+
// The stored session the run belongs to, as the TUI passes its own. Without it
|
|
339
|
+
// each agent's tool-use records carry a made-up `agent:<id>` session id that
|
|
340
|
+
// matches no session file, and `book tool-stats` counts every one separately.
|
|
341
|
+
parentSessionId: env.agents.runContext.sessionId,
|
|
252
342
|
rootRunId: env.agents.runContext.rootRunId,
|
|
253
343
|
parentRunId: env.agents.runContext.runId
|
|
254
344
|
})
|
|
@@ -265,12 +355,40 @@ function createPrintEffectRegistry() {
|
|
|
265
355
|
return new PrintEffectRegistry().register("send-prompt", (effect) => ({
|
|
266
356
|
kind: "prompt",
|
|
267
357
|
prompt: effect.prompt,
|
|
268
|
-
commandContext: effect.context
|
|
358
|
+
commandContext: effect.context,
|
|
359
|
+
projectCommand: effect.projectCommand
|
|
269
360
|
})).register("local-message", (effect, _env, command) => {
|
|
270
361
|
if (effect.isError) throw new PrintCommandUsageError(command, effect.content);
|
|
271
362
|
return { kind: "handled", command, output: effect.content };
|
|
272
|
-
}).register("review", performPrintReview)
|
|
363
|
+
}).register("review", performPrintReview).register("set-goal", (effect, env, command) => {
|
|
364
|
+
if (!env.goal) throw new UnsupportedPrintCommandError(command, GOAL_REFUSAL);
|
|
365
|
+
env.goal.set(effect.condition);
|
|
366
|
+
const kickoff = goalKickoffPrompt(effect.condition);
|
|
367
|
+
return {
|
|
368
|
+
kind: "prompt",
|
|
369
|
+
prompt: kickoff,
|
|
370
|
+
commandContext: {
|
|
371
|
+
command: {
|
|
372
|
+
name: "goal",
|
|
373
|
+
description: "Set a goal Book checks before stopping",
|
|
374
|
+
body: kickoff,
|
|
375
|
+
source: "project"
|
|
376
|
+
},
|
|
377
|
+
resolvedBody: kickoff
|
|
378
|
+
},
|
|
379
|
+
sessionNameSource: effect.condition
|
|
380
|
+
};
|
|
381
|
+
}).register("clear-goal", (_effect, env, command) => {
|
|
382
|
+
if (!env.goal) throw new UnsupportedPrintCommandError(command, GOAL_REFUSAL);
|
|
383
|
+
const cleared = env.goal.clear();
|
|
384
|
+
return {
|
|
385
|
+
kind: "handled",
|
|
386
|
+
command,
|
|
387
|
+
output: cleared ? formatGoalStatus({ state: "cleared", condition: cleared.condition }) : "No goal set"
|
|
388
|
+
};
|
|
389
|
+
});
|
|
273
390
|
}
|
|
391
|
+
var GOAL_REFUSAL = "\u2715 /goal needs a host that checks goals; this one does not.";
|
|
274
392
|
function printBuiltinContext(env, commands) {
|
|
275
393
|
return {
|
|
276
394
|
workspace: env.config.workspace,
|
|
@@ -283,6 +401,7 @@ function printBuiltinContext(env, commands) {
|
|
|
283
401
|
runtimeConfig: env.config,
|
|
284
402
|
mode: env.mode,
|
|
285
403
|
usage: null,
|
|
404
|
+
activeGoal: env.goal?.get(),
|
|
286
405
|
turnDurationMs: 0,
|
|
287
406
|
contextHistory: [],
|
|
288
407
|
compactBoundaries: [],
|
|
@@ -299,6 +418,9 @@ function supportedCommandHelp(builtins, commands) {
|
|
|
299
418
|
const lines = ["Commands supported in print mode:"];
|
|
300
419
|
for (const definition of builtins.getDefinitions()) {
|
|
301
420
|
if (definition.nonInteractive) lines.push(` /${definition.name} \u2014 ${definition.description}`);
|
|
421
|
+
for (const subcommand of definition.nonInteractiveSubcommands ?? []) {
|
|
422
|
+
lines.push(` /${definition.name} ${subcommand} \u2014 ${definition.description}`);
|
|
423
|
+
}
|
|
302
424
|
}
|
|
303
425
|
const custom = commands.map((command) => `/${command.name}`);
|
|
304
426
|
lines.push(
|
|
@@ -326,8 +448,10 @@ async function resolvePrintCommand(input, env, deps = {}) {
|
|
|
326
448
|
const commands = deps.commands ?? discoverCommands(env.config.workspace);
|
|
327
449
|
const context = printBuiltinContext(env, commands);
|
|
328
450
|
const resolved = builtins.resolve(parsed.name, context);
|
|
329
|
-
if (resolved) {
|
|
330
|
-
|
|
451
|
+
if (resolved && !resolved.definition.tuiOnly) {
|
|
452
|
+
const subcommand = parsed.rawArguments.trim().split(/\s+/)[0] ?? "";
|
|
453
|
+
const allowedHere = resolved.definition.nonInteractive || (resolved.definition.nonInteractiveSubcommands?.includes(subcommand) ?? false);
|
|
454
|
+
if (!allowedHere) {
|
|
331
455
|
throw unsupported(
|
|
332
456
|
parsed.name,
|
|
333
457
|
"needs an interactive session: it changes host state instead of producing a prompt.",
|
|
@@ -356,7 +480,8 @@ async function resolvePrintCommand(input, env, deps = {}) {
|
|
|
356
480
|
commands
|
|
357
481
|
);
|
|
358
482
|
}
|
|
359
|
-
|
|
483
|
+
if (dispatch.kind === "handled") return { ...dispatch, command: parsed.name };
|
|
484
|
+
return dispatch.kind === "prompt" ? { ...dispatch, sessionNameSource: dispatch.sessionNameSource ?? input.trim() } : dispatch;
|
|
360
485
|
}
|
|
361
486
|
const custom = commands.find((command) => command.name === parsed.name);
|
|
362
487
|
if (!custom) return { kind: "passthrough", prompt: input };
|
|
@@ -375,7 +500,9 @@ async function resolvePrintCommand(input, env, deps = {}) {
|
|
|
375
500
|
kind: "prompt",
|
|
376
501
|
prompt: body,
|
|
377
502
|
commandContext: commandEnforcementContext(custom, body),
|
|
378
|
-
|
|
503
|
+
sessionNameSource: input.trim(),
|
|
504
|
+
shellErrors,
|
|
505
|
+
projectCommand: custom.source === "project"
|
|
379
506
|
};
|
|
380
507
|
}
|
|
381
508
|
|
|
@@ -406,12 +533,17 @@ var EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
406
533
|
"agent_message",
|
|
407
534
|
"agent_completion",
|
|
408
535
|
"agent_permission",
|
|
536
|
+
"agent_notice",
|
|
409
537
|
"evidence_update",
|
|
538
|
+
"attempt_discarded",
|
|
539
|
+
"retry",
|
|
410
540
|
"agent_apply",
|
|
411
541
|
"hook_event",
|
|
412
542
|
"mode_change",
|
|
413
543
|
"plan_approval",
|
|
544
|
+
"command_result",
|
|
414
545
|
"prompt_suggestions",
|
|
546
|
+
"notice",
|
|
415
547
|
"error",
|
|
416
548
|
"result",
|
|
417
549
|
"done"
|
|
@@ -490,11 +622,71 @@ function createStreamParser(onEvent, options = {}) {
|
|
|
490
622
|
};
|
|
491
623
|
}
|
|
492
624
|
|
|
625
|
+
// src/reader-gone.ts
|
|
626
|
+
import { Writable } from "stream";
|
|
627
|
+
var READER_GONE_CODES = /* @__PURE__ */ new Set([
|
|
628
|
+
"EPIPE",
|
|
629
|
+
"EOF",
|
|
630
|
+
"ERR_STREAM_DESTROYED",
|
|
631
|
+
"ECONNRESET"
|
|
632
|
+
]);
|
|
633
|
+
function isReaderGoneError(error) {
|
|
634
|
+
const code = error?.code;
|
|
635
|
+
return code !== void 0 && READER_GONE_CODES.has(code);
|
|
636
|
+
}
|
|
637
|
+
var READER_FLUSH_CAP_MS = 1e3;
|
|
638
|
+
function createReaderFlush(stdout, options = {}) {
|
|
639
|
+
if (!(stdout instanceof Writable)) return void 0;
|
|
640
|
+
const capMs = options.capMs ?? READER_FLUSH_CAP_MS;
|
|
641
|
+
const controller = new AbortController();
|
|
642
|
+
let pending = 0;
|
|
643
|
+
let slow = false;
|
|
644
|
+
const waiters = /* @__PURE__ */ new Set();
|
|
645
|
+
const drained = () => {
|
|
646
|
+
if (pending > 0) return;
|
|
647
|
+
slow = false;
|
|
648
|
+
for (const release of [...waiters]) release();
|
|
649
|
+
waiters.clear();
|
|
650
|
+
};
|
|
651
|
+
return {
|
|
652
|
+
signal: controller.signal,
|
|
653
|
+
write(line) {
|
|
654
|
+
pending++;
|
|
655
|
+
stdout.write(line, (error) => {
|
|
656
|
+
if (error && isReaderGoneError(error)) controller.abort();
|
|
657
|
+
pending--;
|
|
658
|
+
drained();
|
|
659
|
+
});
|
|
660
|
+
},
|
|
661
|
+
hold(signal) {
|
|
662
|
+
if (pending === 0 || slow || signal?.aborted) return Promise.resolve();
|
|
663
|
+
return new Promise((resolve) => {
|
|
664
|
+
let settled = false;
|
|
665
|
+
const finish = () => {
|
|
666
|
+
if (settled) return;
|
|
667
|
+
settled = true;
|
|
668
|
+
clearTimeout(timer);
|
|
669
|
+
signal?.removeEventListener("abort", finish);
|
|
670
|
+
waiters.delete(finish);
|
|
671
|
+
resolve();
|
|
672
|
+
};
|
|
673
|
+
const timer = setTimeout(() => {
|
|
674
|
+
slow = true;
|
|
675
|
+
finish();
|
|
676
|
+
}, capMs);
|
|
677
|
+
waiters.add(finish);
|
|
678
|
+
signal?.addEventListener("abort", finish, { once: true });
|
|
679
|
+
if (signal?.aborted) finish();
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
493
685
|
// src/headless.ts
|
|
494
686
|
function carriedCostUsd(usage, models) {
|
|
495
687
|
let worst = null;
|
|
496
688
|
for (const model of models) {
|
|
497
|
-
const quote = estimateUsageCost(model, usage);
|
|
689
|
+
const quote = estimateUsageCost(model, usage, "pool");
|
|
498
690
|
if (quote.status !== "known") return null;
|
|
499
691
|
worst = worst === null ? quote.costUsd : Math.max(worst, quote.costUsd);
|
|
500
692
|
}
|
|
@@ -509,26 +701,69 @@ async function readPipedPrompt(stream) {
|
|
|
509
701
|
const prompt = chunks.join("").trim();
|
|
510
702
|
return prompt || void 0;
|
|
511
703
|
}
|
|
704
|
+
function destroyOnAbort(stream, signal) {
|
|
705
|
+
if (!signal) return () => {
|
|
706
|
+
};
|
|
707
|
+
return onAbort(signal, () => {
|
|
708
|
+
const reason = signal.reason instanceof Error ? signal.reason : new Error("Aborted");
|
|
709
|
+
stream.destroy?.(reason);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
async function raceAbort(promise, signal) {
|
|
713
|
+
if (!signal) {
|
|
714
|
+
await promise;
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
if (signal.aborted) return false;
|
|
718
|
+
let release;
|
|
719
|
+
const aborted = new Promise((resolve) => {
|
|
720
|
+
release = onAbort(signal, () => resolve(false));
|
|
721
|
+
});
|
|
722
|
+
try {
|
|
723
|
+
return await Promise.race([promise.then(() => true), aborted]);
|
|
724
|
+
} finally {
|
|
725
|
+
release?.();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
512
728
|
async function runHeadless(config, registry, opts) {
|
|
513
729
|
const mode = resolvePermissionMode(config.settings, opts.mode);
|
|
514
730
|
const stdout = opts.stdout ?? process.stdout;
|
|
731
|
+
const flush = opts.outputFormat === "stream-json" ? createReaderFlush(stdout) : void 0;
|
|
732
|
+
const runSignal = flush && opts.signal ? AbortSignal.any([opts.signal, flush.signal]) : flush?.signal ?? opts.signal;
|
|
515
733
|
const emit = (obj) => {
|
|
516
734
|
if (obj && typeof obj === "object" && "type" in obj) {
|
|
517
735
|
opts.onEvent?.(obj);
|
|
518
736
|
}
|
|
519
|
-
|
|
737
|
+
const line = JSON.stringify(obj) + "\n";
|
|
738
|
+
if (flush) flush.write(line);
|
|
739
|
+
else stdout.write(line);
|
|
740
|
+
};
|
|
741
|
+
const eventState = {
|
|
742
|
+
childLabels: /* @__PURE__ */ new Map(),
|
|
743
|
+
errorReported: false
|
|
520
744
|
};
|
|
521
745
|
let disposeCrashHandlers;
|
|
522
|
-
|
|
746
|
+
let sessionDisposed = false;
|
|
747
|
+
let startedSessionId;
|
|
748
|
+
const agentSession = new AgentSession({
|
|
749
|
+
runtime: new SessionRuntime()
|
|
750
|
+
});
|
|
751
|
+
const disposeSession = () => {
|
|
752
|
+
if (sessionDisposed) return;
|
|
753
|
+
sessionDisposed = true;
|
|
754
|
+
agentSession.dispose("headless_complete");
|
|
755
|
+
};
|
|
523
756
|
const runtime = agentSession.getRuntime();
|
|
524
757
|
runtime.todos.push(...normalizePersistedTodos(opts.plan?.todos));
|
|
525
758
|
if (opts.plan?.tasks?.length) runtime.tasks.push(...opts.plan.tasks);
|
|
759
|
+
agentSession.restoreGoal(opts.goal);
|
|
526
760
|
runtime.planUnrestored = planWasLost({
|
|
527
761
|
planRecordExisted: opts.plan !== void 0,
|
|
528
762
|
priorMessages: opts.history.length,
|
|
529
763
|
todos: runtime.todos.length,
|
|
530
764
|
tasks: runtime.tasks.length
|
|
531
765
|
});
|
|
766
|
+
seedShellIds(runtime.backgroundShells, opts.transcript ?? opts.history, opts.history);
|
|
532
767
|
try {
|
|
533
768
|
if (opts.outputFormat === "stream-json") {
|
|
534
769
|
emit({ type: "system", model: config.model, cwd: config.workspace });
|
|
@@ -544,7 +779,11 @@ async function runHeadless(config, registry, opts) {
|
|
|
544
779
|
let sessionName = opts.sessionName;
|
|
545
780
|
let sessionCreated = opts.sessionCreated === true;
|
|
546
781
|
if (store && !sessionId) {
|
|
547
|
-
sessionId = store.create({
|
|
782
|
+
sessionId = store.create({
|
|
783
|
+
cwd: config.workspace,
|
|
784
|
+
name: sessionName,
|
|
785
|
+
driver: config.sessionDriver
|
|
786
|
+
});
|
|
548
787
|
sessionCreated = true;
|
|
549
788
|
}
|
|
550
789
|
if (store && sessionId && !sessionName) {
|
|
@@ -567,6 +806,7 @@ async function runHeadless(config, registry, opts) {
|
|
|
567
806
|
onHookEvent: createHookEventHandler(opts, emit)
|
|
568
807
|
}
|
|
569
808
|
);
|
|
809
|
+
startedSessionId = sessionId;
|
|
570
810
|
}
|
|
571
811
|
const runtimeSessionId = sessionId ?? crypto.randomUUID();
|
|
572
812
|
const statusWriter = new RunStatusWriter({
|
|
@@ -577,7 +817,10 @@ async function runHeadless(config, registry, opts) {
|
|
|
577
817
|
startedAt: Date.now()
|
|
578
818
|
});
|
|
579
819
|
disposeCrashHandlers = statusWriter.installCrashHandlers();
|
|
580
|
-
const permissionRequired = async () =>
|
|
820
|
+
const permissionRequired = async () => ({
|
|
821
|
+
result: "deny",
|
|
822
|
+
reason: "no_approver"
|
|
823
|
+
});
|
|
581
824
|
const userQuestionStatus = opts.onUserQuestionRequired ? "pending" : "unavailable";
|
|
582
825
|
const askUser = async (request, context) => {
|
|
583
826
|
try {
|
|
@@ -596,14 +839,26 @@ async function runHeadless(config, registry, opts) {
|
|
|
596
839
|
if (mode === "bypassPermissions") return "approve";
|
|
597
840
|
if (!opts.onUserQuestionRequired) return planStopDecision("approval_unavailable");
|
|
598
841
|
const request = buildPlanApprovalQuestion(plan, crypto.randomUUID());
|
|
599
|
-
emitAgentEvent(
|
|
600
|
-
|
|
601
|
-
|
|
842
|
+
emitAgentEvent(
|
|
843
|
+
{ type: "user_question", request, status: userQuestionStatus },
|
|
844
|
+
opts,
|
|
845
|
+
emit,
|
|
846
|
+
eventState
|
|
847
|
+
);
|
|
848
|
+
const response = await askUser(request, { signal: runSignal });
|
|
849
|
+
emitAgentEvent(
|
|
850
|
+
{ type: "user_question_result", requestId: request.id, response },
|
|
851
|
+
opts,
|
|
852
|
+
emit,
|
|
853
|
+
eventState
|
|
854
|
+
);
|
|
602
855
|
return planApprovalFromUserQuestionResponse(request, response);
|
|
603
856
|
};
|
|
604
857
|
const planNotApplied = {};
|
|
605
858
|
let lastUsage = null;
|
|
606
859
|
let lastOutcome = null;
|
|
860
|
+
let mayReclassify = true;
|
|
861
|
+
let finalRunOpeningId;
|
|
607
862
|
const runResults = [];
|
|
608
863
|
const recordRunResult = (result2) => {
|
|
609
864
|
const existing = runResults.findIndex(
|
|
@@ -668,15 +923,7 @@ async function runHeadless(config, registry, opts) {
|
|
|
668
923
|
const contextHistory = [...opts.history];
|
|
669
924
|
const transcript = [...opts.transcript ?? opts.history];
|
|
670
925
|
const compactBoundaries = [...opts.compactBoundaries ?? []];
|
|
671
|
-
|
|
672
|
-
for (const observation of message.fileObservations ?? []) {
|
|
673
|
-
const key = observationKey(observation.workspaceId, observation.path);
|
|
674
|
-
const current = runtime.fileObservationLedger.get(key);
|
|
675
|
-
if (!current || current.timestamp <= observation.timestamp) {
|
|
676
|
-
runtime.fileObservationLedger.set(key, observation);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
926
|
+
seedObservationLedger(runtime.fileObservationLedger, transcript);
|
|
680
927
|
const loopConfig = {
|
|
681
928
|
...config,
|
|
682
929
|
maxTurns: opts.maxTurns ?? config.maxTurns
|
|
@@ -688,7 +935,7 @@ async function runHeadless(config, registry, opts) {
|
|
|
688
935
|
lastOutcome = event.outcome;
|
|
689
936
|
onOutcome(event.outcome);
|
|
690
937
|
}
|
|
691
|
-
emitAgentEvent(event, opts, emit);
|
|
938
|
+
emitAgentEvent(event, opts, emit, eventState);
|
|
692
939
|
},
|
|
693
940
|
// No header rewrite at turn boundaries: load() derives updatedAt from
|
|
694
941
|
// the last appended record, so the on-disk header is never authoritative.
|
|
@@ -702,6 +949,15 @@ async function runHeadless(config, registry, opts) {
|
|
|
702
949
|
emit({ type: "mode_change", mode: newMode });
|
|
703
950
|
}
|
|
704
951
|
},
|
|
952
|
+
/**
|
|
953
|
+
* The hold at a tool boundary. The `tool_use` records for the calls about
|
|
954
|
+
* to run have been written by now, so waiting for them to be taken is
|
|
955
|
+
* what turns a reader that has gone into an abort BEFORE the call runs
|
|
956
|
+
* (#340) instead of one call later. Free when the reader has caught up,
|
|
957
|
+
* bounded when it has not, and absent — so a no-op — for every other
|
|
958
|
+
* output format and for a host sink with no callback to answer.
|
|
959
|
+
*/
|
|
960
|
+
beforeToolExecution: flush ? () => flush.hold(runSignal) : void 0,
|
|
705
961
|
onPlanApprovalRequired: async (plan) => {
|
|
706
962
|
const decision = await decidePlanApproval(plan);
|
|
707
963
|
if (typeof decision === "object" && decision.decision === "stop") {
|
|
@@ -719,6 +975,24 @@ async function runHeadless(config, registry, opts) {
|
|
|
719
975
|
},
|
|
720
976
|
onUserQuestionRequired: askUser,
|
|
721
977
|
userQuestionStatus,
|
|
978
|
+
onRetry: (phase, attempt, max, delayMs, reason) => {
|
|
979
|
+
const limit = max < 0 ? null : max;
|
|
980
|
+
if (opts.outputFormat === "stream-json") {
|
|
981
|
+
emit({
|
|
982
|
+
type: "retry",
|
|
983
|
+
phase,
|
|
984
|
+
attempt,
|
|
985
|
+
max: limit,
|
|
986
|
+
delay_ms: delayMs,
|
|
987
|
+
...reason ? { reason } : {}
|
|
988
|
+
});
|
|
989
|
+
} else if (opts.quiet !== true) {
|
|
990
|
+
process.stderr.write(
|
|
991
|
+
`retry: ${phase} attempt ${attempt}${limit !== null ? `/${limit}` : ""} in ${Math.round(delayMs / 1e3)}s${reason ? ` (${reason})` : ""}
|
|
992
|
+
`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
},
|
|
722
996
|
onHookEvent: createHookEventHandler(opts, emit),
|
|
723
997
|
onUsage: (nextUsage) => {
|
|
724
998
|
lastUsage = nextUsage;
|
|
@@ -739,7 +1013,50 @@ async function runHeadless(config, registry, opts) {
|
|
|
739
1013
|
...hints,
|
|
740
1014
|
trigger: "auto",
|
|
741
1015
|
preContextTokens: usage ? usagePressureTokens(usage) : void 0,
|
|
742
|
-
signal:
|
|
1016
|
+
signal: runSignal,
|
|
1017
|
+
onHookEvent: createHookEventHandler(opts, emit)
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
if (outcome2.result.status === "compacted") {
|
|
1021
|
+
emitCompactBoundary(opts.outputFormat, emit, outcome2.result);
|
|
1022
|
+
lastUsage = null;
|
|
1023
|
+
}
|
|
1024
|
+
return outcome2.result;
|
|
1025
|
+
},
|
|
1026
|
+
// Deferred compaction (plans/async-compaction-plan.md): same plumbing,
|
|
1027
|
+
// reducer on the snapshot first, judge and record at the next boundary.
|
|
1028
|
+
prepareCompact: async (snapshot, usage, hints) => {
|
|
1029
|
+
const outcome2 = await agentSession.prepareCompact({
|
|
1030
|
+
config,
|
|
1031
|
+
history: snapshot,
|
|
1032
|
+
compactBoundaries,
|
|
1033
|
+
sessionId,
|
|
1034
|
+
transcriptOrdinal: transcript.length,
|
|
1035
|
+
runContext,
|
|
1036
|
+
runtime,
|
|
1037
|
+
options: {
|
|
1038
|
+
...hints,
|
|
1039
|
+
trigger: "auto",
|
|
1040
|
+
preContextTokens: usage ? usagePressureTokens(usage) : void 0,
|
|
1041
|
+
signal: hints?.signal ?? runSignal,
|
|
1042
|
+
onHookEvent: createHookEventHandler(opts, emit)
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
return outcome2.status === "prepared" ? outcome2.prepared : outcome2.result;
|
|
1046
|
+
},
|
|
1047
|
+
commitCompact: async (prepared, history, hints) => {
|
|
1048
|
+
const outcome2 = await agentSession.commitCompact({
|
|
1049
|
+
prepared,
|
|
1050
|
+
history,
|
|
1051
|
+
config,
|
|
1052
|
+
sessionId,
|
|
1053
|
+
transcriptOrdinal: transcript.length,
|
|
1054
|
+
runContext,
|
|
1055
|
+
runtime,
|
|
1056
|
+
timelineStore: store,
|
|
1057
|
+
onCommitted: (_result, boundary) => compactBoundaries.push(boundary),
|
|
1058
|
+
options: {
|
|
1059
|
+
signal: hints?.signal ?? runSignal,
|
|
743
1060
|
onHookEvent: createHookEventHandler(opts, emit)
|
|
744
1061
|
}
|
|
745
1062
|
});
|
|
@@ -752,12 +1069,19 @@ async function runHeadless(config, registry, opts) {
|
|
|
752
1069
|
onAssistantMessageComplete: (message) => {
|
|
753
1070
|
if (!transcript.some((item) => item.id === message.id)) transcript.push(message);
|
|
754
1071
|
if (opts.outputFormat === "stream-json" && message.content) {
|
|
755
|
-
emit({
|
|
1072
|
+
emit({
|
|
1073
|
+
type: "assistant",
|
|
1074
|
+
text: message.content,
|
|
1075
|
+
complete: true,
|
|
1076
|
+
...message.hostNotice ? { host_notice: true } : {}
|
|
1077
|
+
});
|
|
756
1078
|
}
|
|
757
1079
|
}
|
|
758
1080
|
});
|
|
759
|
-
const runParentTurn = async (prompt, displayMessage, userMessage, runContext, commandContext) => {
|
|
1081
|
+
const runParentTurn = async (prompt, displayMessage, userMessage, runContext, commandContext, typedText) => {
|
|
1082
|
+
eventState.errorReported = false;
|
|
760
1083
|
let runOutcome;
|
|
1084
|
+
finalRunOpeningId = userMessage.id;
|
|
761
1085
|
lastUsage = null;
|
|
762
1086
|
let updated;
|
|
763
1087
|
try {
|
|
@@ -770,7 +1094,7 @@ async function runHeadless(config, registry, opts) {
|
|
|
770
1094
|
mode,
|
|
771
1095
|
sessionId: runtimeSessionId,
|
|
772
1096
|
timelineStore: store,
|
|
773
|
-
signal:
|
|
1097
|
+
signal: runSignal,
|
|
774
1098
|
callbacks: createRunCallbacks(runContext, (outcome2) => {
|
|
775
1099
|
runOutcome = outcome2;
|
|
776
1100
|
}),
|
|
@@ -783,6 +1107,9 @@ async function runHeadless(config, registry, opts) {
|
|
|
783
1107
|
userFileObservations: userMessage.fileObservations,
|
|
784
1108
|
userAttachments: userMessage.attachments,
|
|
785
1109
|
userMessageKind: userMessage.kind,
|
|
1110
|
+
// The answer walk is bounded by `openingMessageId`, so a derived opening
|
|
1111
|
+
// message no longer lets it walk past the run.
|
|
1112
|
+
userMessageDerived: userMessage.derivedContent,
|
|
786
1113
|
manageSessionHooks: sessionId ? false : void 0,
|
|
787
1114
|
// Completion messages are host-generated notifications, not user
|
|
788
1115
|
// prompts. Keep UserPromptSubmit hooks from rewriting or blocking them.
|
|
@@ -790,6 +1117,8 @@ async function runHeadless(config, registry, opts) {
|
|
|
790
1117
|
parentSessionId: runtimeSessionId,
|
|
791
1118
|
runContext,
|
|
792
1119
|
runtime,
|
|
1120
|
+
// Only what the user typed invokes a skill by `$name`, as in the TUI.
|
|
1121
|
+
typedText,
|
|
793
1122
|
// Command frontmatter enforcement, exactly as the TUI passes it.
|
|
794
1123
|
allowedTools: commandContext?.allowedTools,
|
|
795
1124
|
modelOverride: commandContext?.modelOverride,
|
|
@@ -832,7 +1161,7 @@ async function runHeadless(config, registry, opts) {
|
|
|
832
1161
|
config,
|
|
833
1162
|
mode,
|
|
834
1163
|
sessionId: runtimeSessionId,
|
|
835
|
-
signal:
|
|
1164
|
+
signal: runSignal,
|
|
836
1165
|
agents: {
|
|
837
1166
|
// Built only when an agent-backed command (`/review`) asks for it,
|
|
838
1167
|
// and attached to this session's runtime so it is disposed with the
|
|
@@ -843,18 +1172,39 @@ async function runHeadless(config, registry, opts) {
|
|
|
843
1172
|
permissionMode: mode,
|
|
844
1173
|
eventSink: (event) => {
|
|
845
1174
|
if (event.type === "agent_result") recordManagedRunResult(event.agent);
|
|
846
|
-
emitAgentEvent(event, opts, emit);
|
|
1175
|
+
emitAgentEvent(event, opts, emit, eventState);
|
|
847
1176
|
},
|
|
848
1177
|
hookEventSink: createHookEventHandler(opts, emit)
|
|
849
1178
|
}),
|
|
850
1179
|
runContext
|
|
1180
|
+
},
|
|
1181
|
+
goal: {
|
|
1182
|
+
get: () => agentSession.getGoal(),
|
|
1183
|
+
set: (condition) => {
|
|
1184
|
+
agentSession.setGoal(condition, goalTarget());
|
|
1185
|
+
},
|
|
1186
|
+
clear: () => agentSession.clearGoal(goalTarget())
|
|
851
1187
|
}
|
|
852
1188
|
});
|
|
1189
|
+
const goalTarget = () => ({
|
|
1190
|
+
sessionId,
|
|
1191
|
+
timelineStore: store && sessionId ? store : void 0
|
|
1192
|
+
});
|
|
853
1193
|
const commandResults = [];
|
|
854
1194
|
let commandUsage = null;
|
|
855
1195
|
const prompts = [];
|
|
856
1196
|
if (opts.inputFormat === "text") {
|
|
857
|
-
|
|
1197
|
+
let prompt = opts.prompt;
|
|
1198
|
+
if (prompt === void 0) {
|
|
1199
|
+
const stream = opts.stdin ?? process.stdin;
|
|
1200
|
+
const release = destroyOnAbort(stream, runSignal);
|
|
1201
|
+
try {
|
|
1202
|
+
prompt = await readPipedPrompt(stream);
|
|
1203
|
+
} finally {
|
|
1204
|
+
release();
|
|
1205
|
+
}
|
|
1206
|
+
throwIfAborted(runSignal);
|
|
1207
|
+
}
|
|
858
1208
|
if (!prompt) {
|
|
859
1209
|
throw new Error(
|
|
860
1210
|
"print mode requires a prompt: pass one to -p, pipe one on stdin, or use --input-format stream-json"
|
|
@@ -873,9 +1223,15 @@ async function runHeadless(config, registry, opts) {
|
|
|
873
1223
|
onDiagnostic: (diagnostic) => diagnostics.push(diagnostic)
|
|
874
1224
|
}
|
|
875
1225
|
);
|
|
876
|
-
|
|
877
|
-
|
|
1226
|
+
const release = destroyOnAbort(stream, runSignal);
|
|
1227
|
+
try {
|
|
1228
|
+
for await (const chunk of stream) {
|
|
1229
|
+
parser.feed(chunk);
|
|
1230
|
+
}
|
|
1231
|
+
} finally {
|
|
1232
|
+
release();
|
|
878
1233
|
}
|
|
1234
|
+
throwIfAborted(runSignal);
|
|
879
1235
|
parser.flush();
|
|
880
1236
|
if (diagnostics.length > 0) throw new Error(diagnostics[0]?.message);
|
|
881
1237
|
if (opts.prompt) prompts.unshift(opts.prompt);
|
|
@@ -896,6 +1252,9 @@ async function runHeadless(config, registry, opts) {
|
|
|
896
1252
|
resumedFromRunId: runResults.length === 0 ? opts.resumedFromRunId ?? (opts.history.length > 0 ? [...opts.history].reverse().find((message) => message.role === "user")?.id : void 0) : void 0
|
|
897
1253
|
});
|
|
898
1254
|
runtime.runAccounting.startRoot(runContext, opts.maxBudgetUsd);
|
|
1255
|
+
if (store && sessionId) {
|
|
1256
|
+
agentSession.trackRunUsage(runContext.rootRunId, { sessionId, timelineStore: store });
|
|
1257
|
+
}
|
|
899
1258
|
if (carriedSpend) {
|
|
900
1259
|
runtime.runAccounting.seedRoot(runContext.rootRunId, carriedSpend);
|
|
901
1260
|
}
|
|
@@ -932,6 +1291,12 @@ async function runHeadless(config, registry, opts) {
|
|
|
932
1291
|
} else if (opts.outputFormat === "stream-json" && (dispatch.output !== void 0 || dispatch.data !== void 0)) {
|
|
933
1292
|
emit({ type: "command_result", ...handled });
|
|
934
1293
|
}
|
|
1294
|
+
carriedSpend = {
|
|
1295
|
+
usage: accounting.inclusiveUsage,
|
|
1296
|
+
costUsd: accounting.inclusiveCostUsd,
|
|
1297
|
+
persistedUsage: runtime.runAccounting.persistedUsage(runContext.rootRunId),
|
|
1298
|
+
fromRootRunId: runContext.rootRunId
|
|
1299
|
+
};
|
|
935
1300
|
continue;
|
|
936
1301
|
}
|
|
937
1302
|
const prompt = dispatch.prompt;
|
|
@@ -948,9 +1313,19 @@ async function runHeadless(config, registry, opts) {
|
|
|
948
1313
|
kind: "conversation",
|
|
949
1314
|
timestamp: Date.now()
|
|
950
1315
|
};
|
|
1316
|
+
if (config.autoCompactEnabled !== false && usageAtGate(lastUsage, config)) {
|
|
1317
|
+
const pressure = usagePressureTokens(lastUsage);
|
|
1318
|
+
const gate = compactionGate(config);
|
|
1319
|
+
const masked = maskAtGate(contextHistory, gate, pressure, gate);
|
|
1320
|
+
if (masked.maskedCount > 0) {
|
|
1321
|
+
contextHistory.length = 0;
|
|
1322
|
+
contextHistory.push(...masked.history);
|
|
1323
|
+
if (masked.underLine) lastUsage = null;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
951
1326
|
const contextLimit = resolveContextLimit(config);
|
|
952
1327
|
const hostCompactAttemptKey = `${usagePressureTokens(lastUsage)}:${contextHistory.length}`;
|
|
953
|
-
if (config.autoCompactEnabled !== false && contextLimit != null &&
|
|
1328
|
+
if (config.autoCompactEnabled !== false && contextLimit != null && usageAtGate(lastUsage, config) && lastHostCompactAttemptKey !== hostCompactAttemptKey) {
|
|
954
1329
|
lastHostCompactAttemptKey = hostCompactAttemptKey;
|
|
955
1330
|
try {
|
|
956
1331
|
const outcome2 = await agentSession.compact({
|
|
@@ -971,7 +1346,7 @@ async function runHeadless(config, registry, opts) {
|
|
|
971
1346
|
options: {
|
|
972
1347
|
trigger: "auto",
|
|
973
1348
|
preContextTokens: usagePressureTokens(lastUsage),
|
|
974
|
-
signal:
|
|
1349
|
+
signal: runSignal,
|
|
975
1350
|
upcomingUserIntent: prompt,
|
|
976
1351
|
onHookEvent: createHookEventHandler(opts, emit)
|
|
977
1352
|
}
|
|
@@ -988,20 +1363,38 @@ async function runHeadless(config, registry, opts) {
|
|
|
988
1363
|
displayMessage: prompt,
|
|
989
1364
|
userMessage,
|
|
990
1365
|
sessionName,
|
|
1366
|
+
sessionNameSource: dispatch.kind === "prompt" ? dispatch.sessionNameSource : void 0,
|
|
991
1367
|
timelineStore: store && sessionId ? store : void 0,
|
|
992
1368
|
expandShellInput: false,
|
|
1369
|
+
projectCommand: dispatch.kind === "prompt" ? dispatch.projectCommand : void 0,
|
|
993
1370
|
runtime,
|
|
994
|
-
signal:
|
|
1371
|
+
signal: runSignal
|
|
995
1372
|
});
|
|
1373
|
+
for (const notice of recorded.mentionNotices) process.stderr.write(`${notice}
|
|
1374
|
+
`);
|
|
996
1375
|
sessionName = recorded.sessionName;
|
|
997
1376
|
const contextMessage = recorded.contextMessage;
|
|
998
1377
|
transcript.push(userMessage);
|
|
999
1378
|
rootContexts.set(runContext.rootRunId, runContext);
|
|
1000
|
-
await runParentTurn(
|
|
1379
|
+
await runParentTurn(
|
|
1380
|
+
contextMessage,
|
|
1381
|
+
prompt,
|
|
1382
|
+
userMessage,
|
|
1383
|
+
runContext,
|
|
1384
|
+
commandContext,
|
|
1385
|
+
// What the user typed is the line they submitted, whatever it resolved to: a command's
|
|
1386
|
+
// body or a built-in command's prompt is not.
|
|
1387
|
+
submitted
|
|
1388
|
+
);
|
|
1001
1389
|
const spentSoFar = runtime.runAccounting.snapshotRoot(runContext.rootRunId);
|
|
1002
1390
|
carriedSpend = {
|
|
1003
1391
|
usage: spentSoFar.inclusiveUsage,
|
|
1004
|
-
costUsd: spentSoFar.inclusiveCostUsd
|
|
1392
|
+
costUsd: spentSoFar.inclusiveCostUsd,
|
|
1393
|
+
persistedUsage: runtime.runAccounting.persistedUsage(runContext.rootRunId),
|
|
1394
|
+
// Naming the root the totals came from: the next root's first record
|
|
1395
|
+
// carries this one's remainder, so this one is closed rather than left to
|
|
1396
|
+
// write the same tokens a second time at the end of the session (#336).
|
|
1397
|
+
fromRootRunId: runContext.rootRunId
|
|
1005
1398
|
};
|
|
1006
1399
|
if (planNotApplied.outcome) break;
|
|
1007
1400
|
}
|
|
@@ -1013,7 +1406,11 @@ async function runHeadless(config, registry, opts) {
|
|
|
1013
1406
|
)
|
|
1014
1407
|
);
|
|
1015
1408
|
while (true) {
|
|
1016
|
-
await managedAgentManager.waitForIdle();
|
|
1409
|
+
const idle = !runSignal?.aborted && await raceAbort(managedAgentManager.waitForIdle(), runSignal);
|
|
1410
|
+
if (!idle) {
|
|
1411
|
+
if (mayReclassify) lastOutcome = classifyAbortReason(runSignal?.reason, true);
|
|
1412
|
+
break;
|
|
1413
|
+
}
|
|
1017
1414
|
const pending = (await managedAgentManager.listPendingCompletions()).filter(
|
|
1018
1415
|
(notification) => notification.parentSessionId === runtimeSessionId
|
|
1019
1416
|
);
|
|
@@ -1052,10 +1449,13 @@ async function runHeadless(config, registry, opts) {
|
|
|
1052
1449
|
displayMessage,
|
|
1053
1450
|
contextMessage: userMessage.contextContent,
|
|
1054
1451
|
userMessage,
|
|
1452
|
+
// The local name is deliberately not reassigned from the result: a
|
|
1453
|
+
// notification never names the session (#446).
|
|
1454
|
+
sessionName,
|
|
1055
1455
|
timelineStore: store && sessionId ? store : void 0,
|
|
1056
1456
|
expandShellInput: false,
|
|
1057
1457
|
runtime,
|
|
1058
|
-
signal:
|
|
1458
|
+
signal: runSignal
|
|
1059
1459
|
});
|
|
1060
1460
|
transcript.push(userMessage);
|
|
1061
1461
|
const parentContext = batch[0]?.rootRunId ? rootContexts.get(batch[0].rootRunId) : void 0;
|
|
@@ -1079,6 +1479,10 @@ async function runHeadless(config, registry, opts) {
|
|
|
1079
1479
|
}));
|
|
1080
1480
|
runResults.sort((left, right) => left.context.startedAt - right.context.startedAt);
|
|
1081
1481
|
const reportedUsage = lastUsage ?? commandUsage;
|
|
1482
|
+
mayReclassify = lastOutcome?.status !== "failed";
|
|
1483
|
+
const lastText = finalRunOpeningId === void 0 ? "" : finalAnswerText(contextHistory, finalRunOpeningId);
|
|
1484
|
+
const inline = separateInlineReasoning(lastText);
|
|
1485
|
+
const answer = inline.found ? inline.content.trimEnd() : lastText;
|
|
1082
1486
|
const result = {
|
|
1083
1487
|
messages: contextHistory,
|
|
1084
1488
|
transcript,
|
|
@@ -1089,12 +1493,12 @@ async function runHeadless(config, registry, opts) {
|
|
|
1089
1493
|
runs: runResults,
|
|
1090
1494
|
sessionId,
|
|
1091
1495
|
plan: planNotApplied.outcome,
|
|
1092
|
-
commandResults
|
|
1496
|
+
commandResults,
|
|
1497
|
+
answer
|
|
1093
1498
|
};
|
|
1094
1499
|
if (opts.jsonSchema) {
|
|
1095
|
-
const text = lastAssistantText(contextHistory);
|
|
1096
1500
|
try {
|
|
1097
|
-
result.structured = JSON.parse(
|
|
1501
|
+
result.structured = JSON.parse(answer);
|
|
1098
1502
|
} catch {
|
|
1099
1503
|
result.structuredError = "Failed to parse JSON from assistant output";
|
|
1100
1504
|
}
|
|
@@ -1105,7 +1509,7 @@ async function runHeadless(config, registry, opts) {
|
|
|
1105
1509
|
config,
|
|
1106
1510
|
registry,
|
|
1107
1511
|
contextHistory,
|
|
1108
|
-
|
|
1512
|
+
runSignal,
|
|
1109
1513
|
store?.readImageAttachment ? (attachment) => store.readImageAttachment(runtimeSessionId, attachment) : void 0
|
|
1110
1514
|
);
|
|
1111
1515
|
if (suggestions.length > 0) {
|
|
@@ -1122,8 +1526,7 @@ async function runHeadless(config, registry, opts) {
|
|
|
1122
1526
|
${stopped.message}
|
|
1123
1527
|
`);
|
|
1124
1528
|
} else {
|
|
1125
|
-
|
|
1126
|
-
if (last) stdout.write(last + "\n");
|
|
1529
|
+
if (answer) stdout.write(answer + "\n");
|
|
1127
1530
|
}
|
|
1128
1531
|
} else if (opts.outputFormat === "json") {
|
|
1129
1532
|
emit({
|
|
@@ -1137,15 +1540,25 @@ ${stopped.message}
|
|
|
1137
1540
|
structured: result.structured,
|
|
1138
1541
|
structuredError: result.structuredError,
|
|
1139
1542
|
plan: result.plan,
|
|
1140
|
-
commandResults
|
|
1543
|
+
commandResults,
|
|
1544
|
+
answer
|
|
1141
1545
|
}
|
|
1142
1546
|
});
|
|
1143
1547
|
} else if (opts.outputFormat === "stream-json") {
|
|
1144
1548
|
emit({
|
|
1145
1549
|
type: "result",
|
|
1550
|
+
// At the top level, beside `stopReason`, because that is where a
|
|
1551
|
+
// supervised loop reads the outcome from: `jq -e 'select(.type=="result")
|
|
1552
|
+
// | .outcome.reason=="objective_complete"'` was written against this
|
|
1553
|
+
// record and read a null. `result.outcome` stays for hosts already
|
|
1554
|
+
// reading it there.
|
|
1146
1555
|
stopReason: outcome.reason,
|
|
1556
|
+
outcome,
|
|
1147
1557
|
result: {
|
|
1148
|
-
|
|
1558
|
+
// Off by default: a long run's conversation is half a megabyte of JSONL
|
|
1559
|
+
// on one line, which every reader has to buffer before it can look at
|
|
1560
|
+
// the field it came for. `--include-result-messages` asks for it back.
|
|
1561
|
+
...opts.includeResultMessages ? { messages: contextHistory } : {},
|
|
1149
1562
|
usage: reportedUsage,
|
|
1150
1563
|
accounting: result.accounting,
|
|
1151
1564
|
outcome,
|
|
@@ -1154,28 +1567,42 @@ ${stopped.message}
|
|
|
1154
1567
|
structured: result.structured,
|
|
1155
1568
|
structuredError: result.structuredError,
|
|
1156
1569
|
plan: result.plan,
|
|
1157
|
-
commandResults
|
|
1570
|
+
commandResults,
|
|
1571
|
+
answer
|
|
1158
1572
|
}
|
|
1159
1573
|
});
|
|
1160
1574
|
}
|
|
1575
|
+
if (opts.outputFormat !== "stream-json" && opts.runSource !== "sdk" && outcome.status === "failed" && outcome.message && !answer && !planNotApplied.outcome && !eventState.errorReported) {
|
|
1576
|
+
process.stderr.write(`error: ${outcome.message}
|
|
1577
|
+
`);
|
|
1578
|
+
}
|
|
1161
1579
|
if (sessionId) {
|
|
1162
|
-
|
|
1163
|
-
|
|
1580
|
+
const sessionEndReason = outcome.status === "completed" ? "completion" : outcome.status === "failed" ? "error" : outcome.status === "cancelled" || runSignal?.aborted ? "aborted" : "completion";
|
|
1581
|
+
disposeSession();
|
|
1582
|
+
await agentSession.endLifecycle(config, sessionId, sessionEndReason, {
|
|
1583
|
+
onHookEvent: createHookEventHandler(opts, emit),
|
|
1584
|
+
endOutcome: outcome
|
|
1164
1585
|
});
|
|
1165
1586
|
}
|
|
1166
1587
|
return result;
|
|
1588
|
+
} catch (error) {
|
|
1589
|
+
if (startedSessionId) {
|
|
1590
|
+
disposeSession();
|
|
1591
|
+
await agentSession.endLifecycle(config, startedSessionId, runSignal?.aborted ? "aborted" : "error", {
|
|
1592
|
+
onHookEvent: createHookEventHandler(opts, emit),
|
|
1593
|
+
endOutcome: runSignal?.aborted ? classifyAbortReason(runSignal.reason, false) : createTerminalOutcome("failed", "runtime_error", { partialOutput: false })
|
|
1594
|
+
}).catch((hookError) => {
|
|
1595
|
+
console.warn(
|
|
1596
|
+
`SessionEnd hook failed: ${hookError instanceof Error ? hookError.message : String(hookError)}`
|
|
1597
|
+
);
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
throw error;
|
|
1167
1601
|
} finally {
|
|
1168
1602
|
disposeCrashHandlers?.();
|
|
1169
|
-
|
|
1603
|
+
disposeSession();
|
|
1170
1604
|
}
|
|
1171
1605
|
}
|
|
1172
|
-
function lastAssistantText(history) {
|
|
1173
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
1174
|
-
const m = history[i];
|
|
1175
|
-
if (m.role === "assistant" && m.content) return m.content;
|
|
1176
|
-
}
|
|
1177
|
-
return "";
|
|
1178
|
-
}
|
|
1179
1606
|
function planApprovalStatus(decision) {
|
|
1180
1607
|
return typeof decision === "string" ? decision : decision.decision;
|
|
1181
1608
|
}
|
|
@@ -1183,16 +1610,45 @@ function createHookEventHandler(opts, emit) {
|
|
|
1183
1610
|
if (!opts.includeHookEvents || opts.outputFormat !== "stream-json") return void 0;
|
|
1184
1611
|
return (event, payload) => emit({ type: "hook_event", event, ...payload });
|
|
1185
1612
|
}
|
|
1186
|
-
function emitAgentEvent(event, opts, emit) {
|
|
1613
|
+
function emitAgentEvent(event, opts, emit, state) {
|
|
1187
1614
|
if (event.type === "terminal") return;
|
|
1188
1615
|
if (event.type === "agent_text_delta" && opts.forwardSubagentText !== true) return;
|
|
1189
1616
|
opts.onAgentEvent?.(event);
|
|
1617
|
+
if (event.type === "notice") {
|
|
1618
|
+
if (opts.outputFormat === "stream-json") emit({ type: "notice", message: event.message });
|
|
1619
|
+
else console.warn(event.message);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
if (event.type === "goal_status") {
|
|
1623
|
+
const endsRunFailed = event.event.state === "failed" || event.event.state === "paused";
|
|
1624
|
+
if (opts.outputFormat === "stream-json") emit({ type: "goal_status", ...event.event });
|
|
1625
|
+
else if (opts.outputFormat === "text" && opts.runSource !== "sdk" && (opts.quiet !== true || endsRunFailed)) {
|
|
1626
|
+
process.stderr.write(
|
|
1627
|
+
`goal: ${progressLine(formatGoalStatus(event.event), PROGRESS_NOTICE_MAX)}
|
|
1628
|
+
`
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
if (event.type === "agent_notice") {
|
|
1634
|
+
if (opts.outputFormat === "stream-json") {
|
|
1635
|
+
emit({ type: "agent_notice", agentId: event.agentId, message: event.message });
|
|
1636
|
+
} else {
|
|
1637
|
+
process.stderr.write(`notice: ${progressLine(event.message, PROGRESS_NOTICE_MAX)}
|
|
1638
|
+
`);
|
|
1639
|
+
}
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1190
1642
|
if (event.type === "error") {
|
|
1643
|
+
state.errorReported = true;
|
|
1191
1644
|
if (opts.outputFormat === "stream-json") emit({ type: "error", error: event.error });
|
|
1192
1645
|
else process.stderr.write(`error: ${event.error}
|
|
1193
1646
|
`);
|
|
1194
1647
|
return;
|
|
1195
1648
|
}
|
|
1649
|
+
if (opts.outputFormat === "text" && opts.quiet !== true) {
|
|
1650
|
+
writeTextProgress(event, opts, state);
|
|
1651
|
+
}
|
|
1196
1652
|
if (opts.outputFormat !== "stream-json") return;
|
|
1197
1653
|
switch (event.type) {
|
|
1198
1654
|
case "run_started":
|
|
@@ -1249,6 +1705,67 @@ function emitAgentEvent(event, opts, emit) {
|
|
|
1249
1705
|
break;
|
|
1250
1706
|
}
|
|
1251
1707
|
}
|
|
1708
|
+
var PROGRESS_ARG_MAX = 120;
|
|
1709
|
+
var PROGRESS_ERROR_MAX = 160;
|
|
1710
|
+
var PROGRESS_NOTICE_MAX = 400;
|
|
1711
|
+
function progressLine(text, max) {
|
|
1712
|
+
const first = text.trim().split(/\r?\n/, 1)[0];
|
|
1713
|
+
const printable = Array.from(first, (char) => {
|
|
1714
|
+
const code = char.codePointAt(0) ?? 0;
|
|
1715
|
+
const control = code < 32 || code >= 127 && code <= 159 || code === 1564 || code === 8206 || code === 8207 || code >= 8232 && code <= 8238 || code >= 8294 && code <= 8297;
|
|
1716
|
+
return control ? " " : char;
|
|
1717
|
+
}).join("").trimEnd();
|
|
1718
|
+
const chars = Array.from(printable);
|
|
1719
|
+
return chars.length > max ? `${chars.slice(0, max - 1).join("")}\u2026` : printable;
|
|
1720
|
+
}
|
|
1721
|
+
function writeTextProgress(event, opts, state) {
|
|
1722
|
+
if (event.type === "tool_use") {
|
|
1723
|
+
process.stderr.write(`${toolCallProgress(event.toolCall)}
|
|
1724
|
+
`);
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
if (event.type === "tool_result" && opts.verbose === true) {
|
|
1728
|
+
process.stderr.write(` ${toolResultProgress(event.toolResult)}
|
|
1729
|
+
`);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (event.type === "agent_start") {
|
|
1733
|
+
if (!state.childLabels.has(event.agent.id)) {
|
|
1734
|
+
state.childLabels.set(
|
|
1735
|
+
event.agent.id,
|
|
1736
|
+
uniqueAgentDisplayName(event.agent.profile ?? event.agent.name, state.childLabels.values())
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
if (event.type === "agent_activity") {
|
|
1742
|
+
writeChildToolProgress(event.agentId, event.activity, opts, state);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
function writeChildToolProgress(agentId, activity, opts, state) {
|
|
1746
|
+
if (activity.kind !== "tool" || !activity.toolCall) return;
|
|
1747
|
+
if (activity.status === "running") {
|
|
1748
|
+
const child = progressLine(state.childLabels.get(agentId) ?? "agent", PROGRESS_ARG_MAX);
|
|
1749
|
+
process.stderr.write(` [${child}] ${toolCallProgress(activity.toolCall)}
|
|
1750
|
+
`);
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
if (opts.verbose === true && activity.result) {
|
|
1754
|
+
process.stderr.write(` ${toolResultProgress(activity.result)}
|
|
1755
|
+
`);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
function toolCallProgress(call) {
|
|
1759
|
+
const arg = progressLine(getPrimaryArg(call.arguments), PROGRESS_ARG_MAX);
|
|
1760
|
+
return `[${progressLine(call.name, PROGRESS_ARG_MAX)}]${arg ? ` ${arg}` : ""}`;
|
|
1761
|
+
}
|
|
1762
|
+
function toolResultProgress(result) {
|
|
1763
|
+
const duration = result.metrics?.durationMs;
|
|
1764
|
+
const target = progressLine(result.presentation?.target ?? "", PROGRESS_ARG_MAX);
|
|
1765
|
+
const error = result.status === "success" ? "" : progressLine(toolResultErrorMessage(result) ?? "", PROGRESS_ERROR_MAX);
|
|
1766
|
+
const detail = [target, error].filter(Boolean).join(": ");
|
|
1767
|
+
return `\u2192 ${result.status}${duration !== void 0 ? ` ${Math.round(duration)}ms` : ""}${detail ? ` ${detail}` : ""}`;
|
|
1768
|
+
}
|
|
1252
1769
|
function emitCompactBoundary(outputFormat, emit, result) {
|
|
1253
1770
|
if (outputFormat !== "stream-json") return;
|
|
1254
1771
|
emit({
|
|
@@ -1260,6 +1777,7 @@ function emitCompactBoundary(outputFormat, emit, result) {
|
|
|
1260
1777
|
generation: result.generation,
|
|
1261
1778
|
pre_messages: result.preMessageCount,
|
|
1262
1779
|
post_messages: result.replacementHistory.length,
|
|
1780
|
+
carried_messages: result.carriedCount,
|
|
1263
1781
|
post_tokens: result.postContextTokens,
|
|
1264
1782
|
checkpoint_version: 2,
|
|
1265
1783
|
strategy: result.strategy,
|
|
@@ -1269,12 +1787,17 @@ function emitCompactBoundary(outputFormat, emit, result) {
|
|
|
1269
1787
|
// This generation's status above; the conversation's accumulated one here.
|
|
1270
1788
|
coverage_lifetime_status: result.checkpoint.coverage?.lifetime?.status ?? "complete",
|
|
1271
1789
|
coverage: result.checkpoint.coverage,
|
|
1272
|
-
warning: result.warning
|
|
1790
|
+
warning: result.warning,
|
|
1791
|
+
// The reducer audit, when the host has something to report: suspect inputs
|
|
1792
|
+
// found in the summarized span and inherited constraints the reducer let go of.
|
|
1793
|
+
...result.checkpoint.audit ? { audit: result.checkpoint.audit } : {},
|
|
1794
|
+
// Deferred compaction: the judge's verdict on the steps taken while the reducer ran.
|
|
1795
|
+
...result.judge ? { judge: result.judge } : {}
|
|
1273
1796
|
});
|
|
1274
1797
|
}
|
|
1275
1798
|
async function generatePromptSuggestions(config, _registry, history, signal, resolveAttachment) {
|
|
1276
|
-
const { chatCompletionStream } = await import("./openai-compatible-
|
|
1277
|
-
const { buildMessages } = await import("./context-
|
|
1799
|
+
const { chatCompletionStream } = await import("./openai-compatible-IPUNBJ53.js");
|
|
1800
|
+
const { buildMessages } = await import("./context-C3RAP6K5.js");
|
|
1278
1801
|
const suggestionMessages = await buildMessages(
|
|
1279
1802
|
config,
|
|
1280
1803
|
[
|
|
@@ -1398,7 +1921,7 @@ var StdioProcessTransport = class {
|
|
|
1398
1921
|
return Promise.reject(new Error(`MCP server ${this.name} is not a stdio server`));
|
|
1399
1922
|
}
|
|
1400
1923
|
const child = spawn(this.config.command, this.config.args ?? [], {
|
|
1401
|
-
env:
|
|
1924
|
+
env: buildChildEnv(process.env, this.config.env),
|
|
1402
1925
|
cwd: this.config.cwd,
|
|
1403
1926
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1404
1927
|
});
|
|
@@ -1433,7 +1956,6 @@ var StdioProcessTransport = class {
|
|
|
1433
1956
|
child.stdout?.removeListener("data", onStdoutData);
|
|
1434
1957
|
child.stderr?.removeListener("data", onStderrData);
|
|
1435
1958
|
child.removeListener("error", onChildError);
|
|
1436
|
-
child.stdin?.removeListener("error", onStdinError);
|
|
1437
1959
|
child.removeListener("exit", onExit);
|
|
1438
1960
|
child.removeListener("close", onClose);
|
|
1439
1961
|
this.cleanupListeners = () => {
|
|
@@ -1653,7 +2175,7 @@ function registerElicitationHandler(client, name, options) {
|
|
|
1653
2175
|
}
|
|
1654
2176
|
);
|
|
1655
2177
|
}
|
|
1656
|
-
async function connectMcpServer(name, cfg, options) {
|
|
2178
|
+
async function connectMcpServer(name, cfg, options, failure) {
|
|
1657
2179
|
let handle;
|
|
1658
2180
|
let liveConnection;
|
|
1659
2181
|
let transportClosed = false;
|
|
@@ -1741,6 +2263,7 @@ async function connectMcpServer(name, cfg, options) {
|
|
|
1741
2263
|
}
|
|
1742
2264
|
return conn;
|
|
1743
2265
|
} catch (error) {
|
|
2266
|
+
if (failure) failure.error = error;
|
|
1744
2267
|
const message = redact(error instanceof Error ? error.message : String(error));
|
|
1745
2268
|
options.onDiagnostic({
|
|
1746
2269
|
level: "warn",
|
|
@@ -1758,6 +2281,23 @@ async function connectMcpServer(name, cfg, options) {
|
|
|
1758
2281
|
return null;
|
|
1759
2282
|
}
|
|
1760
2283
|
}
|
|
2284
|
+
function rejectedStreamableHttp(error) {
|
|
2285
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2286
|
+
return /\b(404|405)\b|Not Found|Method Not Allowed/i.test(message);
|
|
2287
|
+
}
|
|
2288
|
+
async function connectRemoteWithSseFallback(name, cfg, options) {
|
|
2289
|
+
const eligible = isRemoteConfig(cfg) && cfg.type !== "sse";
|
|
2290
|
+
if (!eligible) return connectMcpServer(name, cfg, options);
|
|
2291
|
+
const failure = {};
|
|
2292
|
+
const connection = await connectMcpServer(name, cfg, options, failure);
|
|
2293
|
+
if (connection || !rejectedStreamableHttp(failure.error)) return connection;
|
|
2294
|
+
options.onDiagnostic({
|
|
2295
|
+
level: "warn",
|
|
2296
|
+
message: `MCP server "${name}" rejected Streamable HTTP; retrying as legacy SSE. Set "type": "sse" to skip this.`,
|
|
2297
|
+
server: name
|
|
2298
|
+
});
|
|
2299
|
+
return connectMcpServer(name, { ...cfg, type: "sse" }, options);
|
|
2300
|
+
}
|
|
1761
2301
|
function buildToolDefinition(conn, tool, requestTimeoutMs) {
|
|
1762
2302
|
const prefixedName = `mcp__${conn.name}__${tool.name}`;
|
|
1763
2303
|
const live = conn;
|
|
@@ -1836,7 +2376,7 @@ async function connectMcpServers(workspace, options = {}) {
|
|
|
1836
2376
|
servers = declared.filter((server) => server.source === "user");
|
|
1837
2377
|
}
|
|
1838
2378
|
const results = await Promise.allSettled(
|
|
1839
|
-
servers.map(({ name, config }) =>
|
|
2379
|
+
servers.map(({ name, config }) => connectRemoteWithSseFallback(name, config, resolved))
|
|
1840
2380
|
);
|
|
1841
2381
|
const connections = [];
|
|
1842
2382
|
for (const [index, result] of results.entries()) {
|
|
@@ -1866,9 +2406,51 @@ async function disconnectMcpServers(connections) {
|
|
|
1866
2406
|
|
|
1867
2407
|
// src/project-approval-notices.ts
|
|
1868
2408
|
import { join as join2 } from "path";
|
|
2409
|
+
function collectWithheldProviderNotices(gate, workspace) {
|
|
2410
|
+
const notices = [];
|
|
2411
|
+
const approveCommand = (id) => {
|
|
2412
|
+
const { command, note } = providerTrustCommand(workspace, id);
|
|
2413
|
+
return `\`${command}\`${note}`;
|
|
2414
|
+
};
|
|
2415
|
+
for (const dropped of gate.droppedOverrides) {
|
|
2416
|
+
notices.push(`\u26A0 ${describeDroppedOverride(dropped)}`);
|
|
2417
|
+
}
|
|
2418
|
+
for (const entry of gate.partition.reserved) {
|
|
2419
|
+
notices.push(`\u26A0 ${describeReservedProvider(entry)}`);
|
|
2420
|
+
}
|
|
2421
|
+
const pendingIds = new Set(gate.partition.pending.map((entry) => entry.id));
|
|
2422
|
+
for (const entry of gate.partition.pending) {
|
|
2423
|
+
const id = renderUntrustedText(entry.id, 64);
|
|
2424
|
+
const origin = entry.declaration.baseURL ?? entry.declaration.baseUrl;
|
|
2425
|
+
notices.push(
|
|
2426
|
+
`\u26A0 Ignoring workspace-declared provider "${id}" (${describeOrigin(origin)}, key: ${describeApiKeyDeclaration(
|
|
2427
|
+
entry.declaration.apiKey,
|
|
2428
|
+
workspace
|
|
2429
|
+
)}) from ${entry.layers.map((layer) => renderUntrustedText(layer)).join(", ")}: it requires approval. Run ${approveCommand(entry.id)} to review and approve it.`
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2432
|
+
for (const selection of gate.withheldSelections) {
|
|
2433
|
+
if (!pendingIds.has(selection.providerId)) continue;
|
|
2434
|
+
notices.push(
|
|
2435
|
+
`\u26A0 ${describeWithheldSelection(selection)} Run ${approveCommand(selection.providerId)} to approve it.`
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
2438
|
+
for (const selection of gate.unaffectedSelections) {
|
|
2439
|
+
if (!pendingIds.has(selection.providerId)) continue;
|
|
2440
|
+
notices.push(`\u26A0 ${describeUnaffectedSelection(selection)}`);
|
|
2441
|
+
}
|
|
2442
|
+
if (gate.legacyBaseUrl?.state === "pending") {
|
|
2443
|
+
notices.push(
|
|
2444
|
+
`\u26A0 Ignoring baseUrl "${renderUntrustedText(gate.legacyBaseUrl.baseUrl)}" from ${LEGACY_BASE_URL_ID}: it would receive BOOK_API_KEY, so it requires approval. Run ${approveCommand(LEGACY_BASE_URL_ID)} to review and approve it.`
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
return notices;
|
|
2448
|
+
}
|
|
1869
2449
|
function collectWithheldProjectNotices(input) {
|
|
1870
|
-
|
|
1871
|
-
|
|
2450
|
+
const providerNotices = input.providerGate ? collectWithheldProviderNotices(input.providerGate, input.workspace) : [];
|
|
2451
|
+
if (!input.settingsEnabled) return providerNotices;
|
|
2452
|
+
const projectPath = join2(input.workspace, ".book", "settings.json");
|
|
2453
|
+
const projectSettings = isUserSettingsFile(projectPath) ? null : loadSettingsFile(projectPath);
|
|
1872
2454
|
const notices = [];
|
|
1873
2455
|
const allow = partitionProjectAllowRules(
|
|
1874
2456
|
projectSettings?.permissions?.allow ?? [],
|
|
@@ -1893,15 +2475,63 @@ function collectWithheldProjectNotices(input) {
|
|
|
1893
2475
|
`\u26A0 Ignoring ${hooks.pending.length} project-declared hook(s) (${summary}): hooks require approval. Run \`book doctor\` to approve them.`
|
|
1894
2476
|
);
|
|
1895
2477
|
}
|
|
1896
|
-
|
|
2478
|
+
const directories = partitionProjectDirectories(
|
|
2479
|
+
collectDeclaredDirectories(input.workspace, projectSettings?.additionalDirectories ?? []),
|
|
2480
|
+
input.settings.projectDirectories
|
|
2481
|
+
);
|
|
2482
|
+
for (const directory of directories.pending) {
|
|
2483
|
+
notices.push(
|
|
2484
|
+
`\u26A0 Ignoring project-declared additionalDirectories entry "${directory.declared}" (really ${directory.realPath}): it requires approval. Run \`book doctor\` to see how to grant it.`
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
const ignoredSandboxKeys = ignoredWorkspaceSandboxKeys(projectSettings ?? {});
|
|
2488
|
+
if (ignoredSandboxKeys.length > 0) {
|
|
2489
|
+
const listed = ignoredSandboxKeys.map(formatIgnoredWorkspaceSandboxKey).join(", ");
|
|
2490
|
+
notices.push(
|
|
2491
|
+
`\u26A0 Ignoring sandbox settings declared by ${projectPath}: ${listed}. A file inside the workspace may turn the sandbox on and add deny entries, but not loosen it. ` + TRUSTED_SETTINGS_LOCATION
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
const localPath = join2(input.workspace, ".book", "settings.local.json");
|
|
2495
|
+
const localSettings = loadSettingsFile(localPath);
|
|
2496
|
+
const ignoredLocalSandboxKeys = ignoredWorkspaceSandboxKeys(localSettings ?? {});
|
|
2497
|
+
if (ignoredLocalSandboxKeys.length > 0) {
|
|
2498
|
+
const listed = ignoredLocalSandboxKeys.map(formatIgnoredWorkspaceSandboxKey).join(", ");
|
|
2499
|
+
notices.push(
|
|
2500
|
+
`\u26A0 Ignoring sandbox settings declared by ${localPath}: ${listed}. A file inside the workspace may turn the sandbox on and add deny entries, but not loosen it. ` + TRUSTED_SETTINGS_LOCATION
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
const userPath = settingsLayerPaths(input.workspace)[0];
|
|
2504
|
+
const activation = workspaceLayerThatEnabledSandbox([
|
|
2505
|
+
{ path: userPath, trust: "trusted", document: loadSettingsFile(userPath) },
|
|
2506
|
+
{ path: projectPath, trust: "repository", document: projectSettings },
|
|
2507
|
+
{ path: localPath, trust: "local", document: localSettings }
|
|
2508
|
+
]);
|
|
2509
|
+
if (activation) {
|
|
2510
|
+
notices.push(
|
|
2511
|
+
`\u26A0 ${activation} declared sandbox.enabled=true, so sandbox.autoAllowBashIfSandboxed is false for this session: no Bash command is auto-approved. ${TRUSTED_SETTINGS_LOCATION}`
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
return [...notices, ...providerNotices];
|
|
2515
|
+
}
|
|
2516
|
+
function collectSkippedProjectFileNotices(workspace) {
|
|
2517
|
+
return [
|
|
2518
|
+
...collectSkippedInstructionNotices(workspace),
|
|
2519
|
+
...collectSkippedReviewNotices(workspace),
|
|
2520
|
+
...collectSkippedCommandNotices(workspace)
|
|
2521
|
+
];
|
|
1897
2522
|
}
|
|
1898
2523
|
|
|
1899
2524
|
export {
|
|
2525
|
+
runStatusPath,
|
|
1900
2526
|
readRunStatus,
|
|
2527
|
+
cleanupRunStatus,
|
|
2528
|
+
isReaderGoneError,
|
|
1901
2529
|
runHeadless,
|
|
1902
2530
|
buildMcpToolDefinitions,
|
|
1903
2531
|
connectMcpServers,
|
|
1904
2532
|
disconnectMcpServers,
|
|
1905
|
-
|
|
2533
|
+
collectWithheldProviderNotices,
|
|
2534
|
+
collectWithheldProjectNotices,
|
|
2535
|
+
collectSkippedProjectFileNotices
|
|
1906
2536
|
};
|
|
1907
|
-
//# sourceMappingURL=chunk-
|
|
2537
|
+
//# sourceMappingURL=chunk-E5WLQSKA.js.map
|