@gajae-code/coding-agent 0.11.11 → 0.12.1

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 (344) hide show
  1. package/CHANGELOG.md +131 -4
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/cli/read-cli.d.ts +1 -0
  4. package/dist/types/commands/deep-interview.d.ts +9 -0
  5. package/dist/types/commands/harness.d.ts +22 -0
  6. package/dist/types/commands/read.d.ts +6 -0
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/provider-auth-health.d.ts +14 -0
  9. package/dist/types/config/provider-ranking.d.ts +59 -0
  10. package/dist/types/config/settings-schema.d.ts +83 -1
  11. package/dist/types/config/settings.d.ts +2 -0
  12. package/dist/types/exa/mcp-client.d.ts +10 -1
  13. package/dist/types/exec/bash-executor.d.ts +45 -12
  14. package/dist/types/extensibility/extensions/types.d.ts +38 -4
  15. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  16. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  17. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  18. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  19. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  20. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  21. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  22. package/dist/types/extensibility/shared-events.d.ts +6 -0
  23. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  24. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  25. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  26. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  27. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  28. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  29. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  30. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -1
  31. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  32. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  33. package/dist/types/gjc-runtime/team-runtime.d.ts +27 -1
  34. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  35. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  36. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  37. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
  38. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  39. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  40. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  41. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  42. package/dist/types/main.d.ts +1 -1
  43. package/dist/types/memories/index.d.ts +2 -0
  44. package/dist/types/modes/acp/acp-agent.d.ts +10 -19
  45. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  46. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  47. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  48. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  49. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  50. package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
  51. package/dist/types/modes/components/settings-selector.d.ts +11 -0
  52. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  53. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  54. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  55. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  56. package/dist/types/modes/interactive-mode.d.ts +2 -0
  57. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  58. package/dist/types/modes/types.d.ts +2 -0
  59. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  60. package/dist/types/reminders/star-reminder.d.ts +3 -13
  61. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  62. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  63. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  64. package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
  65. package/dist/types/sdk/broker/transport.d.ts +2 -0
  66. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  68. package/dist/types/sdk/bus/config.d.ts +6 -2
  69. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  70. package/dist/types/sdk/bus/index.d.ts +44 -1
  71. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
  72. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  73. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  74. package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
  75. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  76. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  77. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  78. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  79. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  80. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  81. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  82. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  83. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  84. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  85. package/dist/types/sdk/host/host.d.ts +2 -0
  86. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  87. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  88. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  89. package/dist/types/sdk/prompt-status.d.ts +23 -3
  90. package/dist/types/sdk/session.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +82 -12
  92. package/dist/types/session/artifacts.d.ts +8 -0
  93. package/dist/types/session/blob-store.d.ts +57 -4
  94. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  95. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  96. package/dist/types/session/session-manager.d.ts +48 -19
  97. package/dist/types/session/session-storage.d.ts +4 -0
  98. package/dist/types/session/streaming-output.d.ts +96 -12
  99. package/dist/types/setup/credential-import.d.ts +12 -0
  100. package/dist/types/skill-state/active-state.d.ts +12 -4
  101. package/dist/types/skill-state/workflow-hud.d.ts +9 -0
  102. package/dist/types/task/executor.d.ts +3 -2
  103. package/dist/types/task/index.d.ts +3 -1
  104. package/dist/types/task/provider-retry-status.d.ts +8 -0
  105. package/dist/types/task/receipt.d.ts +7 -1
  106. package/dist/types/task/types.d.ts +23 -0
  107. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  108. package/dist/types/tools/bash-interactive.d.ts +4 -1
  109. package/dist/types/tools/bash.d.ts +3 -1
  110. package/dist/types/tools/browser/launch.d.ts +7 -0
  111. package/dist/types/tools/fetch.d.ts +17 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -0
  113. package/dist/types/tools/index.d.ts +4 -0
  114. package/dist/types/tools/output-meta.d.ts +44 -3
  115. package/dist/types/tools/path-utils.d.ts +1 -0
  116. package/dist/types/tools/read-internals.d.ts +12 -0
  117. package/dist/types/tools/read.d.ts +45 -7
  118. package/dist/types/tools/resource-gc.d.ts +4 -0
  119. package/dist/types/tools/review.d.ts +5 -7
  120. package/dist/types/tools/subagent.d.ts +2 -0
  121. package/dist/types/tools/tool-result.d.ts +7 -1
  122. package/dist/types/utils/gh.d.ts +13 -0
  123. package/dist/types/utils/image-resize.d.ts +1 -0
  124. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  125. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  126. package/dist/types/web/search/providers/xai.d.ts +2 -0
  127. package/package.json +11 -13
  128. package/scripts/acp-conformance-agent.ts +218 -0
  129. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  130. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  131. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  132. package/scripts/generate-sdk-operation-inventory.ts +5 -0
  133. package/scripts/resident-memory-bench.ts +1341 -0
  134. package/scripts/run-acp-conformance.ts +285 -0
  135. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  136. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  137. package/src/async/job-manager.ts +78 -20
  138. package/src/capability/index.ts +8 -0
  139. package/src/cli/fast-help.ts +1 -1
  140. package/src/cli/plugin-cli.ts +176 -18
  141. package/src/cli/read-cli.ts +2 -1
  142. package/src/cli.ts +1 -0
  143. package/src/commands/deep-interview.ts +29 -9
  144. package/src/commands/harness.ts +35 -12
  145. package/src/commands/read.ts +11 -2
  146. package/src/commands/team.ts +20 -2
  147. package/src/config/file-lock.ts +11 -2
  148. package/src/config/model-registry.ts +21 -2
  149. package/src/config/provider-auth-health.ts +42 -0
  150. package/src/config/provider-ranking.ts +119 -0
  151. package/src/config/settings-schema.ts +74 -1
  152. package/src/config/settings.ts +246 -108
  153. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  154. package/src/coordinator-mcp/server.ts +5 -12
  155. package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
  156. package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
  157. package/src/defaults/gjc/skills/team/SKILL.md +3 -1
  158. package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
  159. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  160. package/src/defaults/gjc-defaults.ts +4 -4
  161. package/src/eval/py/executor.ts +7 -2
  162. package/src/exa/mcp-client.ts +12 -3
  163. package/src/exec/bash-executor.ts +144 -27
  164. package/src/extensibility/extensions/runner.ts +15 -0
  165. package/src/extensibility/extensions/types.ts +46 -5
  166. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  167. package/src/extensibility/gjc-plugins/index.ts +19 -2
  168. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  169. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  170. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  171. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  172. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  173. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  174. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  175. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  176. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  177. package/src/extensibility/gjc-plugins/types.ts +141 -0
  178. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  179. package/src/extensibility/shared-events.ts +5 -0
  180. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
  181. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  182. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  183. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  184. package/src/gjc-runtime/deep-interview-state.ts +12 -943
  185. package/src/gjc-runtime/gc-render.ts +1 -0
  186. package/src/gjc-runtime/gc-runtime.ts +11 -1
  187. package/src/gjc-runtime/launch-tmux.ts +25 -6
  188. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  189. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  190. package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
  191. package/src/gjc-runtime/state-runtime.ts +2 -1
  192. package/src/gjc-runtime/state-writer.ts +70 -222
  193. package/src/gjc-runtime/team-launch.ts +42 -5
  194. package/src/gjc-runtime/team-runtime.ts +1085 -40
  195. package/src/gjc-runtime/team-store.ts +32 -0
  196. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  197. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  198. package/src/gjc-runtime/team-workers.ts +1 -0
  199. package/src/gjc-runtime/tmux-owner-isolation.ts +28 -0
  200. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  201. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  202. package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
  203. package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
  204. package/src/gjc-runtime/workflow-manifest.ts +38 -247
  205. package/src/hooks/native-skill-hook.ts +17 -4
  206. package/src/hooks/skill-state.ts +1 -23
  207. package/src/internal-urls/docs-index.generated.ts +21 -22
  208. package/src/internal-urls/local-root-gc.ts +127 -0
  209. package/src/main.ts +9 -5
  210. package/src/memories/index.ts +10 -0
  211. package/src/modes/DESIGN.md +98 -0
  212. package/src/modes/acp/acp-agent.ts +339 -191
  213. package/src/modes/acp/acp-event-mapper.ts +14 -7
  214. package/src/modes/acp/acp-mode.ts +39 -3
  215. package/src/modes/components/assistant-message.ts +123 -22
  216. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  217. package/src/modes/components/irc-sidebar.ts +176 -62
  218. package/src/modes/components/model-selector.ts +61 -4
  219. package/src/modes/components/notifications-settings-editor.ts +12 -1
  220. package/src/modes/components/oauth-selector.ts +96 -28
  221. package/src/modes/components/read-tool-group.ts +82 -14
  222. package/src/modes/components/session-observer-overlay.ts +457 -83
  223. package/src/modes/components/settings-selector.ts +116 -23
  224. package/src/modes/components/status-line/gh.ts +16 -0
  225. package/src/modes/components/tool-execution.ts +138 -27
  226. package/src/modes/components/tool-status-header.ts +3 -14
  227. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  228. package/src/modes/controllers/command-controller.ts +4 -0
  229. package/src/modes/controllers/event-controller.ts +109 -27
  230. package/src/modes/controllers/extension-ui-controller.ts +26 -2
  231. package/src/modes/controllers/input-controller.ts +18 -3
  232. package/src/modes/controllers/selector-controller.ts +127 -36
  233. package/src/modes/interactive-mode.ts +62 -3
  234. package/src/modes/irc-observation-ledger.ts +71 -15
  235. package/src/modes/runtime-init.ts +22 -2
  236. package/src/modes/types.ts +2 -0
  237. package/src/modes/utils/ui-helpers.ts +94 -1
  238. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  239. package/src/prompts/agents/architect.md +9 -1
  240. package/src/prompts/agents/critic.md +9 -0
  241. package/src/prompts/system/eager-todo.md +8 -4
  242. package/src/prompts/system/system-prompt.md +11 -5
  243. package/src/prompts/tools/bash.md +5 -5
  244. package/src/prompts/tools/read.md +4 -2
  245. package/src/prompts/tools/search-tool-bm25.md +5 -0
  246. package/src/prompts/tools/task.md +1 -1
  247. package/src/reminders/star-reminder.ts +4 -42
  248. package/src/runtime-mcp/manager.ts +19 -4
  249. package/src/runtime-mcp/smithery-auth.ts +17 -2
  250. package/src/runtime-mcp/smithery-connect.ts +11 -1
  251. package/src/sdk/acp/adapter.ts +22 -6
  252. package/src/sdk/broker/lifecycle.ts +25 -6
  253. package/src/sdk/broker/transport.ts +35 -3
  254. package/src/sdk/bus/attachment-registry.ts +5 -1
  255. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  256. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  257. package/src/sdk/bus/config.ts +18 -2
  258. package/src/sdk/bus/control-drain-lease.ts +104 -0
  259. package/src/sdk/bus/index.ts +984 -146
  260. package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
  261. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  262. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  263. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  264. package/src/sdk/bus/reconciliation-store.ts +303 -0
  265. package/src/sdk/bus/rich-draft.ts +1 -1
  266. package/src/sdk/bus/rich-render.ts +2 -2
  267. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  268. package/src/sdk/bus/telegram-cli.ts +33 -8
  269. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  270. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  271. package/src/sdk/bus/telegram-daemon.ts +916 -23
  272. package/src/sdk/bus/telegram-reference.ts +40 -3
  273. package/src/sdk/bus/threaded-render.ts +48 -3
  274. package/src/sdk/bus/topic-registry.ts +20 -0
  275. package/src/sdk/host/control/dispatch.ts +5 -1
  276. package/src/sdk/host/control/operations.ts +1 -1
  277. package/src/sdk/host/host.ts +23 -1
  278. package/src/sdk/host/query/handlers.ts +55 -0
  279. package/src/sdk/host/query/revision-store.ts +36 -2
  280. package/src/sdk/host/reverse-leases.ts +44 -7
  281. package/src/sdk/mcp/server.ts +46 -18
  282. package/src/sdk/prompt-status.ts +16 -3
  283. package/src/sdk/protocol/operation-inventory.generated.json +51 -0
  284. package/src/sdk/protocol/operation-registry.ts +7 -2
  285. package/src/sdk/session.ts +101 -10
  286. package/src/session/agent-session.ts +1286 -220
  287. package/src/session/artifacts.ts +22 -0
  288. package/src/session/auth-broker-config.ts +10 -3
  289. package/src/session/blob-store.ts +915 -24
  290. package/src/session/internal/managed-session-scope.ts +44 -6
  291. package/src/session/internal/managed-session-storage.ts +48 -6
  292. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  293. package/src/session/session-manager.ts +2364 -438
  294. package/src/session/session-storage.ts +9 -1
  295. package/src/session/streaming-output.ts +500 -111
  296. package/src/setup/credential-import.ts +75 -13
  297. package/src/setup/provider-onboarding.ts +19 -4
  298. package/src/skill-state/active-state.ts +32 -10
  299. package/src/skill-state/workflow-hud.ts +42 -2
  300. package/src/skill-state/workflow-mutation-guard.ts +560 -8
  301. package/src/task/executor.ts +122 -39
  302. package/src/task/index.ts +23 -14
  303. package/src/task/provider-retry-status.ts +16 -0
  304. package/src/task/receipt.ts +23 -7
  305. package/src/task/render.ts +6 -2
  306. package/src/task/types.ts +108 -0
  307. package/src/tools/ask-answer-registry.ts +12 -6
  308. package/src/tools/ask.ts +169 -118
  309. package/src/tools/ast-edit.ts +1 -0
  310. package/src/tools/ast-grep.ts +1 -0
  311. package/src/tools/bash-interactive.ts +8 -3
  312. package/src/tools/bash.ts +503 -60
  313. package/src/tools/browser/launch.ts +41 -6
  314. package/src/tools/fetch.ts +114 -23
  315. package/src/tools/find.ts +1 -0
  316. package/src/tools/image-gen.ts +29 -5
  317. package/src/tools/index.ts +4 -0
  318. package/src/tools/output-meta.ts +281 -20
  319. package/src/tools/path-utils.ts +8 -3
  320. package/src/tools/read-internals.ts +44 -0
  321. package/src/tools/read.ts +1560 -310
  322. package/src/tools/resource-gc.ts +182 -18
  323. package/src/tools/review.ts +5 -7
  324. package/src/tools/search.ts +1 -0
  325. package/src/tools/subagent-render.ts +6 -2
  326. package/src/tools/subagent.ts +6 -0
  327. package/src/tools/telegram-send.ts +6 -3
  328. package/src/tools/tool-result.ts +14 -1
  329. package/src/utils/gh.ts +39 -0
  330. package/src/utils/image-resize.ts +3 -0
  331. package/src/web/search/providers/anthropic.ts +4 -4
  332. package/src/web/search/providers/codex.ts +1 -1
  333. package/src/web/search/providers/kimi.ts +15 -5
  334. package/src/web/search/providers/searxng.ts +47 -4
  335. package/src/web/search/providers/xai.ts +7 -2
  336. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  337. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  338. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  339. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  340. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  341. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  342. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  343. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
  344. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -18,9 +18,15 @@ function safeProbeWindowsJobMemory(): WindowsJobMemoryProbeResult {
18
18
 
19
19
  import { logger } from "@gajae-code/utils";
20
20
  import type { Settings } from "../config/settings";
21
+ import { executeGjcTeamApiOperation, listGjcTeams, readGjcWorkerHeartbeat } from "../gjc-runtime/team-runtime";
21
22
  import { computeMemoryGuardDomain } from "../runtime/memory-domain";
22
- import { chooseMemoryGuardAction, MemoryGuardHost, resolveMemoryGuardPolicy } from "../runtime/memory-guard";
23
- import type { MemoryGuardPolicy } from "../runtime/memory-guard-contract";
23
+ import {
24
+ chooseMemoryGuardAction,
25
+ MemoryGuardHost,
26
+ resolveMemoryGuardPolicy,
27
+ revalidateMemoryGuardAction,
28
+ } from "../runtime/memory-guard";
29
+ import type { MemoryGuardPolicy, MemoryGuardWorkerSample } from "../runtime/memory-guard-contract";
24
30
  import { resolveEffectiveMemoryLimit } from "../runtime/memory-limit";
25
31
  import { listTabsForGc, releaseTabIfGcEligible, type TabGcSnapshot } from "./browser/tab-supervisor";
26
32
  import { cleanupStaleScreenshotFallbackDirs, hasCreatedScreenshotFallbackDir } from "./computer-gc";
@@ -85,6 +91,14 @@ export interface ResourceGcDeps {
85
91
  releaseTab: (name: string, policy: { now: () => number; idleMs: number }) => Promise<boolean>;
86
92
  cleanupScreenshots: (opts: { now: () => number; staleMs: number }) => Promise<{ scanned: number; removed: number }>;
87
93
  screenshotArmed: () => boolean;
94
+ listTeamWorkers?: (cwd: string, sessionId: string) => Promise<MemoryGuardWorkerSample[]>;
95
+ applyTeamWorkerGuard?: (
96
+ cwd: string,
97
+ sessionId: string,
98
+ workerId: string,
99
+ excessBytes: number,
100
+ incidentId: string,
101
+ ) => Promise<void>;
88
102
  }
89
103
 
90
104
  const defaultDeps: ResourceGcDeps = {
@@ -98,10 +112,13 @@ const defaultDeps: ResourceGcDeps = {
98
112
  releaseTab: (name, policy) => releaseTabIfGcEligible(name, policy),
99
113
  cleanupScreenshots: opts => cleanupStaleScreenshotFallbackDirs(opts),
100
114
  screenshotArmed: () => hasCreatedScreenshotFallbackDir(),
115
+ listTeamWorkers: (cwd, sessionId) => sampleTeamWorkers(cwd, sessionId),
116
+ applyTeamWorkerGuard: (cwd, sessionId, workerId, excessBytes, incidentId) =>
117
+ applySelectedTeamWorker(cwd, sessionId, workerId, excessBytes, incidentId),
101
118
  };
102
119
 
103
120
  // ── Controller state (process-global; tabs/browsers are module-global too) ──────────────────
104
- const activeSessions = new Map<string, Settings>();
121
+ const activeSessions = new Map<string, { settings: Settings; cwd: () => string }>();
105
122
  const scheduler = new MemoryGuardHost({
106
123
  run: async () => {
107
124
  await sweepOnce(deps);
@@ -114,11 +131,13 @@ const memoryGuardGcActive = new Set<string>();
114
131
  const memoryGuardLastEvaluatedAt = new Map<string, number>();
115
132
  const memoryGuardRestartAboveSince = new Map<string, number>();
116
133
  const memoryGuardRestartCooldownUntil = new Map<string, number>();
134
+ const memoryGuardWorkerIncidentIds = new Map<string, string>();
117
135
  let deps: ResourceGcDeps = defaultDeps;
118
136
 
119
137
  export interface ResourceGcRegistration {
120
138
  sessionId: string;
121
139
  settings: Settings;
140
+ cwd?: string | (() => string);
122
141
  }
123
142
 
124
143
  function resolveSessionSweepIntervalMs(settings: Settings): number {
@@ -134,7 +153,9 @@ function resolveSessionSweepIntervalMs(settings: Settings): number {
134
153
  * session unregisters.
135
154
  */
136
155
  export function registerResourceGcSession(reg: ResourceGcRegistration): () => void {
137
- activeSessions.set(reg.sessionId, reg.settings);
156
+ const registeredCwd = reg.cwd;
157
+ const cwd = typeof registeredCwd === "function" ? registeredCwd : () => registeredCwd ?? process.cwd();
158
+ activeSessions.set(reg.sessionId, { settings: reg.settings, cwd });
138
159
  const unregisterSchedule = scheduler.register({
139
160
  ownerId: reg.sessionId,
140
161
  intervalMs: resolveSessionSweepIntervalMs(reg.settings),
@@ -149,6 +170,7 @@ export function registerResourceGcSession(reg: ResourceGcRegistration): () => vo
149
170
  if (path === "memoryGuard.enabled" && !resolveMemoryGuardPolicy(reg.settings).enabled) {
150
171
  memoryGuardGcActive.delete(reg.sessionId);
151
172
  memoryGuardRestartAboveSince.delete(reg.sessionId);
173
+ memoryGuardWorkerIncidentIds.delete(reg.sessionId);
152
174
  memoryGuardRestartCooldownUntil.delete(reg.sessionId);
153
175
  memoryGuardLastEvaluatedAt.delete(reg.sessionId);
154
176
  }
@@ -162,6 +184,7 @@ export function registerResourceGcSession(reg: ResourceGcRegistration): () => vo
162
184
  memoryGuardLastEvaluatedAt.delete(reg.sessionId);
163
185
  memoryGuardGcActive.delete(reg.sessionId);
164
186
  memoryGuardRestartAboveSince.delete(reg.sessionId);
187
+ memoryGuardWorkerIncidentIds.delete(reg.sessionId);
165
188
  memoryGuardRestartCooldownUntil.delete(reg.sessionId);
166
189
  unregisterSchedule();
167
190
  unregisterSettings();
@@ -479,13 +502,14 @@ export function __selectMemoryPressureDomainForTest(
479
502
 
480
503
  function sweepMemoryPressureGuard(d: ResourceGcDeps): Promise<void> | undefined {
481
504
  let enabled = false;
482
- for (const [sessionId, settings] of activeSessions) {
505
+ for (const [sessionId, { settings }] of activeSessions) {
483
506
  if (resolveMemoryGuardPolicy(settings).enabled) {
484
507
  enabled = true;
485
508
  continue;
486
509
  }
487
510
  memoryGuardGcActive.delete(sessionId);
488
511
  memoryGuardRestartAboveSince.delete(sessionId);
512
+ memoryGuardWorkerIncidentIds.delete(sessionId);
489
513
  memoryGuardRestartCooldownUntil.delete(sessionId);
490
514
  memoryGuardLastEvaluatedAt.delete(sessionId);
491
515
  }
@@ -493,14 +517,107 @@ function sweepMemoryPressureGuard(d: ResourceGcDeps): Promise<void> | undefined
493
517
  return sweepEnabledMemoryPressureGuard(d);
494
518
  }
495
519
 
520
+ async function readLinuxWorkerRssBytes(pid: number): Promise<number | null> {
521
+ if (process.platform !== "linux" || !Number.isSafeInteger(pid) || pid <= 0) return null;
522
+ try {
523
+ const status = await Bun.file(`/proc/${pid}/status`).text();
524
+ const match = /^VmRSS:\s+(\d+)\s+kB$/m.exec(status);
525
+ if (!match) return null;
526
+ const kibibytes = Number(match[1]);
527
+ return Number.isSafeInteger(kibibytes) ? kibibytes * 1024 : null;
528
+ } catch {
529
+ return null;
530
+ }
531
+ }
532
+
533
+ async function readLinuxProcessStartTime(pid: number): Promise<string | null> {
534
+ try {
535
+ const stat = await Bun.file(`/proc/${pid}/stat`).text();
536
+ const commandEnd = stat.lastIndexOf(")");
537
+ if (commandEnd < 0) return null;
538
+ const startTime = stat
539
+ .slice(commandEnd + 2)
540
+ .trim()
541
+ .split(/\s+/)[19];
542
+ return startTime && /^\d+$/.test(startTime) ? startTime : null;
543
+ } catch {
544
+ return null;
545
+ }
546
+ }
547
+
548
+ async function sampleTeamWorkers(cwd: string, sessionId: string): Promise<MemoryGuardWorkerSample[]> {
549
+ if (process.platform !== "linux") return [];
550
+ const samples: MemoryGuardWorkerSample[] = [];
551
+ for (const team of await listGjcTeams(cwd, { ...process.env, GJC_SESSION_ID: sessionId })) {
552
+ if (team.phase === "complete" || team.phase === "cancelled") continue;
553
+ for (const worker of team.workers) {
554
+ try {
555
+ const heartbeat = await readGjcWorkerHeartbeat(team.team_name, worker.id, cwd, {
556
+ ...process.env,
557
+ GJC_SESSION_ID: sessionId,
558
+ });
559
+ const heartbeatAt = Date.parse(heartbeat?.last_turn_at ?? "");
560
+ if (
561
+ !heartbeat?.alive ||
562
+ !heartbeat.process_start_time ||
563
+ !Number.isFinite(heartbeatAt) ||
564
+ Date.now() - heartbeatAt >= 120_000 ||
565
+ (await readLinuxProcessStartTime(heartbeat.pid)) !== heartbeat.process_start_time
566
+ )
567
+ continue;
568
+ const guard = (await executeGjcTeamApiOperation(
569
+ "read-worker-memory-guard",
570
+ { team_name: team.team_name, worker: worker.id, platform: process.platform },
571
+ cwd,
572
+ { ...process.env, GJC_SESSION_ID: sessionId },
573
+ )) as { automatic_action_allowed?: boolean; state?: string };
574
+ if (!guard.automatic_action_allowed || guard.state === "blocked") continue;
575
+ const bytes = await readLinuxWorkerRssBytes(heartbeat.pid);
576
+ if (bytes === null) continue;
577
+ samples.push({ workerId: `${team.team_name}/${worker.id}`, bytes, accepted: true });
578
+ } catch {
579
+ // Missing or malformed worker authority is not eligible for automatic mutation.
580
+ }
581
+ }
582
+ }
583
+ return samples;
584
+ }
585
+
586
+ async function applySelectedTeamWorker(
587
+ cwd: string,
588
+ sessionId: string,
589
+ workerId: string,
590
+ excessBytes: number,
591
+ incidentId: string,
592
+ ): Promise<void> {
593
+ const separator = workerId.lastIndexOf("/");
594
+ if (separator <= 0 || separator === workerId.length - 1) return;
595
+ const teamName = workerId.slice(0, separator);
596
+ const worker = workerId.slice(separator + 1);
597
+ await executeGjcTeamApiOperation(
598
+ "apply-worker-memory-guard",
599
+ {
600
+ team_name: teamName,
601
+ worker,
602
+ platform: process.platform,
603
+ reason: "production_memory_pressure_sweep",
604
+ incident_id: incidentId,
605
+ candidates: [{ worker_id: worker, platform: process.platform, excess_bytes: excessBytes }],
606
+ },
607
+ cwd,
608
+ { ...process.env, GJC_SESSION_ID: sessionId },
609
+ );
610
+ }
496
611
  async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void> {
497
612
  const now = d.monotonicNow();
498
- const dueSessions: Array<{ sessionId: string; policy: MemoryGuardPolicy }> = [];
499
- for (const [sessionId, settings] of activeSessions) {
613
+ const dueSessions: Array<{ sessionId: string; policy: MemoryGuardPolicy; cwd: string }> = [];
614
+ for (const [sessionId, { settings, cwd: resolveCwd }] of activeSessions) {
500
615
  const policy = resolveMemoryGuardPolicy(settings);
616
+ const cwd = resolveCwd();
501
617
  if (!policy.enabled) {
502
618
  memoryGuardGcActive.delete(sessionId);
503
619
  memoryGuardRestartAboveSince.delete(sessionId);
620
+ memoryGuardWorkerIncidentIds.delete(sessionId);
504
621
  memoryGuardRestartCooldownUntil.delete(sessionId);
505
622
  memoryGuardLastEvaluatedAt.delete(sessionId);
506
623
  continue;
@@ -508,14 +625,15 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
508
625
  const lastEvaluated = memoryGuardLastEvaluatedAt.get(sessionId);
509
626
  if (lastEvaluated !== undefined && now - lastEvaluated < policy.checkIntervalMs) continue;
510
627
  memoryGuardLastEvaluatedAt.set(sessionId, now);
511
- dueSessions.push({ sessionId, policy });
628
+ dueSessions.push({ sessionId, policy, cwd });
512
629
  }
513
630
  if (dueSessions.length === 0) return;
514
631
 
515
632
  const snapshot = await d.memorySnapshot();
516
633
  let gcRequested = false;
517
634
  const gcTelemetry: Array<{ sessionId: string } & Record<string, unknown>> = [];
518
- for (const { sessionId, policy } of dueSessions) {
635
+ let workerActionTaken = false;
636
+ for (const { sessionId, policy, cwd } of dueSessions) {
519
637
  const pressure = __selectMemoryPressureDomainForTest(snapshot, policy.policyLimitBytes);
520
638
  const limit = resolveEffectiveMemoryLimit({
521
639
  hardCapBytes: pressure.hardCapBytes,
@@ -524,20 +642,23 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
524
642
  if (limit.effectiveBytes === null) {
525
643
  memoryGuardGcActive.delete(sessionId);
526
644
  memoryGuardRestartAboveSince.delete(sessionId);
645
+ memoryGuardWorkerIncidentIds.delete(sessionId);
527
646
  memoryGuardRestartCooldownUntil.delete(sessionId);
528
647
  continue;
529
648
  }
649
+ const workerSamples = await (d.listTeamWorkers ?? (async () => []))(cwd, sessionId);
530
650
  const domain = computeMemoryGuardDomain({
531
651
  effectiveLimitBytes: limit.effectiveBytes,
532
652
  totalUsageBytes: pressure.totalUsageBytes,
533
653
  parentBytes: pressure.parentBytes,
534
654
  parentReserveBytes: policy.parentReserveBytes,
535
- workers: [],
655
+ workers: workerSamples,
536
656
  });
537
657
  const decision = chooseMemoryGuardAction({
538
658
  domain,
539
659
  hostSupported: false,
540
- workerSupported: () => false,
660
+ workerSupported: workerId =>
661
+ workerSamples.some(worker => worker.workerId === workerId && worker.accepted !== false),
541
662
  });
542
663
  const usageRatio = pressure.totalUsageBytes / limit.effectiveBytes;
543
664
  if (usageRatio >= policy.gcThresholdRatio) {
@@ -560,17 +681,57 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
560
681
 
561
682
  if (usageRatio < policy.restartThresholdRatio) {
562
683
  memoryGuardRestartAboveSince.delete(sessionId);
684
+ memoryGuardWorkerIncidentIds.delete(sessionId);
563
685
  continue;
564
686
  }
565
687
  const aboveSince = memoryGuardRestartAboveSince.get(sessionId);
566
688
  if (aboveSince === undefined) {
567
689
  memoryGuardRestartAboveSince.set(sessionId, now);
690
+ memoryGuardWorkerIncidentIds.set(sessionId, `worker-pressure:${sessionId}:${Math.trunc(now)}`);
568
691
  continue;
569
692
  }
570
693
  const cooldownUntil = memoryGuardRestartCooldownUntil.get(sessionId) ?? 0;
571
694
  if (now - aboveSince < policy.restartThresholdWindowMs || now < cooldownUntil) continue;
572
- memoryGuardRestartCooldownUntil.set(sessionId, now + policy.cooldownMs);
573
- d.logWarn("Memory guard: restart threshold sustained; restart remains advisory-only", {
695
+ const workerIncidentId =
696
+ memoryGuardWorkerIncidentIds.get(sessionId) ?? `worker-pressure:${sessionId}:${Math.trunc(aboveSince)}`;
697
+ let workerActionAttemptedForSession = false;
698
+ if (decision.kind === "execute" && decision.target.kind === "worker" && !workerActionTaken) {
699
+ const refreshedWorkers = await (d.listTeamWorkers ?? (async () => []))(cwd, sessionId);
700
+ const refreshedDomain = computeMemoryGuardDomain({
701
+ effectiveLimitBytes: limit.effectiveBytes,
702
+ totalUsageBytes: pressure.totalUsageBytes,
703
+ parentBytes: pressure.parentBytes,
704
+ parentReserveBytes: policy.parentReserveBytes,
705
+ workers: refreshedWorkers,
706
+ });
707
+ const refreshedDecision = chooseMemoryGuardAction({
708
+ domain: refreshedDomain,
709
+ hostSupported: false,
710
+ workerSupported: workerId =>
711
+ refreshedWorkers.some(worker => worker.workerId === workerId && worker.accepted !== false),
712
+ });
713
+ const revalidated = revalidateMemoryGuardAction(decision, refreshedDecision);
714
+ if (revalidated.kind !== "execute" || revalidated.target.kind !== "worker") continue;
715
+ workerActionTaken = true;
716
+ workerActionAttemptedForSession = true;
717
+ try {
718
+ await (d.applyTeamWorkerGuard ?? (async () => undefined))(
719
+ cwd,
720
+ sessionId,
721
+ revalidated.target.workerId,
722
+ revalidated.target.excessBytes,
723
+ workerIncidentId,
724
+ );
725
+ } catch (error) {
726
+ d.logWarn("Memory guard: team worker action failed; continuing sweep", {
727
+ sessionId,
728
+ workerId: revalidated.target.workerId,
729
+ error: String(error),
730
+ });
731
+ }
732
+ }
733
+ if (workerActionAttemptedForSession) memoryGuardRestartCooldownUntil.set(sessionId, now + policy.cooldownMs);
734
+ d.logWarn("Memory guard: restart threshold sustained", {
574
735
  sessionId,
575
736
  parentBytes: pressure.parentBytes,
576
737
  totalUsageBytes: pressure.totalUsageBytes,
@@ -598,9 +759,9 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
598
759
 
599
760
  function ownerBrowserPolicy(snapshot: TabGcSnapshot): BrowserGcPolicy | null {
600
761
  if (!snapshot.ownerId) return null;
601
- const settings = activeSessions.get(snapshot.ownerId);
602
- if (!settings) return null;
603
- return resolveBrowserGcPolicy(settings);
762
+ const registration = activeSessions.get(snapshot.ownerId);
763
+ if (!registration) return null;
764
+ return resolveBrowserGcPolicy(registration.settings);
604
765
  }
605
766
 
606
767
  /** Coarse, ordering-only eligibility; the live recheck in releaseTabIfGcEligible is authoritative. */
@@ -639,7 +800,7 @@ async function sweepBrowserTabs(d: ResourceGcDeps): Promise<void> {
639
800
  function pressuredOwnerIds(d: ResourceGcDeps): Set<string> {
640
801
  const rss = d.rssBytes();
641
802
  const owners = new Set<string>();
642
- for (const [sessionId, settings] of activeSessions) {
803
+ for (const [sessionId, { settings }] of activeSessions) {
643
804
  const policy = resolveBrowserGcPolicy(settings);
644
805
  if (policy.enabled && rss > policy.rssLimitBytes) owners.add(sessionId);
645
806
  }
@@ -678,7 +839,7 @@ async function sweepScreenshots(d: ResourceGcDeps): Promise<void> {
678
839
 
679
840
  let staleMs: number | null = null;
680
841
  let scanIntervalMs = Number.POSITIVE_INFINITY;
681
- for (const settings of activeSessions.values()) {
842
+ for (const { settings } of activeSessions.values()) {
682
843
  const policy = resolveComputerGcPolicy(settings);
683
844
  if (!policy.enabled) continue;
684
845
  staleMs = staleMs === null ? policy.staleMs : Math.min(staleMs, policy.staleMs);
@@ -698,6 +859,8 @@ export function __setResourceGcDepsForTest(overrides: Partial<ResourceGcDeps>):
698
859
  ...defaultDeps,
699
860
  ...overrides,
700
861
  monotonicNow: overrides.monotonicNow ?? overrides.now ?? defaultDeps.monotonicNow,
862
+ listTeamWorkers: overrides.listTeamWorkers ?? (async () => []),
863
+ applyTeamWorkerGuard: overrides.applyTeamWorkerGuard ?? (async () => undefined),
701
864
  };
702
865
  }
703
866
 
@@ -749,6 +912,7 @@ export function __resetResourceGcForTest(): void {
749
912
  rssWarningActive = false;
750
913
  memoryGuardGcActive.clear();
751
914
  memoryGuardRestartAboveSince.clear();
915
+ memoryGuardWorkerIncidentIds.clear();
752
916
  memoryGuardRestartCooldownUntil.clear();
753
917
  memoryGuardLastEvaluatedAt.clear();
754
918
  lastScreenshotScanAt = 0;
@@ -188,15 +188,13 @@ export interface SubmitReviewDetails {
188
188
  // Re-export types for external use
189
189
  export type { ReportFindingDetails };
190
190
  /**
191
- * Coerce a tool-side `ReportFindingDetails` into the cross-boundary
192
- * `ReviewFinding` shape consumed by the reviewer agent's JTD output schema.
191
+ * Convert tool-side `ReportFindingDetails` into the numeric-priority
192
+ * `ReviewFinding` shape used by caller-owned reviewer payloads.
193
193
  *
194
194
  * The `report_finding` tool exposes `priority` as a string enum (`"P0".."P3"`)
195
- * for ergonomics, but the bundled reviewer schema (and every custom review
196
- * agent that mirrors it) declares `priority: number`. Without this coercion
197
- * the auto-populated `findings[]` fails JTD validation and every review run
198
- * that surfaces a finding is rejected with `findings.0.priority: expected
199
- * number, received string`.
195
+ * for ergonomics, while reviewer schemas may explicitly declare numeric
196
+ * priorities. This pure conversion does not add findings to completion data;
197
+ * callers that include `findings` own both that field and its output schema.
200
198
  */
201
199
  export function toReviewFinding(details: ReportFindingDetails): ReviewFinding {
202
200
  return {
@@ -287,6 +287,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
287
287
  rawPaths: resolvedPaths,
288
288
  cwd: this.session.cwd,
289
289
  getArtifactsDir: this.session.getArtifactsDir,
290
+ getAuthorizedArtifactsDirs: this.session.getAuthorizedArtifactsDirs,
290
291
  internalUrlAction: "search",
291
292
  trackImmutableSources: true,
292
293
  surfaceExactFilePaths: true,
@@ -192,6 +192,9 @@ function renderSubagentSnapshotBody(
192
192
  }
193
193
  if (snapshot.description) lines.push(` ${theme.fg("dim", `Description: ${snapshot.description}`)}`);
194
194
  if (snapshot.outputRef) lines.push(` ${theme.fg("dim", `Output: ${snapshot.outputRef}`)}`);
195
+ if (snapshot.setupFailureSummary) {
196
+ lines.push(` ${theme.fg("error", `Setup failure: ${snapshot.setupFailureSummary}`)}`);
197
+ }
195
198
  if (snapshot.assignment) {
196
199
  lines.push(` ${theme.fg("dim", "Assignment:")}`);
197
200
  for (const al of snapshot.assignment.split("\n")) lines.push(` ${theme.fg("toolOutput", replaceTabs(al))}`);
@@ -312,8 +315,9 @@ export const subagentToolRenderer = {
312
315
  );
313
316
  }
314
317
 
315
- snapshotSignatures ??= subagents.map(snapshot =>
316
- subagentAwaitRenderedStateSignature([snapshot], result.details),
318
+ snapshotSignatures ??= subagents.map(
319
+ snapshot =>
320
+ `${subagentAwaitRenderedStateSignature([snapshot], result.details)}:${snapshot.setupFailureSummary ?? ""}`,
317
321
  );
318
322
  subagents.forEach((snapshot, index) => {
319
323
  // Fresh per-subagent status line (cheap), then a cached or dynamic body.
@@ -65,6 +65,8 @@ export interface SubagentSnapshot {
65
65
  durationMs: number;
66
66
  resultText?: string;
67
67
  errorText?: string;
68
+ /** Safe setup failure cause retained from the executor launch path. */
69
+ setupFailureSummary?: string;
68
70
  resultPreview?: string;
69
71
  outputRef?: string;
70
72
  truncated?: boolean;
@@ -603,6 +605,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
603
605
  }
604
606
  if (snapshot.description) lines.push(`Description: ${snapshot.description}`);
605
607
  if (snapshot.outputRef) lines.push(`Output: ${snapshot.outputRef}`);
608
+ if (snapshot.setupFailureSummary) lines.push(`Setup failure: ${snapshot.setupFailureSummary}`);
606
609
  if (snapshot.assignment) lines.push("Assignment:", "```", snapshot.assignment, "```");
607
610
  if (snapshot.steerMessage) {
608
611
  lines.push(`Steer (${snapshot.steerState ?? "queued"}):`, "```", snapshot.steerMessage, "```");
@@ -731,6 +734,9 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
731
734
  truncated: output.truncated,
732
735
  }
733
736
  : {}),
737
+ ...(job.setupFailureSummary
738
+ ? { setupFailureSummary: sanitizeText(job.setupFailureSummary, RECEIPT_PREVIEW_WIDTH) }
739
+ : {}),
734
740
  ...(outputRef ? { outputRef } : {}),
735
741
  ...(runningTimeoutGuidance ? { guidance: runningTimeoutGuidance } : {}),
736
742
  ...this.#modelFields(record),
@@ -31,8 +31,10 @@ export class TelegramSendTool implements AgentTool<typeof telegramSendSchema, Te
31
31
  readonly summary = "Send a workspace file to Telegram";
32
32
  readonly loadMode = "discoverable";
33
33
  readonly description =
34
- "Send a file from the current workspace to the connected Telegram chat as a document. The path must resolve " +
35
- "(after following symlinks) to a regular file inside the project root; paths outside the workspace are rejected.";
34
+ "Send a file from the current workspace to the connected Telegram chat. Recognized images are converted to " +
35
+ "Telegram-compatible photos when possible, including WebP; other files are sent as documents with their MIME " +
36
+ "type preserved. The path must resolve (after following symlinks) to a regular file inside the project root; " +
37
+ "paths outside the workspace are rejected.";
36
38
  readonly parameters = telegramSendSchema;
37
39
  readonly strict = true;
38
40
 
@@ -109,6 +111,7 @@ export class TelegramSendTool implements AgentTool<typeof telegramSendSchema, Te
109
111
  };
110
112
  }
111
113
  const abs = contained.path;
114
+ const mime = Bun.file(abs).type;
112
115
 
113
116
  const sink = getTelegramFileSink(sessionId);
114
117
  if (!sink) {
@@ -126,7 +129,7 @@ export class TelegramSendTool implements AgentTool<typeof telegramSendSchema, Te
126
129
  };
127
130
  }
128
131
 
129
- const result = await sink({ path: abs, caption: params.caption });
132
+ const result = await sink({ path: abs, caption: params.caption, mime });
130
133
  if (result.ok) {
131
134
  return {
132
135
  content: [{ type: "text", text: `Sent ${path.basename(abs)} to Telegram.` }],
@@ -1,6 +1,6 @@
1
1
  import type { AgentToolResult } from "@gajae-code/agent-core";
2
2
  import type { ImageContent, TextContent } from "@gajae-code/ai";
3
- import type { OutputSummary, TruncationResult } from "../session/streaming-output";
3
+ import type { OutputSummary, ReadWindow, TruncationResult } from "../session/streaming-output";
4
4
  import type { OutputMeta, TruncationOptions, TruncationSummaryOptions, TruncationTextOptions } from "./output-meta";
5
5
  import { outputMeta } from "./output-meta";
6
6
 
@@ -32,6 +32,19 @@ export class ToolResultBuilder<TDetails extends DetailsWithMeta> {
32
32
  return this;
33
33
  }
34
34
 
35
+ truncationWindows(
36
+ windows: ReadWindow,
37
+ options?: {
38
+ artifactId?: string;
39
+ noticeOwner?: "body";
40
+ maxBytes?: number;
41
+ rangeBase?: "file" | "window";
42
+ },
43
+ ): this {
44
+ this.#meta.truncationWindows(windows, options);
45
+ return this;
46
+ }
47
+
35
48
  truncationFromSummary(summary: OutputSummary, options: TruncationSummaryOptions): this {
36
49
  this.#meta.truncationFromSummary(summary, options);
37
50
  return this;
@@ -0,0 +1,39 @@
1
+ const DEFAULT_GH_TIMEOUT_MS = 5_000;
2
+
3
+ export interface GhResult {
4
+ exitCode: number;
5
+ stdout: string;
6
+ stderr: string;
7
+ timedOut?: boolean;
8
+ }
9
+
10
+ export type RunGh = (args: string[], options?: { timeoutMs?: number }) => Promise<GhResult>;
11
+
12
+ /** Run GitHub CLI without allowing it to consume the parent terminal's input. */
13
+ export async function runGhDefault(args: string[], options?: { timeoutMs?: number }): Promise<GhResult> {
14
+ const ghPath = Bun.which("gh");
15
+ if (!ghPath) {
16
+ return { exitCode: -1, stdout: "", stderr: "gh not found", timedOut: false };
17
+ }
18
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_GH_TIMEOUT_MS;
19
+ let timedOut = false;
20
+ try {
21
+ const proc = Bun.spawn([ghPath, ...args], { stdout: "pipe", stderr: "pipe", stdin: "ignore" });
22
+ const timer = setTimeout(() => {
23
+ timedOut = true;
24
+ proc.kill();
25
+ }, timeoutMs);
26
+ try {
27
+ const [stdout, stderr] = await Promise.all([
28
+ new Response(proc.stdout).text(),
29
+ new Response(proc.stderr).text(),
30
+ ]);
31
+ const exitCode = await proc.exited;
32
+ return { exitCode, stdout, stderr, timedOut };
33
+ } finally {
34
+ clearTimeout(timer);
35
+ }
36
+ } catch (err) {
37
+ return { exitCode: -1, stdout: "", stderr: err instanceof Error ? err.message : String(err), timedOut };
38
+ }
39
+ }
@@ -6,6 +6,7 @@ export interface ImageResizeOptions {
6
6
  maxBytes?: number;
7
7
  jpegQuality?: number;
8
8
  excludeWebP?: boolean;
9
+ forceReencode?: boolean;
9
10
  }
10
11
 
11
12
  export interface ResizedImage {
@@ -30,6 +31,7 @@ const DEFAULT_OPTIONS: Required<Omit<ImageResizeOptions, "excludeWebP">> = {
30
31
  maxHeight: 1568,
31
32
  maxBytes: DEFAULT_IMAGE_RESIZE_MAX_BYTES,
32
33
  jpegQuality: 80,
34
+ forceReencode: false,
33
35
  };
34
36
 
35
37
  /**
@@ -94,6 +96,7 @@ export async function resizeImageBuffer(
94
96
  originalWidth <= opts.maxWidth &&
95
97
  originalHeight <= opts.maxHeight &&
96
98
  originalSize <= comfortableSize &&
99
+ !opts.forceReencode &&
97
100
  !(opts.excludeWebP && sourceMime === "image/webp")
98
101
  ) {
99
102
  return {
@@ -14,7 +14,7 @@ import {
14
14
  buildAnthropicUrl,
15
15
  stripClaudeToolPrefix,
16
16
  } from "@gajae-code/ai";
17
- import { $env } from "@gajae-code/utils";
17
+ import { $credentialEnv, $env } from "@gajae-code/utils";
18
18
  import type {
19
19
  AnthropicApiResponse,
20
20
  AnthropicCitation,
@@ -275,8 +275,8 @@ export async function searchAnthropic(
275
275
  params: SearchParams | AnthropicSearchParams,
276
276
  _legacyStorage?: unknown,
277
277
  ): Promise<SearchResponse> {
278
- const searchApiKey = $env.ANTHROPIC_SEARCH_API_KEY;
279
- const searchBaseUrl = $env.ANTHROPIC_SEARCH_BASE_URL;
278
+ const searchApiKey = $credentialEnv("ANTHROPIC_SEARCH_API_KEY");
279
+ const searchBaseUrl = $credentialEnv("ANTHROPIC_SEARCH_BASE_URL");
280
280
  let auth: AnthropicAuthConfig | undefined;
281
281
  // When reusing the active model's own credentials (native search over a
282
282
  // proxy), prefer its wire model id and carry its request headers through.
@@ -360,7 +360,7 @@ export class AnthropicProvider extends SearchProvider {
360
360
  readonly label = "Anthropic";
361
361
 
362
362
  isAvailable(authStorage: AuthStorage): Promise<boolean> | boolean {
363
- return Boolean($env.ANTHROPIC_SEARCH_API_KEY) || authStorage.hasAuth("anthropic");
363
+ return Boolean($credentialEnv("ANTHROPIC_SEARCH_API_KEY")) || authStorage.hasAuth("anthropic");
364
364
  }
365
365
 
366
366
  search(params: SearchParams): Promise<SearchResponse> {
@@ -36,7 +36,7 @@ const DEFAULT_INSTRUCTIONS =
36
36
  "You are a helpful assistant with web search capabilities. Search the web to answer the user's question accurately and cite your sources.";
37
37
 
38
38
  function getConfiguredModel(): string | undefined {
39
- const configuredModel = $env.PI_CODEX_WEB_SEARCH_MODEL?.trim();
39
+ const configuredModel = ($env.GJC_OPENAI_CODE_WEB_SEARCH_MODEL ?? $env.PI_CODEX_WEB_SEARCH_MODEL)?.trim();
40
40
  return configuredModel ? configuredModel : undefined;
41
41
  }
42
42
 
@@ -5,7 +5,7 @@
5
5
  * Endpoint: POST https://api.kimi.com/coding/v1/search
6
6
  */
7
7
  import type { AuthStorage } from "@gajae-code/ai";
8
- import { $env } from "@gajae-code/utils";
8
+ import { $credentialEnv } from "@gajae-code/utils";
9
9
 
10
10
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
11
11
  import { SearchProviderError } from "../../../web/search/types";
@@ -55,8 +55,17 @@ function asTrimmed(value: string | undefined): string | undefined {
55
55
  return trimmed.length > 0 ? trimmed : undefined;
56
56
  }
57
57
 
58
+ /** Test seam: the Kimi search base URL as resolved from trusted env. */
59
+ export function resolveKimiSearchBaseUrlForTest(): string {
60
+ return resolveBaseUrl();
61
+ }
62
+
58
63
  function resolveBaseUrl(): string {
59
- return asTrimmed($env.MOONSHOT_SEARCH_BASE_URL) ?? asTrimmed($env.KIMI_SEARCH_BASE_URL) ?? KIMI_SEARCH_URL;
64
+ return (
65
+ asTrimmed($credentialEnv("MOONSHOT_SEARCH_BASE_URL")) ??
66
+ asTrimmed($credentialEnv("KIMI_SEARCH_BASE_URL")) ??
67
+ KIMI_SEARCH_URL
68
+ );
60
69
  }
61
70
 
62
71
  /** Find Kimi search credentials from environment or AuthStorage. */
@@ -65,7 +74,8 @@ async function findApiKey(
65
74
  sessionId: string | undefined,
66
75
  signal: AbortSignal | undefined,
67
76
  ): Promise<string | null> {
68
- const envKey = asTrimmed($env.MOONSHOT_SEARCH_API_KEY) ?? asTrimmed($env.KIMI_SEARCH_API_KEY);
77
+ const envKey =
78
+ asTrimmed($credentialEnv("MOONSHOT_SEARCH_API_KEY")) ?? asTrimmed($credentialEnv("KIMI_SEARCH_API_KEY"));
69
79
  if (envKey) return envKey;
70
80
 
71
81
  return (
@@ -157,8 +167,8 @@ export class KimiProvider extends SearchProvider {
157
167
 
158
168
  isAvailable(authStorage: AuthStorage): boolean {
159
169
  return (
160
- !!asTrimmed($env.MOONSHOT_SEARCH_API_KEY) ||
161
- !!asTrimmed($env.KIMI_SEARCH_API_KEY) ||
170
+ !!asTrimmed($credentialEnv("MOONSHOT_SEARCH_API_KEY")) ||
171
+ !!asTrimmed($credentialEnv("KIMI_SEARCH_API_KEY")) ||
162
172
  authStorage.hasAuth("moonshot") ||
163
173
  authStorage.hasAuth("kimi-code")
164
174
  );