@gajae-code/coding-agent 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/CHANGELOG.md +120 -1
  2. package/dist/types/async/job-manager.d.ts +4 -1
  3. package/dist/types/autoresearch/dashboard.d.ts +29 -4
  4. package/dist/types/autoresearch/data-context.d.ts +25 -0
  5. package/dist/types/autoresearch/git.d.ts +65 -7
  6. package/dist/types/autoresearch/harness.d.ts +58 -0
  7. package/dist/types/autoresearch/prompts.d.ts +51 -0
  8. package/dist/types/autoresearch/runs.d.ts +173 -0
  9. package/dist/types/autoresearch/types.d.ts +9 -139
  10. package/dist/types/capability/types.d.ts +20 -0
  11. package/dist/types/cli/accounts-cli.d.ts +1 -0
  12. package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
  13. package/dist/types/config/autorouting-contract.d.ts +146 -0
  14. package/dist/types/config/autorouting-generator.d.ts +40 -0
  15. package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
  16. package/dist/types/config/autorouting.d.ts +35 -0
  17. package/dist/types/config/model-discovery-manager.d.ts +8 -1
  18. package/dist/types/config/model-registry.d.ts +30 -1
  19. package/dist/types/config/provider-selection-policy.d.ts +23 -0
  20. package/dist/types/config/settings-schema.d.ts +61 -3
  21. package/dist/types/config/settings.d.ts +3 -0
  22. package/dist/types/coordinator/contract.d.ts +1 -1
  23. package/dist/types/coordinator-mcp/durability.d.ts +29 -0
  24. package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
  26. package/dist/types/coordinator-mcp/server.d.ts +14 -0
  27. package/dist/types/crash/index-store.d.ts +3 -0
  28. package/dist/types/crash/record-loader.d.ts +2 -0
  29. package/dist/types/crash/upstream/relay.d.ts +5 -3
  30. package/dist/types/cursor.d.ts +12 -0
  31. package/dist/types/defaults/gjc-defaults.d.ts +35 -1
  32. package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
  33. package/dist/types/discovery/helpers.d.ts +1 -1
  34. package/dist/types/edit/index.d.ts +2 -1
  35. package/dist/types/eval/py/executor.d.ts +2 -0
  36. package/dist/types/extensibility/extensions/loader.d.ts +32 -0
  37. package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
  40. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  41. package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
  42. package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
  44. package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
  45. package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
  46. package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
  47. package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
  49. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
  50. package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
  51. package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
  52. package/dist/types/hooks/skill-keywords.d.ts +1 -1
  53. package/dist/types/main.d.ts +0 -10
  54. package/dist/types/modes/acp/acp-agent.d.ts +13 -6
  55. package/dist/types/modes/components/model-selector.d.ts +14 -0
  56. package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
  57. package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
  58. package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
  59. package/dist/types/modes/interactive-mode.d.ts +3 -0
  60. package/dist/types/modes/types.d.ts +9 -0
  61. package/dist/types/rlm/artifacts.d.ts +4 -4
  62. package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
  63. package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
  64. package/dist/types/runtime-mcp/config.d.ts +6 -0
  65. package/dist/types/runtime-mcp/manager.d.ts +10 -0
  66. package/dist/types/sdk/acp/adapter.d.ts +0 -4
  67. package/dist/types/sdk/broker/broker.d.ts +1 -1
  68. package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
  69. package/dist/types/sdk/broker/session-index.d.ts +15 -0
  70. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
  71. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
  72. package/dist/types/sdk/bus/index.d.ts +8 -0
  73. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
  74. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
  75. package/dist/types/sdk/client/client.d.ts +39 -4
  76. package/dist/types/sdk/host/host.d.ts +1 -0
  77. package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
  78. package/dist/types/sdk/host/model-pin.d.ts +29 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +2 -1
  80. package/dist/types/sdk/lifecycle-session.d.ts +13 -0
  81. package/dist/types/sdk/router/session-router.d.ts +27 -1
  82. package/dist/types/sdk/session.d.ts +11 -1
  83. package/dist/types/session/agent-session.d.ts +36 -6
  84. package/dist/types/session/artifacts.d.ts +16 -1
  85. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  86. package/dist/types/session/session-manager.d.ts +84 -2
  87. package/dist/types/session/session-staging-paths.d.ts +3 -0
  88. package/dist/types/skill-state/canonical-skills.d.ts +1 -1
  89. package/dist/types/skill-state/workflow-hud.d.ts +12 -21
  90. package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
  91. package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
  92. package/dist/types/system-prompt.d.ts +23 -0
  93. package/dist/types/task/executor.d.ts +35 -4
  94. package/dist/types/task/index.d.ts +17 -3
  95. package/dist/types/task/receipt.d.ts +3 -1
  96. package/dist/types/task/types.d.ts +144 -0
  97. package/dist/types/tools/atomic-file-write.d.ts +72 -0
  98. package/dist/types/tools/descriptors.d.ts +1 -0
  99. package/dist/types/tools/index.d.ts +36 -1
  100. package/dist/types/tools/move-session.d.ts +40 -0
  101. package/dist/types/tools/python.d.ts +12 -0
  102. package/dist/types/tools/resource-gc.d.ts +0 -3
  103. package/dist/types/tools/search.d.ts +8 -1
  104. package/dist/types/tools/skill.d.ts +9 -4
  105. package/dist/types/tools/subagent.d.ts +33 -3
  106. package/dist/types/utils/xml-escape.d.ts +2 -0
  107. package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
  108. package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
  109. package/package.json +11 -12
  110. package/scripts/build-sdk-package-smoke.ts +7 -5
  111. package/scripts/check-autorouting-tier-map.ts +183 -0
  112. package/scripts/generate-sdk-operation-inventory.ts +12 -0
  113. package/scripts/generate-tool-catalog.ts +40 -5
  114. package/src/async/job-manager.ts +5 -2
  115. package/src/autoresearch/dashboard.ts +134 -314
  116. package/src/autoresearch/data-context.ts +35 -0
  117. package/src/autoresearch/git.ts +186 -26
  118. package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
  119. package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
  120. package/src/autoresearch/prompts/prompt.md +103 -0
  121. package/src/autoresearch/prompts.ts +126 -0
  122. package/src/autoresearch/runs.ts +708 -0
  123. package/src/autoresearch/types.ts +9 -150
  124. package/src/capability/index.ts +4 -4
  125. package/src/capability/types.ts +20 -0
  126. package/src/cli/accounts-cli.ts +21 -5
  127. package/src/cli/config-cli.ts +25 -1
  128. package/src/cli/fast-help.ts +1 -2
  129. package/src/cli/list-models.ts +5 -8
  130. package/src/cli/setup-cli.ts +8 -0
  131. package/src/cli.ts +2 -21
  132. package/src/commands/autoresearch.ts +33 -0
  133. package/src/commands/coordinator.ts +15 -0
  134. package/src/commands/launch.ts +2 -4
  135. package/src/commands/quick-lane.ts +1 -1
  136. package/src/commands/sdk.ts +24 -0
  137. package/src/commands/state.ts +1 -1
  138. package/src/config/autorouting-contract.ts +474 -0
  139. package/src/config/autorouting-generator.ts +238 -0
  140. package/src/config/autorouting-tier-map.ts +6108 -0
  141. package/src/config/autorouting.ts +120 -0
  142. package/src/config/model-discovery-manager.ts +42 -11
  143. package/src/config/model-registry.ts +348 -68
  144. package/src/config/provider-selection-policy.ts +60 -9
  145. package/src/config/settings-schema.ts +195 -6
  146. package/src/config/settings.ts +62 -1
  147. package/src/config.ts +2 -3
  148. package/src/coordinator/contract.ts +0 -1
  149. package/src/coordinator-mcp/codex-handoff.ts +68 -42
  150. package/src/coordinator-mcp/durability.ts +200 -0
  151. package/src/coordinator-mcp/event-webhook.ts +278 -0
  152. package/src/coordinator-mcp/question-state.ts +109 -46
  153. package/src/coordinator-mcp/server.ts +932 -504
  154. package/src/crash/index-store.ts +126 -22
  155. package/src/crash/record-loader.ts +7 -0
  156. package/src/crash/upstream/relay.ts +82 -34
  157. package/src/cursor.ts +84 -1
  158. package/src/debug/index.ts +26 -0
  159. package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
  160. package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
  161. package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
  162. package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
  163. package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
  164. package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
  165. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
  166. package/src/defaults/gjc-defaults.ts +89 -2
  167. package/src/defaults/gjc-skills.generated.ts +24 -17
  168. package/src/discovery/builtin.ts +18 -5
  169. package/src/discovery/helpers.ts +6 -6
  170. package/src/edit/index.ts +5 -2
  171. package/src/eval/py/executor.ts +8 -0
  172. package/src/extensibility/extensions/loader.ts +135 -13
  173. package/src/extensibility/extensions/wrapper.ts +1 -2
  174. package/src/extensibility/gjc-plugins/schema.ts +1 -1
  175. package/src/extensibility/runtime-skill-discovery.ts +2 -2
  176. package/src/extensibility/skill-management.ts +2 -3
  177. package/src/extensibility/skills.ts +16 -8
  178. package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
  179. package/src/gjc-runtime/gc-render.ts +0 -1
  180. package/src/gjc-runtime/gc-runtime.ts +1 -11
  181. package/src/gjc-runtime/launch-tmux.ts +3 -3
  182. package/src/gjc-runtime/psmux-detect.ts +2 -2
  183. package/src/gjc-runtime/python-transcript.ts +58 -0
  184. package/src/gjc-runtime/ralplan-runtime.ts +6 -18
  185. package/src/gjc-runtime/session-layout.ts +18 -7
  186. package/src/gjc-runtime/session-state-sidecar.ts +58 -9
  187. package/src/gjc-runtime/state-renderer.ts +0 -38
  188. package/src/gjc-runtime/state-runtime.ts +43 -22
  189. package/src/gjc-runtime/state-schema.ts +1 -1
  190. package/src/gjc-runtime/tmux-common.ts +7 -7
  191. package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
  192. package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
  193. package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
  194. package/src/gjc-runtime/workflow-command-ref.ts +15 -11
  195. package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
  196. package/src/gjc-runtime/workflow-manifest.ts +43 -83
  197. package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
  198. package/src/hooks/skill-keywords.ts +3 -3
  199. package/src/hooks/skill-state.ts +2 -2
  200. package/src/internal-urls/docs-index.generated.ts +26 -24
  201. package/src/lsp/index.ts +25 -5
  202. package/src/main.ts +2 -27
  203. package/src/modes/acp/acp-agent.ts +200 -75
  204. package/src/modes/components/model-selector.ts +216 -13
  205. package/src/modes/components/oauth-selector.ts +5 -5
  206. package/src/modes/components/skill-hud/render.ts +173 -35
  207. package/src/modes/components/smart-routing-panel.ts +531 -0
  208. package/src/modes/components/tool-status-header.ts +6 -3
  209. package/src/modes/controllers/command-controller.ts +17 -7
  210. package/src/modes/controllers/input-controller.ts +44 -9
  211. package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
  212. package/src/modes/controllers/selector-controller.ts +298 -65
  213. package/src/modes/interactive-mode.ts +8 -1
  214. package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
  215. package/src/modes/types.ts +9 -1
  216. package/src/prompts/agents/planner.md +1 -1
  217. package/src/prompts/system/system-prompt.md +1 -1
  218. package/src/prompts/system/volatile-project-context.md +2 -1
  219. package/src/prompts/tools/move-session.md +15 -0
  220. package/src/prompts/tools/python.md +12 -0
  221. package/src/prompts/tools/skill-discovery.md +1 -1
  222. package/src/prompts/tools/skill.md +2 -2
  223. package/src/prompts/tools/task-summary.md +1 -0
  224. package/src/prompts/tools/task.md +6 -0
  225. package/src/rlm/artifacts.ts +12 -10
  226. package/src/runtime/optional-runtime-services.ts +3 -2
  227. package/src/runtime/workspace-tree-service.ts +11 -3
  228. package/src/runtime-mcp/config.ts +8 -1
  229. package/src/runtime-mcp/manager.ts +33 -0
  230. package/src/sdk/acp/adapter.ts +48 -1
  231. package/src/sdk/broker/broker.ts +30 -2
  232. package/src/sdk/broker/discovery.ts +217 -2
  233. package/src/sdk/broker/lifecycle.ts +385 -59
  234. package/src/sdk/broker/session-index.ts +243 -48
  235. package/src/sdk/broker/transport.ts +1 -0
  236. package/src/sdk/bus/chat-daemon-control.ts +8 -2
  237. package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
  238. package/src/sdk/bus/index.ts +208 -11
  239. package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
  240. package/src/sdk/bus/reconciliation-store.ts +27 -5
  241. package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
  242. package/src/sdk/client/client.ts +204 -22
  243. package/src/sdk/host/host.ts +16 -0
  244. package/src/sdk/host/internal-autorouting-state.ts +13 -0
  245. package/src/sdk/host/model-pin.ts +101 -0
  246. package/src/sdk/host/reverse-leases.ts +2 -1
  247. package/src/sdk/host/session-runtime.test.ts +224 -1
  248. package/src/sdk/host/session-runtime.ts +125 -37
  249. package/src/sdk/lifecycle/service.ts +3 -1
  250. package/src/sdk/lifecycle-session.ts +42 -1
  251. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  252. package/src/sdk/router/session-router.ts +375 -36
  253. package/src/sdk/session.ts +547 -47
  254. package/src/session/account-inventory.ts +1 -1
  255. package/src/session/agent-session.ts +765 -189
  256. package/src/session/artifacts.ts +234 -4
  257. package/src/session/internal/managed-session-storage.ts +91 -4
  258. package/src/session/provider-safety-stop-hint.ts +3 -2
  259. package/src/session/session-manager.ts +775 -16
  260. package/src/session/session-staging-paths.ts +8 -0
  261. package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
  262. package/src/setup/host-plugin-setup.ts +2 -2
  263. package/src/skill-state/active-state.ts +2 -2
  264. package/src/skill-state/canonical-skills.ts +1 -1
  265. package/src/skill-state/initial-phase.ts +1 -1
  266. package/src/skill-state/workflow-hud.ts +41 -41
  267. package/src/skill-state/workflow-mutation-guard.ts +26 -7
  268. package/src/slash-commands/builtin-registry.ts +77 -0
  269. package/src/slash-commands/helpers/autorouting-status.ts +55 -0
  270. package/src/system-prompt.ts +102 -5
  271. package/src/task/discovery.ts +2 -3
  272. package/src/task/executor.ts +617 -43
  273. package/src/task/index.ts +262 -15
  274. package/src/task/receipt.ts +30 -0
  275. package/src/task/render.ts +11 -1
  276. package/src/task/types.ts +316 -0
  277. package/src/tools/atomic-file-write.ts +594 -0
  278. package/src/tools/bisect.ts +3 -2
  279. package/src/tools/descriptors.test.ts +3 -0
  280. package/src/tools/descriptors.ts +24 -0
  281. package/src/tools/image-gen.ts +5 -1
  282. package/src/tools/index.ts +62 -5
  283. package/src/tools/move-session.ts +107 -0
  284. package/src/tools/python.ts +205 -0
  285. package/src/tools/read.ts +88 -15
  286. package/src/tools/renderers.ts +2 -0
  287. package/src/tools/resource-gc.ts +8 -177
  288. package/src/tools/search.ts +30 -6
  289. package/src/tools/skill-discovery.ts +1 -0
  290. package/src/tools/skill.ts +37 -7
  291. package/src/tools/subagent-render.ts +119 -13
  292. package/src/tools/subagent.ts +91 -74
  293. package/src/tools/tool-catalog.generated.ts +64 -2
  294. package/src/tools/write.ts +16 -12
  295. package/src/utils/xml-escape.ts +14 -0
  296. package/src/web/search/providers/codex.ts +2 -2
  297. package/src/workflow/workflow-intent-diff.ts +13 -12
  298. package/src/workflow/workflow-intent-report-consumer.ts +6 -6
  299. package/dist/types/autoresearch/helpers.d.ts +0 -24
  300. package/dist/types/autoresearch/index.d.ts +0 -2
  301. package/dist/types/autoresearch/state.d.ts +0 -17
  302. package/dist/types/autoresearch/storage.d.ts +0 -142
  303. package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
  304. package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
  305. package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
  306. package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
  307. package/dist/types/commands/team.d.ts +0 -24
  308. package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
  309. package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
  310. package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
  311. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
  312. package/dist/types/gjc-runtime/team-store.d.ts +0 -168
  313. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
  314. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
  315. package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
  316. package/dist/types/rlm/index.d.ts +0 -47
  317. package/dist/types/rlm/preset.d.ts +0 -23
  318. package/dist/types/rlm/python-tool.d.ts +0 -16
  319. package/src/autoresearch/command-resume.md +0 -14
  320. package/src/autoresearch/index.ts +0 -536
  321. package/src/autoresearch/prompt.md +0 -103
  322. package/src/autoresearch/resume-message.md +0 -10
  323. package/src/autoresearch/state.ts +0 -273
  324. package/src/autoresearch/storage.ts +0 -699
  325. package/src/autoresearch/tools/init-experiment.ts +0 -272
  326. package/src/autoresearch/tools/log-experiment.ts +0 -524
  327. package/src/autoresearch/tools/run-experiment.ts +0 -475
  328. package/src/autoresearch/tools/update-notes.ts +0 -109
  329. package/src/commands/rlm.ts +0 -19
  330. package/src/commands/team.ts +0 -238
  331. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
  332. package/src/defaults/gjc/skills/team/SKILL.md +0 -467
  333. package/src/gjc-runtime/team-gc.ts +0 -49
  334. package/src/gjc-runtime/team-launch.ts +0 -404
  335. package/src/gjc-runtime/team-notify.ts +0 -326
  336. package/src/gjc-runtime/team-runtime.ts +0 -5923
  337. package/src/gjc-runtime/team-store.ts +0 -878
  338. package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
  339. package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
  340. package/src/gjc-runtime/team-workers.ts +0 -1123
  341. package/src/prompts/system/rlm-report-command.md +0 -1
  342. package/src/prompts/system/rlm-research.md +0 -23
  343. package/src/rlm/index.ts +0 -365
  344. package/src/rlm/preset.ts +0 -76
  345. package/src/rlm/python-tool.ts +0 -68
@@ -18,15 +18,9 @@ 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";
22
21
  import { computeMemoryGuardDomain } from "../runtime/memory-domain";
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";
22
+ import { chooseMemoryGuardAction, MemoryGuardHost, resolveMemoryGuardPolicy } from "../runtime/memory-guard";
23
+ import type { MemoryGuardPolicy } from "../runtime/memory-guard-contract";
30
24
  import { resolveEffectiveMemoryLimit } from "../runtime/memory-limit";
31
25
  import { listTabsForGc, releaseTabIfGcEligible, type TabGcSnapshot } from "./browser/tab-supervisor";
32
26
  import { cleanupStaleScreenshotFallbackDirs, hasCreatedScreenshotFallbackDir } from "./computer-gc";
@@ -91,14 +85,6 @@ export interface ResourceGcDeps {
91
85
  releaseTab: (name: string, policy: { now: () => number; idleMs: number }) => Promise<boolean>;
92
86
  cleanupScreenshots: (opts: { now: () => number; staleMs: number }) => Promise<{ scanned: number; removed: number }>;
93
87
  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>;
102
88
  }
103
89
 
104
90
  /** Request collection without synchronously stopping the main event loop. */
@@ -117,9 +103,6 @@ const defaultDeps: ResourceGcDeps = {
117
103
  releaseTab: (name, policy) => releaseTabIfGcEligible(name, policy),
118
104
  cleanupScreenshots: opts => cleanupStaleScreenshotFallbackDirs(opts),
119
105
  screenshotArmed: () => hasCreatedScreenshotFallbackDir(),
120
- listTeamWorkers: (cwd, sessionId) => sampleTeamWorkers(cwd, sessionId),
121
- applyTeamWorkerGuard: (cwd, sessionId, workerId, excessBytes, incidentId) =>
122
- applySelectedTeamWorker(cwd, sessionId, workerId, excessBytes, incidentId),
123
106
  };
124
107
 
125
108
  // ── Controller state (process-global; tabs/browsers are module-global too) ──────────────────
@@ -135,8 +118,6 @@ let lastScreenshotScanAt = 0;
135
118
  const memoryGuardGcActive = new Set<string>();
136
119
  const memoryGuardLastEvaluatedAt = new Map<string, number>();
137
120
  const memoryGuardRestartAboveSince = new Map<string, number>();
138
- const memoryGuardRestartCooldownUntil = new Map<string, number>();
139
- const memoryGuardWorkerIncidentIds = new Map<string, string>();
140
121
  let deps: ResourceGcDeps = defaultDeps;
141
122
 
142
123
  export interface ResourceGcRegistration {
@@ -175,8 +156,6 @@ export function registerResourceGcSession(reg: ResourceGcRegistration): () => vo
175
156
  if (path === "memoryGuard.enabled" && !resolveMemoryGuardPolicy(reg.settings).enabled) {
176
157
  memoryGuardGcActive.delete(reg.sessionId);
177
158
  memoryGuardRestartAboveSince.delete(reg.sessionId);
178
- memoryGuardWorkerIncidentIds.delete(reg.sessionId);
179
- memoryGuardRestartCooldownUntil.delete(reg.sessionId);
180
159
  memoryGuardLastEvaluatedAt.delete(reg.sessionId);
181
160
  }
182
161
  }
@@ -186,11 +165,9 @@ export function registerResourceGcSession(reg: ResourceGcRegistration): () => vo
186
165
  if (unregistered) return;
187
166
  unregistered = true;
188
167
  activeSessions.delete(reg.sessionId);
189
- memoryGuardLastEvaluatedAt.delete(reg.sessionId);
190
168
  memoryGuardGcActive.delete(reg.sessionId);
191
169
  memoryGuardRestartAboveSince.delete(reg.sessionId);
192
- memoryGuardWorkerIncidentIds.delete(reg.sessionId);
193
- memoryGuardRestartCooldownUntil.delete(reg.sessionId);
170
+ memoryGuardLastEvaluatedAt.delete(reg.sessionId);
194
171
  unregisterSchedule();
195
172
  unregisterSettings();
196
173
  };
@@ -514,105 +491,11 @@ function sweepMemoryPressureGuard(d: ResourceGcDeps): Promise<void> | undefined
514
491
  }
515
492
  memoryGuardGcActive.delete(sessionId);
516
493
  memoryGuardRestartAboveSince.delete(sessionId);
517
- memoryGuardWorkerIncidentIds.delete(sessionId);
518
- memoryGuardRestartCooldownUntil.delete(sessionId);
519
494
  memoryGuardLastEvaluatedAt.delete(sessionId);
520
495
  }
521
496
  if (!enabled) return undefined;
522
497
  return sweepEnabledMemoryPressureGuard(d);
523
498
  }
524
-
525
- async function readLinuxWorkerRssBytes(pid: number): Promise<number | null> {
526
- if (process.platform !== "linux" || !Number.isSafeInteger(pid) || pid <= 0) return null;
527
- try {
528
- const status = await Bun.file(`/proc/${pid}/status`).text();
529
- const match = /^VmRSS:\s+(\d+)\s+kB$/m.exec(status);
530
- if (!match) return null;
531
- const kibibytes = Number(match[1]);
532
- return Number.isSafeInteger(kibibytes) ? kibibytes * 1024 : null;
533
- } catch {
534
- return null;
535
- }
536
- }
537
-
538
- async function readLinuxProcessStartTime(pid: number): Promise<string | null> {
539
- try {
540
- const stat = await Bun.file(`/proc/${pid}/stat`).text();
541
- const commandEnd = stat.lastIndexOf(")");
542
- if (commandEnd < 0) return null;
543
- const startTime = stat
544
- .slice(commandEnd + 2)
545
- .trim()
546
- .split(/\s+/)[19];
547
- return startTime && /^\d+$/.test(startTime) ? startTime : null;
548
- } catch {
549
- return null;
550
- }
551
- }
552
-
553
- async function sampleTeamWorkers(cwd: string, sessionId: string): Promise<MemoryGuardWorkerSample[]> {
554
- if (process.platform !== "linux") return [];
555
- const samples: MemoryGuardWorkerSample[] = [];
556
- for (const team of await listGjcTeams(cwd, { ...process.env, GJC_SESSION_ID: sessionId })) {
557
- if (team.phase === "complete" || team.phase === "cancelled") continue;
558
- for (const worker of team.workers) {
559
- try {
560
- const heartbeat = await readGjcWorkerHeartbeat(team.team_name, worker.id, cwd, {
561
- ...process.env,
562
- GJC_SESSION_ID: sessionId,
563
- });
564
- const heartbeatAt = Date.parse(heartbeat?.last_turn_at ?? "");
565
- if (
566
- !heartbeat?.alive ||
567
- !heartbeat.process_start_time ||
568
- !Number.isFinite(heartbeatAt) ||
569
- Date.now() - heartbeatAt >= 120_000 ||
570
- (await readLinuxProcessStartTime(heartbeat.pid)) !== heartbeat.process_start_time
571
- )
572
- continue;
573
- const guard = (await executeGjcTeamApiOperation(
574
- "read-worker-memory-guard",
575
- { team_name: team.team_name, worker: worker.id, platform: process.platform },
576
- cwd,
577
- { ...process.env, GJC_SESSION_ID: sessionId },
578
- )) as { automatic_action_allowed?: boolean; state?: string };
579
- if (!guard.automatic_action_allowed || guard.state === "blocked") continue;
580
- const bytes = await readLinuxWorkerRssBytes(heartbeat.pid);
581
- if (bytes === null) continue;
582
- samples.push({ workerId: `${team.team_name}/${worker.id}`, bytes, accepted: true });
583
- } catch {
584
- // Missing or malformed worker authority is not eligible for automatic mutation.
585
- }
586
- }
587
- }
588
- return samples;
589
- }
590
-
591
- async function applySelectedTeamWorker(
592
- cwd: string,
593
- sessionId: string,
594
- workerId: string,
595
- excessBytes: number,
596
- incidentId: string,
597
- ): Promise<void> {
598
- const separator = workerId.lastIndexOf("/");
599
- if (separator <= 0 || separator === workerId.length - 1) return;
600
- const teamName = workerId.slice(0, separator);
601
- const worker = workerId.slice(separator + 1);
602
- await executeGjcTeamApiOperation(
603
- "apply-worker-memory-guard",
604
- {
605
- team_name: teamName,
606
- worker,
607
- platform: process.platform,
608
- reason: "production_memory_pressure_sweep",
609
- incident_id: incidentId,
610
- candidates: [{ worker_id: worker, platform: process.platform, excess_bytes: excessBytes }],
611
- },
612
- cwd,
613
- { ...process.env, GJC_SESSION_ID: sessionId },
614
- );
615
- }
616
499
  async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void> {
617
500
  const now = d.monotonicNow();
618
501
  const dueSessions: Array<{ sessionId: string; policy: MemoryGuardPolicy; cwd: string }> = [];
@@ -622,8 +505,6 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
622
505
  if (!policy.enabled) {
623
506
  memoryGuardGcActive.delete(sessionId);
624
507
  memoryGuardRestartAboveSince.delete(sessionId);
625
- memoryGuardWorkerIncidentIds.delete(sessionId);
626
- memoryGuardRestartCooldownUntil.delete(sessionId);
627
508
  memoryGuardLastEvaluatedAt.delete(sessionId);
628
509
  continue;
629
510
  }
@@ -637,8 +518,7 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
637
518
  const snapshot = await d.memorySnapshot();
638
519
  let gcRequested = false;
639
520
  const gcTelemetry: Array<{ sessionId: string } & Record<string, unknown>> = [];
640
- let workerActionTaken = false;
641
- for (const { sessionId, policy, cwd } of dueSessions) {
521
+ for (const { sessionId, policy } of dueSessions) {
642
522
  const pressure = __selectMemoryPressureDomainForTest(snapshot, policy.policyLimitBytes);
643
523
  const limit = resolveEffectiveMemoryLimit({
644
524
  hardCapBytes: pressure.hardCapBytes,
@@ -647,23 +527,20 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
647
527
  if (limit.effectiveBytes === null) {
648
528
  memoryGuardGcActive.delete(sessionId);
649
529
  memoryGuardRestartAboveSince.delete(sessionId);
650
- memoryGuardWorkerIncidentIds.delete(sessionId);
651
- memoryGuardRestartCooldownUntil.delete(sessionId);
530
+ memoryGuardLastEvaluatedAt.delete(sessionId);
652
531
  continue;
653
532
  }
654
- const workerSamples = await (d.listTeamWorkers ?? (async () => []))(cwd, sessionId);
655
533
  const domain = computeMemoryGuardDomain({
656
534
  effectiveLimitBytes: limit.effectiveBytes,
657
535
  totalUsageBytes: pressure.totalUsageBytes,
658
536
  parentBytes: pressure.parentBytes,
659
537
  parentReserveBytes: policy.parentReserveBytes,
660
- workers: workerSamples,
538
+ workers: [],
661
539
  });
662
540
  const decision = chooseMemoryGuardAction({
663
541
  domain,
664
542
  hostSupported: false,
665
- workerSupported: workerId =>
666
- workerSamples.some(worker => worker.workerId === workerId && worker.accepted !== false),
543
+ workerSupported: () => false,
667
544
  });
668
545
  const usageRatio = pressure.totalUsageBytes / limit.effectiveBytes;
669
546
  if (usageRatio >= policy.gcThresholdRatio) {
@@ -686,56 +563,14 @@ async function sweepEnabledMemoryPressureGuard(d: ResourceGcDeps): Promise<void>
686
563
 
687
564
  if (usageRatio < policy.restartThresholdRatio) {
688
565
  memoryGuardRestartAboveSince.delete(sessionId);
689
- memoryGuardWorkerIncidentIds.delete(sessionId);
690
566
  continue;
691
567
  }
692
568
  const aboveSince = memoryGuardRestartAboveSince.get(sessionId);
693
569
  if (aboveSince === undefined) {
694
570
  memoryGuardRestartAboveSince.set(sessionId, now);
695
- memoryGuardWorkerIncidentIds.set(sessionId, `worker-pressure:${sessionId}:${Math.trunc(now)}`);
696
571
  continue;
697
572
  }
698
- const cooldownUntil = memoryGuardRestartCooldownUntil.get(sessionId) ?? 0;
699
- if (now - aboveSince < policy.restartThresholdWindowMs || now < cooldownUntil) continue;
700
- const workerIncidentId =
701
- memoryGuardWorkerIncidentIds.get(sessionId) ?? `worker-pressure:${sessionId}:${Math.trunc(aboveSince)}`;
702
- let workerActionAttemptedForSession = false;
703
- if (decision.kind === "execute" && decision.target.kind === "worker" && !workerActionTaken) {
704
- const refreshedWorkers = await (d.listTeamWorkers ?? (async () => []))(cwd, sessionId);
705
- const refreshedDomain = computeMemoryGuardDomain({
706
- effectiveLimitBytes: limit.effectiveBytes,
707
- totalUsageBytes: pressure.totalUsageBytes,
708
- parentBytes: pressure.parentBytes,
709
- parentReserveBytes: policy.parentReserveBytes,
710
- workers: refreshedWorkers,
711
- });
712
- const refreshedDecision = chooseMemoryGuardAction({
713
- domain: refreshedDomain,
714
- hostSupported: false,
715
- workerSupported: workerId =>
716
- refreshedWorkers.some(worker => worker.workerId === workerId && worker.accepted !== false),
717
- });
718
- const revalidated = revalidateMemoryGuardAction(decision, refreshedDecision);
719
- if (revalidated.kind !== "execute" || revalidated.target.kind !== "worker") continue;
720
- workerActionTaken = true;
721
- workerActionAttemptedForSession = true;
722
- try {
723
- await (d.applyTeamWorkerGuard ?? (async () => undefined))(
724
- cwd,
725
- sessionId,
726
- revalidated.target.workerId,
727
- revalidated.target.excessBytes,
728
- workerIncidentId,
729
- );
730
- } catch (error) {
731
- d.logWarn("Memory guard: team worker action failed; continuing sweep", {
732
- sessionId,
733
- workerId: revalidated.target.workerId,
734
- error: String(error),
735
- });
736
- }
737
- }
738
- if (workerActionAttemptedForSession) memoryGuardRestartCooldownUntil.set(sessionId, now + policy.cooldownMs);
573
+ if (now - aboveSince < policy.restartThresholdWindowMs) continue;
739
574
  d.logWarn("Memory guard: restart threshold sustained", {
740
575
  sessionId,
741
576
  parentBytes: pressure.parentBytes,
@@ -864,8 +699,6 @@ export function __setResourceGcDepsForTest(overrides: Partial<ResourceGcDeps>):
864
699
  ...defaultDeps,
865
700
  ...overrides,
866
701
  monotonicNow: overrides.monotonicNow ?? overrides.now ?? defaultDeps.monotonicNow,
867
- listTeamWorkers: overrides.listTeamWorkers ?? (async () => []),
868
- applyTeamWorkerGuard: overrides.applyTeamWorkerGuard ?? (async () => undefined),
869
702
  };
870
703
  }
871
704
 
@@ -917,8 +750,6 @@ export function __resetResourceGcForTest(): void {
917
750
  rssWarningActive = false;
918
751
  memoryGuardGcActive.clear();
919
752
  memoryGuardRestartAboveSince.clear();
920
- memoryGuardWorkerIncidentIds.clear();
921
- memoryGuardRestartCooldownUntil.clear();
922
753
  memoryGuardLastEvaluatedAt.clear();
923
754
  lastScreenshotScanAt = 0;
924
755
  deps = defaultDeps;
@@ -230,6 +230,13 @@ export interface SearchToolDetails {
230
230
  missingPaths?: string[];
231
231
  }
232
232
 
233
+ export interface SearchToolOptions {
234
+ /** Per-call symmetric context width used by Cursor's native Pi grep frame. */
235
+ context?: number;
236
+ /** Per-call total match cap used by Cursor's native Pi grep frame. */
237
+ totalMatchLimit?: number;
238
+ }
239
+
233
240
  type SearchParams = z.infer<typeof searchSchema>;
234
241
 
235
242
  export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDetails> {
@@ -247,7 +254,10 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
247
254
  readonly parameters = searchSchema;
248
255
  readonly strict = true;
249
256
 
250
- constructor(private readonly session: ToolSession) {}
257
+ constructor(
258
+ private readonly session: ToolSession,
259
+ private readonly options: SearchToolOptions = {},
260
+ ) {}
251
261
 
252
262
  async execute(
253
263
  _toolCallId: string,
@@ -293,8 +303,12 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
293
303
  `or pass a UTF-8 text member.`,
294
304
  );
295
305
  }
296
- const normalizedContextBefore = this.session.settings.get("search.contextBefore");
297
- const normalizedContextAfter = this.session.settings.get("search.contextAfter");
306
+ const normalizedContextBefore = this.options.context ?? this.session.settings.get("search.contextBefore");
307
+ const normalizedContextAfter = this.options.context ?? this.session.settings.get("search.contextAfter");
308
+ const nativeMatchCap =
309
+ this.options.totalMatchLimit === undefined
310
+ ? INTERNAL_TOTAL_CAP
311
+ : Math.min(INTERNAL_TOTAL_CAP, this.options.totalMatchLimit + 1);
298
312
  const ignoreCase = i ?? false;
299
313
  const useGitignore = gitignore ?? true;
300
314
  const patternHasNewline = normalizedPattern.includes("\n") || normalizedPattern.includes("\\n");
@@ -362,7 +376,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
362
376
  hidden: true,
363
377
  gitignore: useGitignore,
364
378
  cache: false,
365
- maxCount: INTERNAL_TOTAL_CAP,
379
+ maxCount: nativeMatchCap,
366
380
  contextBefore: normalizedContextBefore,
367
381
  contextAfter: normalizedContextAfter,
368
382
  maxColumns: DEFAULT_MAX_COLUMN,
@@ -397,7 +411,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
397
411
  hidden: true,
398
412
  gitignore: useGitignore,
399
413
  cache: false,
400
- maxCount: INTERNAL_TOTAL_CAP,
414
+ maxCount: nativeMatchCap,
401
415
  contextBefore: normalizedContextBefore,
402
416
  contextAfter: normalizedContextAfter,
403
417
  maxColumns: DEFAULT_MAX_COLUMN,
@@ -469,6 +483,12 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
469
483
  }
470
484
  }
471
485
  }
486
+ const explicitMatchLimitReached =
487
+ this.options.totalMatchLimit !== undefined &&
488
+ (selectedMatches.length > this.options.totalMatchLimit || result.limitReached);
489
+ if (this.options.totalMatchLimit !== undefined && selectedMatches.length > this.options.totalMatchLimit) {
490
+ selectedMatches.length = this.options.totalMatchLimit;
491
+ }
472
492
  const nextSkip = skipFiles + windowFiles.length;
473
493
  const limitMessage = fileLimitReached
474
494
  ? `Showing files ${skipFiles + 1}-${nextSkip} of ${totalFiles}. Use skip=${nextSkip} for the next page, or narrow paths/pattern.`
@@ -608,7 +628,11 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
608
628
  })),
609
629
  truncated,
610
630
  fileLimitReached: fileLimitReached ? DEFAULT_FILE_LIMIT : undefined,
611
- perFileLimitReached: perFileLimitReached ? perFileMatchCap : undefined,
631
+ perFileLimitReached: explicitMatchLimitReached
632
+ ? this.options.totalMatchLimit
633
+ : perFileLimitReached
634
+ ? perFileMatchCap
635
+ : undefined,
612
636
  displayContent: displayText,
613
637
  missingPaths: missingPaths.length > 0 ? missingPaths : undefined,
614
638
  };
@@ -78,6 +78,7 @@ export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema
78
78
  const source = input.source ?? "all";
79
79
  const result = await discoverRuntimeSkills({
80
80
  cwd: this.#session.cwd,
81
+ home: this.#session.home,
81
82
  query: input.query,
82
83
  source,
83
84
  limit: input.limit,
@@ -10,10 +10,15 @@
10
10
  * project/user skills have no native workflow mode-state, so they are dispatched
11
11
  * directly and tracked by the prompt observer instead.
12
12
  *
13
- * Canonical workflow chaining is refused unless the caller's `current_phase` is
14
- * in `{complete, completed, handoff, failed, cancelled, canceled, inactive}`.
15
- * The agent declares readiness either by writing `current_phase: "handoff"` to
16
- * its mode-state or by running the handoff verb directly.
13
+ * Canonical workflow chaining is refused unless the caller's phase permits it:
14
+ * the generic terminal set `{complete, completed, handoff, failed, cancelled,
15
+ * canceled, inactive}`, the caller's manifest-declared terminal states (e.g.
16
+ * ralplan `final`), or — for autoresearch only — any phase with a declared
17
+ * handoff edge (`intake`/`research`/`verdict`): a research mission never
18
+ * mutates and guards no approval gate, its mission state is durable, so an
19
+ * ongoing or verdict-complete mission may hand off to planning at any point.
20
+ * ralplan/ultragoal keep the explicit write-to-handoff step for live phases:
21
+ * their mid-flight chaining is exactly what the approval-gate guard blocks.
17
22
  */
18
23
 
19
24
  import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
@@ -23,6 +28,7 @@ import { resolveSubskillActivationForSkillInvocation } from "../extensibility/gj
23
28
  import { findRuntimeSkillByName } from "../extensibility/runtime-skill-discovery";
24
29
  import { buildSkillPromptMessage } from "../extensibility/skills";
25
30
  import { runNativeStateCommand } from "../gjc-runtime/state-runtime";
31
+ import { getSkillManifest } from "../gjc-runtime/workflow-manifest";
26
32
  import skillDescription from "../prompts/tools/skill.md" with { type: "text" };
27
33
  import { SKILL_PROMPT_MESSAGE_TYPE } from "../session/messages";
28
34
  import { isCanonicalGjcWorkflowSkill } from "../skill-state/active-state";
@@ -31,6 +37,25 @@ import { ToolError } from "./tool-errors";
31
37
 
32
38
  const TERMINAL_PHASES = new Set(["complete", "completed", "handoff", "failed", "cancelled", "canceled", "inactive"]);
33
39
 
40
+ /**
41
+ * Whether the active canonical workflow's phase permits chaining into another
42
+ * skill. Generic terminal phases always chain; manifest terminal states cover
43
+ * skill-specific termini the generic set misses (ralplan "final"); and
44
+ * autoresearch chains from any phase with a manifest handoff edge because a
45
+ * research mission is safely abandonable mid-flight (read-only, durable
46
+ * mission state, no approval gate). ralplan/ultragoal deliberately stay
47
+ * blocked in live phases so mid-consensus or mid-execution chaining still
48
+ * requires the explicit write-to-handoff step.
49
+ */
50
+ function phasePermitsChain(skill: string, phase: string): boolean {
51
+ if (TERMINAL_PHASES.has(phase)) return true;
52
+ if (!isCanonicalGjcWorkflowSkill(skill)) return false;
53
+ const manifest = getSkillManifest(skill);
54
+ if (manifest.terminalStates.includes(phase)) return true;
55
+ if (skill !== "autoresearch") return false;
56
+ return manifest.transitions.some(transition => transition.from === phase && transition.to === "handoff");
57
+ }
58
+
34
59
  const skillSchema = z.object({
35
60
  name: z.string().describe("skill name as it appears in /skill:<name>"),
36
61
  args: z.string().describe("argument string passed to the skill").optional(),
@@ -122,7 +147,12 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
122
147
 
123
148
  const skill =
124
149
  skills.find(s => s.name === requestedName) ??
125
- (await findRuntimeSkillByName(this.#session.cwd, requestedName, this.#getRuntimeSkillPolicy()));
150
+ (await findRuntimeSkillByName(
151
+ this.#session.cwd,
152
+ requestedName,
153
+ this.#getRuntimeSkillPolicy(),
154
+ this.#session.home,
155
+ ));
126
156
  if (!skill) {
127
157
  const available = skills.map(s => s.name).sort();
128
158
  const hint =
@@ -137,9 +167,9 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
137
167
  // so there is no `gjc state <skill>` command to run for them.
138
168
  if (activeSkill && isCanonicalGjcWorkflowSkill(activeSkill)) {
139
169
  const phase = (this.#session.getActiveSkillPhase?.() ?? "running").trim().toLowerCase();
140
- if (!TERMINAL_PHASES.has(phase)) {
170
+ if (!phasePermitsChain(activeSkill, phase)) {
141
171
  throw new ToolError(
142
- `skill tool: refusing to chain from "${activeSkill}" (phase=${phase}) into "${requestedName}". Finalize the current skill (gjc state ${activeSkill} write --input '{"current_phase":"handoff"}' --json) or run gjc state ${activeSkill} handoff --to ${requestedName} --json directly before chaining.`,
172
+ `skill tool: refusing to chain from "${activeSkill}" (phase=${phase}) into "${requestedName}". Run gjc state ${activeSkill} handoff --to ${requestedName} --json directly, or finalize the current skill first (gjc state ${activeSkill} write --input '{"current_phase":"handoff"}' --json) before chaining.`,
143
173
  );
144
174
  }
145
175
  const cwd = this.#session.cwd;
@@ -11,13 +11,7 @@ import type { Component } from "@gajae-code/tui";
11
11
  import { Text } from "@gajae-code/tui";
12
12
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
13
13
  import type { Theme } from "../modes/theme/theme";
14
- import {
15
- collectProviderDegradationGroups,
16
- hasActiveProviderRetryInProgress,
17
- providerProgressAgeLabel,
18
- providerRetryPhaseLabel,
19
- } from "../task/provider-retry-status";
20
- import { renderSubagentLiveProgress } from "../task/render";
14
+ import { providerRetryPhaseLabel } from "../task/provider-retry-status";
21
15
  import { Ellipsis, Hasher, renderStatusLine } from "../tui";
22
16
  import {
23
17
  formatDuration,
@@ -27,7 +21,12 @@ import {
27
21
  type ToolUIStatus,
28
22
  truncateToWidth,
29
23
  } from "./render-utils";
30
- import { type SubagentSnapshot, type SubagentToolDetails, subagentAwaitRenderedStateSignature } from "./subagent";
24
+ import {
25
+ type SubagentLiveProgress,
26
+ type SubagentSnapshot,
27
+ type SubagentToolDetails,
28
+ subagentAwaitRenderedStateSignature,
29
+ } from "./subagent";
31
30
 
32
31
  export { subagentAwaitRenderedStateSignature } from "./subagent";
33
32
 
@@ -89,6 +88,30 @@ function snapshotHasActiveRetry(snapshot: SubagentSnapshot): boolean {
89
88
  return hasActiveProviderRetryInProgress(snapshot.progress);
90
89
  }
91
90
 
91
+ function hasActiveProviderRetryInProgress(progress: SubagentLiveProgress): boolean {
92
+ return progress.status === "running" && progress.retryState !== undefined;
93
+ }
94
+
95
+ function providerProgressAgeLabel(progress: SubagentLiveProgress["retryState"], nowMs: number): string {
96
+ if (progress?.lastProviderProgressAtMs === undefined) return "no provider events yet";
97
+ const ageSeconds = Math.max(0, Math.floor((nowMs - progress.lastProviderProgressAtMs) / 1000));
98
+ return `last provider progress ${ageSeconds}s ago`;
99
+ }
100
+
101
+ function collectProviderDegradationGroups(
102
+ progress: readonly SubagentLiveProgress[],
103
+ ): Array<{ provider: string; count: number }> {
104
+ const counts = new Map<string, number>();
105
+ for (const item of progress) {
106
+ if (item.status !== "running" || !item.retryState) continue;
107
+ const provider = item.retryState.provider ?? "provider";
108
+ counts.set(provider, (counts.get(provider) ?? 0) + 1);
109
+ }
110
+ return Array.from(counts, ([provider, count]) => ({ provider, count }))
111
+ .filter(group => group.count > 1)
112
+ .sort((a, b) => b.count - a.count || a.provider.localeCompare(b.provider));
113
+ }
114
+
92
115
  function boundSubagentBodyLines(lines: string[], width: number): string[] {
93
116
  return lines.map(line => (line.length > 0 ? truncateToWidth(replaceTabs(line), width, Ellipsis.Omit) : ""));
94
117
  }
@@ -166,6 +189,72 @@ function renderSubagentStatusLine(snapshot: SubagentSnapshot, theme: Theme, spin
166
189
  return `${icon} ${id} ${status} ${duration}`;
167
190
  }
168
191
 
192
+ function renderSubagentLiveProgress(
193
+ progress: SubagentLiveProgress,
194
+ expanded: boolean,
195
+ theme: Theme,
196
+ spinnerFrame?: number,
197
+ staticTime = false,
198
+ ): string[] {
199
+ const lines: string[] = [];
200
+ const prefix = theme.fg("dim", theme.tree.last);
201
+ const iconColor = progress.status === "failed" || progress.status === "aborted" ? "error" : "accent";
202
+ const icon = formatStatusIcon(
203
+ progress.status === "completed" ? "success" : progress.status === "failed" ? "error" : "info",
204
+ theme,
205
+ progress.status === "running" ? spinnerFrame : undefined,
206
+ );
207
+ let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", progress.id)}`;
208
+ if (progress.fastMode && theme.icon.fast) statusLine += ` ${theme.icon.fast}`;
209
+ if (progress.retryState && progress.status === "running") {
210
+ statusLine += ` ${theme.fg("warning", "provider degraded")}`;
211
+ }
212
+ lines.push(statusLine);
213
+
214
+ const continuePrefix = " ";
215
+ if (progress.status === "running") {
216
+ const tool = progress.currentTool ?? progress.recentTool;
217
+ if (tool) lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("muted", tool)}`);
218
+ }
219
+ if (progress.recentOutputSummary) {
220
+ const count = progress.recentOutputSummary.lineCount;
221
+ lines.push(
222
+ `${continuePrefix}${theme.tree.hook} ${theme.fg("dim", `recent output available (${count} ${count === 1 ? "line" : "lines"})`)}`,
223
+ );
224
+ }
225
+ if (progress.retryState && progress.status === "running") {
226
+ const retry = progress.retryState;
227
+ const attemptLabel = retry.unbounded
228
+ ? `attempt ${retry.attempt}, unbounded`
229
+ : `attempt ${retry.attempt} of ${retry.maxAttempts}, bounded`;
230
+ const progressAge = staticTime ? "" : ` · ${providerProgressAgeLabel(retry, Date.now())}`;
231
+ let waitLabel = "";
232
+ if (!staticTime) {
233
+ const remainingMs = Math.max(0, retry.startedAtMs + retry.delayMs - Date.now());
234
+ waitLabel = remainingMs > 0 ? ` in ${formatDuration(remainingMs)}` : " now";
235
+ }
236
+ lines.push(
237
+ `${continuePrefix}${theme.tree.hook} ${theme.fg("warning", `${providerRetryPhaseLabel(retry.kind)} · retrying ${attemptLabel}${waitLabel}${progressAge}`)}`,
238
+ );
239
+ }
240
+ if (progress.retryFailure && progress.status !== "running") {
241
+ const attempts = progress.retryFailure.attempt;
242
+ lines.push(
243
+ `${continuePrefix}${theme.tree.hook} ${theme.fg("error", `auto-retry gave up after ${attempts} attempt${attempts === 1 ? "" : "s"}`)}`,
244
+ );
245
+ }
246
+ if (
247
+ expanded &&
248
+ progress.status === "running" &&
249
+ !progress.currentTool &&
250
+ !progress.recentTool &&
251
+ !progress.recentOutputSummary
252
+ ) {
253
+ lines.push(`${continuePrefix}${theme.fg("dim", "running, no approved activity summary yet")}`);
254
+ }
255
+ return lines;
256
+ }
257
+
169
258
  // Heavy per-subagent body. The cache path uses staticTime=true; the bounded dynamic
170
259
  // path opts into wall-clock displays when an active retry exists in the nested tree.
171
260
  function renderSubagentSnapshotBody(
@@ -198,6 +287,20 @@ function renderSubagentSnapshotBody(
198
287
  if (snapshot.setupFailureSummary) {
199
288
  lines.push(` ${theme.fg("error", `Setup failure: ${snapshot.setupFailureSummary}`)}`);
200
289
  }
290
+ if (snapshot.localErrorSummary) {
291
+ const local = snapshot.localErrorSummary;
292
+ lines.push(` ${theme.fg("error", `Local failure (${local.kind}): ${local.summary}`)}`);
293
+ // Kind-conditional guidance: an overflow is a staging limit that
294
+ // reproduces on re-issue; a snapshot failure is a serialization
295
+ // defect. Rendering the wrong one mislabels the failure mode.
296
+ const guidance =
297
+ local.kind === "local_buffer_overflow"
298
+ ? "Local gjc staging-buffer limit, not a provider or context-window failure; re-issuing reproduces it."
299
+ : local.kind === "local_snapshot_failure"
300
+ ? "Local gjc event-serialization defect, not a provider failure; safe to retry."
301
+ : undefined;
302
+ if (guidance) lines.push(` ${theme.fg("dim", guidance)}`);
303
+ }
201
304
  if (snapshot.assignment) {
202
305
  lines.push(` ${theme.fg("dim", "Assignment:")}`);
203
306
  for (const al of snapshot.assignment.split("\n")) lines.push(` ${theme.fg("toolOutput", replaceTabs(al))}`);
@@ -220,7 +323,7 @@ function renderSubagentSnapshotBody(
220
323
  lines.push(` ${pl}`);
221
324
  }
222
325
  } else if (snapshot.liveProgressAvailable && (snapshot.status === "running" || snapshot.status === "queued")) {
223
- lines.push(` ${theme.fg("dim", "running, no activity yet")}`);
326
+ lines.push(` ${theme.fg("dim", "running, no approved activity summary yet")}`);
224
327
  }
225
328
 
226
329
  const preview = snapshot.errorText?.trim() || snapshot.resultText?.trim();
@@ -265,6 +368,7 @@ export const subagentToolRenderer = {
265
368
  }
266
369
 
267
370
  const runningCount = subagents.filter(s => s.status === "running").length;
371
+ const failedCount = subagents.filter(s => s.status === "failed" || s.status === "not_found").length;
268
372
  const interrupted = result.details?.interrupted === true;
269
373
 
270
374
  // Each snapshot's rendered-state signature is constant for this component
@@ -280,14 +384,16 @@ export const subagentToolRenderer = {
280
384
  // it is never gated by the heavy body cache.
281
385
  const header = renderStatusLine(
282
386
  {
283
- icon: interrupted ? "warning" : runningCount > 0 ? "info" : "success",
387
+ icon: interrupted ? "warning" : runningCount > 0 ? "info" : failedCount > 0 ? "error" : "success",
284
388
  spinnerFrame: !interrupted && runningCount > 0 ? options.spinnerFrame : undefined,
285
- title: interrupted ? "Subagent await interrupted" : "Subagent",
389
+ title: interrupted ? "Subagent await interrupted" : failedCount > 0 ? "Subagent failed" : "Subagent",
286
390
  description: interrupted
287
391
  ? "child subagents continue"
288
392
  : runningCount > 0
289
393
  ? `awaiting ${runningCount} of ${subagents.length}`
290
- : `${subagents.length} ${subagents.length === 1 ? "subagent" : "subagents"}`,
394
+ : failedCount > 0
395
+ ? `${failedCount} ${failedCount === 1 ? "subagent" : "subagents"} failed`
396
+ : `${subagents.length} ${subagents.length === 1 ? "subagent" : "subagents"}`,
291
397
  },
292
398
  theme,
293
399
  );
@@ -320,7 +426,7 @@ export const subagentToolRenderer = {
320
426
 
321
427
  snapshotSignatures ??= subagents.map(
322
428
  snapshot =>
323
- `${subagentAwaitRenderedStateSignature([snapshot], result.details)}:${snapshot.setupFailureSummary ?? ""}`,
429
+ `${subagentAwaitRenderedStateSignature([snapshot], result.details)}:${snapshot.setupFailureSummary ?? ""}:${snapshot.localErrorSummary?.summary ?? ""}`,
324
430
  );
325
431
  subagents.forEach((snapshot, index) => {
326
432
  // Fresh per-subagent status line (cheap), then a cached or dynamic body.