@code-yeongyu/senpi 2026.8.6 → 2026.8.9-2

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 (224) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/dist/core/agent-session.d.ts +14 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +81 -50
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/builtin/bash-timeout/index.d.ts +2 -2
  7. package/dist/core/extensions/builtin/bash-timeout/index.d.ts.map +1 -1
  8. package/dist/core/extensions/builtin/bash-timeout/index.js +17 -20
  9. package/dist/core/extensions/builtin/bash-timeout/index.js.map +1 -1
  10. package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts +7 -12
  11. package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts.map +1 -1
  12. package/dist/core/extensions/builtin/bash-timeout/timeout.js +11 -19
  13. package/dist/core/extensions/builtin/bash-timeout/timeout.js.map +1 -1
  14. package/dist/core/extensions/builtin/cache-keepalive/index.d.ts +30 -0
  15. package/dist/core/extensions/builtin/cache-keepalive/index.d.ts.map +1 -0
  16. package/dist/core/extensions/builtin/cache-keepalive/index.js +281 -0
  17. package/dist/core/extensions/builtin/cache-keepalive/index.js.map +1 -0
  18. package/dist/core/extensions/builtin/compaction/index.js +9 -9
  19. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  20. package/dist/core/extensions/builtin/goal/cache-warm-renderer.d.ts.map +1 -1
  21. package/dist/core/extensions/builtin/goal/cache-warm-renderer.js +21 -8
  22. package/dist/core/extensions/builtin/goal/cache-warm-renderer.js.map +1 -1
  23. package/dist/core/extensions/builtin/goal/cache-warm.d.ts +11 -0
  24. package/dist/core/extensions/builtin/goal/cache-warm.d.ts.map +1 -1
  25. package/dist/core/extensions/builtin/goal/cache-warm.js +16 -0
  26. package/dist/core/extensions/builtin/goal/cache-warm.js.map +1 -1
  27. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  28. package/dist/core/extensions/builtin/goal/index.js +31 -1
  29. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  30. package/dist/core/extensions/builtin/goal/monitor-continuation-types.d.ts +1 -0
  31. package/dist/core/extensions/builtin/goal/monitor-continuation-types.d.ts.map +1 -1
  32. package/dist/core/extensions/builtin/goal/monitor-continuation-types.js.map +1 -1
  33. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts +2 -1
  34. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
  35. package/dist/core/extensions/builtin/goal/monitor-continuation.js +153 -51
  36. package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
  37. package/dist/core/extensions/builtin/goal/prompt.d.ts +1 -1
  38. package/dist/core/extensions/builtin/goal/prompt.d.ts.map +1 -1
  39. package/dist/core/extensions/builtin/goal/prompt.js +21 -7
  40. package/dist/core/extensions/builtin/goal/prompt.js.map +1 -1
  41. package/dist/core/extensions/builtin/goal/store-changed-event.d.ts +8 -0
  42. package/dist/core/extensions/builtin/goal/store-changed-event.d.ts.map +1 -0
  43. package/dist/core/extensions/builtin/goal/store-changed-event.js +9 -0
  44. package/dist/core/extensions/builtin/goal/store-changed-event.js.map +1 -0
  45. package/dist/core/extensions/builtin/goal/wait-progress.d.ts +3 -1
  46. package/dist/core/extensions/builtin/goal/wait-progress.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/goal/wait-progress.js +27 -3
  48. package/dist/core/extensions/builtin/goal/wait-progress.js.map +1 -1
  49. package/dist/core/extensions/builtin/goal/wait-ticker.d.ts +4 -3
  50. package/dist/core/extensions/builtin/goal/wait-ticker.d.ts.map +1 -1
  51. package/dist/core/extensions/builtin/goal/wait-ticker.js +6 -6
  52. package/dist/core/extensions/builtin/goal/wait-ticker.js.map +1 -1
  53. package/dist/core/extensions/builtin/import-repro.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/import-repro.js +9 -0
  55. package/dist/core/extensions/builtin/import-repro.js.map +1 -1
  56. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/index.js +2 -0
  58. package/dist/core/extensions/builtin/index.js.map +1 -1
  59. package/dist/core/extensions/builtin/model-fallback/settings.d.ts.map +1 -1
  60. package/dist/core/extensions/builtin/model-fallback/settings.js +21 -1
  61. package/dist/core/extensions/builtin/model-fallback/settings.js.map +1 -1
  62. package/dist/core/extensions/builtin/monitor-state-event.d.ts +16 -0
  63. package/dist/core/extensions/builtin/monitor-state-event.d.ts.map +1 -1
  64. package/dist/core/extensions/builtin/monitor-state-event.js +15 -0
  65. package/dist/core/extensions/builtin/monitor-state-event.js.map +1 -1
  66. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  67. package/dist/core/extensions/builtin/terminal/extension.js +20 -1
  68. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  69. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts +5 -1
  70. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
  71. package/dist/core/extensions/builtin/terminal/session-bundle.js +19 -2
  72. package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
  73. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts.map +1 -1
  74. package/dist/core/extensions/builtin/terminal/tools/bash.js +24 -10
  75. package/dist/core/extensions/builtin/terminal/tools/bash.js.map +1 -1
  76. package/dist/core/extensions/builtin/terminal/tools/context.d.ts +2 -0
  77. package/dist/core/extensions/builtin/terminal/tools/context.d.ts.map +1 -1
  78. package/dist/core/extensions/builtin/terminal/tools/context.js.map +1 -1
  79. package/dist/core/extensions/builtin/terminal/tools/foreground-window.d.ts +11 -0
  80. package/dist/core/extensions/builtin/terminal/tools/foreground-window.d.ts.map +1 -0
  81. package/dist/core/extensions/builtin/terminal/tools/foreground-window.js +16 -0
  82. package/dist/core/extensions/builtin/terminal/tools/foreground-window.js.map +1 -0
  83. package/dist/core/extensions/builtin/terminal/tools/sleep-wait.d.ts +28 -0
  84. package/dist/core/extensions/builtin/terminal/tools/sleep-wait.d.ts.map +1 -0
  85. package/dist/core/extensions/builtin/terminal/tools/sleep-wait.js +75 -0
  86. package/dist/core/extensions/builtin/terminal/tools/sleep-wait.js.map +1 -0
  87. package/dist/core/extensions/builtin/tps.d.ts.map +1 -1
  88. package/dist/core/extensions/builtin/tps.js +3 -3
  89. package/dist/core/extensions/builtin/tps.js.map +1 -1
  90. package/dist/core/extensions/runner.d.ts +2 -0
  91. package/dist/core/extensions/runner.d.ts.map +1 -1
  92. package/dist/core/extensions/runner.js +15 -2
  93. package/dist/core/extensions/runner.js.map +1 -1
  94. package/dist/core/extensions/types.d.ts +16 -0
  95. package/dist/core/extensions/types.d.ts.map +1 -1
  96. package/dist/core/extensions/types.js.map +1 -1
  97. package/dist/core/resource-loader.d.ts +4 -0
  98. package/dist/core/resource-loader.d.ts.map +1 -1
  99. package/dist/core/resource-loader.js +6 -0
  100. package/dist/core/resource-loader.js.map +1 -1
  101. package/dist/core/retry-fallback/chains.d.ts +13 -1
  102. package/dist/core/retry-fallback/chains.d.ts.map +1 -1
  103. package/dist/core/retry-fallback/chains.js +80 -8
  104. package/dist/core/retry-fallback/chains.js.map +1 -1
  105. package/dist/core/retry-fallback/controller.d.ts +2 -0
  106. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  107. package/dist/core/retry-fallback/controller.js.map +1 -1
  108. package/dist/core/retry-fallback/expansion.d.ts +46 -0
  109. package/dist/core/retry-fallback/expansion.d.ts.map +1 -0
  110. package/dist/core/retry-fallback/expansion.js +113 -0
  111. package/dist/core/retry-fallback/expansion.js.map +1 -0
  112. package/dist/core/retry-fallback/hint-policy.d.ts +14 -0
  113. package/dist/core/retry-fallback/hint-policy.d.ts.map +1 -1
  114. package/dist/core/retry-fallback/hint-policy.js +14 -0
  115. package/dist/core/retry-fallback/hint-policy.js.map +1 -1
  116. package/dist/core/retry-fallback/settings.d.ts +6 -0
  117. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  118. package/dist/core/retry-fallback/settings.js +12 -10
  119. package/dist/core/retry-fallback/settings.js.map +1 -1
  120. package/dist/core/retry-fallback/validate.d.ts.map +1 -1
  121. package/dist/core/retry-fallback/validate.js +14 -1
  122. package/dist/core/retry-fallback/validate.js.map +1 -1
  123. package/dist/core/session-manager.d.ts +1 -1
  124. package/dist/core/session-manager.d.ts.map +1 -1
  125. package/dist/core/session-manager.js.map +1 -1
  126. package/dist/core/settings-manager.d.ts +10 -0
  127. package/dist/core/settings-manager.d.ts.map +1 -1
  128. package/dist/core/settings-manager.js +14 -0
  129. package/dist/core/settings-manager.js.map +1 -1
  130. package/dist/main.d.ts.map +1 -1
  131. package/dist/main.js +18 -3
  132. package/dist/main.js.map +1 -1
  133. package/dist/modes/app-server/threads/goal-handlers.d.ts.map +1 -1
  134. package/dist/modes/app-server/threads/goal-handlers.js +7 -0
  135. package/dist/modes/app-server/threads/goal-handlers.js.map +1 -1
  136. package/dist/modes/interactive/components/favorite-models-selector.d.ts +4 -0
  137. package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
  138. package/dist/modes/interactive/components/favorite-models-selector.js +23 -7
  139. package/dist/modes/interactive/components/favorite-models-selector.js.map +1 -1
  140. package/dist/modes/interactive/components/model-selector.d.ts +1 -0
  141. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  142. package/dist/modes/interactive/components/model-selector.js +11 -7
  143. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  144. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  145. package/dist/modes/interactive/interactive-mode.js +26 -6
  146. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  147. package/dist/modes/interactive/model-search-rank.d.ts +13 -0
  148. package/dist/modes/interactive/model-search-rank.d.ts.map +1 -0
  149. package/dist/modes/interactive/model-search-rank.js +200 -0
  150. package/dist/modes/interactive/model-search-rank.js.map +1 -0
  151. package/dist/modes/interactive/model-search.d.ts +0 -5
  152. package/dist/modes/interactive/model-search.d.ts.map +1 -1
  153. package/dist/modes/interactive/model-search.js +0 -9
  154. package/dist/modes/interactive/model-search.js.map +1 -1
  155. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  156. package/dist/modes/rpc/session-command-router.js +3 -2
  157. package/dist/modes/rpc/session-command-router.js.map +1 -1
  158. package/dist/modes/rpc/session-registry.d.ts +1 -1
  159. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  160. package/dist/modes/rpc/session-registry.js +3 -3
  161. package/dist/modes/rpc/session-registry.js.map +1 -1
  162. package/docs/providers.md +23 -3
  163. package/docs/settings.md +1 -1
  164. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +50 -0
  165. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  166. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/wake-source-state.ts +29 -0
  167. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +10 -0
  168. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +27 -1
  169. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +2 -1
  170. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.d.ts.map +1 -1
  171. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js +4 -7
  172. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js.map +1 -1
  173. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  174. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts +3 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  176. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +13 -0
  177. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  178. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  179. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +6 -3
  180. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  182. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +6 -2
  183. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  184. package/node_modules/@earendil-works/pi-ai/dist/api/warm-prompt-cache.d.ts +17 -0
  185. package/node_modules/@earendil-works/pi-ai/dist/api/warm-prompt-cache.d.ts.map +1 -0
  186. package/node_modules/@earendil-works/pi-ai/dist/api/warm-prompt-cache.js +52 -0
  187. package/node_modules/@earendil-works/pi-ai/dist/api/warm-prompt-cache.js.map +1 -0
  188. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +3 -1
  189. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  190. package/node_modules/@earendil-works/pi-ai/dist/index.js +3 -1
  191. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  192. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  193. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  194. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  195. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai-cn.json +1 -1
  196. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai.json +1 -1
  197. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  198. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  199. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  200. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  201. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  202. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.d.ts.map +1 -1
  203. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.js +120 -1
  204. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.js.map +1 -1
  205. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +2 -0
  206. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  207. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  208. package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.d.ts +3 -1
  209. package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.d.ts.map +1 -1
  210. package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.js +17 -3
  211. package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.js.map +1 -1
  212. package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.d.ts +4 -0
  213. package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.d.ts.map +1 -0
  214. package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.js +8 -0
  215. package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.js.map +1 -0
  216. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  217. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts +12 -0
  218. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts.map +1 -1
  219. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js +34 -2
  220. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js.map +1 -1
  221. package/node_modules/@earendil-works/pi-pty/native/prebuilds/darwin-arm64/senpi_pty.darwin-arm64.node +0 -0
  222. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  223. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  224. package/package.json +6 -6
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "../../types.ts";
2
- export type { BashTimeoutDefaults, BashToolInputLike, EffectiveBashTimeouts } from "./timeout.ts";
3
- export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, BASH_MAX_TIMEOUT_SECONDS, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, resolveEffectiveBashTimeouts, } from "./timeout.ts";
2
+ export type { BashTimeoutDefaults, BashToolInputLike } from "./timeout.ts";
3
+ export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, BASH_MAX_TIMEOUT_SECONDS, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, } from "./timeout.ts";
4
4
  export default function bashTimeoutExtension(pi: ExtensionAPI): void;
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/bash-timeout/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAWrE,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAClG,OAAO,EACN,gBAAgB,EAChB,4BAA4B,EAC5B,wBAAwB,EACxB,sBAAsB,EACtB,0BAA0B,EAC1B,4BAA4B,GAC5B,MAAM,cAAc,CAAC;AAEtB,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA+BnE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/bash-timeout/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAWrE,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EACN,gBAAgB,EAChB,4BAA4B,EAC5B,wBAAwB,EACxB,sBAAsB,EACtB,0BAA0B,GAC1B,MAAM,cAAc,CAAC;AAEtB,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA2BnE"}
@@ -1,36 +1,33 @@
1
1
  import { isAnthropicBashEnabled } from "../anthropic-bash/index.js";
2
- import { applyBashTimeout, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, resolveEffectiveBashTimeouts, } from "./timeout.js";
3
- export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, BASH_MAX_TIMEOUT_SECONDS, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, resolveEffectiveBashTimeouts, } from "./timeout.js";
2
+ import { resolveForegroundWindowSeconds } from "../terminal/tools/foreground-window.js";
3
+ import { applyBashTimeout, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, } from "./timeout.js";
4
+ export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, BASH_MAX_TIMEOUT_SECONDS, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, } from "./timeout.js";
4
5
  export default function bashTimeoutExtension(pi) {
5
6
  const env = typeof process !== "undefined" ? process.env : {};
6
- const baseDefaults = resolveBashTimeoutDefaults(env);
7
- let effective = resolveEffectiveBashTimeouts(baseDefaults, undefined);
8
- /**
9
- * Native Anthropic bash replaces the PTY `bash` tool, and the terminal
10
- * extension steps aside with it — so nothing implements the cache-deadline
11
- * detach. Advertising a cache ceiling there would promise behavior that
12
- * cannot happen, so the budget only applies while the PTY tool is live.
13
- */
14
- const resolveBudget = (ctx) => {
15
- if (isAnthropicBashEnabled() && ctx?.model?.api === "anthropic-messages")
16
- return undefined;
17
- return ctx?.getPromptCacheSafeWaitSeconds?.();
18
- };
19
- pi.on("tool_call", async (event, ctx) => {
7
+ const defaults = resolveBashTimeoutDefaults(env);
8
+ pi.on("tool_call", async (event) => {
20
9
  if (event.toolName !== "bash")
21
10
  return;
22
- effective = resolveEffectiveBashTimeouts(baseDefaults, resolveBudget(ctx));
23
11
  const input = event.input;
24
- const updated = applyBashTimeout(input, effective);
12
+ const updated = applyBashTimeout(input, defaults);
25
13
  if (updated !== input) {
26
14
  const timeout = updated.timeout;
27
15
  if (timeout !== undefined)
28
16
  input.timeout = timeout;
29
17
  }
30
18
  });
19
+ /**
20
+ * Native Anthropic bash replaces the PTY `bash` tool and the terminal
21
+ * extension steps aside with it, so nothing implements the auto-detach the
22
+ * policy would otherwise advertise.
23
+ */
24
+ const resolveWindow = (ctx) => {
25
+ if (isAnthropicBashEnabled() && ctx?.model?.api === "anthropic-messages")
26
+ return undefined;
27
+ return resolveForegroundWindowSeconds(env);
28
+ };
31
29
  pi.on("before_agent_start", async (event, ctx) => {
32
- effective = resolveEffectiveBashTimeouts(baseDefaults, resolveBudget(ctx));
33
- return { systemPrompt: `${event.systemPrompt}${buildBashTimeoutPrompt(effective)}` };
30
+ return { systemPrompt: `${event.systemPrompt}${buildBashTimeoutPrompt(defaults, resolveWindow(ctx))}` };
34
31
  });
35
32
  }
36
33
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/bash-timeout/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEpE,OAAO,EACN,gBAAgB,EAEhB,sBAAsB,EACtB,0BAA0B,EAC1B,4BAA4B,GAC5B,MAAM,cAAc,CAAC;AAGtB,OAAO,EACN,gBAAgB,EAChB,4BAA4B,EAC5B,wBAAwB,EACxB,sBAAsB,EACtB,0BAA0B,EAC1B,4BAA4B,GAC5B,MAAM,cAAc,CAAC;AAEtB,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,EAAgB;IAC5D,MAAM,GAAG,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9D,MAAM,YAAY,GAAG,0BAA0B,CAAC,GAAG,CAAC,CAAC;IACrD,IAAI,SAAS,GAAG,4BAA4B,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IAEtE;;;;;OAKG;IACH,MAAM,aAAa,GAAG,CAAC,GAAiC,EAAsB,EAAE;QAC/E,IAAI,sBAAsB,EAAE,IAAI,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,oBAAoB;YAAE,OAAO,SAAS,CAAC;QAC3F,OAAO,GAAG,EAAE,6BAA6B,EAAE,EAAE,CAAC;IAC/C,CAAC,CAAC;IAEF,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACvC,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM;YAAE,OAAO;QACtC,SAAS,GAAG,4BAA4B,CAAC,YAAY,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3E,MAAM,KAAK,GAAG,KAAK,CAAC,KAA0B,CAAC;QAC/C,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QACnD,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;YAChC,IAAI,OAAO,KAAK,SAAS;gBAAE,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;QACpD,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAChD,SAAS,GAAG,4BAA4B,CAAC,YAAY,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3E,OAAO,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC,YAAY,GAAG,sBAAsB,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;IACtF,CAAC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { ExtensionAPI, ExtensionContext } from \"../../types.ts\";\nimport { isAnthropicBashEnabled } from \"../anthropic-bash/index.ts\";\n\nimport {\n\tapplyBashTimeout,\n\ttype BashToolInputLike,\n\tbuildBashTimeoutPrompt,\n\tresolveBashTimeoutDefaults,\n\tresolveEffectiveBashTimeouts,\n} from \"./timeout.ts\";\n\nexport type { BashTimeoutDefaults, BashToolInputLike, EffectiveBashTimeouts } from \"./timeout.ts\";\nexport {\n\tapplyBashTimeout,\n\tBASH_DEFAULT_TIMEOUT_SECONDS,\n\tBASH_MAX_TIMEOUT_SECONDS,\n\tbuildBashTimeoutPrompt,\n\tresolveBashTimeoutDefaults,\n\tresolveEffectiveBashTimeouts,\n} from \"./timeout.ts\";\n\nexport default function bashTimeoutExtension(pi: ExtensionAPI): void {\n\tconst env = typeof process !== \"undefined\" ? process.env : {};\n\tconst baseDefaults = resolveBashTimeoutDefaults(env);\n\tlet effective = resolveEffectiveBashTimeouts(baseDefaults, undefined);\n\n\t/**\n\t * Native Anthropic bash replaces the PTY `bash` tool, and the terminal\n\t * extension steps aside with it so nothing implements the cache-deadline\n\t * detach. Advertising a cache ceiling there would promise behavior that\n\t * cannot happen, so the budget only applies while the PTY tool is live.\n\t */\n\tconst resolveBudget = (ctx: ExtensionContext | undefined): number | undefined => {\n\t\tif (isAnthropicBashEnabled() && ctx?.model?.api === \"anthropic-messages\") return undefined;\n\t\treturn ctx?.getPromptCacheSafeWaitSeconds?.();\n\t};\n\n\tpi.on(\"tool_call\", async (event, ctx) => {\n\t\tif (event.toolName !== \"bash\") return;\n\t\teffective = resolveEffectiveBashTimeouts(baseDefaults, resolveBudget(ctx));\n\t\tconst input = event.input as BashToolInputLike;\n\t\tconst updated = applyBashTimeout(input, effective);\n\t\tif (updated !== input) {\n\t\t\tconst timeout = updated.timeout;\n\t\t\tif (timeout !== undefined) input.timeout = timeout;\n\t\t}\n\t});\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\teffective = resolveEffectiveBashTimeouts(baseDefaults, resolveBudget(ctx));\n\t\treturn { systemPrompt: `${event.systemPrompt}${buildBashTimeoutPrompt(effective)}` };\n\t});\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/bash-timeout/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AAExF,OAAO,EACN,gBAAgB,EAEhB,sBAAsB,EACtB,0BAA0B,GAC1B,MAAM,cAAc,CAAC;AAGtB,OAAO,EACN,gBAAgB,EAChB,4BAA4B,EAC5B,wBAAwB,EACxB,sBAAsB,EACtB,0BAA0B,GAC1B,MAAM,cAAc,CAAC;AAEtB,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,EAAgB;IAC5D,MAAM,GAAG,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9D,MAAM,QAAQ,GAAG,0BAA0B,CAAC,GAAG,CAAC,CAAC;IAEjD,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAClC,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM;YAAE,OAAO;QACtC,MAAM,KAAK,GAAG,KAAK,CAAC,KAA0B,CAAC;QAC/C,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAClD,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACvB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;YAChC,IAAI,OAAO,KAAK,SAAS;gBAAE,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;QACpD,CAAC;IACF,CAAC,CAAC,CAAC;IAEH;;;;OAIG;IACH,MAAM,aAAa,GAAG,CAAC,GAAiC,EAAsB,EAAE;QAC/E,IAAI,sBAAsB,EAAE,IAAI,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,oBAAoB;YAAE,OAAO,SAAS,CAAC;QAC3F,OAAO,8BAA8B,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC,CAAC;IAEF,EAAE,CAAC,EAAE,CAAC,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAChD,OAAO,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC,YAAY,GAAG,sBAAsB,CAAC,QAAQ,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC;IACzG,CAAC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { ExtensionAPI, ExtensionContext } from \"../../types.ts\";\nimport { isAnthropicBashEnabled } from \"../anthropic-bash/index.ts\";\nimport { resolveForegroundWindowSeconds } from \"../terminal/tools/foreground-window.ts\";\n\nimport {\n\tapplyBashTimeout,\n\ttype BashToolInputLike,\n\tbuildBashTimeoutPrompt,\n\tresolveBashTimeoutDefaults,\n} from \"./timeout.ts\";\n\nexport type { BashTimeoutDefaults, BashToolInputLike } from \"./timeout.ts\";\nexport {\n\tapplyBashTimeout,\n\tBASH_DEFAULT_TIMEOUT_SECONDS,\n\tBASH_MAX_TIMEOUT_SECONDS,\n\tbuildBashTimeoutPrompt,\n\tresolveBashTimeoutDefaults,\n} from \"./timeout.ts\";\n\nexport default function bashTimeoutExtension(pi: ExtensionAPI): void {\n\tconst env = typeof process !== \"undefined\" ? process.env : {};\n\tconst defaults = resolveBashTimeoutDefaults(env);\n\n\tpi.on(\"tool_call\", async (event) => {\n\t\tif (event.toolName !== \"bash\") return;\n\t\tconst input = event.input as BashToolInputLike;\n\t\tconst updated = applyBashTimeout(input, defaults);\n\t\tif (updated !== input) {\n\t\t\tconst timeout = updated.timeout;\n\t\t\tif (timeout !== undefined) input.timeout = timeout;\n\t\t}\n\t});\n\n\t/**\n\t * Native Anthropic bash replaces the PTY `bash` tool and the terminal\n\t * extension steps aside with it, so nothing implements the auto-detach the\n\t * policy would otherwise advertise.\n\t */\n\tconst resolveWindow = (ctx: ExtensionContext | undefined): number | undefined => {\n\t\tif (isAnthropicBashEnabled() && ctx?.model?.api === \"anthropic-messages\") return undefined;\n\t\treturn resolveForegroundWindowSeconds(env);\n\t};\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\treturn { systemPrompt: `${event.systemPrompt}${buildBashTimeoutPrompt(defaults, resolveWindow(ctx))}` };\n\t});\n}\n"]}
@@ -1,5 +1,5 @@
1
- export declare const BASH_DEFAULT_TIMEOUT_SECONDS = 120;
2
- export declare const BASH_MAX_TIMEOUT_SECONDS = 600;
1
+ export declare const BASH_DEFAULT_TIMEOUT_SECONDS = 1800;
2
+ export declare const BASH_MAX_TIMEOUT_SECONDS = 1800;
3
3
  export interface BashTimeoutDefaults {
4
4
  defaultSeconds: number;
5
5
  maxSeconds: number;
@@ -12,17 +12,12 @@ export interface BashToolInputLike {
12
12
  [key: string]: unknown;
13
13
  }
14
14
  export declare function applyBashTimeout<TInput extends BashToolInputLike>(input: TInput, defaults: BashTimeoutDefaults): TInput;
15
- export interface EffectiveBashTimeouts extends BashTimeoutDefaults {
16
- cacheCapped?: boolean;
17
- }
18
15
  /**
19
- * Lower the recommended maximum to the prompt-cache safe-wait budget, so the
20
- * model is never told to block past the point where the cache expires. The two
21
- * `min()` expressions are the complete policy: a budget below the injected
22
- * default pulls that default down with it, keeping the prompt, the injected
23
- * value, and the terminal detach deadline consistent.
16
+ * `foregroundWindowSeconds` is `undefined` when no PTY bash tool is live (native
17
+ * Anthropic bash replaces it). Nothing implements auto-detach then, so the
18
+ * policy must not promise it a prompt that describes impossible behavior is
19
+ * worse than a silent one.
24
20
  */
25
- export declare function resolveEffectiveBashTimeouts(defaults: BashTimeoutDefaults, safeWaitSeconds: number | undefined): Required<EffectiveBashTimeouts>;
26
- export declare function buildBashTimeoutPrompt(defaults: EffectiveBashTimeouts): string;
21
+ export declare function buildBashTimeoutPrompt(defaults: BashTimeoutDefaults, foregroundWindowSeconds?: number): string;
27
22
  export {};
28
23
  //# sourceMappingURL=timeout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"timeout.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/bash-timeout/timeout.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,MAAM,WAAW,mBAAmB;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AASlD,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,OAAO,GAAG,mBAAmB,CAM5E;AAED,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,iBAAiB,EAChE,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,mBAAmB,GAC3B,MAAM,CAMR;AAED,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC3C,QAAQ,EAAE,mBAAmB,EAC7B,eAAe,EAAE,MAAM,GAAG,SAAS,GACjC,QAAQ,CAAC,qBAAqB,CAAC,CAMjC;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAM9E"}
1
+ {"version":3,"file":"timeout.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/bash-timeout/timeout.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,OAAO,CAAC;AACjD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AASlD,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,OAAO,GAAG,mBAAmB,CAM5E;AAED,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,wBAAgB,gBAAgB,CAAC,MAAM,SAAS,iBAAiB,EAChE,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,mBAAmB,GAC3B,MAAM,CAMR;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,mBAAmB,EAAE,uBAAuB,CAAC,EAAE,MAAM,GAAG,MAAM,CAO9G"}
@@ -1,5 +1,5 @@
1
- export const BASH_DEFAULT_TIMEOUT_SECONDS = 120;
2
- export const BASH_MAX_TIMEOUT_SECONDS = 600;
1
+ export const BASH_DEFAULT_TIMEOUT_SECONDS = 1800;
2
+ export const BASH_MAX_TIMEOUT_SECONDS = 1800;
3
3
  function parsePositiveInt(value) {
4
4
  if (!value)
5
5
  return undefined;
@@ -23,24 +23,16 @@ export function applyBashTimeout(input, defaults) {
23
23
  return input;
24
24
  }
25
25
  /**
26
- * Lower the recommended maximum to the prompt-cache safe-wait budget, so the
27
- * model is never told to block past the point where the cache expires. The two
28
- * `min()` expressions are the complete policy: a budget below the injected
29
- * default pulls that default down with it, keeping the prompt, the injected
30
- * value, and the terminal detach deadline consistent.
26
+ * `foregroundWindowSeconds` is `undefined` when no PTY bash tool is live (native
27
+ * Anthropic bash replaces it). Nothing implements auto-detach then, so the
28
+ * policy must not promise it a prompt that describes impossible behavior is
29
+ * worse than a silent one.
31
30
  */
32
- export function resolveEffectiveBashTimeouts(defaults, safeWaitSeconds) {
33
- if (safeWaitSeconds === undefined || safeWaitSeconds >= defaults.maxSeconds) {
34
- return { ...defaults, cacheCapped: false };
35
- }
36
- const maxSeconds = Math.min(defaults.maxSeconds, safeWaitSeconds);
37
- return { defaultSeconds: Math.min(defaults.defaultSeconds, maxSeconds), maxSeconds, cacheCapped: true };
38
- }
39
- export function buildBashTimeoutPrompt(defaults) {
31
+ export function buildBashTimeoutPrompt(defaults, foregroundWindowSeconds) {
40
32
  const minutes = (seconds) => (seconds % 60 === 0 ? `${seconds / 60} min` : `${seconds}s`);
41
- const maxReason = defaults.cacheCapped
42
- ? ` This ceiling is the prompt cache lifetime of the active model minus a safety buffer: blocking past it expires the cache and forces a full re-read on the next request. A foreground command still running at that point is handed to a background session alive instead of being killed; those sessions stay live, so stop the ones you no longer need with \`kill_bash\`.`
43
- : "";
44
- return `\n## Bash Tool Timeout Policy\n\nThe \`bash\` tool enforces timeouts even when you omit the \`timeout\` parameter:\n\n- Default timeout: ${defaults.defaultSeconds}s (${minutes(defaults.defaultSeconds)}). Applied automatically when you do not set \`timeout\`.\n- Recommended maximum timeout: ${defaults.maxSeconds}s (${minutes(defaults.maxSeconds)}).${maxReason} Explicit \`timeout\` values are preserved because different hosts may use different timeout units.\n- For long-running commands (builds, installs, test suites), set an explicit \`timeout\` that fits the workload. Do not assume commands run forever.\n- For commands that legitimately need to run beyond the recommended maximum, start them with \`run_in_background: true\` and watch the decisive output with \`monitor\` instead of raising the timeout.\n`;
33
+ const detachRules = foregroundWindowSeconds === undefined
34
+ ? ""
35
+ : `\n- Foreground blocking stops at the ~${foregroundWindowSeconds}s window. A command still running then auto-detaches alive to a background session with a \`bash_id\` and keeps running until it exits, hits the kill deadline, or is stopped with \`kill_bash\`.\n- Completion arrives automatically as a notification carrying the exit status and output tail, so end your turn rather than poll. Use \`bash_output\` only for a midpoint peek.`;
36
+ return `\n## Bash Tool Timeout Policy\n\nThe \`bash\` tool's \`timeout\` parameter is the process kill deadline, not how long you wait for output: the command is killed when it reaches the deadline.\n\n- Default timeout: ${defaults.defaultSeconds}s (${minutes(defaults.defaultSeconds)}). Applied automatically when you do not set \`timeout\`.\n- Recommended maximum timeout: ${defaults.maxSeconds}s (${minutes(defaults.maxSeconds)}). Explicit \`timeout\` values are preserved because different hosts may use different timeout units.${detachRules}\n- Waiting on an observable condition (a log line, a CI check, a server coming up) belongs to \`monitor({command, filter})\`, never a foreground \`sleep\` or poll loop.\n- Sessions started with \`run_in_background: true\` ignore \`timeout\` and live until exit or \`kill_bash\`.\n`;
45
37
  }
46
38
  //# sourceMappingURL=timeout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/bash-timeout/timeout.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAChD,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAS5C,SAAS,gBAAgB,CAAC,KAAyB;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9D,OAAO,MAAM,CAAC;AACf,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,GAAY;IACtD,MAAM,EAAE,+BAA+B,EAAE,cAAc,EAAE,2BAA2B,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC;IACzG,MAAM,cAAc,GAAG,gBAAgB,CAAC,cAAc,CAAC,IAAI,4BAA4B,CAAC;IACxF,MAAM,MAAM,GAAG,gBAAgB,CAAC,UAAU,CAAC,IAAI,wBAAwB,CAAC;IACxE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACpD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC;AACvC,CAAC;AAQD,MAAM,UAAU,gBAAgB,CAC/B,KAAa,EACb,QAA6B;IAE7B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,cAAc,EAAE,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAMD;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAC3C,QAA6B,EAC7B,eAAmC;IAEnC,IAAI,eAAe,KAAK,SAAS,IAAI,eAAe,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC7E,OAAO,EAAE,GAAG,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IAClE,OAAO,EAAE,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AACzG,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,QAA+B;IACrE,MAAM,OAAO,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,CAAC,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;IAC1G,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW;QACrC,CAAC,CAAC,6WAA6W;QAC/W,CAAC,CAAC,EAAE,CAAC;IACN,OAAO,4IAA4I,QAAQ,CAAC,cAAc,MAAM,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,6FAA6F,QAAQ,CAAC,UAAU,MAAM,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,SAAS,scAAsc,CAAC;AACxzB,CAAC","sourcesContent":["export const BASH_DEFAULT_TIMEOUT_SECONDS = 120;\nexport const BASH_MAX_TIMEOUT_SECONDS = 600;\n\nexport interface BashTimeoutDefaults {\n\tdefaultSeconds: number;\n\tmaxSeconds: number;\n}\n\ntype EnvLike = Record<string, string | undefined>;\n\nfunction parsePositiveInt(value: string | undefined): number | undefined {\n\tif (!value) return undefined;\n\tconst parsed = Number.parseInt(value, 10);\n\tif (!Number.isFinite(parsed) || parsed <= 0) return undefined;\n\treturn parsed;\n}\n\nexport function resolveBashTimeoutDefaults(env: EnvLike): BashTimeoutDefaults {\n\tconst { PI_BASH_DEFAULT_TIMEOUT_SECONDS: defaultTimeout, PI_BASH_MAX_TIMEOUT_SECONDS: maxTimeout } = env;\n\tconst defaultSeconds = parsePositiveInt(defaultTimeout) ?? BASH_DEFAULT_TIMEOUT_SECONDS;\n\tconst rawMax = parsePositiveInt(maxTimeout) ?? BASH_MAX_TIMEOUT_SECONDS;\n\tconst maxSeconds = Math.max(rawMax, defaultSeconds);\n\treturn { defaultSeconds, maxSeconds };\n}\n\nexport interface BashToolInputLike {\n\tcommand: string;\n\ttimeout?: number;\n\t[key: string]: unknown;\n}\n\nexport function applyBashTimeout<TInput extends BashToolInputLike>(\n\tinput: TInput,\n\tdefaults: BashTimeoutDefaults,\n): TInput {\n\tconst current = input.timeout;\n\tif (current === undefined || current <= 0) {\n\t\treturn { ...input, timeout: defaults.defaultSeconds };\n\t}\n\treturn input;\n}\n\nexport interface EffectiveBashTimeouts extends BashTimeoutDefaults {\n\tcacheCapped?: boolean;\n}\n\n/**\n * Lower the recommended maximum to the prompt-cache safe-wait budget, so the\n * model is never told to block past the point where the cache expires. The two\n * `min()` expressions are the complete policy: a budget below the injected\n * default pulls that default down with it, keeping the prompt, the injected\n * value, and the terminal detach deadline consistent.\n */\nexport function resolveEffectiveBashTimeouts(\n\tdefaults: BashTimeoutDefaults,\n\tsafeWaitSeconds: number | undefined,\n): Required<EffectiveBashTimeouts> {\n\tif (safeWaitSeconds === undefined || safeWaitSeconds >= defaults.maxSeconds) {\n\t\treturn { ...defaults, cacheCapped: false };\n\t}\n\tconst maxSeconds = Math.min(defaults.maxSeconds, safeWaitSeconds);\n\treturn { defaultSeconds: Math.min(defaults.defaultSeconds, maxSeconds), maxSeconds, cacheCapped: true };\n}\n\nexport function buildBashTimeoutPrompt(defaults: EffectiveBashTimeouts): string {\n\tconst minutes = (seconds: number): string => (seconds % 60 === 0 ? `${seconds / 60} min` : `${seconds}s`);\n\tconst maxReason = defaults.cacheCapped\n\t\t? ` This ceiling is the prompt cache lifetime of the active model minus a safety buffer: blocking past it expires the cache and forces a full re-read on the next request. A foreground command still running at that point is handed to a background session alive instead of being killed; those sessions stay live, so stop the ones you no longer need with \\`kill_bash\\`.`\n\t\t: \"\";\n\treturn `\\n## Bash Tool Timeout Policy\\n\\nThe \\`bash\\` tool enforces timeouts even when you omit the \\`timeout\\` parameter:\\n\\n- Default timeout: ${defaults.defaultSeconds}s (${minutes(defaults.defaultSeconds)}). Applied automatically when you do not set \\`timeout\\`.\\n- Recommended maximum timeout: ${defaults.maxSeconds}s (${minutes(defaults.maxSeconds)}).${maxReason} Explicit \\`timeout\\` values are preserved because different hosts may use different timeout units.\\n- For long-running commands (builds, installs, test suites), set an explicit \\`timeout\\` that fits the workload. Do not assume commands run forever.\\n- For commands that legitimately need to run beyond the recommended maximum, start them with \\`run_in_background: true\\` and watch the decisive output with \\`monitor\\` instead of raising the timeout.\\n`;\n}\n"]}
1
+ {"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/bash-timeout/timeout.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC;AACjD,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAS7C,SAAS,gBAAgB,CAAC,KAAyB;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9D,OAAO,MAAM,CAAC;AACf,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,GAAY;IACtD,MAAM,EAAE,+BAA+B,EAAE,cAAc,EAAE,2BAA2B,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC;IACzG,MAAM,cAAc,GAAG,gBAAgB,CAAC,cAAc,CAAC,IAAI,4BAA4B,CAAC;IACxF,MAAM,MAAM,GAAG,gBAAgB,CAAC,UAAU,CAAC,IAAI,wBAAwB,CAAC;IACxE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACpD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC;AACvC,CAAC;AAQD,MAAM,UAAU,gBAAgB,CAC/B,KAAa,EACb,QAA6B;IAE7B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,cAAc,EAAE,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAA6B,EAAE,uBAAgC;IACrG,MAAM,OAAO,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,CAAC,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;IAC1G,MAAM,WAAW,GAChB,uBAAuB,KAAK,SAAS;QACpC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,yCAAyC,uBAAuB,oXAAoX,CAAC;IACzb,OAAO,wNAAwN,QAAQ,CAAC,cAAc,MAAM,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,6FAA6F,QAAQ,CAAC,UAAU,MAAM,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,wGAAwG,WAAW,2RAA2R,CAAC;AAC9zB,CAAC","sourcesContent":["export const BASH_DEFAULT_TIMEOUT_SECONDS = 1800;\nexport const BASH_MAX_TIMEOUT_SECONDS = 1800;\n\nexport interface BashTimeoutDefaults {\n\tdefaultSeconds: number;\n\tmaxSeconds: number;\n}\n\ntype EnvLike = Record<string, string | undefined>;\n\nfunction parsePositiveInt(value: string | undefined): number | undefined {\n\tif (!value) return undefined;\n\tconst parsed = Number.parseInt(value, 10);\n\tif (!Number.isFinite(parsed) || parsed <= 0) return undefined;\n\treturn parsed;\n}\n\nexport function resolveBashTimeoutDefaults(env: EnvLike): BashTimeoutDefaults {\n\tconst { PI_BASH_DEFAULT_TIMEOUT_SECONDS: defaultTimeout, PI_BASH_MAX_TIMEOUT_SECONDS: maxTimeout } = env;\n\tconst defaultSeconds = parsePositiveInt(defaultTimeout) ?? BASH_DEFAULT_TIMEOUT_SECONDS;\n\tconst rawMax = parsePositiveInt(maxTimeout) ?? BASH_MAX_TIMEOUT_SECONDS;\n\tconst maxSeconds = Math.max(rawMax, defaultSeconds);\n\treturn { defaultSeconds, maxSeconds };\n}\n\nexport interface BashToolInputLike {\n\tcommand: string;\n\ttimeout?: number;\n\t[key: string]: unknown;\n}\n\nexport function applyBashTimeout<TInput extends BashToolInputLike>(\n\tinput: TInput,\n\tdefaults: BashTimeoutDefaults,\n): TInput {\n\tconst current = input.timeout;\n\tif (current === undefined || current <= 0) {\n\t\treturn { ...input, timeout: defaults.defaultSeconds };\n\t}\n\treturn input;\n}\n\n/**\n * `foregroundWindowSeconds` is `undefined` when no PTY bash tool is live (native\n * Anthropic bash replaces it). Nothing implements auto-detach then, so the\n * policy must not promise it a prompt that describes impossible behavior is\n * worse than a silent one.\n */\nexport function buildBashTimeoutPrompt(defaults: BashTimeoutDefaults, foregroundWindowSeconds?: number): string {\n\tconst minutes = (seconds: number): string => (seconds % 60 === 0 ? `${seconds / 60} min` : `${seconds}s`);\n\tconst detachRules =\n\t\tforegroundWindowSeconds === undefined\n\t\t\t? \"\"\n\t\t\t: `\\n- Foreground blocking stops at the ~${foregroundWindowSeconds}s window. A command still running then auto-detaches alive to a background session with a \\`bash_id\\` and keeps running until it exits, hits the kill deadline, or is stopped with \\`kill_bash\\`.\\n- Completion arrives automatically as a notification carrying the exit status and output tail, so end your turn rather than poll. Use \\`bash_output\\` only for a midpoint peek.`;\n\treturn `\\n## Bash Tool Timeout Policy\\n\\nThe \\`bash\\` tool's \\`timeout\\` parameter is the process kill deadline, not how long you wait for output: the command is killed when it reaches the deadline.\\n\\n- Default timeout: ${defaults.defaultSeconds}s (${minutes(defaults.defaultSeconds)}). Applied automatically when you do not set \\`timeout\\`.\\n- Recommended maximum timeout: ${defaults.maxSeconds}s (${minutes(defaults.maxSeconds)}). Explicit \\`timeout\\` values are preserved because different hosts may use different timeout units.${detachRules}\\n- Waiting on an observable condition (a log line, a CI check, a server coming up) belongs to \\`monitor({command, filter})\\`, never a foreground \\`sleep\\` or poll loop.\\n- Sessions started with \\`run_in_background: true\\` ignore \\`timeout\\` and live until exit or \\`kill_bash\\`.\\n`;\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import { type Context, type Model, type WarmPromptCacheOptions, type WarmPromptCacheResult } from "@earendil-works/pi-ai";
2
+ import type { EntryRenderer, ExtensionFactory } from "../../types.ts";
3
+ export declare const CACHE_KEEPALIVE_ENTRY_TYPE = "cache-keepalive";
4
+ export declare const CACHE_WARM_PING_EVENT = "cache_warm_ping";
5
+ interface StartedEntry {
6
+ readonly phase: "started";
7
+ }
8
+ interface PingEntry {
9
+ readonly phase: "ping";
10
+ readonly iteration: number;
11
+ readonly cacheRead: number;
12
+ readonly cacheWrite: number;
13
+ readonly estimatedCostUsd: number;
14
+ readonly cumulativeEstimatedUsd: number;
15
+ }
16
+ interface StoppedEntry {
17
+ readonly phase: "stopped";
18
+ readonly iterations: number;
19
+ readonly stopReason: string;
20
+ readonly cumulativeEstimatedUsd: number;
21
+ }
22
+ export type CacheKeepAliveEntryData = StartedEntry | PingEntry | StoppedEntry;
23
+ type WarmPromptCacheFn = (model: Model<any>, context: Context, options?: WarmPromptCacheOptions) => Promise<WarmPromptCacheResult>;
24
+ export declare const renderCacheKeepAliveEntry: EntryRenderer<CacheKeepAliveEntryData>;
25
+ export declare function createCacheKeepAliveExtension(dependencies?: {
26
+ readonly warmPromptCache?: WarmPromptCacheFn;
27
+ }): ExtensionFactory;
28
+ declare const _default: ExtensionFactory;
29
+ export default _default;
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/cache-keepalive/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,OAAO,EAEZ,KAAK,KAAK,EAGV,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAE1B,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,aAAa,EAAkC,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAItG,eAAO,MAAM,0BAA0B,oBAAoB,CAAC;AAC5D,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AAEvD,UAAU,YAAY;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC1B;AAED,UAAU,SAAS;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACxC;AAED,UAAU,YAAY;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,MAAM,uBAAuB,GAAG,YAAY,GAAG,SAAS,GAAG,YAAY,CAAC;AAE9E,KAAK,iBAAiB,GAAG,CACxB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,sBAAsB,KAC5B,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAEpC,eAAO,MAAM,yBAAyB,EAAE,aAAa,CAAC,uBAAuB,CAS3E,CAAC;AAEH,wBAAgB,6BAA6B,CAC5C,YAAY,GAAE;IAAE,QAAQ,CAAC,eAAe,CAAC,EAAE,iBAAiB,CAAA;CAAO,GACjE,gBAAgB,CAwNlB"}
@@ -0,0 +1,281 @@
1
+ import { isAnthropicApiBaseUrl, resolvePromptCacheTtlSeconds, warmPromptCache, } from "@earendil-works/pi-ai";
2
+ import { convertToLlm, filterContextExcludedMessages } from "../../../messages.js";
3
+ import { noticeEntryRenderer } from "../../notice/index.js";
4
+ import { formatWarmTokenCount } from "../goal/cache-warm.js";
5
+ import { GOAL_CONTINUATION_TIMER_STATE_EVENT } from "../goal/monitor-continuation.js";
6
+ export const CACHE_KEEPALIVE_ENTRY_TYPE = "cache-keepalive";
7
+ export const CACHE_WARM_PING_EVENT = "cache_warm_ping";
8
+ export const renderCacheKeepAliveEntry = noticeEntryRenderer((entry) => {
9
+ const data = entry.data;
10
+ if (data?.phase !== "ping")
11
+ return undefined;
12
+ const refreshed = data.cacheRead + data.cacheWrite;
13
+ return {
14
+ title: `⚡ Warm ping #${data.iteration} · ~${formatWarmTokenCount(refreshed)} tokens refreshed · ${formatUsd(data.estimatedCostUsd)}`,
15
+ why: "Refreshed the active Anthropic prompt cache while the session was idle.",
16
+ expandedLine: `cache read ${data.cacheRead} · cache write ${data.cacheWrite} · session total ${formatUsd(data.cumulativeEstimatedUsd)}`,
17
+ };
18
+ });
19
+ export function createCacheKeepAliveExtension(dependencies = {}) {
20
+ const warm = dependencies.warmPromptCache ?? warmPromptCache;
21
+ return (pi) => {
22
+ let ctx;
23
+ let timer;
24
+ let inFlight = false;
25
+ let generation = 0;
26
+ let active = false;
27
+ let goalTimerArmed = false;
28
+ let attempts = 0;
29
+ let cumulativeEstimatedUsd = 0;
30
+ let lastCompletedAtMs;
31
+ let lastMessages = [];
32
+ let lastUsage;
33
+ pi.registerEntryRenderer(CACHE_KEEPALIVE_ENTRY_TYPE, renderCacheKeepAliveEntry);
34
+ const unsubscribeGoalTimer = pi.events?.on(GOAL_CONTINUATION_TIMER_STATE_EVENT, (data) => {
35
+ if (!isGoalTimerState(data))
36
+ return;
37
+ goalTimerArmed = data.armed;
38
+ if (data.armed)
39
+ stop("goal-timer-armed");
40
+ });
41
+ function append(data) {
42
+ pi.appendEntry(CACHE_KEEPALIVE_ENTRY_TYPE, data);
43
+ }
44
+ function stop(reason, forceEntry = false) {
45
+ const shouldAppend = active || inFlight || timer !== undefined || forceEntry;
46
+ generation += 1;
47
+ if (timer !== undefined)
48
+ clearTimeout(timer);
49
+ timer = undefined;
50
+ active = false;
51
+ if (shouldAppend) {
52
+ append({
53
+ phase: "stopped",
54
+ iterations: attempts,
55
+ stopReason: reason,
56
+ cumulativeEstimatedUsd,
57
+ });
58
+ }
59
+ }
60
+ function arm() {
61
+ if (timer !== undefined || inFlight)
62
+ return;
63
+ const current = ctx;
64
+ const settings = current?.getPromptCacheKeepAliveSettings?.();
65
+ if (!settings?.enabled || current?.model === undefined || lastCompletedAtMs === undefined)
66
+ return;
67
+ if (!isWarmSupportedModel(current.model))
68
+ return;
69
+ if (!current.isIdle()) {
70
+ stop("agent-busy");
71
+ return;
72
+ }
73
+ if (current.hasPendingMessages()) {
74
+ stop("pending-messages");
75
+ return;
76
+ }
77
+ if (goalTimerArmed) {
78
+ stop("goal-timer-armed");
79
+ return;
80
+ }
81
+ const safeWaitSeconds = current.getPromptCacheSafeWaitSeconds?.();
82
+ if (safeWaitSeconds === undefined || !Number.isFinite(safeWaitSeconds))
83
+ return;
84
+ if (attempts >= Math.max(0, settings.maxRequestsPerSession)) {
85
+ stop("max-requests", true);
86
+ return;
87
+ }
88
+ const projectedUsd = projectedPingCost(current.model, lastUsage);
89
+ if (cumulativeEstimatedUsd + projectedUsd > Math.max(0, settings.maxCostUsdPerSession)) {
90
+ stop("cost-cap", true);
91
+ return;
92
+ }
93
+ if (!active) {
94
+ active = true;
95
+ append({ phase: "started" });
96
+ }
97
+ const intervalMs = Math.max(0, safeWaitSeconds - Math.max(0, settings.marginSeconds)) * 1000;
98
+ const delayMs = Math.max(0, lastCompletedAtMs + intervalMs - Date.now());
99
+ const scheduledGeneration = generation;
100
+ timer = setTimeout(() => {
101
+ timer = undefined;
102
+ void ping(scheduledGeneration);
103
+ }, delayMs);
104
+ }
105
+ async function ping(scheduledGeneration) {
106
+ if (scheduledGeneration !== generation)
107
+ return;
108
+ const current = ctx;
109
+ const settings = current?.getPromptCacheKeepAliveSettings?.();
110
+ if (current === undefined ||
111
+ settings?.enabled !== true ||
112
+ current.model === undefined ||
113
+ !isWarmSupportedModel(current.model) ||
114
+ !current.isIdle() ||
115
+ current.hasPendingMessages() ||
116
+ goalTimerArmed ||
117
+ attempts >= Math.max(0, settings.maxRequestsPerSession) ||
118
+ cumulativeEstimatedUsd + projectedPingCost(current.model, lastUsage) >
119
+ Math.max(0, settings.maxCostUsdPerSession)) {
120
+ arm();
121
+ return;
122
+ }
123
+ inFlight = true;
124
+ attempts += 1;
125
+ const iteration = attempts;
126
+ const pingGeneration = generation;
127
+ try {
128
+ const preparation = await current.prepareProviderRequest?.(lastMessages);
129
+ if (pingGeneration !== generation) {
130
+ inFlight = false;
131
+ arm();
132
+ return;
133
+ }
134
+ const preparedMessages = preparation?.messages ?? lastMessages;
135
+ const activeToolNames = new Set(pi.getActiveTools());
136
+ const tools = pi
137
+ .getAllTools()
138
+ .filter((tool) => activeToolNames.has(tool.name))
139
+ .map(({ name, description, parameters }) => ({ name, description, parameters }));
140
+ const auth = await current.modelRegistry.getApiKeyAndHeaders(current.model);
141
+ if (pingGeneration !== generation) {
142
+ inFlight = false;
143
+ arm();
144
+ return;
145
+ }
146
+ if (!auth.ok)
147
+ throw new Error(auth.error);
148
+ const authHeaders = auth.headers ?? {};
149
+ const headers = preparation ? await preparation.transformHeaders(authHeaders) : authHeaders;
150
+ const result = await warm(current.model, {
151
+ systemPrompt: current.getSystemPrompt(),
152
+ messages: convertToLlm(filterContextExcludedMessages(preparedMessages)),
153
+ ...(tools.length > 0 ? { tools } : {}),
154
+ }, {
155
+ apiKey: auth.apiKey,
156
+ headers,
157
+ sessionId: current.sessionManager.getSessionId(),
158
+ cacheRetention: current.model.cacheRetention,
159
+ onPayload: preparation ? async (payload) => await preparation.transformPayload(payload) : undefined,
160
+ });
161
+ if (pingGeneration !== generation) {
162
+ inFlight = false;
163
+ arm();
164
+ return;
165
+ }
166
+ if (!result.supported) {
167
+ inFlight = false;
168
+ stop("unsupported-model");
169
+ return;
170
+ }
171
+ const estimatedCostUsd = actualPingCost(current.model, result.usage);
172
+ cumulativeEstimatedUsd += estimatedCostUsd;
173
+ lastCompletedAtMs = Date.now();
174
+ const cachedTokens = result.usage.cacheRead + result.usage.cacheWrite;
175
+ const ttlSeconds = resolvePromptCacheTtlSeconds(current.model) ?? 0;
176
+ pi.events?.emit(CACHE_WARM_PING_EVENT, { iteration, cachedTokens, ttlSeconds, estimatedCostUsd });
177
+ append({
178
+ phase: "ping",
179
+ iteration,
180
+ cacheRead: result.usage.cacheRead,
181
+ cacheWrite: result.usage.cacheWrite,
182
+ estimatedCostUsd,
183
+ cumulativeEstimatedUsd,
184
+ });
185
+ inFlight = false;
186
+ arm();
187
+ }
188
+ catch {
189
+ inFlight = false;
190
+ if (pingGeneration === generation)
191
+ stop("provider-error");
192
+ else
193
+ arm();
194
+ }
195
+ }
196
+ pi.on("session_start", (_event, nextCtx) => {
197
+ stop("session-restart");
198
+ ctx = nextCtx;
199
+ attempts = 0;
200
+ cumulativeEstimatedUsd = 0;
201
+ lastMessages = nextCtx.sessionManager.buildSessionContext().messages;
202
+ const usage = lastAssistantUsage(lastMessages);
203
+ lastUsage = usage;
204
+ lastCompletedAtMs = lastAssistantTimestamp(lastMessages);
205
+ arm();
206
+ });
207
+ pi.on("agent_end", (event, nextCtx) => {
208
+ ctx = nextCtx;
209
+ const usage = lastAssistantUsage(event.messages);
210
+ if (usage?.stopReason === "error") {
211
+ stop("provider-error");
212
+ return;
213
+ }
214
+ lastMessages = [...event.messages];
215
+ lastUsage = usage;
216
+ lastCompletedAtMs = Date.now();
217
+ arm();
218
+ });
219
+ pi.on("model_select", (_event, nextCtx) => {
220
+ ctx = nextCtx;
221
+ stop("model-changed");
222
+ arm();
223
+ });
224
+ pi.on("agent_start", () => stop("agent-busy"));
225
+ pi.on("input", () => stop("user-input"));
226
+ pi.on("session_shutdown", () => {
227
+ stop("session-dispose");
228
+ unsubscribeGoalTimer?.();
229
+ ctx = undefined;
230
+ });
231
+ };
232
+ }
233
+ function isWarmSupportedModel(model) {
234
+ return model.api === "anthropic-messages" && isAnthropicApiBaseUrl(model.baseUrl);
235
+ }
236
+ function lastAssistantUsage(messages) {
237
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
238
+ const message = messages[index];
239
+ if (message.role !== "assistant")
240
+ continue;
241
+ return {
242
+ input: finiteTokens(message.usage.input),
243
+ cacheRead: finiteTokens(message.usage.cacheRead),
244
+ cacheWrite: finiteTokens(message.usage.cacheWrite),
245
+ stopReason: message.stopReason,
246
+ };
247
+ }
248
+ return undefined;
249
+ }
250
+ function lastAssistantTimestamp(messages) {
251
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
252
+ const message = messages[index];
253
+ if (message.role === "assistant" && Number.isFinite(message.timestamp))
254
+ return message.timestamp;
255
+ }
256
+ return undefined;
257
+ }
258
+ function projectedPingCost(model, usage) {
259
+ const promptTokens = (usage?.input ?? 0) + (usage?.cacheRead ?? 0) + (usage?.cacheWrite ?? 0);
260
+ return (promptTokens * Math.max(model.cost.cacheRead, model.cost.cacheWrite)) / 1_000_000;
261
+ }
262
+ function actualPingCost(model, usage) {
263
+ return ((usage.cacheRead * model.cost.cacheRead +
264
+ usage.cacheWrite * model.cost.cacheWrite +
265
+ usage.input * model.cost.input) /
266
+ 1_000_000);
267
+ }
268
+ function finiteTokens(value) {
269
+ return Number.isFinite(value) && value > 0 ? value : 0;
270
+ }
271
+ function formatUsd(value) {
272
+ return `$${value.toFixed(3)}`;
273
+ }
274
+ function isGoalTimerState(value) {
275
+ return (typeof value === "object" &&
276
+ value !== null &&
277
+ typeof value.armed === "boolean" &&
278
+ typeof value.kind === "string");
279
+ }
280
+ export default createCacheKeepAliveExtension();
281
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/cache-keepalive/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport {\n\ttype Context,\n\tisAnthropicApiBaseUrl,\n\ttype Model,\n\tresolvePromptCacheTtlSeconds,\n\ttype Tool,\n\ttype WarmPromptCacheOptions,\n\ttype WarmPromptCacheResult,\n\twarmPromptCache,\n} from \"@earendil-works/pi-ai\";\nimport { convertToLlm, filterContextExcludedMessages } from \"../../../messages.ts\";\nimport { noticeEntryRenderer } from \"../../notice/index.ts\";\nimport type { EntryRenderer, ExtensionAPI, ExtensionContext, ExtensionFactory } from \"../../types.ts\";\nimport { formatWarmTokenCount } from \"../goal/cache-warm.ts\";\nimport { GOAL_CONTINUATION_TIMER_STATE_EVENT } from \"../goal/monitor-continuation.ts\";\n\nexport const CACHE_KEEPALIVE_ENTRY_TYPE = \"cache-keepalive\";\nexport const CACHE_WARM_PING_EVENT = \"cache_warm_ping\";\n\ninterface StartedEntry {\n\treadonly phase: \"started\";\n}\n\ninterface PingEntry {\n\treadonly phase: \"ping\";\n\treadonly iteration: number;\n\treadonly cacheRead: number;\n\treadonly cacheWrite: number;\n\treadonly estimatedCostUsd: number;\n\treadonly cumulativeEstimatedUsd: number;\n}\n\ninterface StoppedEntry {\n\treadonly phase: \"stopped\";\n\treadonly iterations: number;\n\treadonly stopReason: string;\n\treadonly cumulativeEstimatedUsd: number;\n}\n\nexport type CacheKeepAliveEntryData = StartedEntry | PingEntry | StoppedEntry;\n\ntype WarmPromptCacheFn = (\n\tmodel: Model<any>,\n\tcontext: Context,\n\toptions?: WarmPromptCacheOptions,\n) => Promise<WarmPromptCacheResult>;\n\nexport const renderCacheKeepAliveEntry: EntryRenderer<CacheKeepAliveEntryData> = noticeEntryRenderer((entry) => {\n\tconst data = entry.data;\n\tif (data?.phase !== \"ping\") return undefined;\n\tconst refreshed = data.cacheRead + data.cacheWrite;\n\treturn {\n\t\ttitle: `⚡ Warm ping #${data.iteration} · ~${formatWarmTokenCount(refreshed)} tokens refreshed · ${formatUsd(data.estimatedCostUsd)}`,\n\t\twhy: \"Refreshed the active Anthropic prompt cache while the session was idle.\",\n\t\texpandedLine: `cache read ${data.cacheRead} · cache write ${data.cacheWrite} · session total ${formatUsd(data.cumulativeEstimatedUsd)}`,\n\t};\n});\n\nexport function createCacheKeepAliveExtension(\n\tdependencies: { readonly warmPromptCache?: WarmPromptCacheFn } = {},\n): ExtensionFactory {\n\tconst warm = dependencies.warmPromptCache ?? warmPromptCache;\n\treturn (pi: ExtensionAPI) => {\n\t\tlet ctx: ExtensionContext | undefined;\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tlet inFlight = false;\n\t\tlet generation = 0;\n\t\tlet active = false;\n\t\tlet goalTimerArmed = false;\n\t\tlet attempts = 0;\n\t\tlet cumulativeEstimatedUsd = 0;\n\t\tlet lastCompletedAtMs: number | undefined;\n\t\tlet lastMessages: AgentMessage[] = [];\n\t\tlet lastUsage: { input: number; cacheRead: number; cacheWrite: number } | undefined;\n\n\t\tpi.registerEntryRenderer(CACHE_KEEPALIVE_ENTRY_TYPE, renderCacheKeepAliveEntry);\n\n\t\tconst unsubscribeGoalTimer = pi.events?.on(GOAL_CONTINUATION_TIMER_STATE_EVENT, (data) => {\n\t\t\tif (!isGoalTimerState(data)) return;\n\t\t\tgoalTimerArmed = data.armed;\n\t\t\tif (data.armed) stop(\"goal-timer-armed\");\n\t\t});\n\n\t\tfunction append(data: CacheKeepAliveEntryData): void {\n\t\t\tpi.appendEntry(CACHE_KEEPALIVE_ENTRY_TYPE, data);\n\t\t}\n\n\t\tfunction stop(reason: string, forceEntry = false): void {\n\t\t\tconst shouldAppend = active || inFlight || timer !== undefined || forceEntry;\n\t\t\tgeneration += 1;\n\t\t\tif (timer !== undefined) clearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t\tactive = false;\n\t\t\tif (shouldAppend) {\n\t\t\t\tappend({\n\t\t\t\t\tphase: \"stopped\",\n\t\t\t\t\titerations: attempts,\n\t\t\t\t\tstopReason: reason,\n\t\t\t\t\tcumulativeEstimatedUsd,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tfunction arm(): void {\n\t\t\tif (timer !== undefined || inFlight) return;\n\t\t\tconst current = ctx;\n\t\t\tconst settings = current?.getPromptCacheKeepAliveSettings?.();\n\t\t\tif (!settings?.enabled || current?.model === undefined || lastCompletedAtMs === undefined) return;\n\t\t\tif (!isWarmSupportedModel(current.model)) return;\n\t\t\tif (!current.isIdle()) {\n\t\t\t\tstop(\"agent-busy\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (current.hasPendingMessages()) {\n\t\t\t\tstop(\"pending-messages\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (goalTimerArmed) {\n\t\t\t\tstop(\"goal-timer-armed\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst safeWaitSeconds = current.getPromptCacheSafeWaitSeconds?.();\n\t\t\tif (safeWaitSeconds === undefined || !Number.isFinite(safeWaitSeconds)) return;\n\t\t\tif (attempts >= Math.max(0, settings.maxRequestsPerSession)) {\n\t\t\t\tstop(\"max-requests\", true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst projectedUsd = projectedPingCost(current.model, lastUsage);\n\t\t\tif (cumulativeEstimatedUsd + projectedUsd > Math.max(0, settings.maxCostUsdPerSession)) {\n\t\t\t\tstop(\"cost-cap\", true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!active) {\n\t\t\t\tactive = true;\n\t\t\t\tappend({ phase: \"started\" });\n\t\t\t}\n\t\t\tconst intervalMs = Math.max(0, safeWaitSeconds - Math.max(0, settings.marginSeconds)) * 1000;\n\t\t\tconst delayMs = Math.max(0, lastCompletedAtMs + intervalMs - Date.now());\n\t\t\tconst scheduledGeneration = generation;\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\ttimer = undefined;\n\t\t\t\tvoid ping(scheduledGeneration);\n\t\t\t}, delayMs);\n\t\t}\n\n\t\tasync function ping(scheduledGeneration: number): Promise<void> {\n\t\t\tif (scheduledGeneration !== generation) return;\n\t\t\tconst current = ctx;\n\t\t\tconst settings = current?.getPromptCacheKeepAliveSettings?.();\n\t\t\tif (\n\t\t\t\tcurrent === undefined ||\n\t\t\t\tsettings?.enabled !== true ||\n\t\t\t\tcurrent.model === undefined ||\n\t\t\t\t!isWarmSupportedModel(current.model) ||\n\t\t\t\t!current.isIdle() ||\n\t\t\t\tcurrent.hasPendingMessages() ||\n\t\t\t\tgoalTimerArmed ||\n\t\t\t\tattempts >= Math.max(0, settings.maxRequestsPerSession) ||\n\t\t\t\tcumulativeEstimatedUsd + projectedPingCost(current.model, lastUsage) >\n\t\t\t\t\tMath.max(0, settings.maxCostUsdPerSession)\n\t\t\t) {\n\t\t\t\tarm();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tinFlight = true;\n\t\t\tattempts += 1;\n\t\t\tconst iteration = attempts;\n\t\t\tconst pingGeneration = generation;\n\t\t\ttry {\n\t\t\t\tconst preparation = await current.prepareProviderRequest?.(lastMessages);\n\t\t\t\tif (pingGeneration !== generation) {\n\t\t\t\t\tinFlight = false;\n\t\t\t\t\tarm();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst preparedMessages = preparation?.messages ?? lastMessages;\n\t\t\t\tconst activeToolNames = new Set(pi.getActiveTools());\n\t\t\t\tconst tools: Tool[] = pi\n\t\t\t\t\t.getAllTools()\n\t\t\t\t\t.filter((tool) => activeToolNames.has(tool.name))\n\t\t\t\t\t.map(({ name, description, parameters }) => ({ name, description, parameters }));\n\t\t\t\tconst auth = await current.modelRegistry.getApiKeyAndHeaders(current.model);\n\t\t\t\tif (pingGeneration !== generation) {\n\t\t\t\t\tinFlight = false;\n\t\t\t\t\tarm();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!auth.ok) throw new Error(auth.error);\n\t\t\t\tconst authHeaders = auth.headers ?? {};\n\t\t\t\tconst headers = preparation ? await preparation.transformHeaders(authHeaders) : authHeaders;\n\t\t\t\tconst result = await warm(\n\t\t\t\t\tcurrent.model,\n\t\t\t\t\t{\n\t\t\t\t\t\tsystemPrompt: current.getSystemPrompt(),\n\t\t\t\t\t\tmessages: convertToLlm(filterContextExcludedMessages(preparedMessages)),\n\t\t\t\t\t\t...(tools.length > 0 ? { tools } : {}),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tapiKey: auth.apiKey,\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tsessionId: current.sessionManager.getSessionId(),\n\t\t\t\t\t\tcacheRetention: current.model.cacheRetention,\n\t\t\t\t\t\tonPayload: preparation ? async (payload) => await preparation.transformPayload(payload) : undefined,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tif (pingGeneration !== generation) {\n\t\t\t\t\tinFlight = false;\n\t\t\t\t\tarm();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!result.supported) {\n\t\t\t\t\tinFlight = false;\n\t\t\t\t\tstop(\"unsupported-model\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst estimatedCostUsd = actualPingCost(current.model, result.usage);\n\t\t\t\tcumulativeEstimatedUsd += estimatedCostUsd;\n\t\t\t\tlastCompletedAtMs = Date.now();\n\t\t\t\tconst cachedTokens = result.usage.cacheRead + result.usage.cacheWrite;\n\t\t\t\tconst ttlSeconds = resolvePromptCacheTtlSeconds(current.model) ?? 0;\n\t\t\t\tpi.events?.emit(CACHE_WARM_PING_EVENT, { iteration, cachedTokens, ttlSeconds, estimatedCostUsd });\n\t\t\t\tappend({\n\t\t\t\t\tphase: \"ping\",\n\t\t\t\t\titeration,\n\t\t\t\t\tcacheRead: result.usage.cacheRead,\n\t\t\t\t\tcacheWrite: result.usage.cacheWrite,\n\t\t\t\t\testimatedCostUsd,\n\t\t\t\t\tcumulativeEstimatedUsd,\n\t\t\t\t});\n\t\t\t\tinFlight = false;\n\t\t\t\tarm();\n\t\t\t} catch {\n\t\t\t\tinFlight = false;\n\t\t\t\tif (pingGeneration === generation) stop(\"provider-error\");\n\t\t\t\telse arm();\n\t\t\t}\n\t\t}\n\n\t\tpi.on(\"session_start\", (_event, nextCtx) => {\n\t\t\tstop(\"session-restart\");\n\t\t\tctx = nextCtx;\n\t\t\tattempts = 0;\n\t\t\tcumulativeEstimatedUsd = 0;\n\t\t\tlastMessages = nextCtx.sessionManager.buildSessionContext().messages;\n\t\t\tconst usage = lastAssistantUsage(lastMessages);\n\t\t\tlastUsage = usage;\n\t\t\tlastCompletedAtMs = lastAssistantTimestamp(lastMessages);\n\t\t\tarm();\n\t\t});\n\n\t\tpi.on(\"agent_end\", (event, nextCtx) => {\n\t\t\tctx = nextCtx;\n\t\t\tconst usage = lastAssistantUsage(event.messages);\n\t\t\tif (usage?.stopReason === \"error\") {\n\t\t\t\tstop(\"provider-error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlastMessages = [...event.messages];\n\t\t\tlastUsage = usage;\n\t\t\tlastCompletedAtMs = Date.now();\n\t\t\tarm();\n\t\t});\n\n\t\tpi.on(\"model_select\", (_event, nextCtx) => {\n\t\t\tctx = nextCtx;\n\t\t\tstop(\"model-changed\");\n\t\t\tarm();\n\t\t});\n\t\tpi.on(\"agent_start\", () => stop(\"agent-busy\"));\n\t\tpi.on(\"input\", () => stop(\"user-input\"));\n\t\tpi.on(\"session_shutdown\", () => {\n\t\t\tstop(\"session-dispose\");\n\t\t\tunsubscribeGoalTimer?.();\n\t\t\tctx = undefined;\n\t\t});\n\t};\n}\n\nfunction isWarmSupportedModel(model: Model<any>): model is Model<\"anthropic-messages\"> {\n\treturn model.api === \"anthropic-messages\" && isAnthropicApiBaseUrl(model.baseUrl);\n}\n\nfunction lastAssistantUsage(\n\tmessages: readonly AgentMessage[],\n): { input: number; cacheRead: number; cacheWrite: number; stopReason?: string } | undefined {\n\tfor (let index = messages.length - 1; index >= 0; index -= 1) {\n\t\tconst message = messages[index];\n\t\tif (message.role !== \"assistant\") continue;\n\t\treturn {\n\t\t\tinput: finiteTokens(message.usage.input),\n\t\t\tcacheRead: finiteTokens(message.usage.cacheRead),\n\t\t\tcacheWrite: finiteTokens(message.usage.cacheWrite),\n\t\t\tstopReason: message.stopReason,\n\t\t};\n\t}\n\treturn undefined;\n}\n\nfunction lastAssistantTimestamp(messages: readonly AgentMessage[]): number | undefined {\n\tfor (let index = messages.length - 1; index >= 0; index -= 1) {\n\t\tconst message = messages[index];\n\t\tif (message.role === \"assistant\" && Number.isFinite(message.timestamp)) return message.timestamp;\n\t}\n\treturn undefined;\n}\n\nfunction projectedPingCost(\n\tmodel: Model<any>,\n\tusage: { input: number; cacheRead: number; cacheWrite: number } | undefined,\n): number {\n\tconst promptTokens = (usage?.input ?? 0) + (usage?.cacheRead ?? 0) + (usage?.cacheWrite ?? 0);\n\treturn (promptTokens * Math.max(model.cost.cacheRead, model.cost.cacheWrite)) / 1_000_000;\n}\n\nfunction actualPingCost(model: Model<any>, usage: { input: number; cacheRead: number; cacheWrite: number }): number {\n\treturn (\n\t\t(usage.cacheRead * model.cost.cacheRead +\n\t\t\tusage.cacheWrite * model.cost.cacheWrite +\n\t\t\tusage.input * model.cost.input) /\n\t\t1_000_000\n\t);\n}\n\nfunction finiteTokens(value: number): number {\n\treturn Number.isFinite(value) && value > 0 ? value : 0;\n}\n\nfunction formatUsd(value: number): string {\n\treturn `$${value.toFixed(3)}`;\n}\n\nfunction isGoalTimerState(value: unknown): value is { armed: boolean; kind: string } {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\ttypeof (value as { armed?: unknown }).armed === \"boolean\" &&\n\t\ttypeof (value as { kind?: unknown }).kind === \"string\"\n\t);\n}\n\nexport default createCacheKeepAliveExtension();\n"]}
@@ -288,19 +288,19 @@ export default function compactionExtension(pi, remoteCompactionDependencies = {
288
288
  }
289
289
  }
290
290
  const result = await applyGeneratedCompaction(ctx, pendingJob.snapshot, () => speculativeGeneration, compaction, feedbackSignal);
291
- if (result.applied || result.reason === "stale") {
291
+ if (result.applied) {
292
292
  speculativeJob = undefined;
293
- if (result.applied)
294
- getLogger(ctx).debug("speculative_applied", {
295
- generation: pendingJob.generation,
296
- origin: "speculative",
297
- });
298
- else
299
- getLogger(ctx).debug("speculative_stale", { generation: pendingJob.generation });
293
+ getLogger(ctx).debug("speculative_applied", {
294
+ generation: pendingJob.generation,
295
+ origin: "speculative",
296
+ });
300
297
  endCompactionFeedback(ctx, feedbackSignal, result);
301
298
  return result;
302
299
  }
303
- if (result.reason === "rejected") {
300
+ if (result.reason === "stale") {
301
+ getLogger(ctx).debug("speculative_stale", { generation: pendingJob.generation });
302
+ }
303
+ else if (result.reason === "rejected") {
304
304
  feedbackSignal = ctx.beginCompaction?.({ reason: "extension" });
305
305
  }
306
306
  speculativeJob = undefined;