@code-yeongyu/senpi 2026.8.29 → 2026.8.30

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 (200) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/core/agent-session.d.ts +45 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +312 -25
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/bash-executor.d.ts.map +1 -1
  7. package/dist/core/bash-executor.js +155 -37
  8. package/dist/core/bash-executor.js.map +1 -1
  9. package/dist/core/credential-accounts.d.ts.map +1 -1
  10. package/dist/core/credential-accounts.js +23 -9
  11. package/dist/core/credential-accounts.js.map +1 -1
  12. package/dist/core/credential-pool/env-slots.d.ts.map +1 -1
  13. package/dist/core/credential-pool/env-slots.js +2 -0
  14. package/dist/core/credential-pool/env-slots.js.map +1 -1
  15. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +8 -2
  16. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  17. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +160 -47
  18. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  19. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +31 -8
  21. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  22. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
  23. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +8 -28
  24. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
  25. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  26. package/dist/core/extensions/builtin/permission-system/external-dir.js +15 -5
  27. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  28. package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
  29. package/dist/core/extensions/builtin/permission-system/parsers.js +16 -1
  30. package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
  31. package/dist/core/extensions/builtin/terminal/manager.d.ts +5 -0
  32. package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/terminal/manager.js +31 -3
  34. package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
  35. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts +8 -0
  36. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts.map +1 -0
  37. package/dist/core/extensions/builtin/terminal/monitor-permission.js +16 -0
  38. package/dist/core/extensions/builtin/terminal/monitor-permission.js.map +1 -0
  39. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -0
  40. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  41. package/dist/core/extensions/builtin/terminal/monitor-registry.js +403 -3
  42. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  43. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/terminal/session-bundle.js +1 -0
  45. package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
  46. package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -2
  48. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
  49. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -0
  50. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
  51. package/dist/core/extensions/builtin/terminal/tools/monitor.js +40 -2
  52. package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
  53. package/dist/core/model-resolver.js +2 -2
  54. package/dist/core/model-resolver.js.map +1 -1
  55. package/dist/core/session-manager.d.ts.map +1 -1
  56. package/dist/core/session-manager.js +10 -1
  57. package/dist/core/session-manager.js.map +1 -1
  58. package/dist/core/settings-manager.d.ts +7 -0
  59. package/dist/core/settings-manager.d.ts.map +1 -1
  60. package/dist/core/settings-manager.js +6 -0
  61. package/dist/core/settings-manager.js.map +1 -1
  62. package/dist/core/tools/bash.d.ts.map +1 -1
  63. package/dist/core/tools/bash.js +97 -15
  64. package/dist/core/tools/bash.js.map +1 -1
  65. package/dist/core/tools/index.d.ts +1 -0
  66. package/dist/core/tools/index.d.ts.map +1 -1
  67. package/dist/core/tools/index.js +8 -0
  68. package/dist/core/tools/index.js.map +1 -1
  69. package/dist/core/tools/output-accumulator.d.ts +1 -1
  70. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  71. package/dist/core/tools/output-accumulator.js +76 -34
  72. package/dist/core/tools/output-accumulator.js.map +1 -1
  73. package/dist/modes/interactive/components/footer.d.ts +7 -0
  74. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/footer.js +34 -8
  76. package/dist/modes/interactive/components/footer.js.map +1 -1
  77. package/dist/modes/interactive/grok/chrome.d.ts +2 -0
  78. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  79. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  80. package/dist/modes/interactive/interactive-host-runtime.d.ts +51 -1
  81. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  82. package/dist/modes/interactive/interactive-host-runtime.js +235 -20
  83. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  84. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  85. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  86. package/dist/modes/interactive/interactive-mode.js +114 -2
  87. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  88. package/dist/modes/rpc/connection-handler.d.ts +8 -1
  89. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  90. package/dist/modes/rpc/connection-handler.js +145 -49
  91. package/dist/modes/rpc/connection-handler.js.map +1 -1
  92. package/dist/modes/rpc/host-ensure.d.ts +2 -0
  93. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  94. package/dist/modes/rpc/host-ensure.js +15 -2
  95. package/dist/modes/rpc/host-ensure.js.map +1 -1
  96. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  97. package/dist/modes/rpc/host-lifecycle.js +31 -12
  98. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  99. package/dist/modes/rpc/host-watchdog.d.ts +2 -0
  100. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  101. package/dist/modes/rpc/host-watchdog.js +23 -12
  102. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  103. package/dist/modes/rpc/multi-session-host.js +6 -4
  104. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  105. package/dist/modes/rpc/rpc-client.d.ts +10 -4
  106. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  107. package/dist/modes/rpc/rpc-client.js +45 -10
  108. package/dist/modes/rpc/rpc-client.js.map +1 -1
  109. package/dist/modes/rpc/rpc-input-validation.d.ts +1 -0
  110. package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
  111. package/dist/modes/rpc/rpc-input-validation.js +66 -0
  112. package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
  113. package/dist/modes/rpc/rpc-types.d.ts +79 -1
  114. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  115. package/dist/modes/rpc/rpc-types.js.map +1 -1
  116. package/dist/modes/rpc/session-binding.d.ts +1 -0
  117. package/dist/modes/rpc/session-binding.d.ts.map +1 -1
  118. package/dist/modes/rpc/session-binding.js +21 -1
  119. package/dist/modes/rpc/session-binding.js.map +1 -1
  120. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  121. package/dist/modes/rpc/session-command-router.js +1 -0
  122. package/dist/modes/rpc/session-command-router.js.map +1 -1
  123. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  124. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  125. package/dist/modes/rpc/session-event-writer.js +54 -7
  126. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  127. package/dist/modes/rpc/session-extension-ui-requests.d.ts +1 -0
  128. package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -1
  129. package/dist/modes/rpc/session-extension-ui-requests.js +5 -0
  130. package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -1
  131. package/dist/modes/rpc/session-registry.d.ts +10 -2
  132. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  133. package/dist/modes/rpc/session-registry.js +58 -4
  134. package/dist/modes/rpc/session-registry.js.map +1 -1
  135. package/dist/modes/rpc/socket-event-fanout.d.ts +22 -0
  136. package/dist/modes/rpc/socket-event-fanout.d.ts.map +1 -0
  137. package/dist/modes/rpc/socket-event-fanout.js +84 -0
  138. package/dist/modes/rpc/socket-event-fanout.js.map +1 -0
  139. package/docs/rpc.md +3 -1
  140. package/docs/settings.md +34 -0
  141. package/docs/windows.md +2 -0
  142. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +38 -0
  143. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  144. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +139 -90
  145. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -4
  146. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  147. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +40 -20
  148. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  149. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +3 -3
  150. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  151. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  152. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +1 -1
  153. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
  154. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +28 -9
  155. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
  156. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  157. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  158. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +58 -31
  159. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  160. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +20 -0
  161. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -0
  162. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +443 -0
  163. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -0
  164. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
  165. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  166. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +2 -0
  167. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  168. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  170. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  171. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  172. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  173. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  174. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  176. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  177. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  178. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  179. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  180. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  182. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  183. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  184. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  185. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  186. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  187. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +7 -0
  188. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  189. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -0
  190. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  191. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +1 -1
  192. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -1
  193. package/node_modules/@earendil-works/pi-tui/dist/mux.js +2 -2
  194. package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -1
  195. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +1 -0
  196. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  197. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +19 -0
  198. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  199. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  200. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAgLrB"}
1
+ {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAStD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAuSrB"}
@@ -7,11 +7,13 @@
7
7
  */
8
8
  import { randomBytes } from "node:crypto";
9
9
  import { createWriteStream } from "node:fs";
10
+ import { rm } from "node:fs/promises";
10
11
  import { tmpdir } from "node:os";
11
12
  import { join } from "node:path";
12
13
  import { stripAnsi } from "../utils/ansi.js";
13
14
  import { sanitizeBinaryOutput } from "../utils/shell.js";
14
15
  import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
16
+ const NORMAL_CALLBACK_SETTLEMENT_TIMEOUT_MS = 5_000;
15
17
  // ============================================================================
16
18
  // Implementation
17
19
  // ============================================================================
@@ -21,13 +23,37 @@ import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
21
23
  */
22
24
  export async function executeBashWithOperations(command, cwd, operations, options) {
23
25
  const outputChunks = [];
26
+ const callbackAbortController = new AbortController();
27
+ const executionSignal = options?.signal
28
+ ? AbortSignal.any([options.signal, callbackAbortController.signal])
29
+ : callbackAbortController.signal;
30
+ let callbackError;
31
+ let hasCallbackError = false;
32
+ let callbacksAbandoned = false;
33
+ const callbackPromises = new Set();
34
+ const waitForCallbacks = async (allowAbandonment) => {
35
+ if (callbackPromises.size === 0)
36
+ return true;
37
+ const settled = Promise.allSettled([...callbackPromises]).then(() => true);
38
+ let timeout;
39
+ const bounded = new Promise((resolve) => {
40
+ timeout = setTimeout(resolve, allowAbandonment ? 100 : NORMAL_CALLBACK_SETTLEMENT_TIMEOUT_MS, false);
41
+ timeout.unref?.();
42
+ });
43
+ try {
44
+ return await Promise.race([settled, bounded]);
45
+ }
46
+ finally {
47
+ if (timeout)
48
+ clearTimeout(timeout);
49
+ }
50
+ };
24
51
  let outputChunkStart = 0;
25
52
  let outputBytes = 0;
26
53
  const maxOutputBytes = DEFAULT_MAX_BYTES * 2;
27
54
  let tempFilePath;
28
55
  let tempFileStream;
29
56
  let tempFileError;
30
- let tempFileErrorListener;
31
57
  let totalBytes = 0;
32
58
  const compactOutputChunks = () => {
33
59
  if (outputChunkStart < 64) {
@@ -44,10 +70,9 @@ export async function executeBashWithOperations(command, cwd, operations, option
44
70
  const id = randomBytes(8).toString("hex");
45
71
  tempFilePath = join(tmpdir(), `pi-bash-${id}.log`);
46
72
  tempFileStream = createWriteStream(tempFilePath);
47
- tempFileErrorListener = (error) => {
73
+ tempFileStream.on("error", (error) => {
48
74
  tempFileError ??= error;
49
- };
50
- tempFileStream.on("error", tempFileErrorListener);
75
+ });
51
76
  for (let i = outputChunkStart; i < outputChunks.length; i++) {
52
77
  const chunk = outputChunks[i];
53
78
  if (chunk !== undefined) {
@@ -62,42 +87,84 @@ export async function executeBashWithOperations(command, cwd, operations, option
62
87
  return;
63
88
  }
64
89
  if (tempFileError) {
65
- if (tempFileErrorListener)
66
- stream.off("error", tempFileErrorListener);
67
- stream.destroy();
90
+ await new Promise((resolve) => {
91
+ if (stream.closed) {
92
+ resolve();
93
+ return;
94
+ }
95
+ stream.once("close", resolve);
96
+ stream.destroy();
97
+ });
68
98
  throw tempFileError;
69
99
  }
70
100
  await new Promise((resolve, reject) => {
71
- let settled = false;
72
- const cleanup = () => {
101
+ let finished = false;
102
+ let closed = false;
103
+ let streamError;
104
+ const settle = () => {
105
+ if (!closed) {
106
+ return;
107
+ }
108
+ stream.off("finish", onFinish);
73
109
  stream.off("error", onError);
74
- if (tempFileErrorListener)
75
- stream.off("error", tempFileErrorListener);
76
110
  stream.off("close", onClose);
77
- };
78
- const settle = (error) => {
79
- if (settled)
80
- return;
81
- settled = true;
82
- cleanup();
83
- if (error)
84
- reject(error);
85
- else
111
+ if (streamError) {
112
+ reject(streamError);
113
+ }
114
+ else if (finished) {
86
115
  resolve();
116
+ }
117
+ else {
118
+ reject(new Error("Bash spill stream closed before finish"));
119
+ }
87
120
  };
88
121
  const onError = (error) => {
89
- tempFileError ??= error;
122
+ streamError ??= error;
123
+ };
124
+ const onFinish = () => {
125
+ finished = true;
90
126
  };
91
127
  const onClose = () => {
92
- settle(tempFileError);
128
+ closed = true;
129
+ if (!finished && !stream.destroyed) {
130
+ stream.destroy();
131
+ }
132
+ settle();
93
133
  };
94
- stream.on("error", onError);
134
+ stream.once("error", onError);
135
+ stream.once("finish", onFinish);
95
136
  stream.once("close", onClose);
96
137
  stream.end();
97
138
  });
98
139
  };
140
+ const removeTempFile = async () => {
141
+ if (!tempFilePath) {
142
+ return;
143
+ }
144
+ const path = tempFilePath;
145
+ await rm(path, { force: true });
146
+ tempFilePath = undefined;
147
+ };
148
+ const closeTempFileAndCleanup = async (primaryError) => {
149
+ let finalError = primaryError;
150
+ try {
151
+ await closeTempFileStream();
152
+ }
153
+ catch (closeError) {
154
+ finalError = new AggregateError([primaryError, closeError], "Bash output cleanup failed");
155
+ }
156
+ try {
157
+ await removeTempFile();
158
+ }
159
+ catch (unlinkError) {
160
+ finalError = new AggregateError([finalError, unlinkError], "Bash output cleanup failed");
161
+ }
162
+ throw finalError;
163
+ };
99
164
  const decoder = new TextDecoder();
100
165
  const onData = (data) => {
166
+ if (callbackAbortController.signal.aborted)
167
+ return;
101
168
  totalBytes += data.length;
102
169
  // Sanitize: strip ANSI, replace binary garbage, normalize newlines
103
170
  const text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\r/g, "");
@@ -121,7 +188,33 @@ export async function executeBashWithOperations(command, cwd, operations, option
121
188
  compactOutputChunks();
122
189
  // Stream to callback
123
190
  if (options?.onChunk) {
124
- options.onChunk(text);
191
+ try {
192
+ const callbackResult = options.onChunk(text);
193
+ if (callbackResult && typeof callbackResult.then === "function") {
194
+ const callbackPromise = Promise.resolve(callbackResult).then(() => undefined, (error) => {
195
+ if (!hasCallbackError) {
196
+ callbackError = error;
197
+ hasCallbackError = true;
198
+ }
199
+ if (callbacksAbandoned) {
200
+ process.emitWarning(`Bash output callback rejected after the command timeout: ${error instanceof Error ? error.message : String(error)}`, { code: "BASH_CALLBACK_ERROR" });
201
+ }
202
+ callbackAbortController.abort();
203
+ throw error;
204
+ });
205
+ callbackPromises.add(callbackPromise);
206
+ void callbackPromise.catch(() => { }).finally(() => callbackPromises.delete(callbackPromise));
207
+ return callbackPromise;
208
+ }
209
+ }
210
+ catch (error) {
211
+ if (!hasCallbackError) {
212
+ callbackError = error;
213
+ hasCallbackError = true;
214
+ }
215
+ callbackAbortController.abort();
216
+ throw error;
217
+ }
125
218
  }
126
219
  };
127
220
  const finishDecoder = () => {
@@ -141,40 +234,65 @@ export async function executeBashWithOperations(command, cwd, operations, option
141
234
  return { fullOutput, truncationResult };
142
235
  }
143
236
  catch (error) {
144
- try {
145
- await closeTempFileStream();
146
- }
147
- catch (closeError) {
148
- throw new AggregateError([error, closeError], "Bash output finalization and spill cleanup failed");
149
- }
150
- throw error;
237
+ return await closeTempFileAndCleanup(error);
151
238
  }
152
239
  };
153
240
  let result;
154
241
  try {
155
242
  result = await operations.exec(command, cwd, {
156
243
  onData,
157
- signal: options?.signal,
244
+ signal: executionSignal,
158
245
  });
159
246
  }
160
247
  catch (err) {
248
+ await waitForCallbacks(options?.signal?.aborted === true);
161
249
  // Check if it was an abort
250
+ if (hasCallbackError) {
251
+ return await closeTempFileAndCleanup(callbackError);
252
+ }
162
253
  if (options?.signal?.aborted) {
163
254
  const { fullOutput, truncationResult } = await prepareFinalOutput();
164
- await closeTempFileStream();
255
+ try {
256
+ await closeTempFileStream();
257
+ }
258
+ catch (error) {
259
+ return await closeTempFileAndCleanup(error);
260
+ }
261
+ try {
262
+ await removeTempFile();
263
+ }
264
+ catch (cleanupError) {
265
+ throw new AggregateError([cleanupError], "Bash output cleanup failed");
266
+ }
165
267
  return {
166
268
  output: truncationResult.truncated ? truncationResult.content : fullOutput,
167
269
  exitCode: undefined,
168
270
  cancelled: true,
169
271
  truncated: truncationResult.truncated,
170
- fullOutputPath: tempFilePath,
171
272
  };
172
273
  }
173
- await closeTempFileStream();
174
- throw err;
274
+ return await closeTempFileAndCleanup(err);
275
+ }
276
+ // Normal completion has a generous bound for observer callbacks: unlike the
277
+ // cancellation path, it must not hang forever on an observer that never
278
+ // settles, while still allowing legitimately slow callbacks to finish. A
279
+ // callback that exceeds the public bound is an execution failure, not silent
280
+ // abandonment, so its spill is cleaned before the caller is released.
281
+ const callbacksSettled = await waitForCallbacks(false);
282
+ if (!callbacksSettled) {
283
+ callbacksAbandoned = true;
284
+ return await closeTempFileAndCleanup(new Error(`Bash output callback did not settle within ${NORMAL_CALLBACK_SETTLEMENT_TIMEOUT_MS}ms`));
285
+ }
286
+ if (hasCallbackError) {
287
+ return await closeTempFileAndCleanup(callbackError);
175
288
  }
176
289
  const { fullOutput, truncationResult } = await prepareFinalOutput();
177
- await closeTempFileStream();
290
+ try {
291
+ await closeTempFileStream();
292
+ }
293
+ catch (error) {
294
+ return await closeTempFileAndCleanup(error);
295
+ }
178
296
  const cancelled = options?.signal?.aborted ?? false;
179
297
  return {
180
298
  output: truncationResult.truncated ? truncationResult.content : fullOutput,
@@ -1 +1 @@
1
- {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA0BtE,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC9C,OAAe,EACf,GAAW,EACX,UAA0B,EAC1B,OAA6B;IAE7B,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,MAAM,cAAc,GAAG,iBAAiB,GAAG,CAAC,CAAC;IAE7C,IAAI,YAAgC,CAAC;IACrC,IAAI,cAAuC,CAAC;IAC5C,IAAI,aAAgC,CAAC;IACrC,IAAI,qBAA2D,CAAC;IAChE,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,MAAM,mBAAmB,GAAG,GAAG,EAAE;QAChC,IAAI,gBAAgB,GAAG,EAAE,EAAE,CAAC;YAC3B,OAAO;QACR,CAAC;QACD,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;QACzC,gBAAgB,GAAG,CAAC,CAAC;IACtB,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEvE,MAAM,cAAc,GAAG,GAAG,EAAE;QAC3B,IAAI,YAAY,EAAE,CAAC;YAClB,OAAO;QACR,CAAC;QACD,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC1C,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;QACnD,cAAc,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;QACjD,qBAAqB,GAAG,CAAC,KAAK,EAAE,EAAE;YACjC,aAAa,KAAK,KAAK,CAAC;QACzB,CAAC,CAAC;QACF,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;QAClD,KAAK,IAAI,CAAC,GAAG,gBAAgB,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7D,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;YAC9B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACzB,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC7B,CAAC;QACF,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,mBAAmB,GAAG,KAAK,IAAI,EAAE;QACtC,MAAM,MAAM,GAAG,cAAc,CAAC;QAC9B,cAAc,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,OAAO;QACR,CAAC;QACD,IAAI,aAAa,EAAE,CAAC;YACnB,IAAI,qBAAqB;gBAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;YACtE,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,aAAa,CAAC;QACrB,CAAC;QAED,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,MAAM,OAAO,GAAG,GAAG,EAAE;gBACpB,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBAC7B,IAAI,qBAAqB;oBAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;gBACtE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9B,CAAC,CAAC;YACF,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE;gBAChC,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,EAAE,CAAC;gBACV,IAAI,KAAK;oBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;;oBACpB,OAAO,EAAE,CAAC;YAChB,CAAC,CAAC;YACF,MAAM,OAAO,GAAG,CAAC,KAAY,EAAE,EAAE;gBAChC,aAAa,KAAK,KAAK,CAAC;YACzB,CAAC,CAAC;YACF,MAAM,OAAO,GAAG,GAAG,EAAE;gBACpB,MAAM,CAAC,aAAa,CAAC,CAAC;YACvB,CAAC,CAAC;YACF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9B,MAAM,CAAC,GAAG,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAElC,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE;QAC/B,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC;QAE1B,mEAAmE;QACnE,MAAM,IAAI,GAAG,oBAAoB,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAExG,kDAAkD;QAClD,IAAI,UAAU,GAAG,iBAAiB,EAAE,CAAC;YACpC,cAAc,EAAE,CAAC;QAClB,CAAC;QAED,IAAI,cAAc,EAAE,CAAC;YACpB,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC;QAC3B,OAAO,WAAW,GAAG,cAAc,IAAI,gBAAgB,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnF,MAAM,OAAO,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;YAC/C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC3B,MAAM;YACP,CAAC;YACD,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;YAC9B,gBAAgB,EAAE,CAAC;QACpB,CAAC;QACD,mBAAmB,EAAE,CAAC;QAEtB,qBAAqB;QACrB,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;YACtB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,GAAG,EAAE;QAC1B,MAAM,SAAS,GAAG,oBAAoB,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACvF,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;QACzC,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,kBAAkB,GAAG,KAAK,IAAI,EAAE;QACrC,IAAI,CAAC;YACJ,aAAa,EAAE,CAAC;YAChB,MAAM,UAAU,GAAG,UAAU,EAAE,CAAC;YAChC,MAAM,gBAAgB,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;YAClD,IAAI,gBAAgB,CAAC,SAAS,EAAE,CAAC;gBAChC,cAAc,EAAE,CAAC;YAClB,CAAC;YACD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC;gBACJ,MAAM,mBAAmB,EAAE,CAAC;YAC7B,CAAC;YAAC,OAAO,UAAU,EAAE,CAAC;gBACrB,MAAM,IAAI,cAAc,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,mDAAmD,CAAC,CAAC;YACpG,CAAC;YACD,MAAM,KAAK,CAAC;QACb,CAAC;IACF,CAAC,CAAC;IAEF,IAAI,MAAmD,CAAC;IACxD,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YAC5C,MAAM;YACN,MAAM,EAAE,OAAO,EAAE,MAAM;SACvB,CAAC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,2BAA2B;QAC3B,IAAI,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC9B,MAAM,EAAE,UAAU,EAAE,gBAAgB,EAAE,GAAG,MAAM,kBAAkB,EAAE,CAAC;YACpE,MAAM,mBAAmB,EAAE,CAAC;YAC5B,OAAO;gBACN,MAAM,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU;gBAC1E,QAAQ,EAAE,SAAS;gBACnB,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,gBAAgB,CAAC,SAAS;gBACrC,cAAc,EAAE,YAAY;aAC5B,CAAC;QACH,CAAC;QAED,MAAM,mBAAmB,EAAE,CAAC;QAE5B,MAAM,GAAG,CAAC;IACX,CAAC;IAED,MAAM,EAAE,UAAU,EAAE,gBAAgB,EAAE,GAAG,MAAM,kBAAkB,EAAE,CAAC;IACpE,MAAM,mBAAmB,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,KAAK,CAAC;IAEpD,OAAO;QACN,MAAM,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU;QAC1E,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,SAAS,CAAC;QAChE,SAAS;QACT,SAAS,EAAE,gBAAgB,CAAC,SAAS;QACrC,cAAc,EAAE,YAAY;KAC5B,CAAC;AACH,CAAC","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stripAnsi } from \"../utils/ansi.ts\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport type { BashOperations } from \"./tools/bash.ts\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputChunkStart = 0;\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet tempFileError: Error | undefined;\n\tlet tempFileErrorListener: ((error: Error) => void) | undefined;\n\tlet totalBytes = 0;\n\n\tconst compactOutputChunks = () => {\n\t\tif (outputChunkStart < 64) {\n\t\t\treturn;\n\t\t}\n\t\toutputChunks.splice(0, outputChunkStart);\n\t\toutputChunkStart = 0;\n\t};\n\n\tconst outputText = () => outputChunks.slice(outputChunkStart).join(\"\");\n\n\tconst ensureTempFile = () => {\n\t\tif (tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\ttempFileErrorListener = (error) => {\n\t\t\ttempFileError ??= error;\n\t\t};\n\t\ttempFileStream.on(\"error\", tempFileErrorListener);\n\t\tfor (let i = outputChunkStart; i < outputChunks.length; i++) {\n\t\t\tconst chunk = outputChunks[i];\n\t\t\tif (chunk !== undefined) {\n\t\t\t\ttempFileStream.write(chunk);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst closeTempFileStream = async () => {\n\t\tconst stream = tempFileStream;\n\t\ttempFileStream = undefined;\n\t\tif (!stream) {\n\t\t\treturn;\n\t\t}\n\t\tif (tempFileError) {\n\t\t\tif (tempFileErrorListener) stream.off(\"error\", tempFileErrorListener);\n\t\t\tstream.destroy();\n\t\t\tthrow tempFileError;\n\t\t}\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tlet settled = false;\n\t\t\tconst cleanup = () => {\n\t\t\t\tstream.off(\"error\", onError);\n\t\t\t\tif (tempFileErrorListener) stream.off(\"error\", tempFileErrorListener);\n\t\t\t\tstream.off(\"close\", onClose);\n\t\t\t};\n\t\t\tconst settle = (error?: Error) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tconst onError = (error: Error) => {\n\t\t\t\ttempFileError ??= error;\n\t\t\t};\n\t\t\tconst onClose = () => {\n\t\t\t\tsettle(tempFileError);\n\t\t\t};\n\t\t\tstream.on(\"error\", onError);\n\t\t\tstream.once(\"close\", onClose);\n\t\t\tstream.end();\n\t\t});\n\t};\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\tensureTempFile();\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunkStart < outputChunks.length - 1) {\n\t\t\tconst removed = outputChunks[outputChunkStart];\n\t\t\tif (removed === undefined) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\toutputBytes -= removed.length;\n\t\t\toutputChunkStart++;\n\t\t}\n\t\tcompactOutputChunks();\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\tconst finishDecoder = () => {\n\t\tconst finalText = sanitizeBinaryOutput(stripAnsi(decoder.decode())).replace(/\\r/g, \"\");\n\t\tif (finalText.length > 0) {\n\t\t\tonData(Buffer.from(finalText, \"utf-8\"));\n\t\t}\n\t};\n\n\tconst prepareFinalOutput = async () => {\n\t\ttry {\n\t\t\tfinishDecoder();\n\t\t\tconst fullOutput = outputText();\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\tif (truncationResult.truncated) {\n\t\t\t\tensureTempFile();\n\t\t\t}\n\t\t\treturn { fullOutput, truncationResult };\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait closeTempFileStream();\n\t\t\t} catch (closeError) {\n\t\t\t\tthrow new AggregateError([error, closeError], \"Bash output finalization and spill cleanup failed\");\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tlet result: Awaited<ReturnType<BashOperations[\"exec\"]>>;\n\ttry {\n\t\tresult = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\t} catch (err) {\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst { fullOutput, truncationResult } = await prepareFinalOutput();\n\t\t\tawait closeTempFileStream();\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tawait closeTempFileStream();\n\n\t\tthrow err;\n\t}\n\n\tconst { fullOutput, truncationResult } = await prepareFinalOutput();\n\tawait closeTempFileStream();\n\tconst cancelled = options?.signal?.aborted ?? false;\n\n\treturn {\n\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\tcancelled,\n\t\ttruncated: truncationResult.truncated,\n\t\tfullOutputPath: tempFilePath,\n\t};\n}\n"]}
1
+ {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stripAnsi } from \"../utils/ansi.ts\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport type { BashOperations } from \"./tools/bash.ts\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.ts\";\n\nconst NORMAL_CALLBACK_SETTLEMENT_TIMEOUT_MS = 5_000;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tconst callbackAbortController = new AbortController();\n\tconst executionSignal = options?.signal\n\t\t? AbortSignal.any([options.signal, callbackAbortController.signal])\n\t\t: callbackAbortController.signal;\n\tlet callbackError: unknown;\n\tlet hasCallbackError = false;\n\tlet callbacksAbandoned = false;\n\tconst callbackPromises = new Set<Promise<void>>();\n\tconst waitForCallbacks = async (allowAbandonment: boolean): Promise<boolean> => {\n\t\tif (callbackPromises.size === 0) return true;\n\t\tconst settled = Promise.allSettled([...callbackPromises]).then(() => true);\n\t\tlet timeout: ReturnType<typeof setTimeout> | undefined;\n\t\tconst bounded = new Promise<false>((resolve) => {\n\t\t\ttimeout = setTimeout(resolve, allowAbandonment ? 100 : NORMAL_CALLBACK_SETTLEMENT_TIMEOUT_MS, false);\n\t\t\ttimeout.unref?.();\n\t\t});\n\t\ttry {\n\t\t\treturn await Promise.race([settled, bounded]);\n\t\t} finally {\n\t\t\tif (timeout) clearTimeout(timeout);\n\t\t}\n\t};\n\tlet outputChunkStart = 0;\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet tempFileError: Error | undefined;\n\tlet totalBytes = 0;\n\n\tconst compactOutputChunks = () => {\n\t\tif (outputChunkStart < 64) {\n\t\t\treturn;\n\t\t}\n\t\toutputChunks.splice(0, outputChunkStart);\n\t\toutputChunkStart = 0;\n\t};\n\n\tconst outputText = () => outputChunks.slice(outputChunkStart).join(\"\");\n\n\tconst ensureTempFile = () => {\n\t\tif (tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\ttempFileStream.on(\"error\", (error) => {\n\t\t\ttempFileError ??= error;\n\t\t});\n\t\tfor (let i = outputChunkStart; i < outputChunks.length; i++) {\n\t\t\tconst chunk = outputChunks[i];\n\t\t\tif (chunk !== undefined) {\n\t\t\t\ttempFileStream.write(chunk);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst closeTempFileStream = async () => {\n\t\tconst stream = tempFileStream;\n\t\ttempFileStream = undefined;\n\t\tif (!stream) {\n\t\t\treturn;\n\t\t}\n\t\tif (tempFileError) {\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tif (stream.closed) {\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tstream.once(\"close\", resolve);\n\t\t\t\tstream.destroy();\n\t\t\t});\n\t\t\tthrow tempFileError;\n\t\t}\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tlet finished = false;\n\t\t\tlet closed = false;\n\t\t\tlet streamError: Error | undefined;\n\t\t\tconst settle = () => {\n\t\t\t\tif (!closed) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tstream.off(\"finish\", onFinish);\n\t\t\t\tstream.off(\"error\", onError);\n\t\t\t\tstream.off(\"close\", onClose);\n\t\t\t\tif (streamError) {\n\t\t\t\t\treject(streamError);\n\t\t\t\t} else if (finished) {\n\t\t\t\t\tresolve();\n\t\t\t\t} else {\n\t\t\t\t\treject(new Error(\"Bash spill stream closed before finish\"));\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst onError = (error: Error) => {\n\t\t\t\tstreamError ??= error;\n\t\t\t};\n\t\t\tconst onFinish = () => {\n\t\t\t\tfinished = true;\n\t\t\t};\n\t\t\tconst onClose = () => {\n\t\t\t\tclosed = true;\n\t\t\t\tif (!finished && !stream.destroyed) {\n\t\t\t\t\tstream.destroy();\n\t\t\t\t}\n\t\t\t\tsettle();\n\t\t\t};\n\t\t\tstream.once(\"error\", onError);\n\t\t\tstream.once(\"finish\", onFinish);\n\t\t\tstream.once(\"close\", onClose);\n\t\t\tstream.end();\n\t\t});\n\t};\n\n\tconst removeTempFile = async (): Promise<void> => {\n\t\tif (!tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst path = tempFilePath;\n\t\tawait rm(path, { force: true });\n\t\ttempFilePath = undefined;\n\t};\n\n\tconst closeTempFileAndCleanup = async (primaryError: unknown): Promise<never> => {\n\t\tlet finalError = primaryError;\n\t\ttry {\n\t\t\tawait closeTempFileStream();\n\t\t} catch (closeError) {\n\t\t\tfinalError = new AggregateError([primaryError, closeError], \"Bash output cleanup failed\");\n\t\t}\n\t\ttry {\n\t\t\tawait removeTempFile();\n\t\t} catch (unlinkError) {\n\t\t\tfinalError = new AggregateError([finalError, unlinkError], \"Bash output cleanup failed\");\n\t\t}\n\t\tthrow finalError;\n\t};\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\tif (callbackAbortController.signal.aborted) return;\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\tensureTempFile();\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunkStart < outputChunks.length - 1) {\n\t\t\tconst removed = outputChunks[outputChunkStart];\n\t\t\tif (removed === undefined) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\toutputBytes -= removed.length;\n\t\t\toutputChunkStart++;\n\t\t}\n\t\tcompactOutputChunks();\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\ttry {\n\t\t\t\tconst callbackResult = (options.onChunk as (chunk: string) => unknown)(text);\n\t\t\t\tif (callbackResult && typeof (callbackResult as { then?: unknown }).then === \"function\") {\n\t\t\t\t\tconst callbackPromise = Promise.resolve(callbackResult).then(\n\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t\t(error) => {\n\t\t\t\t\t\t\tif (!hasCallbackError) {\n\t\t\t\t\t\t\t\tcallbackError = error;\n\t\t\t\t\t\t\t\thasCallbackError = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (callbacksAbandoned) {\n\t\t\t\t\t\t\t\tprocess.emitWarning(\n\t\t\t\t\t\t\t\t\t`Bash output callback rejected after the command timeout: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t\t\t\t\t\t{ code: \"BASH_CALLBACK_ERROR\" },\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallbackAbortController.abort();\n\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\tcallbackPromises.add(callbackPromise);\n\t\t\t\t\tvoid callbackPromise.catch(() => {}).finally(() => callbackPromises.delete(callbackPromise));\n\t\t\t\t\treturn callbackPromise;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (!hasCallbackError) {\n\t\t\t\t\tcallbackError = error;\n\t\t\t\t\thasCallbackError = true;\n\t\t\t\t}\n\t\t\t\tcallbackAbortController.abort();\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t};\n\n\tconst finishDecoder = () => {\n\t\tconst finalText = sanitizeBinaryOutput(stripAnsi(decoder.decode())).replace(/\\r/g, \"\");\n\t\tif (finalText.length > 0) {\n\t\t\tonData(Buffer.from(finalText, \"utf-8\"));\n\t\t}\n\t};\n\n\tconst prepareFinalOutput = async () => {\n\t\ttry {\n\t\t\tfinishDecoder();\n\t\t\tconst fullOutput = outputText();\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\tif (truncationResult.truncated) {\n\t\t\t\tensureTempFile();\n\t\t\t}\n\t\t\treturn { fullOutput, truncationResult };\n\t\t} catch (error) {\n\t\t\treturn await closeTempFileAndCleanup(error);\n\t\t}\n\t};\n\n\tlet result: Awaited<ReturnType<BashOperations[\"exec\"]>>;\n\ttry {\n\t\tresult = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: executionSignal,\n\t\t});\n\t} catch (err) {\n\t\tawait waitForCallbacks(options?.signal?.aborted === true);\n\t\t// Check if it was an abort\n\t\tif (hasCallbackError) {\n\t\t\treturn await closeTempFileAndCleanup(callbackError);\n\t\t}\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst { fullOutput, truncationResult } = await prepareFinalOutput();\n\t\t\ttry {\n\t\t\t\tawait closeTempFileStream();\n\t\t\t} catch (error) {\n\t\t\t\treturn await closeTempFileAndCleanup(error);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait removeTempFile();\n\t\t\t} catch (cleanupError) {\n\t\t\t\tthrow new AggregateError([cleanupError], \"Bash output cleanup failed\");\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t};\n\t\t}\n\n\t\treturn await closeTempFileAndCleanup(err);\n\t}\n\n\t// Normal completion has a generous bound for observer callbacks: unlike the\n\t// cancellation path, it must not hang forever on an observer that never\n\t// settles, while still allowing legitimately slow callbacks to finish. A\n\t// callback that exceeds the public bound is an execution failure, not silent\n\t// abandonment, so its spill is cleaned before the caller is released.\n\tconst callbacksSettled = await waitForCallbacks(false);\n\tif (!callbacksSettled) {\n\t\tcallbacksAbandoned = true;\n\t\treturn await closeTempFileAndCleanup(\n\t\t\tnew Error(`Bash output callback did not settle within ${NORMAL_CALLBACK_SETTLEMENT_TIMEOUT_MS}ms`),\n\t\t);\n\t}\n\tif (hasCallbackError) {\n\t\treturn await closeTempFileAndCleanup(callbackError);\n\t}\n\tconst { fullOutput, truncationResult } = await prepareFinalOutput();\n\ttry {\n\t\tawait closeTempFileStream();\n\t} catch (error) {\n\t\treturn await closeTempFileAndCleanup(error);\n\t}\n\tconst cancelled = options?.signal?.aborted ?? false;\n\n\treturn {\n\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\tcancelled,\n\t\ttruncated: truncationResult.truncated,\n\t\tfullOutputPath: tempFilePath,\n\t};\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"credential-accounts.d.ts","sourceRoot":"","sources":["../../src/core/credential-accounts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,wBAAwB,EAAwC,MAAM,kCAAkC,CAAC;AAGlH,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEjE,mFAAmF;AACnF,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CACzB,CAAC;AAIF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMzD;AAuCD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,CAAC,EAAE,wBAAwB,GACnC,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAErC;AAED,2FAA2F;AAC3F,wBAAsB,2BAA2B,CAChD,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,GAAE,wBAAyD,GACnE,OAAO,CAAC,wBAAwB,EAAE,CAAC,CA8BrC;AAED,4DAA4D;AAC5D,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,CAAC,EAAE,wBAAwB,GACnC,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC5C,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,CAAC,EAAE,wBAAwB,GACnC,OAAO,CAAC,IAAI,CAAC,CAef"}
1
+ {"version":3,"file":"credential-accounts.d.ts","sourceRoot":"","sources":["../../src/core/credential-accounts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,wBAAwB,EAAwC,MAAM,kCAAkC,CAAC;AAIlH,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEjE,mFAAmF;AACnF,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CACzB,CAAC;AAIF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMzD;AAuCD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,CAAC,EAAE,wBAAwB,GACnC,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAErC;AAED,2FAA2F;AAC3F,wBAAsB,2BAA2B,CAChD,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,GAAE,wBAAyD,GACnE,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAuCrC;AAED,4DAA4D;AAC5D,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,CAAC,EAAE,wBAAwB,GACnC,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC5C,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,CAAC,EAAE,wBAAwB,GACnC,OAAO,CAAC,IAAI,CAAC,CAef"}
@@ -3,6 +3,7 @@ import { listSlots, pinSlot, removeSlot } from "@earendil-works/pi-ai/auth/pool/
3
3
  import { discoverEnvSlots } from "./credential-pool/env-slots.js";
4
4
  import { CredentialSlotRepository, slotHealth } from "./credential-pool/state-store.js";
5
5
  import { emitProviderAccountsChanged } from "./extensions/builtin/claude-sdk-oauth/account-events.js";
6
+ import { SENTINEL_OAUTH_FIELDS } from "./extensions/builtin/claude-sdk-oauth/accounts.js";
6
7
  const ACCOUNT_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
7
8
  export function assertValidAccountName(name) {
8
9
  if (!ACCOUNT_NAME_PATTERN.test(name)) {
@@ -57,17 +58,26 @@ export async function summarizeCredentialAccounts(provider, stored, env = proces
57
58
  const credential = pooledFrom(stored);
58
59
  const now = Date.now();
59
60
  const pinned = pinnedName(credential);
61
+ const summaries = [];
60
62
  if (credential) {
61
63
  const state = await repository.listSlots(provider, "stored");
62
- return listSlots(credential).map((slot) => ({
63
- name: slot.name,
64
- source: slot.source ?? "login",
65
- blocked: slotBlocked(slot, state[slot.name], now),
66
- pinned: pinned === slot.name,
67
- }));
64
+ const storedAccounts = provider === "claude-sdk-oauth"
65
+ ? Array.isArray(credential.accounts)
66
+ ? listSlots(credential)
67
+ : []
68
+ : listSlots(credential);
69
+ for (const slot of storedAccounts) {
70
+ summaries.push({
71
+ name: slot.name,
72
+ source: slot.source ?? "login",
73
+ blocked: slotBlocked(slot, state[slot.name], now),
74
+ pinned: pinned === slot.name,
75
+ });
76
+ }
77
+ if (provider !== "claude-sdk-oauth")
78
+ return summaries;
68
79
  }
69
80
  const state = await repository.listSlots(provider, "env");
70
- const summaries = [];
71
81
  for (const slot of discoverEnvSlots(provider, (name) => env[name])) {
72
82
  const persisted = state[slot.name];
73
83
  const revision = await repository.envCredentialRevision(slot.envVarName, slot.key);
@@ -93,8 +103,12 @@ export async function pinCredentialAccount(storage, provider, name, env = proces
93
103
  }
94
104
  }
95
105
  await storage.modify(provider, async (current) => {
96
- if (current === undefined)
97
- throw new Error(`No stored credential for provider: ${provider}`);
106
+ if (current === undefined) {
107
+ if (provider !== "claude-sdk-oauth" || name === null) {
108
+ throw new Error(`No stored credential for provider: ${provider}`);
109
+ }
110
+ return pinSlot({ type: "oauth", ...SENTINEL_OAUTH_FIELDS, accounts: [] }, name);
111
+ }
98
112
  if (name === null) {
99
113
  if (pinnedName(current) === undefined)
100
114
  return current;
@@ -1 +1 @@
1
- {"version":3,"file":"credential-accounts.js","sourceRoot":"","sources":["../../src/core/credential-accounts.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, join } from \"node:path\";\nimport type { Credential } from \"@earendil-works/pi-ai\";\nimport { listSlots, type PooledCredential, pinSlot, removeSlot } from \"@earendil-works/pi-ai/auth/pool/slots\";\nimport type { AuthStorage } from \"./auth-storage.ts\";\nimport { discoverEnvSlots } from \"./credential-pool/env-slots.ts\";\nimport { CredentialSlotRepository, type CredentialSlotState, slotHealth } from \"./credential-pool/state-store.ts\";\nimport { emitProviderAccountsChanged } from \"./extensions/builtin/claude-sdk-oauth/account-events.ts\";\n\nexport type CredentialAccountSource = \"login\" | \"import\" | \"env\";\n\n/** Account metadata safe to surface: names and health only, never key material. */\nexport type CredentialAccountSummary = {\n\treadonly name: string;\n\treadonly source: CredentialAccountSource;\n\treadonly blocked: boolean;\n\treadonly pinned: boolean;\n};\n\nconst ACCOUNT_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;\n\nexport function assertValidAccountName(name: string): void {\n\tif (!ACCOUNT_NAME_PATTERN.test(name)) {\n\t\tthrow new Error(\n\t\t\t`Invalid account name '${name}': use letters, digits, '-' or '_', starting with a letter or digit`,\n\t\t);\n\t}\n}\n\nfunction pooledFrom(credential: Credential | undefined): PooledCredential | undefined {\n\treturn credential === undefined ? undefined : credential;\n}\n\nfunction pinnedName(credential: PooledCredential | undefined): string | undefined {\n\treturn credential?.pinned;\n}\n\nfunction numberField(value: object, key: string): number | undefined {\n\tconst found = Object.entries(value).find(([candidate]) => candidate === key)?.[1];\n\treturn typeof found === \"number\" ? found : undefined;\n}\n\nfunction defaultRepository(storage: AuthStorage): CredentialSlotRepository {\n\tconst authPath = storage.getStoragePath();\n\treturn new CredentialSlotRepository(authPath ? join(dirname(authPath), \"credential-pool-state.json\") : undefined);\n}\n\nfunction stringField(value: object, key: string): string | undefined {\n\tconst found = Object.entries(value).find(([candidate]) => candidate === key)?.[1];\n\treturn typeof found === \"string\" ? found : undefined;\n}\n\n/**\n * Block state has two sources and both are authoritative: a slot persisted by an\n * existing provider lane carries its own `blockedUntil`/`blockReason` inside\n * auth.json, while generic pool failover records health in the sidecar. Reading\n * only one would silently downgrade a real block to \"available\".\n */\nfunction slotBlocked(slot: object, sidecar: CredentialSlotState | undefined, now: number): boolean {\n\tif (slotHealth(sidecar, now) === \"blocked\") return true;\n\tconst reason = stringField(slot, \"blockReason\");\n\tif (reason === \"auth_error\" || reason === \"account_disabled\") return true;\n\tconst blockedUntil = numberField(slot, \"blockedUntil\");\n\treturn blockedUntil !== undefined && blockedUntil > now;\n}\n\n/**\n * Lists a provider's credential accounts for ANY provider, not just one lane.\n * Stored slots own the listing when a credential exists; env slots are listed\n * only when nothing is stored, mirroring resolution precedence exactly. Health\n * comes from the pool sidecar, so a cooldown persisted by a failover shows up\n * here without auth.json ever carrying block state.\n */\nexport async function getCredentialAccounts(\n\tstorage: AuthStorage,\n\tprovider: string,\n\tenv: NodeJS.ProcessEnv = process.env,\n\trepository?: CredentialSlotRepository,\n): Promise<CredentialAccountSummary[]> {\n\treturn summarizeCredentialAccounts(provider, storage.get(provider), env, repository ?? defaultRepository(storage));\n}\n\n/** Storage-free variant for callers that already hold the credential (e.g. auth check). */\nexport async function summarizeCredentialAccounts(\n\tprovider: string,\n\tstored: Credential | undefined,\n\tenv: NodeJS.ProcessEnv = process.env,\n\trepository: CredentialSlotRepository = new CredentialSlotRepository(),\n): Promise<CredentialAccountSummary[]> {\n\tconst credential = pooledFrom(stored);\n\tconst now = Date.now();\n\tconst pinned = pinnedName(credential);\n\n\tif (credential) {\n\t\tconst state = await repository.listSlots(provider, \"stored\");\n\t\treturn listSlots(credential).map((slot) => ({\n\t\t\tname: slot.name,\n\t\t\tsource: slot.source ?? \"login\",\n\t\t\tblocked: slotBlocked(slot, state[slot.name], now),\n\t\t\tpinned: pinned === slot.name,\n\t\t}));\n\t}\n\n\tconst state = await repository.listSlots(provider, \"env\");\n\tconst summaries: CredentialAccountSummary[] = [];\n\tfor (const slot of discoverEnvSlots(provider, (name) => env[name])) {\n\t\tconst persisted = state[slot.name];\n\t\tconst revision = await repository.envCredentialRevision(slot.envVarName, slot.key);\n\t\t// A block belongs to the value that earned it; a rotated env key starts clean.\n\t\tconst applicable = persisted?.credentialRevision === revision ? persisted : undefined;\n\t\tsummaries.push({\n\t\t\tname: slot.name,\n\t\t\tsource: \"env\",\n\t\t\tblocked: slotHealth(applicable, now) === \"blocked\",\n\t\t\tpinned: pinned === slot.name,\n\t\t});\n\t}\n\treturn summaries;\n}\n\n/** Pins one slot, or clears the pin when `name` is null. */\nexport async function pinCredentialAccount(\n\tstorage: AuthStorage,\n\tprovider: string,\n\tname: string | null,\n\tenv: NodeJS.ProcessEnv = process.env,\n\trepository?: CredentialSlotRepository,\n): Promise<void> {\n\tconst repo = repository ?? defaultRepository(storage);\n\tif (name !== null) {\n\t\tassertValidAccountName(name);\n\t\tconst accounts = await getCredentialAccounts(storage, provider, env, repo);\n\t\tif (!accounts.some((account) => account.name === name)) {\n\t\t\tthrow new Error(`Provider account not found: ${name}`);\n\t\t}\n\t}\n\tawait storage.modify(provider, async (current) => {\n\t\tif (current === undefined) throw new Error(`No stored credential for provider: ${provider}`);\n\t\tif (name === null) {\n\t\t\tif (pinnedName(current) === undefined) return current;\n\t\t\tconst { pinned: _pinned, ...unpinned } = { ...current, pinned: undefined };\n\t\t\treturn unpinned;\n\t\t}\n\t\treturn pinSlot(current, name);\n\t});\n\t// Subscribed clients re-read the list after any mutation; skipping this leaves\n\t// a desktop account picker showing a stale pin.\n\temitProviderAccountsChanged(provider);\n}\n\n/**\n * Removes one stored slot. Env-backed accounts are refused: they are owned by\n * the environment, and deleting one here would silently disagree with the\n * variable that still defines it.\n */\nexport async function removeCredentialAccount(\n\tstorage: AuthStorage,\n\tprovider: string,\n\tname: string,\n\tenv: NodeJS.ProcessEnv = process.env,\n\trepository?: CredentialSlotRepository,\n): Promise<void> {\n\tconst repo = repository ?? defaultRepository(storage);\n\tconst accounts = await getCredentialAccounts(storage, provider, env, repo);\n\tconst account = accounts.find((candidate) => candidate.name === name);\n\tif (!account) throw new Error(`Provider account not found: ${name}`);\n\tif (account.source === \"env\") {\n\t\tthrow new Error(`Environment provider account cannot be removed: ${name}`);\n\t}\n\tconst next = await storage.read(provider);\n\tif (next === undefined) throw new Error(`No stored credential for provider: ${provider}`);\n\tconst remaining = removeSlot(next, name);\n\tif (remaining === undefined) await storage.delete(provider);\n\telse await storage.modify(provider, async () => remaining);\n\tawait repo.mutateSlotState(provider, \"stored\", name, () => undefined);\n\temitProviderAccountsChanged(provider);\n}\n"]}
1
+ {"version":3,"file":"credential-accounts.js","sourceRoot":"","sources":["../../src/core/credential-accounts.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, join } from \"node:path\";\nimport type { Credential } from \"@earendil-works/pi-ai\";\nimport { listSlots, type PooledCredential, pinSlot, removeSlot } from \"@earendil-works/pi-ai/auth/pool/slots\";\nimport type { AuthStorage } from \"./auth-storage.ts\";\nimport { discoverEnvSlots } from \"./credential-pool/env-slots.ts\";\nimport { CredentialSlotRepository, type CredentialSlotState, slotHealth } from \"./credential-pool/state-store.ts\";\nimport { emitProviderAccountsChanged } from \"./extensions/builtin/claude-sdk-oauth/account-events.ts\";\nimport { SENTINEL_OAUTH_FIELDS } from \"./extensions/builtin/claude-sdk-oauth/accounts.ts\";\n\nexport type CredentialAccountSource = \"login\" | \"import\" | \"env\";\n\n/** Account metadata safe to surface: names and health only, never key material. */\nexport type CredentialAccountSummary = {\n\treadonly name: string;\n\treadonly source: CredentialAccountSource;\n\treadonly blocked: boolean;\n\treadonly pinned: boolean;\n};\n\nconst ACCOUNT_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;\n\nexport function assertValidAccountName(name: string): void {\n\tif (!ACCOUNT_NAME_PATTERN.test(name)) {\n\t\tthrow new Error(\n\t\t\t`Invalid account name '${name}': use letters, digits, '-' or '_', starting with a letter or digit`,\n\t\t);\n\t}\n}\n\nfunction pooledFrom(credential: Credential | undefined): PooledCredential | undefined {\n\treturn credential === undefined ? undefined : credential;\n}\n\nfunction pinnedName(credential: PooledCredential | undefined): string | undefined {\n\treturn credential?.pinned;\n}\n\nfunction numberField(value: object, key: string): number | undefined {\n\tconst found = Object.entries(value).find(([candidate]) => candidate === key)?.[1];\n\treturn typeof found === \"number\" ? found : undefined;\n}\n\nfunction defaultRepository(storage: AuthStorage): CredentialSlotRepository {\n\tconst authPath = storage.getStoragePath();\n\treturn new CredentialSlotRepository(authPath ? join(dirname(authPath), \"credential-pool-state.json\") : undefined);\n}\n\nfunction stringField(value: object, key: string): string | undefined {\n\tconst found = Object.entries(value).find(([candidate]) => candidate === key)?.[1];\n\treturn typeof found === \"string\" ? found : undefined;\n}\n\n/**\n * Block state has two sources and both are authoritative: a slot persisted by an\n * existing provider lane carries its own `blockedUntil`/`blockReason` inside\n * auth.json, while generic pool failover records health in the sidecar. Reading\n * only one would silently downgrade a real block to \"available\".\n */\nfunction slotBlocked(slot: object, sidecar: CredentialSlotState | undefined, now: number): boolean {\n\tif (slotHealth(sidecar, now) === \"blocked\") return true;\n\tconst reason = stringField(slot, \"blockReason\");\n\tif (reason === \"auth_error\" || reason === \"account_disabled\") return true;\n\tconst blockedUntil = numberField(slot, \"blockedUntil\");\n\treturn blockedUntil !== undefined && blockedUntil > now;\n}\n\n/**\n * Lists a provider's credential accounts for ANY provider, not just one lane.\n * Stored slots own the listing when a credential exists; env slots are listed\n * only when nothing is stored, mirroring resolution precedence exactly. Health\n * comes from the pool sidecar, so a cooldown persisted by a failover shows up\n * here without auth.json ever carrying block state.\n */\nexport async function getCredentialAccounts(\n\tstorage: AuthStorage,\n\tprovider: string,\n\tenv: NodeJS.ProcessEnv = process.env,\n\trepository?: CredentialSlotRepository,\n): Promise<CredentialAccountSummary[]> {\n\treturn summarizeCredentialAccounts(provider, storage.get(provider), env, repository ?? defaultRepository(storage));\n}\n\n/** Storage-free variant for callers that already hold the credential (e.g. auth check). */\nexport async function summarizeCredentialAccounts(\n\tprovider: string,\n\tstored: Credential | undefined,\n\tenv: NodeJS.ProcessEnv = process.env,\n\trepository: CredentialSlotRepository = new CredentialSlotRepository(),\n): Promise<CredentialAccountSummary[]> {\n\tconst credential = pooledFrom(stored);\n\tconst now = Date.now();\n\tconst pinned = pinnedName(credential);\n\tconst summaries: CredentialAccountSummary[] = [];\n\n\tif (credential) {\n\t\tconst state = await repository.listSlots(provider, \"stored\");\n\t\tconst storedAccounts =\n\t\t\tprovider === \"claude-sdk-oauth\"\n\t\t\t\t? Array.isArray(credential.accounts)\n\t\t\t\t\t? listSlots(credential)\n\t\t\t\t\t: []\n\t\t\t\t: listSlots(credential);\n\t\tfor (const slot of storedAccounts) {\n\t\t\tsummaries.push({\n\t\t\t\tname: slot.name,\n\t\t\t\tsource: slot.source ?? \"login\",\n\t\t\t\tblocked: slotBlocked(slot, state[slot.name], now),\n\t\t\t\tpinned: pinned === slot.name,\n\t\t\t});\n\t\t}\n\t\tif (provider !== \"claude-sdk-oauth\") return summaries;\n\t}\n\n\tconst state = await repository.listSlots(provider, \"env\");\n\tfor (const slot of discoverEnvSlots(provider, (name) => env[name])) {\n\t\tconst persisted = state[slot.name];\n\t\tconst revision = await repository.envCredentialRevision(slot.envVarName, slot.key);\n\t\t// A block belongs to the value that earned it; a rotated env key starts clean.\n\t\tconst applicable = persisted?.credentialRevision === revision ? persisted : undefined;\n\t\tsummaries.push({\n\t\t\tname: slot.name,\n\t\t\tsource: \"env\",\n\t\t\tblocked: slotHealth(applicable, now) === \"blocked\",\n\t\t\tpinned: pinned === slot.name,\n\t\t});\n\t}\n\treturn summaries;\n}\n\n/** Pins one slot, or clears the pin when `name` is null. */\nexport async function pinCredentialAccount(\n\tstorage: AuthStorage,\n\tprovider: string,\n\tname: string | null,\n\tenv: NodeJS.ProcessEnv = process.env,\n\trepository?: CredentialSlotRepository,\n): Promise<void> {\n\tconst repo = repository ?? defaultRepository(storage);\n\tif (name !== null) {\n\t\tassertValidAccountName(name);\n\t\tconst accounts = await getCredentialAccounts(storage, provider, env, repo);\n\t\tif (!accounts.some((account) => account.name === name)) {\n\t\t\tthrow new Error(`Provider account not found: ${name}`);\n\t\t}\n\t}\n\tawait storage.modify(provider, async (current) => {\n\t\tif (current === undefined) {\n\t\t\tif (provider !== \"claude-sdk-oauth\" || name === null) {\n\t\t\t\tthrow new Error(`No stored credential for provider: ${provider}`);\n\t\t\t}\n\t\t\treturn pinSlot({ type: \"oauth\", ...SENTINEL_OAUTH_FIELDS, accounts: [] }, name);\n\t\t}\n\t\tif (name === null) {\n\t\t\tif (pinnedName(current) === undefined) return current;\n\t\t\tconst { pinned: _pinned, ...unpinned } = { ...current, pinned: undefined };\n\t\t\treturn unpinned;\n\t\t}\n\t\treturn pinSlot(current, name);\n\t});\n\t// Subscribed clients re-read the list after any mutation; skipping this leaves\n\t// a desktop account picker showing a stale pin.\n\temitProviderAccountsChanged(provider);\n}\n\n/**\n * Removes one stored slot. Env-backed accounts are refused: they are owned by\n * the environment, and deleting one here would silently disagree with the\n * variable that still defines it.\n */\nexport async function removeCredentialAccount(\n\tstorage: AuthStorage,\n\tprovider: string,\n\tname: string,\n\tenv: NodeJS.ProcessEnv = process.env,\n\trepository?: CredentialSlotRepository,\n): Promise<void> {\n\tconst repo = repository ?? defaultRepository(storage);\n\tconst accounts = await getCredentialAccounts(storage, provider, env, repo);\n\tconst account = accounts.find((candidate) => candidate.name === name);\n\tif (!account) throw new Error(`Provider account not found: ${name}`);\n\tif (account.source === \"env\") {\n\t\tthrow new Error(`Environment provider account cannot be removed: ${name}`);\n\t}\n\tconst next = await storage.read(provider);\n\tif (next === undefined) throw new Error(`No stored credential for provider: ${provider}`);\n\tconst remaining = removeSlot(next, name);\n\tif (remaining === undefined) await storage.delete(provider);\n\telse await storage.modify(provider, async () => remaining);\n\tawait repo.mutateSlotState(provider, \"stored\", name, () => undefined);\n\temitProviderAccountsChanged(provider);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"env-slots.d.ts","sourceRoot":"","sources":["../../../src/core/credential-pool/env-slots.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,MAAM,MAAM,iBAAiB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,KAAK,CAAC;CACd,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIpE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,iBAAiB,EAAE,CAYnH"}
1
+ {"version":3,"file":"env-slots.d.ts","sourceRoot":"","sources":["../../../src/core/credential-pool/env-slots.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,MAAM,MAAM,iBAAiB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,KAAK,CAAC;CACd,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKpE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,iBAAiB,EAAE,CAYnH"}
@@ -6,6 +6,8 @@ export const MAX_ENV_SLOT_INDEX = 16;
6
6
  * canonical mapping lists several variables.
7
7
  */
8
8
  export function primaryEnvVar(providerId) {
9
+ if (providerId === "claude-sdk-oauth")
10
+ return "CLAUDE_CODE_OAUTH_TOKEN";
9
11
  const envVars = getApiKeyEnvVars(providerId);
10
12
  if (!envVars || envVars.length === 0)
11
13
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"env-slots.js","sourceRoot":"","sources":["../../../src/core/credential-pool/env-slots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AASrC;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC/C,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACvD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,GAAyC;IAC7F,MAAM,OAAO,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAC1C,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,MAAM,KAAK,GAAwB,EAAE,CAAC;IACtC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IAC1B,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACrF,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,kBAAkB,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1D,MAAM,UAAU,GAAG,GAAG,OAAO,IAAI,KAAK,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9B,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC","sourcesContent":["import { getApiKeyEnvVars } from \"@earendil-works/pi-ai\";\n\nexport const MAX_ENV_SLOT_INDEX = 16;\n\nexport type EnvCredentialSlot = {\n\tname: string;\n\tenvVarName: string;\n\tkey: string;\n\tsource: \"env\";\n};\n\n/**\n * Anthropic maps to bearer/OAuth token vars ahead of its API-key var; numbered\n * slots must extend the API-key lane, so prefer the `*_API_KEY` entry when the\n * canonical mapping lists several variables.\n */\nexport function primaryEnvVar(providerId: string): string | undefined {\n\tconst envVars = getApiKeyEnvVars(providerId);\n\tif (!envVars || envVars.length === 0) return undefined;\n\treturn envVars.find((name) => name.endsWith(\"_API_KEY\")) ?? envVars[0];\n}\n\n/**\n * Discovers numbered env credential slots: the primary var yields slot `env`,\n * and `<VAR>_2` .. `<VAR>_16` yield `env-2` .. `env-16`. Discovery is\n * gap-tolerant - a configured `<VAR>_3` with no `<VAR>_2` is still found and no\n * slot is invented for the gap. Stored credentials keep outranking every env\n * slot because resolution consults env only when nothing is stored.\n */\nexport function discoverEnvSlots(providerId: string, env: (name: string) => string | undefined): EnvCredentialSlot[] {\n\tconst primary = primaryEnvVar(providerId);\n\tif (!primary) return [];\n\tconst slots: EnvCredentialSlot[] = [];\n\tconst base = env(primary);\n\tif (base) slots.push({ name: \"env\", envVarName: primary, key: base, source: \"env\" });\n\tfor (let index = 2; index <= MAX_ENV_SLOT_INDEX; index++) {\n\t\tconst envVarName = `${primary}_${index}`;\n\t\tconst value = env(envVarName);\n\t\tif (value) slots.push({ name: `env-${index}`, envVarName, key: value, source: \"env\" });\n\t}\n\treturn slots;\n}\n"]}
1
+ {"version":3,"file":"env-slots.js","sourceRoot":"","sources":["../../../src/core/credential-pool/env-slots.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AASrC;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC/C,IAAI,UAAU,KAAK,kBAAkB;QAAE,OAAO,yBAAyB,CAAC;IACxE,MAAM,OAAO,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACvD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,GAAyC;IAC7F,MAAM,OAAO,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAC1C,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,MAAM,KAAK,GAAwB,EAAE,CAAC;IACtC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IAC1B,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACrF,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,kBAAkB,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1D,MAAM,UAAU,GAAG,GAAG,OAAO,IAAI,KAAK,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9B,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC","sourcesContent":["import { getApiKeyEnvVars } from \"@earendil-works/pi-ai\";\n\nexport const MAX_ENV_SLOT_INDEX = 16;\n\nexport type EnvCredentialSlot = {\n\tname: string;\n\tenvVarName: string;\n\tkey: string;\n\tsource: \"env\";\n};\n\n/**\n * Anthropic maps to bearer/OAuth token vars ahead of its API-key var; numbered\n * slots must extend the API-key lane, so prefer the `*_API_KEY` entry when the\n * canonical mapping lists several variables.\n */\nexport function primaryEnvVar(providerId: string): string | undefined {\n\tif (providerId === \"claude-sdk-oauth\") return \"CLAUDE_CODE_OAUTH_TOKEN\";\n\tconst envVars = getApiKeyEnvVars(providerId);\n\tif (!envVars || envVars.length === 0) return undefined;\n\treturn envVars.find((name) => name.endsWith(\"_API_KEY\")) ?? envVars[0];\n}\n\n/**\n * Discovers numbered env credential slots: the primary var yields slot `env`,\n * and `<VAR>_2` .. `<VAR>_16` yield `env-2` .. `env-16`. Discovery is\n * gap-tolerant - a configured `<VAR>_3` with no `<VAR>_2` is still found and no\n * slot is invented for the gap. Stored credentials keep outranking every env\n * slot because resolution consults env only when nothing is stored.\n */\nexport function discoverEnvSlots(providerId: string, env: (name: string) => string | undefined): EnvCredentialSlot[] {\n\tconst primary = primaryEnvVar(providerId);\n\tif (!primary) return [];\n\tconst slots: EnvCredentialSlot[] = [];\n\tconst base = env(primary);\n\tif (base) slots.push({ name: \"env\", envVarName: primary, key: base, source: \"env\" });\n\tfor (let index = 2; index <= MAX_ENV_SLOT_INDEX; index++) {\n\t\tconst envVarName = `${primary}_${index}`;\n\t\tconst value = env(envVarName);\n\t\tif (value) slots.push({ name: `env-${index}`, envVarName, key: value, source: \"env\" });\n\t}\n\treturn slots;\n}\n"]}
@@ -6,14 +6,20 @@ interface RecoveryMetadata {
6
6
  todoSnapshot?: unknown;
7
7
  checkpoint?: unknown;
8
8
  }
9
+ export type DeterministicFallbackRejectionReason = "missing-preparation-boundary" | "unsafe-retained-content" | "retained-token-budget-exceeded" | "atomic-tool-chain-cut" | "context-reconstruction-failed";
9
10
  interface DeterministicFallbackDetails {
10
11
  schema: "senpi.compaction.deterministic-fallback.v1";
11
12
  origin: "required-compaction-recovery";
12
13
  failureKind: RequiredCompactionFallbackFailure;
13
14
  taskIntent?: string;
14
- retainedSuffix?: "prepared" | "latest-user-turn";
15
+ retainedSuffix?: "prepared" | "latest-user-turn" | "earlier-safe-boundary";
16
+ }
17
+ export interface DeterministicFallbackDiagnostic {
18
+ rejectionReason?: DeterministicFallbackRejectionReason;
19
+ candidatesChecked?: number;
20
+ budgetExceeded?: boolean;
15
21
  }
16
22
  export declare function classifyRequiredCompactionFallbackFailure(error: unknown): RequiredCompactionFallbackFailure | undefined;
17
- export declare function createRequiredCompactionFallback(preparation: CompactionPreparation, contextWindow: number, failureKind: RequiredCompactionFallbackFailure, metadata: RecoveryMetadata, branchEntries?: SessionEntry[]): CompactionResult<DeterministicFallbackDetails> | undefined;
23
+ export declare function createRequiredCompactionFallback(preparation: CompactionPreparation, contextWindow: number, failureKind: RequiredCompactionFallbackFailure, metadata: RecoveryMetadata, branchEntries?: SessionEntry[], diagnostics?: DeterministicFallbackDiagnostic): CompactionResult<DeterministicFallbackDetails> | undefined;
18
24
  export {};
19
25
  //# sourceMappingURL=deterministic-fallback.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deterministic-fallback.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/deterministic-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,gBAAgB,EAAkB,MAAM,8BAA8B,CAAC;AAGjH,OAAO,EAA6C,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAM3G,MAAM,MAAM,iCAAiC,GAC1C,uBAAuB,GACvB,2BAA2B,GAC3B,kCAAkC,CAAC;AAEtC,UAAU,gBAAgB;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,4BAA4B;IACrC,MAAM,EAAE,4CAA4C,CAAC;IACrD,MAAM,EAAE,8BAA8B,CAAC;IACvC,WAAW,EAAE,iCAAiC,CAAC;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,UAAU,GAAG,kBAAkB,CAAC;CACjD;AAuED,wBAAgB,yCAAyC,CACxD,KAAK,EAAE,OAAO,GACZ,iCAAiC,GAAG,SAAS,CAW/C;AAED,wBAAgB,gCAAgC,CAC/C,WAAW,EAAE,qBAAqB,EAClC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,iCAAiC,EAC9C,QAAQ,EAAE,gBAAgB,EAC1B,aAAa,GAAE,YAAY,EAAO,GAChC,gBAAgB,CAAC,4BAA4B,CAAC,GAAG,SAAS,CA8E5D"}
1
+ {"version":3,"file":"deterministic-fallback.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/deterministic-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,gBAAgB,EAAkB,MAAM,8BAA8B,CAAC;AAGjH,OAAO,EAIN,KAAK,YAAY,EACjB,MAAM,6BAA6B,CAAC;AAMrC,MAAM,MAAM,iCAAiC,GAC1C,uBAAuB,GACvB,2BAA2B,GAC3B,kCAAkC,CAAC;AAEtC,UAAU,gBAAgB;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,oCAAoC,GAC7C,8BAA8B,GAC9B,yBAAyB,GACzB,gCAAgC,GAChC,uBAAuB,GACvB,+BAA+B,CAAC;AAEnC,UAAU,4BAA4B;IACrC,MAAM,EAAE,4CAA4C,CAAC;IACrD,MAAM,EAAE,8BAA8B,CAAC;IACvC,WAAW,EAAE,iCAAiC,CAAC;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,UAAU,GAAG,kBAAkB,GAAG,uBAAuB,CAAC;CAC3E;AAED,MAAM,WAAW,+BAA+B;IAC/C,eAAe,CAAC,EAAE,oCAAoC,CAAC;IACvD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AA8CD,wBAAgB,yCAAyC,CACxD,KAAK,EAAE,OAAO,GACZ,iCAAiC,GAAG,SAAS,CAW/C;AAED,wBAAgB,gCAAgC,CAC/C,WAAW,EAAE,qBAAqB,EAClC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,iCAAiC,EAC9C,QAAQ,EAAE,gBAAgB,EAC1B,aAAa,GAAE,YAAY,EAAO,EAClC,WAAW,CAAC,EAAE,+BAA+B,GAC3C,gBAAgB,CAAC,4BAA4B,CAAC,GAAG,SAAS,CAiN5D"}