@letrquan/book 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +2747 -40
  2. package/README.md +132 -1311
  3. package/dist/additional-roots-H7UNAEHS.js +31 -0
  4. package/dist/{app-MOXXWEOY.js → app-GE4OHJOC.js} +5740 -4045
  5. package/dist/app-GE4OHJOC.js.map +1 -0
  6. package/dist/{chunk-5GDZ22YP.js → chunk-224FJZLH.js} +99 -108
  7. package/dist/chunk-224FJZLH.js.map +1 -0
  8. package/dist/chunk-2AAOF5B5.js +28 -0
  9. package/dist/chunk-2AAOF5B5.js.map +1 -0
  10. package/dist/{chunk-CAGOB2N4.js → chunk-4AS7LKKQ.js} +6 -4
  11. package/dist/chunk-4AS7LKKQ.js.map +1 -0
  12. package/dist/chunk-525CAZPY.js +120 -0
  13. package/dist/chunk-525CAZPY.js.map +1 -0
  14. package/dist/{chunk-URLQAXTR.js → chunk-56SIEMDX.js} +2 -2
  15. package/dist/chunk-56SIEMDX.js.map +1 -0
  16. package/dist/{chunk-XIFFTATU.js → chunk-5YHTKWJN.js} +5420 -4031
  17. package/dist/chunk-5YHTKWJN.js.map +1 -0
  18. package/dist/chunk-62QL5QUP.js +283 -0
  19. package/dist/chunk-62QL5QUP.js.map +1 -0
  20. package/dist/{chunk-KWHQ2XXL.js → chunk-67UZEWWF.js} +3 -3
  21. package/dist/chunk-67UZEWWF.js.map +1 -0
  22. package/dist/chunk-6OQQOMZR.js +785 -0
  23. package/dist/chunk-6OQQOMZR.js.map +1 -0
  24. package/dist/{chunk-GTVB4HEM.js → chunk-AQEQX4FF.js} +344 -315
  25. package/dist/chunk-AQEQX4FF.js.map +1 -0
  26. package/dist/chunk-C7ADUNPR.js +273 -0
  27. package/dist/chunk-C7ADUNPR.js.map +1 -0
  28. package/dist/{chunk-E75IPST5.js → chunk-EFJ2NH6E.js} +12 -58
  29. package/dist/chunk-EFJ2NH6E.js.map +1 -0
  30. package/dist/{chunk-5KLVH3PY.js → chunk-HQI7NYGX.js} +5 -3
  31. package/dist/chunk-HQI7NYGX.js.map +1 -0
  32. package/dist/chunk-IME7R2UI.js +6541 -0
  33. package/dist/chunk-IME7R2UI.js.map +1 -0
  34. package/dist/chunk-J4F2QLM2.js +273 -0
  35. package/dist/chunk-J4F2QLM2.js.map +1 -0
  36. package/dist/{chunk-MAQXCUR4.js → chunk-J62VZYOB.js} +444 -112
  37. package/dist/chunk-J62VZYOB.js.map +1 -0
  38. package/dist/chunk-JTPOI3KU.js +826 -0
  39. package/dist/chunk-JTPOI3KU.js.map +1 -0
  40. package/dist/{chunk-WKBO5R4O.js → chunk-KBQS35OV.js} +2 -2
  41. package/dist/chunk-KBQS35OV.js.map +1 -0
  42. package/dist/chunk-KXAYUIKP.js +90 -0
  43. package/dist/chunk-KXAYUIKP.js.map +1 -0
  44. package/dist/{chunk-VZQXX3WW.js → chunk-QYBD22H5.js} +1 -1
  45. package/dist/chunk-QYBD22H5.js.map +1 -0
  46. package/dist/{chunk-V4BOYR52.js → chunk-RFJ6Q6LE.js} +763 -198
  47. package/dist/chunk-RFJ6Q6LE.js.map +1 -0
  48. package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
  49. package/dist/chunk-RT5SATF7.js.map +1 -0
  50. package/dist/{chunk-C7D4NJ2D.js → chunk-S4JMID4G.js} +68 -26
  51. package/dist/chunk-S4JMID4G.js.map +1 -0
  52. package/dist/{chunk-ANSJPYGI.js → chunk-SOW4WRQP.js} +3 -3
  53. package/dist/chunk-SOW4WRQP.js.map +1 -0
  54. package/dist/{chunk-J7MIZ5CA.js → chunk-TLX3H2CL.js} +518 -79
  55. package/dist/chunk-TLX3H2CL.js.map +1 -0
  56. package/dist/{chunk-DGMQEI6Y.js → chunk-VPNLCMVR.js} +22 -9
  57. package/dist/chunk-VPNLCMVR.js.map +1 -0
  58. package/dist/{chunk-BTZZUCLI.js → chunk-W3XFHEF4.js} +173 -29
  59. package/dist/chunk-W3XFHEF4.js.map +1 -0
  60. package/dist/{chunk-574C6MQR.js → chunk-X4NJT436.js} +130 -60
  61. package/dist/chunk-X4NJT436.js.map +1 -0
  62. package/dist/chunk-X66I2NQO.js +137 -0
  63. package/dist/chunk-X66I2NQO.js.map +1 -0
  64. package/dist/chunk-XHAO3K7I.js +18 -0
  65. package/dist/chunk-XHAO3K7I.js.map +1 -0
  66. package/dist/{chunk-3U5IBM24.js → chunk-Y2VHN7TC.js} +2 -2
  67. package/dist/chunk-Y2VHN7TC.js.map +1 -0
  68. package/dist/chunk-YC6W4EJH.js +947 -0
  69. package/dist/chunk-YC6W4EJH.js.map +1 -0
  70. package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
  71. package/dist/chunk-ZPSZZUWF.js.map +1 -0
  72. package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-VBIU6BLM.js} +6 -5
  73. package/dist/context-CBOBDY5Q.js +38 -0
  74. package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-ZVT4V7IM.js} +5 -5
  75. package/dist/index.js +996 -155
  76. package/dist/index.js.map +1 -1
  77. package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-T4I4SEHU.js} +13 -9
  78. package/dist/interactive-assets-T4I4SEHU.js.map +1 -0
  79. package/dist/job-runner.js +159 -111
  80. package/dist/job-runner.js.map +1 -1
  81. package/dist/job-supervisor.d.ts +2 -0
  82. package/dist/job-supervisor.js +112 -0
  83. package/dist/job-supervisor.js.map +1 -0
  84. package/dist/loader-S77OVTHH.js +18 -0
  85. package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-HJ2VDYMC.js} +5 -5
  86. package/dist/{mcp-config-YIJIES2C.js → mcp-config-RNB5LEVE.js} +3 -3
  87. package/dist/memory-store-DZUT5HUH.js +75 -0
  88. package/dist/{model-window-store-YFVNXCIP.js → model-window-store-MTSQ5JDJ.js} +8 -4
  89. package/dist/openai-compatible-2OB54YST.js +14 -0
  90. package/dist/{permission-approvals-76PVREFD.js → permission-approvals-YLV6UC5W.js} +5 -5
  91. package/dist/permissions-DHOKF3W2.js +45 -0
  92. package/dist/sandbox-WRYTZH3D.js +41 -0
  93. package/dist/{scrollback-LUKLIBVI.js → scrollback-35ME2GFX.js} +32 -22
  94. package/dist/scrollback-35ME2GFX.js.map +1 -0
  95. package/dist/sdk.d.ts +2219 -2361
  96. package/dist/sdk.js +53 -38
  97. package/dist/sdk.js.map +1 -1
  98. package/dist/settings-loader-JUNPGAAM.js +35 -0
  99. package/dist/settings-loader-JUNPGAAM.js.map +1 -0
  100. package/dist/{shell-selection-V6QAYXPX.js → shell-selection-CDHGJ4GC.js} +2 -2
  101. package/dist/shell-selection-CDHGJ4GC.js.map +1 -0
  102. package/package.json +23 -22
  103. package/dist/app-MOXXWEOY.js.map +0 -1
  104. package/dist/chunk-24AXE6SP.js.map +0 -1
  105. package/dist/chunk-3U5IBM24.js.map +0 -1
  106. package/dist/chunk-574C6MQR.js.map +0 -1
  107. package/dist/chunk-5GDZ22YP.js.map +0 -1
  108. package/dist/chunk-5KLVH3PY.js.map +0 -1
  109. package/dist/chunk-7N4J557C.js.map +0 -1
  110. package/dist/chunk-A7JLBK2Y.js +0 -3506
  111. package/dist/chunk-A7JLBK2Y.js.map +0 -1
  112. package/dist/chunk-ANSJPYGI.js.map +0 -1
  113. package/dist/chunk-BTZZUCLI.js.map +0 -1
  114. package/dist/chunk-C7D4NJ2D.js.map +0 -1
  115. package/dist/chunk-CAGOB2N4.js.map +0 -1
  116. package/dist/chunk-DGMQEI6Y.js.map +0 -1
  117. package/dist/chunk-E75IPST5.js.map +0 -1
  118. package/dist/chunk-EYGD6X2T.js +0 -180
  119. package/dist/chunk-EYGD6X2T.js.map +0 -1
  120. package/dist/chunk-GTVB4HEM.js.map +0 -1
  121. package/dist/chunk-J7MIZ5CA.js.map +0 -1
  122. package/dist/chunk-KWHQ2XXL.js.map +0 -1
  123. package/dist/chunk-L6LK2QIQ.js +0 -657
  124. package/dist/chunk-L6LK2QIQ.js.map +0 -1
  125. package/dist/chunk-MAQXCUR4.js.map +0 -1
  126. package/dist/chunk-S4XL7HOM.js +0 -402
  127. package/dist/chunk-S4XL7HOM.js.map +0 -1
  128. package/dist/chunk-URLQAXTR.js.map +0 -1
  129. package/dist/chunk-V4BOYR52.js.map +0 -1
  130. package/dist/chunk-VZQXX3WW.js.map +0 -1
  131. package/dist/chunk-WKBO5R4O.js.map +0 -1
  132. package/dist/chunk-XIFFTATU.js.map +0 -1
  133. package/dist/context-ZC5IFEAX.js +0 -25
  134. package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
  135. package/dist/loader-4IDS2RIE.js +0 -15
  136. package/dist/openai-compatible-RHZUBHV6.js +0 -10
  137. package/dist/permissions-CHCKTFQQ.js +0 -28
  138. package/dist/sandbox-CNVAQDA7.js +0 -23
  139. package/dist/scrollback-LUKLIBVI.js.map +0 -1
  140. package/dist/settings-loader-M2QP5TX2.js +0 -21
  141. package/patches/ink+6.8.0.patch +0 -13
  142. package/scripts/apply-ink-patch.mjs +0 -29
  143. package/scripts/ink-patch.mjs +0 -59
  144. package/scripts/verify-ink-patch.mjs +0 -11
  145. /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-H7UNAEHS.js.map} +0 -0
  146. /package/dist/{context-ZC5IFEAX.js.map → command-approvals-VBIU6BLM.js.map} +0 -0
  147. /package/dist/{hook-approvals-CTUFKEJT.js.map → context-CBOBDY5Q.js.map} +0 -0
  148. /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-ZVT4V7IM.js.map} +0 -0
  149. /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-S77OVTHH.js.map} +0 -0
  150. /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-HJ2VDYMC.js.map} +0 -0
  151. /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-RNB5LEVE.js.map} +0 -0
  152. /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-DZUT5HUH.js.map} +0 -0
  153. /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-MTSQ5JDJ.js.map} +0 -0
  154. /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-2OB54YST.js.map} +0 -0
  155. /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-YLV6UC5W.js.map} +0 -0
  156. /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-DHOKF3W2.js.map} +0 -0
  157. /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-WRYTZH3D.js.map} +0 -0
@@ -5,57 +5,82 @@ import {
5
5
  runHostReview,
6
6
  toElicitationFields,
7
7
  validateElicitationContent
8
- } from "./chunk-V4BOYR52.js";
8
+ } from "./chunk-RFJ6Q6LE.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
- shouldCompact,
23
+ separateInlineReasoning,
20
24
  takeAgentCompletionBatch,
21
- usagePressureTokens
22
- } from "./chunk-XIFFTATU.js";
25
+ uniqueAgentDisplayName
26
+ } from "./chunk-5YHTKWJN.js";
23
27
  import {
28
+ consoleMcpDiagnosticSink,
29
+ resolveMcpServerList
30
+ } from "./chunk-Y2VHN7TC.js";
31
+ import {
32
+ TRUSTED_SETTINGS_LOCATION,
33
+ compactionGate,
24
34
  createBuiltinCommandRegistry,
25
35
  estimateUsageCost,
26
36
  getPackageVersion,
27
37
  planWasLost,
28
38
  resolveContextLimit,
29
- resolvePermissionMode
30
- } from "./chunk-A7JLBK2Y.js";
39
+ resolvePermissionMode,
40
+ usageAtGate,
41
+ usagePressureTokens
42
+ } from "./chunk-IME7R2UI.js";
43
+ import {
44
+ formatIgnoredWorkspaceSandboxKey,
45
+ ignoredWorkspaceSandboxKeys,
46
+ loadSettingsFile,
47
+ settingsLayerPaths,
48
+ workspaceLayerThatEnabledSandbox
49
+ } from "./chunk-W3XFHEF4.js";
50
+ import {
51
+ onAbort,
52
+ seedObservationLedger,
53
+ throwIfAborted
54
+ } from "./chunk-J62VZYOB.js";
31
55
  import {
32
56
  commandEnforcementContext,
33
57
  discoverCommands,
34
58
  parseSlashInput,
35
59
  resolveCommandBody
36
- } from "./chunk-E75IPST5.js";
60
+ } from "./chunk-EFJ2NH6E.js";
37
61
  import {
38
- consoleMcpDiagnosticSink,
39
- resolveMcpServerList
40
- } from "./chunk-3U5IBM24.js";
62
+ buildChildEnv
63
+ } from "./chunk-XHAO3K7I.js";
64
+ import {
65
+ collectDeclaredDirectories,
66
+ partitionProjectDirectories
67
+ } from "./chunk-525CAZPY.js";
41
68
  import {
42
- observationKey,
69
+ getPrimaryArg,
43
70
  toolFailure,
71
+ toolResultErrorMessage,
44
72
  toolSuccess
45
- } from "./chunk-5GDZ22YP.js";
46
- import {
47
- loadSettingsFile
48
- } from "./chunk-BTZZUCLI.js";
73
+ } from "./chunk-224FJZLH.js";
49
74
  import {
50
75
  partitionProjectAllowRules
51
- } from "./chunk-WKBO5R4O.js";
76
+ } from "./chunk-KBQS35OV.js";
52
77
  import {
53
78
  collectDeclaredHooks,
54
79
  partitionProjectHooks
55
- } from "./chunk-ANSJPYGI.js";
80
+ } from "./chunk-SOW4WRQP.js";
56
81
  import {
57
82
  resolveBookHome
58
- } from "./chunk-24AXE6SP.js";
83
+ } from "./chunk-ZPSZZUWF.js";
59
84
 
60
85
  // src/run-status.ts
61
86
  import {
@@ -326,7 +351,7 @@ async function resolvePrintCommand(input, env, deps = {}) {
326
351
  const commands = deps.commands ?? discoverCommands(env.config.workspace);
327
352
  const context = printBuiltinContext(env, commands);
328
353
  const resolved = builtins.resolve(parsed.name, context);
329
- if (resolved) {
354
+ if (resolved && !resolved.definition.tuiOnly) {
330
355
  if (!resolved.definition.nonInteractive) {
331
356
  throw unsupported(
332
357
  parsed.name,
@@ -406,12 +431,17 @@ var EVENT_TYPES = /* @__PURE__ */ new Set([
406
431
  "agent_message",
407
432
  "agent_completion",
408
433
  "agent_permission",
434
+ "agent_notice",
409
435
  "evidence_update",
436
+ "attempt_discarded",
437
+ "retry",
410
438
  "agent_apply",
411
439
  "hook_event",
412
440
  "mode_change",
413
441
  "plan_approval",
442
+ "command_result",
414
443
  "prompt_suggestions",
444
+ "notice",
415
445
  "error",
416
446
  "result",
417
447
  "done"
@@ -490,6 +520,66 @@ function createStreamParser(onEvent, options = {}) {
490
520
  };
491
521
  }
492
522
 
523
+ // src/reader-gone.ts
524
+ import { Writable } from "stream";
525
+ var READER_GONE_CODES = /* @__PURE__ */ new Set([
526
+ "EPIPE",
527
+ "EOF",
528
+ "ERR_STREAM_DESTROYED",
529
+ "ECONNRESET"
530
+ ]);
531
+ function isReaderGoneError(error) {
532
+ const code = error?.code;
533
+ return code !== void 0 && READER_GONE_CODES.has(code);
534
+ }
535
+ var READER_FLUSH_CAP_MS = 1e3;
536
+ function createReaderFlush(stdout, options = {}) {
537
+ if (!(stdout instanceof Writable)) return void 0;
538
+ const capMs = options.capMs ?? READER_FLUSH_CAP_MS;
539
+ const controller = new AbortController();
540
+ let pending = 0;
541
+ let slow = false;
542
+ const waiters = /* @__PURE__ */ new Set();
543
+ const drained = () => {
544
+ if (pending > 0) return;
545
+ slow = false;
546
+ for (const release of [...waiters]) release();
547
+ waiters.clear();
548
+ };
549
+ return {
550
+ signal: controller.signal,
551
+ write(line) {
552
+ pending++;
553
+ stdout.write(line, (error) => {
554
+ if (error && isReaderGoneError(error)) controller.abort();
555
+ pending--;
556
+ drained();
557
+ });
558
+ },
559
+ hold(signal) {
560
+ if (pending === 0 || slow || signal?.aborted) return Promise.resolve();
561
+ return new Promise((resolve) => {
562
+ let settled = false;
563
+ const finish = () => {
564
+ if (settled) return;
565
+ settled = true;
566
+ clearTimeout(timer);
567
+ signal?.removeEventListener("abort", finish);
568
+ waiters.delete(finish);
569
+ resolve();
570
+ };
571
+ const timer = setTimeout(() => {
572
+ slow = true;
573
+ finish();
574
+ }, capMs);
575
+ waiters.add(finish);
576
+ signal?.addEventListener("abort", finish, { once: true });
577
+ if (signal?.aborted) finish();
578
+ });
579
+ }
580
+ };
581
+ }
582
+
493
583
  // src/headless.ts
494
584
  function carriedCostUsd(usage, models) {
495
585
  let worst = null;
@@ -509,17 +599,63 @@ async function readPipedPrompt(stream) {
509
599
  const prompt = chunks.join("").trim();
510
600
  return prompt || void 0;
511
601
  }
602
+ function destroyOnAbort(stream, signal) {
603
+ if (!signal) return () => {
604
+ };
605
+ return onAbort(signal, () => {
606
+ const reason = signal.reason instanceof Error ? signal.reason : new Error("Aborted");
607
+ stream.destroy?.(reason);
608
+ });
609
+ }
610
+ async function raceAbort(promise, signal) {
611
+ if (!signal) {
612
+ await promise;
613
+ return true;
614
+ }
615
+ if (signal.aborted) return false;
616
+ let release;
617
+ const aborted = new Promise((resolve) => {
618
+ release = onAbort(signal, () => resolve(false));
619
+ });
620
+ try {
621
+ return await Promise.race([promise.then(() => true), aborted]);
622
+ } finally {
623
+ release?.();
624
+ }
625
+ }
512
626
  async function runHeadless(config, registry, opts) {
513
627
  const mode = resolvePermissionMode(config.settings, opts.mode);
514
628
  const stdout = opts.stdout ?? process.stdout;
629
+ const flush = opts.outputFormat === "stream-json" ? createReaderFlush(stdout) : void 0;
630
+ const runSignal = flush && opts.signal ? AbortSignal.any([opts.signal, flush.signal]) : flush?.signal ?? opts.signal;
515
631
  const emit = (obj) => {
516
632
  if (obj && typeof obj === "object" && "type" in obj) {
517
633
  opts.onEvent?.(obj);
518
634
  }
519
- stdout.write(JSON.stringify(obj) + "\n");
635
+ const line = JSON.stringify(obj) + "\n";
636
+ if (flush) flush.write(line);
637
+ else stdout.write(line);
638
+ };
639
+ const eventState = {
640
+ childLabels: /* @__PURE__ */ new Map(),
641
+ errorReported: false
520
642
  };
521
643
  let disposeCrashHandlers;
522
- const agentSession = new AgentSession();
644
+ let sessionDisposed = false;
645
+ let startedSessionId;
646
+ const agentSession = new AgentSession({
647
+ // The transcript is the rendered conversation and still carries the tool
648
+ // records compaction summarized out of the context history, so prefer it
649
+ // when a resume supplies both.
650
+ runtime: new SessionRuntime({
651
+ history: opts.transcript?.length ? opts.transcript : opts.history
652
+ })
653
+ });
654
+ const disposeSession = () => {
655
+ if (sessionDisposed) return;
656
+ sessionDisposed = true;
657
+ agentSession.dispose("headless_complete");
658
+ };
523
659
  const runtime = agentSession.getRuntime();
524
660
  runtime.todos.push(...normalizePersistedTodos(opts.plan?.todos));
525
661
  if (opts.plan?.tasks?.length) runtime.tasks.push(...opts.plan.tasks);
@@ -544,7 +680,11 @@ async function runHeadless(config, registry, opts) {
544
680
  let sessionName = opts.sessionName;
545
681
  let sessionCreated = opts.sessionCreated === true;
546
682
  if (store && !sessionId) {
547
- sessionId = store.create({ cwd: config.workspace, name: sessionName });
683
+ sessionId = store.create({
684
+ cwd: config.workspace,
685
+ name: sessionName,
686
+ driver: config.sessionDriver
687
+ });
548
688
  sessionCreated = true;
549
689
  }
550
690
  if (store && sessionId && !sessionName) {
@@ -567,6 +707,7 @@ async function runHeadless(config, registry, opts) {
567
707
  onHookEvent: createHookEventHandler(opts, emit)
568
708
  }
569
709
  );
710
+ startedSessionId = sessionId;
570
711
  }
571
712
  const runtimeSessionId = sessionId ?? crypto.randomUUID();
572
713
  const statusWriter = new RunStatusWriter({
@@ -577,7 +718,10 @@ async function runHeadless(config, registry, opts) {
577
718
  startedAt: Date.now()
578
719
  });
579
720
  disposeCrashHandlers = statusWriter.installCrashHandlers();
580
- const permissionRequired = async () => "deny";
721
+ const permissionRequired = async () => ({
722
+ result: "deny",
723
+ reason: "no_approver"
724
+ });
581
725
  const userQuestionStatus = opts.onUserQuestionRequired ? "pending" : "unavailable";
582
726
  const askUser = async (request, context) => {
583
727
  try {
@@ -596,14 +740,26 @@ async function runHeadless(config, registry, opts) {
596
740
  if (mode === "bypassPermissions") return "approve";
597
741
  if (!opts.onUserQuestionRequired) return planStopDecision("approval_unavailable");
598
742
  const request = buildPlanApprovalQuestion(plan, crypto.randomUUID());
599
- emitAgentEvent({ type: "user_question", request, status: userQuestionStatus }, opts, emit);
600
- const response = await askUser(request, { signal: opts.signal });
601
- emitAgentEvent({ type: "user_question_result", requestId: request.id, response }, opts, emit);
743
+ emitAgentEvent(
744
+ { type: "user_question", request, status: userQuestionStatus },
745
+ opts,
746
+ emit,
747
+ eventState
748
+ );
749
+ const response = await askUser(request, { signal: runSignal });
750
+ emitAgentEvent(
751
+ { type: "user_question_result", requestId: request.id, response },
752
+ opts,
753
+ emit,
754
+ eventState
755
+ );
602
756
  return planApprovalFromUserQuestionResponse(request, response);
603
757
  };
604
758
  const planNotApplied = {};
605
759
  let lastUsage = null;
606
760
  let lastOutcome = null;
761
+ let mayReclassify = true;
762
+ let finalRunOpeningId;
607
763
  const runResults = [];
608
764
  const recordRunResult = (result2) => {
609
765
  const existing = runResults.findIndex(
@@ -668,15 +824,7 @@ async function runHeadless(config, registry, opts) {
668
824
  const contextHistory = [...opts.history];
669
825
  const transcript = [...opts.transcript ?? opts.history];
670
826
  const compactBoundaries = [...opts.compactBoundaries ?? []];
671
- for (const message of transcript) {
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
- }
827
+ seedObservationLedger(runtime.fileObservationLedger, transcript);
680
828
  const loopConfig = {
681
829
  ...config,
682
830
  maxTurns: opts.maxTurns ?? config.maxTurns
@@ -688,7 +836,7 @@ async function runHeadless(config, registry, opts) {
688
836
  lastOutcome = event.outcome;
689
837
  onOutcome(event.outcome);
690
838
  }
691
- emitAgentEvent(event, opts, emit);
839
+ emitAgentEvent(event, opts, emit, eventState);
692
840
  },
693
841
  // No header rewrite at turn boundaries: load() derives updatedAt from
694
842
  // the last appended record, so the on-disk header is never authoritative.
@@ -702,6 +850,15 @@ async function runHeadless(config, registry, opts) {
702
850
  emit({ type: "mode_change", mode: newMode });
703
851
  }
704
852
  },
853
+ /**
854
+ * The hold at a tool boundary. The `tool_use` records for the calls about
855
+ * to run have been written by now, so waiting for them to be taken is
856
+ * what turns a reader that has gone into an abort BEFORE the call runs
857
+ * (#340) instead of one call later. Free when the reader has caught up,
858
+ * bounded when it has not, and absent — so a no-op — for every other
859
+ * output format and for a host sink with no callback to answer.
860
+ */
861
+ beforeToolExecution: flush ? () => flush.hold(runSignal) : void 0,
705
862
  onPlanApprovalRequired: async (plan) => {
706
863
  const decision = await decidePlanApproval(plan);
707
864
  if (typeof decision === "object" && decision.decision === "stop") {
@@ -719,6 +876,24 @@ async function runHeadless(config, registry, opts) {
719
876
  },
720
877
  onUserQuestionRequired: askUser,
721
878
  userQuestionStatus,
879
+ onRetry: (phase, attempt, max, delayMs, reason) => {
880
+ const limit = max < 0 ? null : max;
881
+ if (opts.outputFormat === "stream-json") {
882
+ emit({
883
+ type: "retry",
884
+ phase,
885
+ attempt,
886
+ max: limit,
887
+ delay_ms: delayMs,
888
+ ...reason ? { reason } : {}
889
+ });
890
+ } else if (opts.quiet !== true) {
891
+ process.stderr.write(
892
+ `retry: ${phase} attempt ${attempt}${limit !== null ? `/${limit}` : ""} in ${Math.round(delayMs / 1e3)}s${reason ? ` (${reason})` : ""}
893
+ `
894
+ );
895
+ }
896
+ },
722
897
  onHookEvent: createHookEventHandler(opts, emit),
723
898
  onUsage: (nextUsage) => {
724
899
  lastUsage = nextUsage;
@@ -739,7 +914,50 @@ async function runHeadless(config, registry, opts) {
739
914
  ...hints,
740
915
  trigger: "auto",
741
916
  preContextTokens: usage ? usagePressureTokens(usage) : void 0,
742
- signal: opts.signal,
917
+ signal: runSignal,
918
+ onHookEvent: createHookEventHandler(opts, emit)
919
+ }
920
+ });
921
+ if (outcome2.result.status === "compacted") {
922
+ emitCompactBoundary(opts.outputFormat, emit, outcome2.result);
923
+ lastUsage = null;
924
+ }
925
+ return outcome2.result;
926
+ },
927
+ // Deferred compaction (plans/async-compaction-plan.md): same plumbing,
928
+ // reducer on the snapshot first, judge and record at the next boundary.
929
+ prepareCompact: async (snapshot, usage, hints) => {
930
+ const outcome2 = await agentSession.prepareCompact({
931
+ config,
932
+ history: snapshot,
933
+ compactBoundaries,
934
+ sessionId,
935
+ transcriptOrdinal: transcript.length,
936
+ runContext,
937
+ runtime,
938
+ options: {
939
+ ...hints,
940
+ trigger: "auto",
941
+ preContextTokens: usage ? usagePressureTokens(usage) : void 0,
942
+ signal: hints?.signal ?? runSignal,
943
+ onHookEvent: createHookEventHandler(opts, emit)
944
+ }
945
+ });
946
+ return outcome2.status === "prepared" ? outcome2.prepared : outcome2.result;
947
+ },
948
+ commitCompact: async (prepared, history, hints) => {
949
+ const outcome2 = await agentSession.commitCompact({
950
+ prepared,
951
+ history,
952
+ config,
953
+ sessionId,
954
+ transcriptOrdinal: transcript.length,
955
+ runContext,
956
+ runtime,
957
+ timelineStore: store,
958
+ onCommitted: (_result, boundary) => compactBoundaries.push(boundary),
959
+ options: {
960
+ signal: hints?.signal ?? runSignal,
743
961
  onHookEvent: createHookEventHandler(opts, emit)
744
962
  }
745
963
  });
@@ -752,12 +970,19 @@ async function runHeadless(config, registry, opts) {
752
970
  onAssistantMessageComplete: (message) => {
753
971
  if (!transcript.some((item) => item.id === message.id)) transcript.push(message);
754
972
  if (opts.outputFormat === "stream-json" && message.content) {
755
- emit({ type: "assistant", text: message.content, complete: true });
973
+ emit({
974
+ type: "assistant",
975
+ text: message.content,
976
+ complete: true,
977
+ ...message.hostNotice ? { host_notice: true } : {}
978
+ });
756
979
  }
757
980
  }
758
981
  });
759
982
  const runParentTurn = async (prompt, displayMessage, userMessage, runContext, commandContext) => {
983
+ eventState.errorReported = false;
760
984
  let runOutcome;
985
+ finalRunOpeningId = userMessage.id;
761
986
  lastUsage = null;
762
987
  let updated;
763
988
  try {
@@ -770,7 +995,7 @@ async function runHeadless(config, registry, opts) {
770
995
  mode,
771
996
  sessionId: runtimeSessionId,
772
997
  timelineStore: store,
773
- signal: opts.signal,
998
+ signal: runSignal,
774
999
  callbacks: createRunCallbacks(runContext, (outcome2) => {
775
1000
  runOutcome = outcome2;
776
1001
  }),
@@ -783,6 +1008,9 @@ async function runHeadless(config, registry, opts) {
783
1008
  userFileObservations: userMessage.fileObservations,
784
1009
  userAttachments: userMessage.attachments,
785
1010
  userMessageKind: userMessage.kind,
1011
+ // The answer walk is bounded by `openingMessageId`, so a derived opening
1012
+ // message no longer lets it walk past the run.
1013
+ userMessageDerived: userMessage.derivedContent,
786
1014
  manageSessionHooks: sessionId ? false : void 0,
787
1015
  // Completion messages are host-generated notifications, not user
788
1016
  // prompts. Keep UserPromptSubmit hooks from rewriting or blocking them.
@@ -832,7 +1060,7 @@ async function runHeadless(config, registry, opts) {
832
1060
  config,
833
1061
  mode,
834
1062
  sessionId: runtimeSessionId,
835
- signal: opts.signal,
1063
+ signal: runSignal,
836
1064
  agents: {
837
1065
  // Built only when an agent-backed command (`/review`) asks for it,
838
1066
  // and attached to this session's runtime so it is disposed with the
@@ -843,7 +1071,7 @@ async function runHeadless(config, registry, opts) {
843
1071
  permissionMode: mode,
844
1072
  eventSink: (event) => {
845
1073
  if (event.type === "agent_result") recordManagedRunResult(event.agent);
846
- emitAgentEvent(event, opts, emit);
1074
+ emitAgentEvent(event, opts, emit, eventState);
847
1075
  },
848
1076
  hookEventSink: createHookEventHandler(opts, emit)
849
1077
  }),
@@ -854,7 +1082,17 @@ async function runHeadless(config, registry, opts) {
854
1082
  let commandUsage = null;
855
1083
  const prompts = [];
856
1084
  if (opts.inputFormat === "text") {
857
- const prompt = opts.prompt ?? await readPipedPrompt(opts.stdin ?? process.stdin);
1085
+ let prompt = opts.prompt;
1086
+ if (prompt === void 0) {
1087
+ const stream = opts.stdin ?? process.stdin;
1088
+ const release = destroyOnAbort(stream, runSignal);
1089
+ try {
1090
+ prompt = await readPipedPrompt(stream);
1091
+ } finally {
1092
+ release();
1093
+ }
1094
+ throwIfAborted(runSignal);
1095
+ }
858
1096
  if (!prompt) {
859
1097
  throw new Error(
860
1098
  "print mode requires a prompt: pass one to -p, pipe one on stdin, or use --input-format stream-json"
@@ -873,9 +1111,15 @@ async function runHeadless(config, registry, opts) {
873
1111
  onDiagnostic: (diagnostic) => diagnostics.push(diagnostic)
874
1112
  }
875
1113
  );
876
- for await (const chunk of stream) {
877
- parser.feed(chunk);
1114
+ const release = destroyOnAbort(stream, runSignal);
1115
+ try {
1116
+ for await (const chunk of stream) {
1117
+ parser.feed(chunk);
1118
+ }
1119
+ } finally {
1120
+ release();
878
1121
  }
1122
+ throwIfAborted(runSignal);
879
1123
  parser.flush();
880
1124
  if (diagnostics.length > 0) throw new Error(diagnostics[0]?.message);
881
1125
  if (opts.prompt) prompts.unshift(opts.prompt);
@@ -896,6 +1140,9 @@ async function runHeadless(config, registry, opts) {
896
1140
  resumedFromRunId: runResults.length === 0 ? opts.resumedFromRunId ?? (opts.history.length > 0 ? [...opts.history].reverse().find((message) => message.role === "user")?.id : void 0) : void 0
897
1141
  });
898
1142
  runtime.runAccounting.startRoot(runContext, opts.maxBudgetUsd);
1143
+ if (store && sessionId) {
1144
+ agentSession.trackRunUsage(runContext.rootRunId, { sessionId, timelineStore: store });
1145
+ }
899
1146
  if (carriedSpend) {
900
1147
  runtime.runAccounting.seedRoot(runContext.rootRunId, carriedSpend);
901
1148
  }
@@ -932,6 +1179,12 @@ async function runHeadless(config, registry, opts) {
932
1179
  } else if (opts.outputFormat === "stream-json" && (dispatch.output !== void 0 || dispatch.data !== void 0)) {
933
1180
  emit({ type: "command_result", ...handled });
934
1181
  }
1182
+ carriedSpend = {
1183
+ usage: accounting.inclusiveUsage,
1184
+ costUsd: accounting.inclusiveCostUsd,
1185
+ persistedUsage: runtime.runAccounting.persistedUsage(runContext.rootRunId),
1186
+ fromRootRunId: runContext.rootRunId
1187
+ };
935
1188
  continue;
936
1189
  }
937
1190
  const prompt = dispatch.prompt;
@@ -948,9 +1201,19 @@ async function runHeadless(config, registry, opts) {
948
1201
  kind: "conversation",
949
1202
  timestamp: Date.now()
950
1203
  };
1204
+ if (config.autoCompactEnabled !== false && usageAtGate(lastUsage, config)) {
1205
+ const pressure = usagePressureTokens(lastUsage);
1206
+ const gate = compactionGate(config);
1207
+ const masked = maskAtGate(contextHistory, gate, pressure, gate);
1208
+ if (masked.maskedCount > 0) {
1209
+ contextHistory.length = 0;
1210
+ contextHistory.push(...masked.history);
1211
+ if (masked.underLine) lastUsage = null;
1212
+ }
1213
+ }
951
1214
  const contextLimit = resolveContextLimit(config);
952
1215
  const hostCompactAttemptKey = `${usagePressureTokens(lastUsage)}:${contextHistory.length}`;
953
- if (config.autoCompactEnabled !== false && contextLimit != null && shouldCompact(lastUsage, contextLimit) && lastHostCompactAttemptKey !== hostCompactAttemptKey) {
1216
+ if (config.autoCompactEnabled !== false && contextLimit != null && usageAtGate(lastUsage, config) && lastHostCompactAttemptKey !== hostCompactAttemptKey) {
954
1217
  lastHostCompactAttemptKey = hostCompactAttemptKey;
955
1218
  try {
956
1219
  const outcome2 = await agentSession.compact({
@@ -971,7 +1234,7 @@ async function runHeadless(config, registry, opts) {
971
1234
  options: {
972
1235
  trigger: "auto",
973
1236
  preContextTokens: usagePressureTokens(lastUsage),
974
- signal: opts.signal,
1237
+ signal: runSignal,
975
1238
  upcomingUserIntent: prompt,
976
1239
  onHookEvent: createHookEventHandler(opts, emit)
977
1240
  }
@@ -991,7 +1254,7 @@ async function runHeadless(config, registry, opts) {
991
1254
  timelineStore: store && sessionId ? store : void 0,
992
1255
  expandShellInput: false,
993
1256
  runtime,
994
- signal: opts.signal
1257
+ signal: runSignal
995
1258
  });
996
1259
  sessionName = recorded.sessionName;
997
1260
  const contextMessage = recorded.contextMessage;
@@ -1001,7 +1264,12 @@ async function runHeadless(config, registry, opts) {
1001
1264
  const spentSoFar = runtime.runAccounting.snapshotRoot(runContext.rootRunId);
1002
1265
  carriedSpend = {
1003
1266
  usage: spentSoFar.inclusiveUsage,
1004
- costUsd: spentSoFar.inclusiveCostUsd
1267
+ costUsd: spentSoFar.inclusiveCostUsd,
1268
+ persistedUsage: runtime.runAccounting.persistedUsage(runContext.rootRunId),
1269
+ // Naming the root the totals came from: the next root's first record
1270
+ // carries this one's remainder, so this one is closed rather than left to
1271
+ // write the same tokens a second time at the end of the session (#336).
1272
+ fromRootRunId: runContext.rootRunId
1005
1273
  };
1006
1274
  if (planNotApplied.outcome) break;
1007
1275
  }
@@ -1013,7 +1281,11 @@ async function runHeadless(config, registry, opts) {
1013
1281
  )
1014
1282
  );
1015
1283
  while (true) {
1016
- await managedAgentManager.waitForIdle();
1284
+ const idle = !runSignal?.aborted && await raceAbort(managedAgentManager.waitForIdle(), runSignal);
1285
+ if (!idle) {
1286
+ if (mayReclassify) lastOutcome = classifyAbortReason(runSignal?.reason, true);
1287
+ break;
1288
+ }
1017
1289
  const pending = (await managedAgentManager.listPendingCompletions()).filter(
1018
1290
  (notification) => notification.parentSessionId === runtimeSessionId
1019
1291
  );
@@ -1055,7 +1327,7 @@ async function runHeadless(config, registry, opts) {
1055
1327
  timelineStore: store && sessionId ? store : void 0,
1056
1328
  expandShellInput: false,
1057
1329
  runtime,
1058
- signal: opts.signal
1330
+ signal: runSignal
1059
1331
  });
1060
1332
  transcript.push(userMessage);
1061
1333
  const parentContext = batch[0]?.rootRunId ? rootContexts.get(batch[0].rootRunId) : void 0;
@@ -1079,6 +1351,10 @@ async function runHeadless(config, registry, opts) {
1079
1351
  }));
1080
1352
  runResults.sort((left, right) => left.context.startedAt - right.context.startedAt);
1081
1353
  const reportedUsage = lastUsage ?? commandUsage;
1354
+ mayReclassify = lastOutcome?.status !== "failed";
1355
+ const lastText = finalRunOpeningId === void 0 ? "" : finalAnswerText(contextHistory, finalRunOpeningId);
1356
+ const inline = separateInlineReasoning(lastText);
1357
+ const answer = inline.found ? inline.content.trimEnd() : lastText;
1082
1358
  const result = {
1083
1359
  messages: contextHistory,
1084
1360
  transcript,
@@ -1089,12 +1365,12 @@ async function runHeadless(config, registry, opts) {
1089
1365
  runs: runResults,
1090
1366
  sessionId,
1091
1367
  plan: planNotApplied.outcome,
1092
- commandResults
1368
+ commandResults,
1369
+ answer
1093
1370
  };
1094
1371
  if (opts.jsonSchema) {
1095
- const text = lastAssistantText(contextHistory);
1096
1372
  try {
1097
- result.structured = JSON.parse(text);
1373
+ result.structured = JSON.parse(answer);
1098
1374
  } catch {
1099
1375
  result.structuredError = "Failed to parse JSON from assistant output";
1100
1376
  }
@@ -1105,7 +1381,7 @@ async function runHeadless(config, registry, opts) {
1105
1381
  config,
1106
1382
  registry,
1107
1383
  contextHistory,
1108
- opts.signal,
1384
+ runSignal,
1109
1385
  store?.readImageAttachment ? (attachment) => store.readImageAttachment(runtimeSessionId, attachment) : void 0
1110
1386
  );
1111
1387
  if (suggestions.length > 0) {
@@ -1122,8 +1398,7 @@ async function runHeadless(config, registry, opts) {
1122
1398
  ${stopped.message}
1123
1399
  `);
1124
1400
  } else {
1125
- const last = lastAssistantText(contextHistory);
1126
- if (last) stdout.write(last + "\n");
1401
+ if (answer) stdout.write(answer + "\n");
1127
1402
  }
1128
1403
  } else if (opts.outputFormat === "json") {
1129
1404
  emit({
@@ -1137,15 +1412,25 @@ ${stopped.message}
1137
1412
  structured: result.structured,
1138
1413
  structuredError: result.structuredError,
1139
1414
  plan: result.plan,
1140
- commandResults
1415
+ commandResults,
1416
+ answer
1141
1417
  }
1142
1418
  });
1143
1419
  } else if (opts.outputFormat === "stream-json") {
1144
1420
  emit({
1145
1421
  type: "result",
1422
+ // At the top level, beside `stopReason`, because that is where a
1423
+ // supervised loop reads the outcome from: `jq -e 'select(.type=="result")
1424
+ // | .outcome.reason=="objective_complete"'` was written against this
1425
+ // record and read a null. `result.outcome` stays for hosts already
1426
+ // reading it there.
1146
1427
  stopReason: outcome.reason,
1428
+ outcome,
1147
1429
  result: {
1148
- messages: contextHistory,
1430
+ // Off by default: a long run's conversation is half a megabyte of JSONL
1431
+ // on one line, which every reader has to buffer before it can look at
1432
+ // the field it came for. `--include-result-messages` asks for it back.
1433
+ ...opts.includeResultMessages ? { messages: contextHistory } : {},
1149
1434
  usage: reportedUsage,
1150
1435
  accounting: result.accounting,
1151
1436
  outcome,
@@ -1154,27 +1439,41 @@ ${stopped.message}
1154
1439
  structured: result.structured,
1155
1440
  structuredError: result.structuredError,
1156
1441
  plan: result.plan,
1157
- commandResults
1442
+ commandResults,
1443
+ answer
1158
1444
  }
1159
1445
  });
1160
1446
  }
1447
+ if (opts.outputFormat !== "stream-json" && opts.runSource !== "sdk" && outcome.status === "failed" && outcome.message && !answer && !planNotApplied.outcome && !eventState.errorReported) {
1448
+ process.stderr.write(`error: ${outcome.message}
1449
+ `);
1450
+ }
1161
1451
  if (sessionId) {
1162
- await agentSession.endLifecycle(config, sessionId, "completion", {
1163
- onHookEvent: createHookEventHandler(opts, emit)
1452
+ const sessionEndReason = outcome.status === "completed" ? "completion" : outcome.status === "failed" ? "error" : outcome.status === "cancelled" || runSignal?.aborted ? "aborted" : "completion";
1453
+ disposeSession();
1454
+ await agentSession.endLifecycle(config, sessionId, sessionEndReason, {
1455
+ onHookEvent: createHookEventHandler(opts, emit),
1456
+ endOutcome: outcome
1164
1457
  });
1165
1458
  }
1166
1459
  return result;
1460
+ } catch (error) {
1461
+ if (startedSessionId) {
1462
+ disposeSession();
1463
+ await agentSession.endLifecycle(config, startedSessionId, runSignal?.aborted ? "aborted" : "error", {
1464
+ onHookEvent: createHookEventHandler(opts, emit),
1465
+ endOutcome: runSignal?.aborted ? classifyAbortReason(runSignal.reason, false) : createTerminalOutcome("failed", "runtime_error", { partialOutput: false })
1466
+ }).catch((hookError) => {
1467
+ console.warn(
1468
+ `SessionEnd hook failed: ${hookError instanceof Error ? hookError.message : String(hookError)}`
1469
+ );
1470
+ });
1471
+ }
1472
+ throw error;
1167
1473
  } finally {
1168
1474
  disposeCrashHandlers?.();
1169
- agentSession.dispose("headless_complete");
1170
- }
1171
- }
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;
1475
+ disposeSession();
1176
1476
  }
1177
- return "";
1178
1477
  }
1179
1478
  function planApprovalStatus(decision) {
1180
1479
  return typeof decision === "string" ? decision : decision.decision;
@@ -1183,16 +1482,34 @@ function createHookEventHandler(opts, emit) {
1183
1482
  if (!opts.includeHookEvents || opts.outputFormat !== "stream-json") return void 0;
1184
1483
  return (event, payload) => emit({ type: "hook_event", event, ...payload });
1185
1484
  }
1186
- function emitAgentEvent(event, opts, emit) {
1485
+ function emitAgentEvent(event, opts, emit, state) {
1187
1486
  if (event.type === "terminal") return;
1188
1487
  if (event.type === "agent_text_delta" && opts.forwardSubagentText !== true) return;
1189
1488
  opts.onAgentEvent?.(event);
1489
+ if (event.type === "notice") {
1490
+ if (opts.outputFormat === "stream-json") emit({ type: "notice", message: event.message });
1491
+ else console.warn(event.message);
1492
+ return;
1493
+ }
1494
+ if (event.type === "agent_notice") {
1495
+ if (opts.outputFormat === "stream-json") {
1496
+ emit({ type: "agent_notice", agentId: event.agentId, message: event.message });
1497
+ } else {
1498
+ process.stderr.write(`notice: ${progressLine(event.message, PROGRESS_NOTICE_MAX)}
1499
+ `);
1500
+ }
1501
+ return;
1502
+ }
1190
1503
  if (event.type === "error") {
1504
+ state.errorReported = true;
1191
1505
  if (opts.outputFormat === "stream-json") emit({ type: "error", error: event.error });
1192
1506
  else process.stderr.write(`error: ${event.error}
1193
1507
  `);
1194
1508
  return;
1195
1509
  }
1510
+ if (opts.outputFormat === "text" && opts.quiet !== true) {
1511
+ writeTextProgress(event, opts, state);
1512
+ }
1196
1513
  if (opts.outputFormat !== "stream-json") return;
1197
1514
  switch (event.type) {
1198
1515
  case "run_started":
@@ -1249,6 +1566,67 @@ function emitAgentEvent(event, opts, emit) {
1249
1566
  break;
1250
1567
  }
1251
1568
  }
1569
+ var PROGRESS_ARG_MAX = 120;
1570
+ var PROGRESS_ERROR_MAX = 160;
1571
+ var PROGRESS_NOTICE_MAX = 400;
1572
+ function progressLine(text, max) {
1573
+ const first = text.trim().split(/\r?\n/, 1)[0];
1574
+ const printable = Array.from(first, (char) => {
1575
+ const code = char.codePointAt(0) ?? 0;
1576
+ const control = code < 32 || code >= 127 && code <= 159 || code === 1564 || code === 8206 || code === 8207 || code >= 8232 && code <= 8238 || code >= 8294 && code <= 8297;
1577
+ return control ? " " : char;
1578
+ }).join("").trimEnd();
1579
+ const chars = Array.from(printable);
1580
+ return chars.length > max ? `${chars.slice(0, max - 1).join("")}\u2026` : printable;
1581
+ }
1582
+ function writeTextProgress(event, opts, state) {
1583
+ if (event.type === "tool_use") {
1584
+ process.stderr.write(`${toolCallProgress(event.toolCall)}
1585
+ `);
1586
+ return;
1587
+ }
1588
+ if (event.type === "tool_result" && opts.verbose === true) {
1589
+ process.stderr.write(` ${toolResultProgress(event.toolResult)}
1590
+ `);
1591
+ return;
1592
+ }
1593
+ if (event.type === "agent_start") {
1594
+ if (!state.childLabels.has(event.agent.id)) {
1595
+ state.childLabels.set(
1596
+ event.agent.id,
1597
+ uniqueAgentDisplayName(event.agent.profile ?? event.agent.name, state.childLabels.values())
1598
+ );
1599
+ }
1600
+ return;
1601
+ }
1602
+ if (event.type === "agent_activity") {
1603
+ writeChildToolProgress(event.agentId, event.activity, opts, state);
1604
+ }
1605
+ }
1606
+ function writeChildToolProgress(agentId, activity, opts, state) {
1607
+ if (activity.kind !== "tool" || !activity.toolCall) return;
1608
+ if (activity.status === "running") {
1609
+ const child = progressLine(state.childLabels.get(agentId) ?? "agent", PROGRESS_ARG_MAX);
1610
+ process.stderr.write(` [${child}] ${toolCallProgress(activity.toolCall)}
1611
+ `);
1612
+ return;
1613
+ }
1614
+ if (opts.verbose === true && activity.result) {
1615
+ process.stderr.write(` ${toolResultProgress(activity.result)}
1616
+ `);
1617
+ }
1618
+ }
1619
+ function toolCallProgress(call) {
1620
+ const arg = progressLine(getPrimaryArg(call.arguments), PROGRESS_ARG_MAX);
1621
+ return `[${progressLine(call.name, PROGRESS_ARG_MAX)}]${arg ? ` ${arg}` : ""}`;
1622
+ }
1623
+ function toolResultProgress(result) {
1624
+ const duration = result.metrics?.durationMs;
1625
+ const target = progressLine(result.presentation?.target ?? "", PROGRESS_ARG_MAX);
1626
+ const error = result.status === "success" ? "" : progressLine(toolResultErrorMessage(result) ?? "", PROGRESS_ERROR_MAX);
1627
+ const detail = [target, error].filter(Boolean).join(": ");
1628
+ return `\u2192 ${result.status}${duration !== void 0 ? ` ${Math.round(duration)}ms` : ""}${detail ? ` ${detail}` : ""}`;
1629
+ }
1252
1630
  function emitCompactBoundary(outputFormat, emit, result) {
1253
1631
  if (outputFormat !== "stream-json") return;
1254
1632
  emit({
@@ -1260,6 +1638,7 @@ function emitCompactBoundary(outputFormat, emit, result) {
1260
1638
  generation: result.generation,
1261
1639
  pre_messages: result.preMessageCount,
1262
1640
  post_messages: result.replacementHistory.length,
1641
+ carried_messages: result.carriedCount,
1263
1642
  post_tokens: result.postContextTokens,
1264
1643
  checkpoint_version: 2,
1265
1644
  strategy: result.strategy,
@@ -1269,12 +1648,17 @@ function emitCompactBoundary(outputFormat, emit, result) {
1269
1648
  // This generation's status above; the conversation's accumulated one here.
1270
1649
  coverage_lifetime_status: result.checkpoint.coverage?.lifetime?.status ?? "complete",
1271
1650
  coverage: result.checkpoint.coverage,
1272
- warning: result.warning
1651
+ warning: result.warning,
1652
+ // The reducer audit, when the host has something to report: suspect inputs
1653
+ // found in the summarized span and inherited constraints the reducer let go of.
1654
+ ...result.checkpoint.audit ? { audit: result.checkpoint.audit } : {},
1655
+ // Deferred compaction: the judge's verdict on the steps taken while the reducer ran.
1656
+ ...result.judge ? { judge: result.judge } : {}
1273
1657
  });
1274
1658
  }
1275
1659
  async function generatePromptSuggestions(config, _registry, history, signal, resolveAttachment) {
1276
- const { chatCompletionStream } = await import("./openai-compatible-RHZUBHV6.js");
1277
- const { buildMessages } = await import("./context-ZC5IFEAX.js");
1660
+ const { chatCompletionStream } = await import("./openai-compatible-2OB54YST.js");
1661
+ const { buildMessages } = await import("./context-CBOBDY5Q.js");
1278
1662
  const suggestionMessages = await buildMessages(
1279
1663
  config,
1280
1664
  [
@@ -1398,7 +1782,7 @@ var StdioProcessTransport = class {
1398
1782
  return Promise.reject(new Error(`MCP server ${this.name} is not a stdio server`));
1399
1783
  }
1400
1784
  const child = spawn(this.config.command, this.config.args ?? [], {
1401
- env: { ...process.env, ...this.config.env },
1785
+ env: buildChildEnv(process.env, this.config.env),
1402
1786
  cwd: this.config.cwd,
1403
1787
  stdio: ["pipe", "pipe", "pipe"]
1404
1788
  });
@@ -1433,7 +1817,6 @@ var StdioProcessTransport = class {
1433
1817
  child.stdout?.removeListener("data", onStdoutData);
1434
1818
  child.stderr?.removeListener("data", onStderrData);
1435
1819
  child.removeListener("error", onChildError);
1436
- child.stdin?.removeListener("error", onStdinError);
1437
1820
  child.removeListener("exit", onExit);
1438
1821
  child.removeListener("close", onClose);
1439
1822
  this.cleanupListeners = () => {
@@ -1653,7 +2036,7 @@ function registerElicitationHandler(client, name, options) {
1653
2036
  }
1654
2037
  );
1655
2038
  }
1656
- async function connectMcpServer(name, cfg, options) {
2039
+ async function connectMcpServer(name, cfg, options, failure) {
1657
2040
  let handle;
1658
2041
  let liveConnection;
1659
2042
  let transportClosed = false;
@@ -1741,6 +2124,7 @@ async function connectMcpServer(name, cfg, options) {
1741
2124
  }
1742
2125
  return conn;
1743
2126
  } catch (error) {
2127
+ if (failure) failure.error = error;
1744
2128
  const message = redact(error instanceof Error ? error.message : String(error));
1745
2129
  options.onDiagnostic({
1746
2130
  level: "warn",
@@ -1758,6 +2142,23 @@ async function connectMcpServer(name, cfg, options) {
1758
2142
  return null;
1759
2143
  }
1760
2144
  }
2145
+ function rejectedStreamableHttp(error) {
2146
+ const message = error instanceof Error ? error.message : String(error);
2147
+ return /\b(404|405)\b|Not Found|Method Not Allowed/i.test(message);
2148
+ }
2149
+ async function connectRemoteWithSseFallback(name, cfg, options) {
2150
+ const eligible = isRemoteConfig(cfg) && cfg.type !== "sse";
2151
+ if (!eligible) return connectMcpServer(name, cfg, options);
2152
+ const failure = {};
2153
+ const connection = await connectMcpServer(name, cfg, options, failure);
2154
+ if (connection || !rejectedStreamableHttp(failure.error)) return connection;
2155
+ options.onDiagnostic({
2156
+ level: "warn",
2157
+ message: `MCP server "${name}" rejected Streamable HTTP; retrying as legacy SSE. Set "type": "sse" to skip this.`,
2158
+ server: name
2159
+ });
2160
+ return connectMcpServer(name, { ...cfg, type: "sse" }, options);
2161
+ }
1761
2162
  function buildToolDefinition(conn, tool, requestTimeoutMs) {
1762
2163
  const prefixedName = `mcp__${conn.name}__${tool.name}`;
1763
2164
  const live = conn;
@@ -1836,7 +2237,7 @@ async function connectMcpServers(workspace, options = {}) {
1836
2237
  servers = declared.filter((server) => server.source === "user");
1837
2238
  }
1838
2239
  const results = await Promise.allSettled(
1839
- servers.map(({ name, config }) => connectMcpServer(name, config, resolved))
2240
+ servers.map(({ name, config }) => connectRemoteWithSseFallback(name, config, resolved))
1840
2241
  );
1841
2242
  const connections = [];
1842
2243
  for (const [index, result] of results.entries()) {
@@ -1868,7 +2269,8 @@ async function disconnectMcpServers(connections) {
1868
2269
  import { join as join2 } from "path";
1869
2270
  function collectWithheldProjectNotices(input) {
1870
2271
  if (!input.settingsEnabled) return [];
1871
- const projectSettings = loadSettingsFile(join2(input.workspace, ".book", "settings.json"));
2272
+ const projectPath = join2(input.workspace, ".book", "settings.json");
2273
+ const projectSettings = loadSettingsFile(projectPath);
1872
2274
  const notices = [];
1873
2275
  const allow = partitionProjectAllowRules(
1874
2276
  projectSettings?.permissions?.allow ?? [],
@@ -1893,15 +2295,52 @@ function collectWithheldProjectNotices(input) {
1893
2295
  `\u26A0 Ignoring ${hooks.pending.length} project-declared hook(s) (${summary}): hooks require approval. Run \`book doctor\` to approve them.`
1894
2296
  );
1895
2297
  }
2298
+ const directories = partitionProjectDirectories(
2299
+ collectDeclaredDirectories(input.workspace, projectSettings?.additionalDirectories ?? []),
2300
+ input.settings.projectDirectories
2301
+ );
2302
+ for (const directory of directories.pending) {
2303
+ notices.push(
2304
+ `\u26A0 Ignoring project-declared additionalDirectories entry "${directory.declared}" (really ${directory.realPath}): it requires approval. Run \`book doctor\` to see how to grant it.`
2305
+ );
2306
+ }
2307
+ const ignoredSandboxKeys = ignoredWorkspaceSandboxKeys(projectSettings ?? {});
2308
+ if (ignoredSandboxKeys.length > 0) {
2309
+ const listed = ignoredSandboxKeys.map(formatIgnoredWorkspaceSandboxKey).join(", ");
2310
+ notices.push(
2311
+ `\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
2312
+ );
2313
+ }
2314
+ const localPath = join2(input.workspace, ".book", "settings.local.json");
2315
+ const localSettings = loadSettingsFile(localPath);
2316
+ const ignoredLocalSandboxKeys = ignoredWorkspaceSandboxKeys(localSettings ?? {});
2317
+ if (ignoredLocalSandboxKeys.length > 0) {
2318
+ const listed = ignoredLocalSandboxKeys.map(formatIgnoredWorkspaceSandboxKey).join(", ");
2319
+ notices.push(
2320
+ `\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
2321
+ );
2322
+ }
2323
+ const userPath = settingsLayerPaths(input.workspace)[0];
2324
+ const activation = workspaceLayerThatEnabledSandbox([
2325
+ { path: userPath, trust: "trusted", document: loadSettingsFile(userPath) },
2326
+ { path: projectPath, trust: "repository", document: projectSettings },
2327
+ { path: localPath, trust: "local", document: localSettings }
2328
+ ]);
2329
+ if (activation) {
2330
+ notices.push(
2331
+ `\u26A0 ${activation} declared sandbox.enabled=true, so sandbox.autoAllowBashIfSandboxed is false for this session: no Bash command is auto-approved. ${TRUSTED_SETTINGS_LOCATION}`
2332
+ );
2333
+ }
1896
2334
  return notices;
1897
2335
  }
1898
2336
 
1899
2337
  export {
1900
2338
  readRunStatus,
2339
+ isReaderGoneError,
1901
2340
  runHeadless,
1902
2341
  buildMcpToolDefinitions,
1903
2342
  connectMcpServers,
1904
2343
  disconnectMcpServers,
1905
2344
  collectWithheldProjectNotices
1906
2345
  };
1907
- //# sourceMappingURL=chunk-J7MIZ5CA.js.map
2346
+ //# sourceMappingURL=chunk-TLX3H2CL.js.map