@gajae-code/coding-agent 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/types/cli/config-cli.d.ts +11 -1
  3. package/dist/types/cli/notify-cli.d.ts +1 -0
  4. package/dist/types/cli/session-picker.d.ts +2 -1
  5. package/dist/types/cli/setup-cli.d.ts +1 -1
  6. package/dist/types/commands/mcp-serve.d.ts +33 -0
  7. package/dist/types/commands/mcp.d.ts +1 -1
  8. package/dist/types/commands/notify.d.ts +0 -2
  9. package/dist/types/commands/sdk.d.ts +1 -1
  10. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  11. package/dist/types/config/file-lock.d.ts +14 -4
  12. package/dist/types/config/keybindings.d.ts +42 -1
  13. package/dist/types/config/model-auth.d.ts +3 -0
  14. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  15. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  16. package/dist/types/config/model-equivalence.d.ts +20 -0
  17. package/dist/types/config/model-registry.d.ts +16 -15
  18. package/dist/types/config/model-resolver.d.ts +28 -18
  19. package/dist/types/config/models-config-schema.d.ts +11 -0
  20. package/dist/types/config/settings-schema.d.ts +56 -1
  21. package/dist/types/config/settings.d.ts +14 -11
  22. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  23. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  24. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  25. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  26. package/dist/types/discovery/helpers.d.ts +0 -6
  27. package/dist/types/edit/index.d.ts +3 -0
  28. package/dist/types/eval/py/env.d.ts +9 -0
  29. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  30. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  31. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  32. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  33. package/dist/types/extensibility/shared-events.d.ts +2 -0
  34. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  35. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  36. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  37. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  38. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  39. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  40. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  41. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  42. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  43. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  44. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  45. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  46. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  47. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  48. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  49. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  50. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  51. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  52. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  53. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  54. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  55. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  56. package/dist/types/goals/runtime.d.ts +1 -0
  57. package/dist/types/goals/state.d.ts +8 -0
  58. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  59. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  60. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  61. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  62. package/dist/types/hindsight/state.d.ts +11 -4
  63. package/dist/types/lsp/lspmux.d.ts +5 -3
  64. package/dist/types/lsp/path-trust.d.ts +1 -0
  65. package/dist/types/main.d.ts +3 -2
  66. package/dist/types/memories/storage.d.ts +1 -0
  67. package/dist/types/modes/action-registry.d.ts +29 -0
  68. package/dist/types/modes/components/chrome.d.ts +14 -0
  69. package/dist/types/modes/components/command-palette.d.ts +27 -0
  70. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  71. package/dist/types/modes/components/index.d.ts +2 -1
  72. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  73. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  74. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  75. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  76. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  77. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  78. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  79. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  80. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  81. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  82. package/dist/types/modes/components/status-line.d.ts +1 -94
  83. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  84. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  85. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  86. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  87. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  88. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  89. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  90. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  91. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  92. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  93. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  94. package/dist/types/modes/interactive-mode.d.ts +19 -17
  95. package/dist/types/modes/jobs-observer.d.ts +2 -0
  96. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  97. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
  98. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  99. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  100. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  101. package/dist/types/modes/types.d.ts +20 -13
  102. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  103. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  104. package/dist/types/sdk/broker/broker.d.ts +78 -12
  105. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  106. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  107. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  108. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  109. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  110. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  111. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  112. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  113. package/dist/types/sdk/bus/config.d.ts +11 -0
  114. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  115. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  116. package/dist/types/sdk/bus/index.d.ts +27 -0
  117. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  118. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  119. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  120. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  121. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  122. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  123. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  124. package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
  125. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  126. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  127. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  128. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  129. package/dist/types/sdk/host/host.d.ts +2 -0
  130. package/dist/types/sdk/index.d.ts +1 -0
  131. package/dist/types/sdk/session-directory.d.ts +56 -0
  132. package/dist/types/session/agent-session.d.ts +82 -47
  133. package/dist/types/session/agent-storage.d.ts +7 -0
  134. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  135. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  136. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  137. package/dist/types/session/messages.d.ts +16 -0
  138. package/dist/types/session/session-manager-internal.d.ts +14 -0
  139. package/dist/types/session/session-manager.d.ts +82 -12
  140. package/dist/types/session/session-storage.d.ts +43 -3
  141. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  142. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  143. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  144. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  145. package/dist/types/system-prompt.d.ts +2 -11
  146. package/dist/types/task/executor.d.ts +3 -1
  147. package/dist/types/thinking.d.ts +7 -0
  148. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  149. package/dist/types/tools/ask.d.ts +64 -2
  150. package/dist/types/tools/bash.d.ts +3 -0
  151. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  152. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  153. package/dist/types/tools/cron.d.ts +2 -0
  154. package/dist/types/tools/fetch.d.ts +1 -0
  155. package/dist/types/tools/index.d.ts +30 -15
  156. package/dist/types/tools/output-meta.d.ts +1 -0
  157. package/dist/types/tools/read.d.ts +3 -0
  158. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  159. package/dist/types/utils/event-bus.d.ts +1 -1
  160. package/dist/types/web/search/index.d.ts +3 -1
  161. package/package.json +21 -9
  162. package/scripts/build-sdk-package-smoke.ts +34 -1
  163. package/scripts/generate-hotkeys-docs.ts +74 -0
  164. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  165. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  166. package/src/async/job-manager.ts +49 -6
  167. package/src/cli/config-cli.ts +75 -2
  168. package/src/cli/notify-cli.ts +92 -26
  169. package/src/cli/session-picker.ts +20 -3
  170. package/src/cli/setup-cli.ts +1 -1
  171. package/src/cli.ts +1 -1
  172. package/src/commands/config.ts +1 -1
  173. package/src/commands/harness.ts +34 -106
  174. package/src/commands/launch.ts +6 -4
  175. package/src/commands/mcp-serve.ts +85 -8
  176. package/src/commands/mcp.ts +2 -2
  177. package/src/commands/notify.ts +8 -4
  178. package/src/commands/sdk.ts +61 -40
  179. package/src/commands/ultragoal.ts +4 -1
  180. package/src/config/atomic-yaml-patch.ts +80 -13
  181. package/src/config/file-lock.ts +105 -43
  182. package/src/config/keybindings.ts +64 -20
  183. package/src/config/model-auth.ts +9 -0
  184. package/src/config/model-bindings-applier.ts +139 -0
  185. package/src/config/model-discovery-manager.ts +224 -0
  186. package/src/config/model-equivalence.ts +66 -0
  187. package/src/config/model-registry.ts +169 -302
  188. package/src/config/model-resolver.ts +361 -369
  189. package/src/config/models-config-schema.ts +5 -4
  190. package/src/config/settings-schema.ts +159 -2
  191. package/src/config/settings.ts +458 -482
  192. package/src/coordinator-mcp/policy.ts +76 -11
  193. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  194. package/src/coordinator-mcp/question-state.ts +778 -0
  195. package/src/coordinator-mcp/server.ts +1702 -238
  196. package/src/debug/index.ts +1 -1
  197. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  198. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  199. package/src/discovery/helpers.ts +1 -50
  200. package/src/edit/index.ts +64 -0
  201. package/src/eval/py/env.ts +26 -0
  202. package/src/eval/py/kernel.ts +5 -3
  203. package/src/extensibility/extensions/loader.ts +9 -1
  204. package/src/extensibility/extensions/runner.ts +12 -3
  205. package/src/extensibility/extensions/types.ts +39 -1
  206. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  207. package/src/extensibility/hooks/runner.ts +2 -2
  208. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  209. package/src/extensibility/shared-events.ts +2 -0
  210. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  211. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  212. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  213. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  214. package/src/gjc-runtime/launch-tmux.ts +2 -5
  215. package/src/gjc-runtime/launch-worktree.ts +5 -5
  216. package/src/gjc-runtime/linux-proc.ts +71 -0
  217. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  218. package/src/gjc-runtime/session-resolution.ts +18 -3
  219. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  220. package/src/gjc-runtime/state-migrations.ts +1 -1
  221. package/src/gjc-runtime/state-runtime.ts +320 -249
  222. package/src/gjc-runtime/state-writer.ts +36 -40
  223. package/src/gjc-runtime/team-launch.ts +319 -0
  224. package/src/gjc-runtime/team-notify.ts +326 -0
  225. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  226. package/src/gjc-runtime/team-store.ts +828 -0
  227. package/src/gjc-runtime/team-workers.ts +962 -0
  228. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  229. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  230. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  231. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  232. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  233. package/src/gjc-runtime/ultragoal-guard.ts +29 -5
  234. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  235. package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
  236. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  237. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  238. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  239. package/src/goals/continuation-timeout-guard.ts +79 -0
  240. package/src/goals/runtime.ts +7 -5
  241. package/src/goals/state.ts +18 -0
  242. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  243. package/src/harness-control-plane/phase-rollup.ts +4 -7
  244. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  245. package/src/harness-control-plane/receipt-spool.ts +51 -7
  246. package/src/harness-control-plane/session-lease.ts +12 -130
  247. package/src/hindsight/backend.ts +26 -27
  248. package/src/hindsight/state.ts +101 -32
  249. package/src/hooks/skill-state.ts +17 -11
  250. package/src/internal-urls/docs-index.generated.ts +21 -18
  251. package/src/lsp/client.ts +1 -1
  252. package/src/lsp/config.ts +145 -26
  253. package/src/lsp/index.ts +1 -1
  254. package/src/lsp/lspmux.ts +39 -10
  255. package/src/lsp/path-trust.ts +81 -0
  256. package/src/main.ts +28 -8
  257. package/src/memories/index.ts +1 -0
  258. package/src/memories/storage.ts +6 -2
  259. package/src/modes/action-registry.ts +165 -0
  260. package/src/modes/components/chrome.ts +40 -0
  261. package/src/modes/components/command-palette.ts +168 -0
  262. package/src/modes/components/custom-editor.ts +1 -1
  263. package/src/modes/components/custom-provider-wizard.ts +51 -14
  264. package/src/modes/components/index.ts +2 -1
  265. package/src/modes/components/pet-selector.ts +11 -17
  266. package/src/modes/components/plan-preview-overlay.ts +295 -0
  267. package/src/modes/components/queue-mode-selector.ts +10 -27
  268. package/src/modes/components/queue-pane.ts +79 -0
  269. package/src/modes/components/read-tool-group.ts +13 -0
  270. package/src/modes/components/session-observer-overlay.ts +207 -772
  271. package/src/modes/components/sessions-dashboard.ts +181 -0
  272. package/src/modes/components/settings-selector.ts +1 -1
  273. package/src/modes/components/show-images-selector.ts +10 -24
  274. package/src/modes/components/status-line/types.ts +1 -1
  275. package/src/modes/components/status-line.ts +1 -969
  276. package/src/modes/components/tasks-pane.ts +73 -0
  277. package/src/modes/components/theme-selector.ts +11 -31
  278. package/src/modes/components/tool-execution.ts +22 -0
  279. package/src/modes/components/tool-status-header.ts +1056 -0
  280. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  281. package/src/modes/controllers/btw-controller.ts +1 -0
  282. package/src/modes/controllers/event-controller.ts +3 -2
  283. package/src/modes/controllers/extension-ui-controller.ts +24 -1
  284. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  285. package/src/modes/controllers/input-controller.ts +764 -229
  286. package/src/modes/controllers/mode-gate.ts +24 -0
  287. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  288. package/src/modes/controllers/selector-controller.ts +225 -10
  289. package/src/modes/controllers/stt-controller.ts +125 -0
  290. package/src/modes/interactive-mode.ts +289 -1150
  291. package/src/modes/jobs-observer.ts +3 -0
  292. package/src/modes/runtime-init.ts +8 -0
  293. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  294. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
  295. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  296. package/src/modes/tasks-aggregator.ts +199 -0
  297. package/src/modes/transcript-item-registry.ts +229 -0
  298. package/src/modes/types.ts +45 -18
  299. package/src/modes/utils/ui-helpers.ts +17 -9
  300. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  301. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  302. package/src/prompts/agents/architect.md +2 -8
  303. package/src/prompts/agents/critic.md +2 -8
  304. package/src/prompts/agents/executor.md +3 -0
  305. package/src/prompts/agents/planner.md +4 -12
  306. package/src/prompts/system/custom-system-prompt.md +1 -23
  307. package/src/prompts/system/plan-mode-approved.md +8 -0
  308. package/src/prompts/system/system-prompt.md +17 -96
  309. package/src/prompts/tools/bash.md +4 -4
  310. package/src/prompts/tools/browser.md +15 -52
  311. package/src/prompts/tools/find.md +2 -2
  312. package/src/prompts/tools/hashline.md +13 -55
  313. package/src/prompts/tools/read.md +3 -6
  314. package/src/prompts/tools/search-tool-bm25.md +0 -2
  315. package/src/prompts/tools/search.md +2 -4
  316. package/src/prompts/tools/skill-discovery.md +1 -2
  317. package/src/prompts/tools/task.md +1 -1
  318. package/src/runtime-mcp/manager.ts +1 -1
  319. package/src/sdk/broker/broker.ts +206 -78
  320. package/src/sdk/broker/discovery.ts +1 -1
  321. package/src/sdk/broker/ensure.ts +3 -1
  322. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  323. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  324. package/src/sdk/broker/lifecycle.ts +1399 -142
  325. package/src/sdk/broker/process-incarnation.ts +5 -13
  326. package/src/sdk/broker/session-index.ts +145 -56
  327. package/src/sdk/broker/state-version.ts +21 -0
  328. package/src/sdk/broker/transport.ts +1 -1
  329. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  330. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  331. package/src/sdk/bus/chat-effect-journal.ts +10 -2
  332. package/src/sdk/bus/config-commands.ts +5 -4
  333. package/src/sdk/bus/config.ts +98 -0
  334. package/src/sdk/bus/conversation-store.ts +5 -29
  335. package/src/sdk/bus/daemon-paths.ts +2 -0
  336. package/src/sdk/bus/html-format.ts +20 -3
  337. package/src/sdk/bus/index.ts +778 -93
  338. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  339. package/src/sdk/bus/natives-augment.d.ts +26 -1
  340. package/src/sdk/bus/notification-service.ts +168 -20
  341. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  342. package/src/sdk/bus/recent-activity.ts +188 -74
  343. package/src/sdk/bus/rich-document.ts +258 -0
  344. package/src/sdk/bus/rich-render.ts +62 -70
  345. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  346. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  347. package/src/sdk/bus/telegram-daemon.ts +1389 -237
  348. package/src/sdk/bus/threaded-inbound.ts +8 -3
  349. package/src/sdk/bus/threaded-render.ts +57 -1
  350. package/src/sdk/bus/topic-registry.ts +120 -13
  351. package/src/sdk/host/control/dispatch.ts +14 -2
  352. package/src/sdk/host/control/operations.ts +6 -1
  353. package/src/sdk/host/host.ts +31 -1
  354. package/src/sdk/host/query/handlers.ts +20 -22
  355. package/src/sdk/host/query/revision-store.ts +80 -57
  356. package/src/sdk/index.ts +1 -0
  357. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  358. package/src/sdk/session-directory.ts +141 -0
  359. package/src/sdk/session.ts +458 -479
  360. package/src/session/agent-session.ts +1700 -583
  361. package/src/session/agent-storage.ts +18 -0
  362. package/src/session/cache-economics.ts +25 -8
  363. package/src/session/fallback-chain-controller.ts +53 -3
  364. package/src/session/internal/managed-session-scope.ts +2226 -0
  365. package/src/session/internal/managed-session-storage.ts +453 -0
  366. package/src/session/messages.ts +91 -2
  367. package/src/session/session-manager-internal.ts +33 -0
  368. package/src/session/session-manager.ts +1112 -333
  369. package/src/session/session-storage.ts +444 -39
  370. package/src/session/volatile-context-pruning.ts +78 -0
  371. package/src/setup/credential-auto-import.ts +2 -1
  372. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  373. package/src/setup/provider-onboarding.ts +36 -61
  374. package/src/setup/provider-presets.json +5 -0
  375. package/src/skill-state/active-state.ts +3 -13
  376. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  377. package/src/skill-state/workflow-state-contract.ts +6 -41
  378. package/src/slash-commands/builtin-registry.ts +46 -11
  379. package/src/system-prompt.ts +37 -44
  380. package/src/task/agents.ts +19 -1
  381. package/src/task/executor.ts +90 -6
  382. package/src/task/index.ts +36 -21
  383. package/src/thinking.ts +21 -6
  384. package/src/tool-discovery/tool-index.ts +2 -134
  385. package/src/tools/ask.ts +157 -21
  386. package/src/tools/ast-edit.ts +1 -1
  387. package/src/tools/bash.ts +38 -0
  388. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  389. package/src/tools/browser/tab-supervisor.ts +282 -36
  390. package/src/tools/computer.ts +10 -3
  391. package/src/tools/cron.ts +30 -16
  392. package/src/tools/eval.ts +1 -2
  393. package/src/tools/fetch.ts +11 -4
  394. package/src/tools/index.ts +76 -24
  395. package/src/tools/output-meta.ts +13 -5
  396. package/src/tools/read.ts +37 -1
  397. package/src/tools/resource-gc.ts +2 -2
  398. package/src/tools/search-tool-bm25.ts +11 -74
  399. package/src/utils/event-bus.ts +9 -4
  400. package/src/web/search/index.ts +3 -3
  401. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  402. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  403. package/src/discovery/plugin-dir-roots.ts +0 -28
  404. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -0,0 +1,962 @@
1
+ /** File-backed worker lifecycle, heartbeat, stale-claim recovery, and shutdown state. */
2
+ import { createHash } from "node:crypto";
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import type {
6
+ GjcTeamConfig,
7
+ GjcTeamLivenessRecoveryReason,
8
+ GjcTeamLivenessRecoveryResult,
9
+ GjcTeamPhase,
10
+ GjcTeamRecoveredClaim,
11
+ GjcTeamShutdownMode,
12
+ GjcTeamSnapshot,
13
+ GjcTeamWorker,
14
+ GjcTeamWorkerLifecycle,
15
+ GjcTeamWorkerLifecycleState,
16
+ GjcWorkerStatusState,
17
+ WorkerHeartbeatFile,
18
+ WorkerStatusFile,
19
+ } from "./team-runtime";
20
+ import type { GjcTeamTask, GjcTeamTaskClaim, GjcTeamTaskMutationCapability } from "./team-store";
21
+ import {
22
+ getGjcTeamTaskCompletionEvidenceFailure,
23
+ isCanonicalPersistedGjcTeamTask,
24
+ isCanonicalPersistedGjcTeamTaskClaim,
25
+ isGjcTeamTaskCompletionVerified,
26
+ normalizeGjcTeamTask,
27
+ } from "./team-store";
28
+
29
+ export interface GjcTeamWorkerLifecycleContext {
30
+ config: GjcTeamConfig;
31
+ worker: GjcTeamWorker;
32
+ cwd: string;
33
+ env: NodeJS.ProcessEnv;
34
+ }
35
+
36
+ export interface GjcTeamWorkerRuntime {
37
+ findTeamDir(teamName: string, cwd: string, env: NodeJS.ProcessEnv): Promise<string>;
38
+ readConfig(dir: string): Promise<GjcTeamConfig>;
39
+ assertKnownWorker(config: GjcTeamConfig, worker: string): void;
40
+ assertKnownParticipant(config: GjcTeamConfig, worker: string): void;
41
+ findKnownWorker(config: GjcTeamConfig, worker: string): GjcTeamWorker;
42
+ workerDir(dir: string, worker: string): string;
43
+ readJson<T>(filePath: string): Promise<T | null>;
44
+ writeJson(filePath: string, value: unknown): Promise<void>;
45
+ appendEvent(
46
+ dir: string,
47
+ event: { type: string; worker?: string; task_id?: string; message: string; data?: Record<string, unknown> },
48
+ ): Promise<unknown>;
49
+ now(): string;
50
+ nowMs(): number;
51
+ stableHash(value: string): string;
52
+ }
53
+
54
+ export interface GjcTeamWorkerOrchestrationRuntime extends GjcTeamWorkerRuntime {
55
+ readTasks(dir: string): Promise<GjcTeamTask[]>;
56
+ withTaskMutation<T>(dir: string, fn: (capability: GjcTeamTaskMutationCapability) => Promise<T>): Promise<T>;
57
+ appendTelemetry(
58
+ dir: string,
59
+ event: { type: string; message: string; data?: Record<string, unknown> },
60
+ ): Promise<unknown>;
61
+ paneBelongsToTeamTarget(config: GjcTeamConfig, paneId: string): boolean;
62
+ parseDurationEnv(env: NodeJS.ProcessEnv, name: string, fallback: number): number;
63
+ parseHeartbeatStaleMs(env: NodeJS.ProcessEnv): number;
64
+ killWorkerPanes(config: GjcTeamConfig): void;
65
+ removeCleanCreatedWorktrees(workers: GjcTeamWorker[]): Promise<void>;
66
+ readMonitorSnapshot(dir: string): Promise<unknown>;
67
+ hasPendingIntegration(dir: string, config: GjcTeamConfig, monitor: unknown): Promise<boolean>;
68
+ writePhase(dir: string, phase: GjcTeamPhase): Promise<void>;
69
+ readSnapshot(teamName: string, cwd: string, env: NodeJS.ProcessEnv): Promise<GjcTeamSnapshot>;
70
+ }
71
+
72
+ export function workerLifecycleContext(
73
+ config: GjcTeamConfig,
74
+ worker: GjcTeamWorker,
75
+ cwd: string,
76
+ env: NodeJS.ProcessEnv,
77
+ ): GjcTeamWorkerLifecycleContext {
78
+ return { config, worker, cwd, env };
79
+ }
80
+
81
+ const lifecyclePath = (runtime: GjcTeamWorkerRuntime, dir: string, worker: string) =>
82
+ path.join(runtime.workerDir(dir, worker), "lifecycle.json");
83
+ const statusPath = (runtime: GjcTeamWorkerRuntime, dir: string, worker: string) =>
84
+ path.join(runtime.workerDir(dir, worker), "status.json");
85
+ const heartbeatPath = (runtime: GjcTeamWorkerRuntime, dir: string, worker: string) =>
86
+ path.join(runtime.workerDir(dir, worker), "heartbeat.json");
87
+
88
+ async function readRuntimeJson<T>(runtime: GjcTeamWorkerRuntime, filePath: string): Promise<T | null> {
89
+ try {
90
+ return await runtime.readJson<T>(filePath);
91
+ } catch {
92
+ // Mutable worker records are operator-visible state. Treat malformed records as absent
93
+ // so continuation fails closed while liveness reconciliation can still recover claims.
94
+ return null;
95
+ }
96
+ }
97
+
98
+ export type GjcShutdownAuthority =
99
+ | { state: "proven_absent" }
100
+ | { state: "valid_present" }
101
+ | { state: "invalid_or_unreadable" };
102
+
103
+ export function isGjcShutdownRequestRecord(value: unknown): boolean {
104
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
105
+ const record = value as Record<string, unknown>;
106
+ return (
107
+ typeof record.worker === "string" &&
108
+ record.worker.length > 0 &&
109
+ typeof record.requested_by === "string" &&
110
+ record.requested_by.length > 0 &&
111
+ typeof record.request_id === "string" &&
112
+ record.request_id.length > 0 &&
113
+ (record.mode === "graceful" || record.mode === "force" || record.mode === "abort") &&
114
+ typeof record.requested_at === "string" &&
115
+ Number.isFinite(Date.parse(record.requested_at))
116
+ );
117
+ }
118
+
119
+ function isEnoent(error: unknown): boolean {
120
+ return typeof error === "object" && error !== null && (error as { code?: unknown }).code === "ENOENT";
121
+ }
122
+
123
+ /** Distinguishes a missing shutdown record from a present JSON `null` or invalid record. */
124
+ export async function readGjcShutdownAuthority(
125
+ runtime: GjcTeamWorkerRuntime,
126
+ filePath: string,
127
+ ): Promise<GjcShutdownAuthority> {
128
+ try {
129
+ await fs.access(filePath);
130
+ } catch (error) {
131
+ if (isEnoent(error)) return { state: "proven_absent" };
132
+ return { state: "invalid_or_unreadable" };
133
+ }
134
+ try {
135
+ const value = await runtime.readJson<unknown>(filePath);
136
+ return isGjcShutdownRequestRecord(value) ? { state: "valid_present" } : { state: "invalid_or_unreadable" };
137
+ } catch {
138
+ return { state: "invalid_or_unreadable" };
139
+ }
140
+ }
141
+
142
+ export function parseGjcWorkerStatusState(value: unknown): GjcWorkerStatusState {
143
+ return typeof value === "string" &&
144
+ ["idle", "working", "blocked", "done", "failed", "draining", "unknown"].includes(value)
145
+ ? (value as GjcWorkerStatusState)
146
+ : "unknown";
147
+ }
148
+
149
+ export function lifecycleStateForWorkerStatus(status: GjcWorkerStatusState): GjcTeamWorkerLifecycleState {
150
+ switch (status) {
151
+ case "working":
152
+ return "working";
153
+ case "draining":
154
+ return "draining";
155
+ case "failed":
156
+ return "failed";
157
+ case "unknown":
158
+ return "unknown";
159
+ case "idle":
160
+ case "blocked":
161
+ case "done":
162
+ return "ready";
163
+ }
164
+ }
165
+
166
+ export async function readWorkerStatusFile(
167
+ runtime: GjcTeamWorkerRuntime,
168
+ dir: string,
169
+ worker: string,
170
+ ): Promise<WorkerStatusFile> {
171
+ return (
172
+ (await readRuntimeJson<WorkerStatusFile>(runtime, statusPath(runtime, dir, worker))) ?? {
173
+ state: "unknown",
174
+ updated_at: runtime.now(),
175
+ }
176
+ );
177
+ }
178
+
179
+ export async function readWorkerLifecycleRecord(
180
+ runtime: GjcTeamWorkerRuntime,
181
+ dir: string,
182
+ worker: GjcTeamWorker,
183
+ ): Promise<GjcTeamWorkerLifecycle> {
184
+ const workerStatus = await readWorkerStatusFile(runtime, dir, worker.id);
185
+ const heartbeat = await readRuntimeJson<WorkerHeartbeatFile>(runtime, heartbeatPath(runtime, dir, worker.id));
186
+ const raw = await readRuntimeJson<Partial<GjcTeamWorkerLifecycle>>(runtime, lifecyclePath(runtime, dir, worker.id));
187
+ const shutdownAck = await readRuntimeJson<Record<string, unknown>>(
188
+ runtime,
189
+ path.join(runtime.workerDir(dir, worker.id), "shutdown-ack.json"),
190
+ );
191
+
192
+ const lifecycle: GjcTeamWorkerLifecycle = {
193
+ worker: worker.id,
194
+ lifecycle_state: parseLifecycleState(raw?.lifecycle_state),
195
+ worker_status_state: parseGjcWorkerStatusState(workerStatus.state),
196
+ pane_id: worker.pane_id ?? raw?.pane_id,
197
+ updated_at: raw?.updated_at ?? workerStatus.updated_at ?? runtime.now(),
198
+ };
199
+ if (typeof raw?.pid === "number") lifecycle.pid = raw.pid;
200
+ else if (typeof heartbeat?.pid === "number") lifecycle.pid = heartbeat.pid;
201
+ if (raw?.started_at) lifecycle.started_at = raw.started_at;
202
+ if (raw?.stopped_at) lifecycle.stopped_at = raw.stopped_at;
203
+ if (raw?.stop_reason) lifecycle.stop_reason = raw.stop_reason;
204
+ if (raw?.shutdown_request_id) lifecycle.shutdown_request_id = raw.shutdown_request_id;
205
+ if (raw?.shutdown_requested_at) lifecycle.shutdown_requested_at = raw.shutdown_requested_at;
206
+ if (raw?.shutdown_mode === "graceful" || raw?.shutdown_mode === "force" || raw?.shutdown_mode === "abort")
207
+ lifecycle.shutdown_mode = raw.shutdown_mode;
208
+ if (typeof shutdownAck?.acknowledged_at === "string")
209
+ lifecycle.shutdown_acknowledged_at = shutdownAck.acknowledged_at;
210
+ if (typeof shutdownAck?.status === "string") lifecycle.shutdown_ack_status = shutdownAck.status;
211
+ return lifecycle;
212
+ }
213
+
214
+ const parseLifecycleState = (value: unknown): GjcTeamWorkerLifecycleState =>
215
+ typeof value === "string" &&
216
+ ["starting", "ready", "working", "draining", "stopped", "failed", "unknown"].includes(value)
217
+ ? (value as GjcTeamWorkerLifecycleState)
218
+ : "unknown";
219
+
220
+ export async function readWorkerLifecycleById(
221
+ runtime: GjcTeamWorkerRuntime,
222
+ dir: string,
223
+ config: GjcTeamConfig,
224
+ ): Promise<Record<string, GjcTeamWorkerLifecycle>> {
225
+ const records = await Promise.all(config.workers.map(worker => readWorkerLifecycleRecord(runtime, dir, worker)));
226
+ return Object.fromEntries(records.map(record => [record.worker, record]));
227
+ }
228
+
229
+ export async function writeWorkerLifecycleRecord(
230
+ runtime: GjcTeamWorkerRuntime,
231
+ dir: string,
232
+ worker: GjcTeamWorker,
233
+ lifecycleState: GjcTeamWorkerLifecycleState,
234
+ updates: Partial<GjcTeamWorkerLifecycle> = {},
235
+ ): Promise<GjcTeamWorkerLifecycle> {
236
+ const current = await readWorkerLifecycleRecord(runtime, dir, worker);
237
+ const next: GjcTeamWorkerLifecycle = {
238
+ ...current,
239
+ ...updates,
240
+ worker: worker.id,
241
+ lifecycle_state: lifecycleState,
242
+ worker_status_state: current.worker_status_state,
243
+ pane_id: updates.pane_id ?? worker.pane_id ?? current.pane_id,
244
+ updated_at: runtime.now(),
245
+ };
246
+ await runtime.writeJson(lifecyclePath(runtime, dir, worker.id), next);
247
+ return next;
248
+ }
249
+
250
+ export async function writeWorkerLifecycleForConfig(
251
+ runtime: GjcTeamWorkerRuntime,
252
+ dir: string,
253
+ config: GjcTeamConfig,
254
+ lifecycleState: GjcTeamWorkerLifecycleState,
255
+ updatesFor: (worker: GjcTeamWorker) => Partial<GjcTeamWorkerLifecycle> = () => ({}),
256
+ ): Promise<Record<string, GjcTeamWorkerLifecycle>> {
257
+ const records = await Promise.all(
258
+ config.workers.map(worker =>
259
+ writeWorkerLifecycleRecord(runtime, dir, worker, lifecycleState, updatesFor(worker)),
260
+ ),
261
+ );
262
+ return Object.fromEntries(records.map(record => [record.worker, record]));
263
+ }
264
+
265
+ export async function readGjcWorkerStatus(
266
+ runtime: GjcTeamWorkerRuntime,
267
+ teamName: string,
268
+ worker: string,
269
+ cwd = process.cwd(),
270
+ env: NodeJS.ProcessEnv = process.env,
271
+ ): Promise<WorkerStatusFile> {
272
+ const dir = await runtime.findTeamDir(teamName, cwd, env);
273
+ runtime.assertKnownWorker(await runtime.readConfig(dir), worker);
274
+ return readWorkerStatusFile(runtime, dir, worker);
275
+ }
276
+
277
+ export async function updateGjcWorkerStatus(
278
+ runtime: GjcTeamWorkerRuntime,
279
+ teamName: string,
280
+ worker: string,
281
+ status: GjcWorkerStatusState,
282
+ cwd = process.cwd(),
283
+ env: NodeJS.ProcessEnv = process.env,
284
+ currentTaskId?: string,
285
+ reason?: string,
286
+ ): Promise<WorkerStatusFile> {
287
+ const dir = await runtime.findTeamDir(teamName, cwd, env);
288
+ const config = await runtime.readConfig(dir);
289
+ const teamWorker = runtime.findKnownWorker(config, worker);
290
+ const value: WorkerStatusFile = {
291
+ state: status,
292
+ ...(currentTaskId ? { current_task_id: currentTaskId } : {}),
293
+ ...(reason?.trim() ? { reason: reason.trim() } : {}),
294
+ updated_at: runtime.now(),
295
+ };
296
+ await runtime.writeJson(statusPath(runtime, dir, worker), value);
297
+ const current = await readWorkerLifecycleRecord(runtime, dir, teamWorker);
298
+ await writeWorkerLifecycleRecord(
299
+ runtime,
300
+ dir,
301
+ teamWorker,
302
+ current.lifecycle_state === "stopped" ? "stopped" : lifecycleStateForWorkerStatus(status),
303
+ );
304
+ await runtime.appendEvent(dir, {
305
+ type: "worker_status_updated",
306
+ worker,
307
+ message: `Worker ${worker} reported ${status}`,
308
+ data: { status, current_task_id: currentTaskId },
309
+ });
310
+ return value;
311
+ }
312
+
313
+ export async function readGjcWorkerHeartbeat(
314
+ runtime: GjcTeamWorkerRuntime,
315
+ teamName: string,
316
+ worker: string,
317
+ cwd = process.cwd(),
318
+ env: NodeJS.ProcessEnv = process.env,
319
+ ): Promise<WorkerHeartbeatFile | null> {
320
+ const dir = await runtime.findTeamDir(teamName, cwd, env);
321
+ runtime.assertKnownWorker(await runtime.readConfig(dir), worker);
322
+ return readRuntimeJson<WorkerHeartbeatFile>(runtime, heartbeatPath(runtime, dir, worker));
323
+ }
324
+
325
+ export async function updateGjcWorkerHeartbeat(
326
+ runtime: GjcTeamWorkerRuntime,
327
+ teamName: string,
328
+ worker: string,
329
+ heartbeat: WorkerHeartbeatFile,
330
+ cwd = process.cwd(),
331
+ env: NodeJS.ProcessEnv = process.env,
332
+ ): Promise<WorkerHeartbeatFile> {
333
+ const dir = await runtime.findTeamDir(teamName, cwd, env);
334
+ runtime.assertKnownWorker(await runtime.readConfig(dir), worker);
335
+ const value = { ...heartbeat, last_turn_at: heartbeat.last_turn_at || runtime.now() };
336
+ await runtime.writeJson(heartbeatPath(runtime, dir, worker), value);
337
+ return value;
338
+ }
339
+
340
+ export async function writeGjcWorkerStartupAck(
341
+ runtime: GjcTeamWorkerRuntime,
342
+ teamName: string,
343
+ worker: string,
344
+ cwd: string,
345
+ env: NodeJS.ProcessEnv,
346
+ input: Record<string, unknown>,
347
+ ): Promise<Record<string, unknown>> {
348
+ const dir = await runtime.findTeamDir(teamName, cwd, env);
349
+ const teamWorker = runtime.findKnownWorker(await runtime.readConfig(dir), worker);
350
+ const ack = {
351
+ worker,
352
+ pid: typeof input.pid === "number" ? input.pid : undefined,
353
+ session: typeof input.session === "string" ? input.session : undefined,
354
+ protocol_version: String(input.protocol_version ?? "1"),
355
+ ack_at: runtime.now(),
356
+ };
357
+ await runtime.writeJson(path.join(runtime.workerDir(dir, worker), "startup-ack.json"), ack);
358
+ await writeWorkerLifecycleRecord(runtime, dir, teamWorker, "ready", {
359
+ pane_id: teamWorker.pane_id,
360
+ pid: typeof input.pid === "number" ? input.pid : undefined,
361
+ started_at: ack.ack_at,
362
+ });
363
+ await runtime.appendEvent(dir, {
364
+ type: "worker_startup_ack",
365
+ worker,
366
+ message: `Worker ${worker} acknowledged startup`,
367
+ });
368
+ return ack;
369
+ }
370
+
371
+ export async function writeGjcShutdownRequest(
372
+ runtime: GjcTeamWorkerRuntime,
373
+ teamName: string,
374
+ worker: string,
375
+ requestedBy: string,
376
+ cwd = process.cwd(),
377
+ env: NodeJS.ProcessEnv = process.env,
378
+ requestId: string,
379
+ mode: GjcTeamShutdownMode = "graceful",
380
+ requestedAt = runtime.now(),
381
+ ): Promise<Record<string, unknown>> {
382
+ const dir = await runtime.findTeamDir(teamName, cwd, env);
383
+ const config = await runtime.readConfig(dir);
384
+ const teamWorker = runtime.findKnownWorker(config, worker);
385
+ runtime.assertKnownParticipant(config, requestedBy);
386
+ const value = { worker, requested_by: requestedBy, request_id: requestId, mode, requested_at: requestedAt };
387
+ await runtime.writeJson(path.join(runtime.workerDir(dir, worker), "shutdown-request.json"), value);
388
+ await writeWorkerLifecycleRecord(runtime, dir, teamWorker, "draining", {
389
+ shutdown_request_id: requestId,
390
+ shutdown_requested_at: requestedAt,
391
+ shutdown_mode: mode,
392
+ });
393
+ await runtime.appendEvent(dir, {
394
+ type: "worker_shutdown_requested",
395
+ worker,
396
+ message: `Worker ${worker} shutdown requested`,
397
+ data: { requested_by: requestedBy, request_id: requestId, mode },
398
+ });
399
+ return value;
400
+ }
401
+
402
+ export async function readGjcShutdownAck(
403
+ runtime: GjcTeamWorkerRuntime,
404
+ teamName: string,
405
+ worker: string,
406
+ cwd = process.cwd(),
407
+ env: NodeJS.ProcessEnv = process.env,
408
+ ): Promise<Record<string, unknown> | null> {
409
+ const dir = await runtime.findTeamDir(teamName, cwd, env);
410
+ runtime.assertKnownWorker(await runtime.readConfig(dir), worker);
411
+ return runtime.readJson<Record<string, unknown>>(path.join(runtime.workerDir(dir, worker), "shutdown-ack.json"));
412
+ }
413
+
414
+ function addRecoveryReason(reasons: GjcTeamLivenessRecoveryReason[], reason: GjcTeamLivenessRecoveryReason): void {
415
+ if (!reasons.includes(reason)) reasons.push(reason);
416
+ }
417
+
418
+ function claimFromUnknown(value: unknown): GjcTeamTaskClaim | undefined {
419
+ if (typeof value !== "object" || value == null) return undefined;
420
+ const record = value as Record<string, unknown>;
421
+ const owner = typeof record.owner === "string" ? record.owner : "";
422
+ const token = typeof record.token === "string" ? record.token : "";
423
+ const leasedUntil = typeof record.leased_until === "string" ? record.leased_until : "";
424
+ return owner && token && leasedUntil ? { owner, token, leased_until: leasedUntil } : undefined;
425
+ }
426
+
427
+ async function hasCurrentContinuationClaimAuthority(
428
+ runtime: GjcTeamWorkerOrchestrationRuntime,
429
+ dir: string,
430
+ worker: string,
431
+ task: GjcTeamTask,
432
+ claim: GjcTeamTaskClaim,
433
+ ): Promise<boolean> {
434
+ let canonicalClaim: unknown;
435
+ let canonicalTask: unknown;
436
+ try {
437
+ canonicalClaim = await runtime.readJson<unknown>(path.join(dir, "claims", `${task.id}.json`));
438
+ canonicalTask = await runtime.readJson<unknown>(path.join(dir, "tasks", `${task.id}.json`));
439
+ } catch {
440
+ return false;
441
+ }
442
+ if (!isCanonicalPersistedGjcTeamTaskClaim(canonicalClaim)) return false;
443
+ if (
444
+ canonicalClaim.owner !== claim.owner ||
445
+ canonicalClaim.token !== claim.token ||
446
+ canonicalClaim.leased_until !== claim.leased_until
447
+ )
448
+ return false;
449
+ if (!isCanonicalPersistedGjcTeamTask(canonicalTask, task.id)) return false;
450
+ return (
451
+ canonicalTask.version === task.version &&
452
+ canonicalTask.status === "in_progress" &&
453
+ canonicalTask.owner === worker &&
454
+ canonicalTask.assignee === worker &&
455
+ canonicalTask.claim?.owner === canonicalClaim.owner &&
456
+ canonicalTask.claim?.token === canonicalClaim.token &&
457
+ canonicalTask.claim?.leased_until === canonicalClaim.leased_until
458
+ );
459
+ }
460
+
461
+ function claimIsExpired(value: string | undefined, nowMs: number): boolean {
462
+ if (!value) return false;
463
+ const timestamp = Date.parse(value);
464
+ return Number.isFinite(timestamp) && timestamp <= nowMs;
465
+ }
466
+
467
+ async function livenessReasons(
468
+ runtime: GjcTeamWorkerOrchestrationRuntime,
469
+ dir: string,
470
+ config: GjcTeamConfig,
471
+ worker: GjcTeamWorker,
472
+ env: NodeJS.ProcessEnv,
473
+ ): Promise<GjcTeamLivenessRecoveryReason[]> {
474
+ const reasons: GjcTeamLivenessRecoveryReason[] = [];
475
+ const lifecycle = await readWorkerLifecycleRecord(runtime, dir, worker);
476
+ const heartbeat = await readRuntimeJson<WorkerHeartbeatFile>(runtime, heartbeatPath(runtime, dir, worker.id));
477
+
478
+ if (lifecycle.lifecycle_state === "failed") addRecoveryReason(reasons, "worker_lifecycle_failed");
479
+ if (lifecycle.lifecycle_state === "stopped") addRecoveryReason(reasons, "worker_lifecycle_stopped");
480
+ const staleMs = runtime.parseHeartbeatStaleMs(env);
481
+ const heartbeatAt = Date.parse(heartbeat?.last_turn_at ?? worker.last_heartbeat);
482
+ if (staleMs > 0 && Number.isFinite(heartbeatAt) && runtime.nowMs() - heartbeatAt >= staleMs)
483
+ addRecoveryReason(reasons, "stale_heartbeat");
484
+ if (
485
+ !config.dry_run &&
486
+ (!worker.pane_id?.startsWith("%") || !runtime.paneBelongsToTeamTarget(config, worker.pane_id))
487
+ )
488
+ addRecoveryReason(reasons, "missing_pane");
489
+ return reasons;
490
+ }
491
+
492
+ function isValidRecordedWorkerStatus(status: WorkerStatusFile): boolean {
493
+ return (
494
+ typeof status === "object" &&
495
+ status !== null &&
496
+ ["idle", "working", "blocked", "done", "failed", "draining", "unknown"].includes(status.state) &&
497
+ typeof status.updated_at === "string" &&
498
+ Number.isFinite(Date.parse(status.updated_at))
499
+ );
500
+ }
501
+
502
+ function isContinuationLifecycleEligible(lifecycle: GjcTeamWorkerLifecycle, status: WorkerStatusFile): boolean {
503
+ return (
504
+ isValidRecordedWorkerStatus(status) &&
505
+ (lifecycle.lifecycle_state === "ready" || lifecycle.lifecycle_state === "working") &&
506
+ status.state !== "draining" &&
507
+ status.state !== "failed" &&
508
+ status.state !== "unknown"
509
+ );
510
+ }
511
+
512
+ export const GJC_TEAM_CONTINUATION_PROMPT =
513
+ "Continue only your current claimed GJC team task. Re-read current GJC team state; do not replay prior output; report status.";
514
+
515
+ function canonicalJson(value: unknown): string {
516
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
517
+ if (typeof value === "object" && value !== null) {
518
+ const record = value as Record<string, unknown>;
519
+ return `{${Object.keys(record)
520
+ .sort()
521
+ .map(key => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
522
+ .join(",")}}`;
523
+ }
524
+ return JSON.stringify(value) ?? "null";
525
+ }
526
+
527
+ export function gjcContinuationReservationDigest(reservation: Record<string, unknown>): string {
528
+ return createHash("sha256").update(canonicalJson(reservation)).digest("hex");
529
+ }
530
+
531
+ const reservationKeys = new Set([
532
+ "schema_version",
533
+ "incident_hash",
534
+ "team_name",
535
+ "worker",
536
+ "task_id",
537
+ "owner",
538
+ "claim_token",
539
+ "task_version",
540
+ "leased_until",
541
+ "heartbeat_at",
542
+ "pane_id",
543
+ "tmux_target",
544
+ "attempt",
545
+ "reserved_at",
546
+ "hold_until",
547
+ "prompt_version",
548
+ "prompt_sha256",
549
+ "dispatch_protocol",
550
+ ]);
551
+
552
+ export function isValidGjcContinuationReservation(
553
+ value: unknown,
554
+ incident: string,
555
+ attempt: number,
556
+ config: GjcTeamConfig,
557
+ worker: string,
558
+ task: GjcTeamTask,
559
+ claim: GjcTeamTaskClaim,
560
+ heartbeatAt: string,
561
+ paneId: string,
562
+ ): value is Record<string, unknown> {
563
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
564
+ const record = value as Record<string, unknown>;
565
+ if (
566
+ Object.keys(record).length !== reservationKeys.size ||
567
+ Object.keys(record).some(key => !reservationKeys.has(key))
568
+ )
569
+ return false;
570
+ const reservedAt = typeof record.reserved_at === "string" ? Date.parse(record.reserved_at) : Number.NaN;
571
+ const holdUntil = typeof record.hold_until === "string" ? Date.parse(record.hold_until) : Number.NaN;
572
+ const leaseUntil = Date.parse(claim.leased_until);
573
+ return (
574
+ record.schema_version === 1 &&
575
+ record.incident_hash === incident &&
576
+ record.team_name === config.team_name &&
577
+ record.worker === worker &&
578
+ record.task_id === task.id &&
579
+ record.owner === claim.owner &&
580
+ record.claim_token === claim.token &&
581
+ record.task_version === task.version &&
582
+ record.leased_until === claim.leased_until &&
583
+ record.heartbeat_at === heartbeatAt &&
584
+ record.pane_id === paneId &&
585
+ record.tmux_target === config.tmux_target &&
586
+ record.attempt === attempt &&
587
+ record.prompt_version === 1 &&
588
+ record.dispatch_protocol === "tmux_command_sequence_v1" &&
589
+ record.prompt_sha256 === createHash("sha256").update(GJC_TEAM_CONTINUATION_PROMPT).digest("hex") &&
590
+ Number.isFinite(reservedAt) &&
591
+ Number.isFinite(holdUntil) &&
592
+ Number.isFinite(leaseUntil) &&
593
+ holdUntil - reservedAt === (attempt === 1 ? 30_000 : 120_000) &&
594
+ leaseUntil >= holdUntil
595
+ );
596
+ }
597
+
598
+ export function isValidGjcContinuationOutcome(
599
+ value: unknown,
600
+ reservation: Record<string, unknown>,
601
+ incident: string,
602
+ attempt: number,
603
+ ): value is Record<string, unknown> {
604
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
605
+ const outcome = value as Record<string, unknown>;
606
+ if (
607
+ outcome.schema_version !== 1 ||
608
+ outcome.incident_hash !== incident ||
609
+ outcome.attempt !== attempt ||
610
+ outcome.reservation_sha256 !== gjcContinuationReservationDigest(reservation) ||
611
+ typeof outcome.recorded_at !== "string" ||
612
+ !Number.isFinite(Date.parse(outcome.recorded_at)) ||
613
+ typeof outcome.reason !== "string" ||
614
+ outcome.reason.length === 0 ||
615
+ outcome.reason.length > 160
616
+ )
617
+ return false;
618
+ const exit = outcome.tmux_exit_code;
619
+ const error = outcome.tmux_error;
620
+ const validError =
621
+ typeof error === "object" &&
622
+ error !== null &&
623
+ Object.keys(error).length >= 2 &&
624
+ Object.keys(error).length <= 3 &&
625
+ Object.keys(error).every(key => key === "name" || key === "code" || key === "message") &&
626
+ typeof (error as Record<string, unknown>).name === "string" &&
627
+ typeof (error as Record<string, unknown>).message === "string" &&
628
+ String((error as Record<string, unknown>).name).length > 0 &&
629
+ String((error as Record<string, unknown>).message).length > 0 &&
630
+ String((error as Record<string, unknown>).name).length <= 120 &&
631
+ String((error as Record<string, unknown>).message).length <= 400 &&
632
+ ((error as Record<string, unknown>).code === undefined ||
633
+ (typeof (error as Record<string, unknown>).code === "string" &&
634
+ String((error as Record<string, unknown>).code).length > 0 &&
635
+ String((error as Record<string, unknown>).code).length <= 120));
636
+ const baseKeys = [
637
+ "schema_version",
638
+ "incident_hash",
639
+ "attempt",
640
+ "reservation_sha256",
641
+ "recorded_at",
642
+ "result",
643
+ "reason",
644
+ ];
645
+ const hasExactKeys = (keys: readonly string[]) =>
646
+ Object.keys(outcome).length === keys.length && Object.keys(outcome).every(key => keys.includes(key));
647
+ if (outcome.result === "sent") {
648
+ const dispatchedAt = Date.parse(String(outcome.dispatched_at));
649
+ const holdUntil = Date.parse(String(outcome.hold_until));
650
+ const leaseUntil = Date.parse(String(reservation.leased_until));
651
+ return (
652
+ hasExactKeys([...baseKeys, "tmux_exit_code", "dispatched_at", "hold_until"]) &&
653
+ outcome.reason === "tmux_sent" &&
654
+ exit === 0 &&
655
+ Number.isFinite(dispatchedAt) &&
656
+ Number.isFinite(holdUntil) &&
657
+ Number.isFinite(leaseUntil) &&
658
+ holdUntil - dispatchedAt === (attempt === 1 ? 30_000 : 120_000) &&
659
+ leaseUntil >= holdUntil
660
+ );
661
+ }
662
+ if (outcome.result === "skipped")
663
+ return (
664
+ hasExactKeys(baseKeys) &&
665
+ [
666
+ "invalid_or_absent_running_phase",
667
+ "invalid_pane_authority",
668
+ "shutdown_requested",
669
+ "invalid_shutdown_authority",
670
+ "heartbeat_changed",
671
+ "invalid_worker_lifecycle_or_status",
672
+ "claim_changed",
673
+ "invalid_or_expired_lease",
674
+ "invalid_authority_inventory",
675
+ "lease_does_not_cover_hold",
676
+ "unsupported_send_keys_transport",
677
+ ].includes(outcome.reason)
678
+ );
679
+ if (outcome.result !== "unknown") return false;
680
+ return (
681
+ (outcome.reason === "tmux_missing_exit_code" &&
682
+ exit === undefined &&
683
+ error === undefined &&
684
+ hasExactKeys(baseKeys)) ||
685
+ (outcome.reason === "tmux_nonzero_exit" &&
686
+ typeof exit === "number" &&
687
+ Number.isInteger(exit) &&
688
+ exit !== 0 &&
689
+ error === undefined &&
690
+ hasExactKeys([...baseKeys, "tmux_exit_code"])) ||
691
+ (outcome.reason === "tmux_dispatch_threw" &&
692
+ exit === undefined &&
693
+ validError &&
694
+ hasExactKeys([...baseKeys, "tmux_error"]))
695
+ );
696
+ }
697
+
698
+ async function hasActiveContinuationHold(
699
+ runtime: GjcTeamWorkerOrchestrationRuntime,
700
+ dir: string,
701
+ worker: string,
702
+ config: GjcTeamConfig,
703
+ task: GjcTeamTask,
704
+ claim: GjcTeamTaskClaim,
705
+ ): Promise<boolean> {
706
+ if (!(await hasCurrentContinuationClaimAuthority(runtime, dir, worker, task, claim))) return false;
707
+ const heartbeat = await readRuntimeJson<WorkerHeartbeatFile>(runtime, heartbeatPath(runtime, dir, worker));
708
+
709
+ const teamWorker = config.workers.find(candidate => candidate.id === worker);
710
+ const paneId = teamWorker?.pane_id;
711
+ const lifecycle = teamWorker ? await readWorkerLifecycleRecord(runtime, dir, teamWorker) : undefined;
712
+ const status = await readWorkerStatusFile(runtime, dir, worker);
713
+ const phase = await readRuntimeJson<Record<string, unknown>>(runtime, path.join(dir, "phase.json"));
714
+ const shutdownAuthority = await readGjcShutdownAuthority(
715
+ runtime,
716
+ path.join(runtime.workerDir(dir, worker), "shutdown-request.json"),
717
+ );
718
+
719
+ if (
720
+ !heartbeat?.last_turn_at ||
721
+ !paneId ||
722
+ !lifecycle ||
723
+ !isContinuationLifecycleEligible(lifecycle, status) ||
724
+ phase?.current_phase !== "running" ||
725
+ shutdownAuthority.state !== "proven_absent"
726
+ )
727
+ return false;
728
+ const incident = runtime.stableHash(
729
+ [
730
+ config.team_name,
731
+ worker,
732
+ task.id,
733
+ claim.owner,
734
+ claim.token,
735
+ task.version,
736
+ heartbeat.last_turn_at,
737
+ paneId,
738
+ config.tmux_target,
739
+ ].join(":"),
740
+ );
741
+ const continuations = path.join(runtime.workerDir(dir, worker), "continuations", incident);
742
+ for (const attempt of [1, 2]) {
743
+ const reservation = await readRuntimeJson<Record<string, unknown>>(
744
+ runtime,
745
+ path.join(continuations, `attempt-0${attempt}.reservation.json`),
746
+ );
747
+
748
+ if (
749
+ !isValidGjcContinuationReservation(
750
+ reservation,
751
+ incident,
752
+ attempt,
753
+ config,
754
+ worker,
755
+ task,
756
+ claim,
757
+ heartbeat.last_turn_at,
758
+ paneId,
759
+ )
760
+ )
761
+ return false;
762
+ const reservationHoldUntil = Date.parse(String(reservation.hold_until));
763
+ if (!Number.isFinite(reservationHoldUntil)) return false;
764
+ const outcomePath = path.join(continuations, `attempt-0${attempt}.outcome.json`);
765
+ let outcome: Record<string, unknown> | null;
766
+ try {
767
+ outcome = await runtime.readJson<Record<string, unknown>>(outcomePath);
768
+ } catch (error) {
769
+ if (!isEnoent(error)) return false;
770
+ outcome = null;
771
+ }
772
+ const nowMs = runtime.nowMs();
773
+ // Only proven ENOENT is crash-ambiguous. Present JSON null, malformed, or unreadable
774
+ // outcomes are invalid authority and must not retain a recovery hold.
775
+ if (outcome === null) {
776
+ try {
777
+ await fs.access(outcomePath);
778
+ } catch (error) {
779
+ if (!isEnoent(error)) return false;
780
+ if (reservationHoldUntil <= nowMs) continue;
781
+ if (claimIsExpired(claim.leased_until, nowMs)) return false;
782
+ return true;
783
+ }
784
+ return false;
785
+ }
786
+ if (!isValidGjcContinuationOutcome(outcome, reservation, incident, attempt)) return false;
787
+ if (outcome.result === "skipped") continue;
788
+ const holdUntil = outcome.result === "sent" ? Date.parse(String(outcome.hold_until)) : reservationHoldUntil;
789
+ if (!Number.isFinite(holdUntil) || holdUntil <= nowMs) continue;
790
+ if (claimIsExpired(claim.leased_until, nowMs)) return false;
791
+ return true;
792
+ }
793
+ return false;
794
+ }
795
+
796
+ export async function reconcileGjcTeamStaleClaims(
797
+ runtime: GjcTeamWorkerOrchestrationRuntime,
798
+ teamName: string,
799
+ dir: string,
800
+ config: GjcTeamConfig,
801
+ env: NodeJS.ProcessEnv,
802
+ ): Promise<GjcTeamLivenessRecoveryResult> {
803
+ return runtime.withTaskMutation(dir, capability =>
804
+ reconcileGjcTeamStaleClaimsUnlocked(runtime, teamName, dir, config, env, capability),
805
+ );
806
+ }
807
+
808
+ export async function reconcileGjcTeamStaleClaimsUnlocked(
809
+ runtime: GjcTeamWorkerOrchestrationRuntime,
810
+ teamName: string,
811
+ dir: string,
812
+ config: GjcTeamConfig,
813
+ env: NodeJS.ProcessEnv,
814
+ capability: GjcTeamTaskMutationCapability,
815
+ ): Promise<GjcTeamLivenessRecoveryResult> {
816
+ const staleWorkers: Record<string, GjcTeamLivenessRecoveryReason[]> = {};
817
+ for (const worker of config.workers) {
818
+ const reasons = await livenessReasons(runtime, dir, config, worker, env);
819
+ if (reasons.length === 0) continue;
820
+ staleWorkers[worker.id] = reasons;
821
+ if (reasons.includes("missing_pane") && !reasons.includes("worker_lifecycle_stopped")) {
822
+ try {
823
+ await fs.access(runtime.workerDir(dir, worker.id));
824
+ await writeWorkerLifecycleRecord(runtime, dir, worker, "failed", { stop_reason: "pane_missing" });
825
+ } catch (error) {
826
+ if (!isEnoent(error)) throw error;
827
+ }
828
+ }
829
+ }
830
+ const recoveredClaims: GjcTeamRecoveredClaim[] = [];
831
+ for (const task of await runtime.readTasks(dir)) {
832
+ if (task.status === "completed" || task.status === "failed") continue;
833
+ const claimPath = path.join(dir, "claims", `${task.id}.json`);
834
+ const claim = task.claim ?? claimFromUnknown(await readRuntimeJson<unknown>(runtime, claimPath));
835
+ if (!claim) continue;
836
+ const reasons = [...(staleWorkers[claim.owner] ?? [])];
837
+ if (claimIsExpired(claim.leased_until, runtime.nowMs())) addRecoveryReason(reasons, "claim_expired");
838
+ if (reasons.length === 0) continue;
839
+ if (
840
+ reasons.length === 1 &&
841
+ reasons[0] === "stale_heartbeat" &&
842
+ (await hasActiveContinuationHold(runtime, dir, claim.owner, config, task, claim))
843
+ )
844
+ continue;
845
+ await fs.rm(claimPath, { force: true });
846
+ recoveredClaims.push({ task_id: task.id, worker: claim.owner, reasons });
847
+ if (task.status === "in_progress")
848
+ await capability.writeRecovered(
849
+ normalizeGjcTeamTask({
850
+ ...task,
851
+ status: "pending",
852
+ assignee: undefined,
853
+ claim: undefined,
854
+ version: task.version + 1,
855
+ updated_at: runtime.now(),
856
+ }),
857
+ );
858
+ await runtime.appendEvent(dir, {
859
+ type: "task_claim_recovered",
860
+ task_id: task.id,
861
+ worker: claim.owner,
862
+ message:
863
+ task.status === "in_progress" ? "Recovered task from stale worker claim" : "Removed stale task claim file",
864
+ data: { reasons },
865
+ });
866
+ }
867
+ if (recoveredClaims.length > 0)
868
+ await runtime.appendTelemetry(dir, {
869
+ type: "team_liveness_recovery",
870
+ message: `Recovered ${recoveredClaims.length} stale team task claim(s)`,
871
+ data: { team_name: teamName, recovered_claims: recoveredClaims },
872
+ });
873
+ return { recovered_claims: recoveredClaims, stale_workers: staleWorkers };
874
+ }
875
+
876
+ export async function shutdownGjcTeamWorkers(
877
+ runtime: GjcTeamWorkerOrchestrationRuntime,
878
+ teamName: string,
879
+ cwd: string,
880
+ env: NodeJS.ProcessEnv,
881
+ ): Promise<GjcTeamSnapshot> {
882
+ const dir = await runtime.findTeamDir(teamName, cwd, env);
883
+ const config = await runtime.readConfig(dir);
884
+ const tasks = await runtime.readTasks(dir);
885
+ const evidenceFailures = tasks.flatMap(task => {
886
+ const reason = task.status === "completed" ? getGjcTeamTaskCompletionEvidenceFailure(task) : null;
887
+ return reason ? [{ task_id: task.id, reason }] : [];
888
+ });
889
+ const requestId = `shutdown-${runtime.stableHash([config.team_name, runtime.now(), crypto.randomUUID()].join(":"))}`;
890
+ const requestedAt = runtime.now();
891
+ await Promise.all(
892
+ config.workers.map(worker =>
893
+ writeGjcShutdownRequest(
894
+ runtime,
895
+ teamName,
896
+ worker.id,
897
+ "leader-fixed",
898
+ cwd,
899
+ env,
900
+ requestId,
901
+ "graceful",
902
+ requestedAt,
903
+ ),
904
+ ),
905
+ );
906
+ const completionVerified = tasks.length === 0 || tasks.every(isGjcTeamTaskCompletionVerified);
907
+ const pendingIntegration = completionVerified
908
+ ? await runtime.hasPendingIntegration(dir, config, await runtime.readMonitorSnapshot(dir))
909
+ : false;
910
+ runtime.killWorkerPanes(config);
911
+ await runtime.removeCleanCreatedWorktrees(config.workers);
912
+ const stopped: GjcTeamConfig = {
913
+ ...config,
914
+ workers: config.workers.map(worker => ({ ...worker, status: "stopped", last_heartbeat: runtime.now() })),
915
+ updated_at: runtime.now(),
916
+ };
917
+ await runtime.writeJson(path.join(dir, "config.json"), stopped);
918
+ await writeWorkerLifecycleForConfig(runtime, dir, stopped, "stopped", worker => ({
919
+ pane_id: worker.pane_id,
920
+ stopped_at: stopped.updated_at,
921
+ stop_reason: "graceful_shutdown",
922
+ shutdown_request_id: requestId,
923
+ shutdown_requested_at: requestedAt,
924
+ shutdown_mode: "graceful",
925
+ }));
926
+ const lifecycle = await readWorkerLifecycleById(runtime, dir, stopped);
927
+ const graceful = stopped.workers.every(
928
+ worker =>
929
+ lifecycle[worker.id]?.lifecycle_state === "stopped" &&
930
+ lifecycle[worker.id]?.shutdown_request_id === requestId &&
931
+ lifecycle[worker.id]?.shutdown_mode === "graceful",
932
+ );
933
+ const phase: GjcTeamPhase =
934
+ completionVerified && graceful
935
+ ? pendingIntegration
936
+ ? "awaiting_integration"
937
+ : "complete"
938
+ : evidenceFailures.length > 0 || tasks.some(task => task.status === "failed" || task.status === "blocked")
939
+ ? "failed"
940
+ : "cancelled";
941
+ await runtime.writePhase(dir, phase);
942
+ const data: Record<string, unknown> = {
943
+ phase,
944
+ shutdown_request_id: requestId,
945
+ graceful_shutdown_complete: graceful,
946
+ };
947
+ if (evidenceFailures.length > 0) data.evidence_failures = evidenceFailures;
948
+ await runtime.appendEvent(dir, {
949
+ type: "team_shutdown",
950
+ message:
951
+ phase === "complete"
952
+ ? "Shut down native gjc team runtime after completed tasks"
953
+ : "Shut down native gjc team runtime with incomplete tasks",
954
+ data,
955
+ });
956
+ await runtime.appendTelemetry(dir, {
957
+ type: "team_shutdown",
958
+ message: `Native gjc team runtime stopped with phase ${phase}`,
959
+ data: { shutdown_request_id: requestId, graceful_shutdown_complete: graceful },
960
+ });
961
+ return runtime.readSnapshot(config.team_name, cwd, env);
962
+ }