@gajae-code/coding-agent 0.11.11 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/CHANGELOG.md +131 -4
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/cli/read-cli.d.ts +1 -0
  4. package/dist/types/commands/deep-interview.d.ts +9 -0
  5. package/dist/types/commands/harness.d.ts +22 -0
  6. package/dist/types/commands/read.d.ts +6 -0
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/provider-auth-health.d.ts +14 -0
  9. package/dist/types/config/provider-ranking.d.ts +59 -0
  10. package/dist/types/config/settings-schema.d.ts +83 -1
  11. package/dist/types/config/settings.d.ts +2 -0
  12. package/dist/types/exa/mcp-client.d.ts +10 -1
  13. package/dist/types/exec/bash-executor.d.ts +45 -12
  14. package/dist/types/extensibility/extensions/types.d.ts +38 -4
  15. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  16. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  17. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  18. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  19. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  20. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  21. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  22. package/dist/types/extensibility/shared-events.d.ts +6 -0
  23. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  24. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  25. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  26. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  27. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  28. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  29. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  30. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -1
  31. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  32. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  33. package/dist/types/gjc-runtime/team-runtime.d.ts +27 -1
  34. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  35. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  36. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  37. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
  38. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  39. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  40. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  41. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  42. package/dist/types/main.d.ts +1 -1
  43. package/dist/types/memories/index.d.ts +2 -0
  44. package/dist/types/modes/acp/acp-agent.d.ts +10 -19
  45. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  46. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  47. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  48. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  49. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  50. package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
  51. package/dist/types/modes/components/settings-selector.d.ts +11 -0
  52. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  53. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  54. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  55. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  56. package/dist/types/modes/interactive-mode.d.ts +2 -0
  57. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  58. package/dist/types/modes/types.d.ts +2 -0
  59. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  60. package/dist/types/reminders/star-reminder.d.ts +3 -13
  61. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  62. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  63. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  64. package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
  65. package/dist/types/sdk/broker/transport.d.ts +2 -0
  66. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  68. package/dist/types/sdk/bus/config.d.ts +6 -2
  69. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  70. package/dist/types/sdk/bus/index.d.ts +44 -1
  71. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
  72. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  73. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  74. package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
  75. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  76. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  77. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  78. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  79. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  80. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  81. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  82. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  83. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  84. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  85. package/dist/types/sdk/host/host.d.ts +2 -0
  86. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  87. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  88. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  89. package/dist/types/sdk/prompt-status.d.ts +23 -3
  90. package/dist/types/sdk/session.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +82 -12
  92. package/dist/types/session/artifacts.d.ts +8 -0
  93. package/dist/types/session/blob-store.d.ts +57 -4
  94. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  95. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  96. package/dist/types/session/session-manager.d.ts +48 -19
  97. package/dist/types/session/session-storage.d.ts +4 -0
  98. package/dist/types/session/streaming-output.d.ts +96 -12
  99. package/dist/types/setup/credential-import.d.ts +12 -0
  100. package/dist/types/skill-state/active-state.d.ts +12 -4
  101. package/dist/types/skill-state/workflow-hud.d.ts +9 -0
  102. package/dist/types/task/executor.d.ts +3 -2
  103. package/dist/types/task/index.d.ts +3 -1
  104. package/dist/types/task/provider-retry-status.d.ts +8 -0
  105. package/dist/types/task/receipt.d.ts +7 -1
  106. package/dist/types/task/types.d.ts +23 -0
  107. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  108. package/dist/types/tools/bash-interactive.d.ts +4 -1
  109. package/dist/types/tools/bash.d.ts +3 -1
  110. package/dist/types/tools/browser/launch.d.ts +7 -0
  111. package/dist/types/tools/fetch.d.ts +17 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -0
  113. package/dist/types/tools/index.d.ts +4 -0
  114. package/dist/types/tools/output-meta.d.ts +44 -3
  115. package/dist/types/tools/path-utils.d.ts +1 -0
  116. package/dist/types/tools/read-internals.d.ts +12 -0
  117. package/dist/types/tools/read.d.ts +45 -7
  118. package/dist/types/tools/resource-gc.d.ts +4 -0
  119. package/dist/types/tools/review.d.ts +5 -7
  120. package/dist/types/tools/subagent.d.ts +2 -0
  121. package/dist/types/tools/tool-result.d.ts +7 -1
  122. package/dist/types/utils/gh.d.ts +13 -0
  123. package/dist/types/utils/image-resize.d.ts +1 -0
  124. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  125. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  126. package/dist/types/web/search/providers/xai.d.ts +2 -0
  127. package/package.json +11 -13
  128. package/scripts/acp-conformance-agent.ts +218 -0
  129. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  130. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  131. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  132. package/scripts/generate-sdk-operation-inventory.ts +5 -0
  133. package/scripts/resident-memory-bench.ts +1341 -0
  134. package/scripts/run-acp-conformance.ts +285 -0
  135. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  136. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  137. package/src/async/job-manager.ts +78 -20
  138. package/src/capability/index.ts +8 -0
  139. package/src/cli/fast-help.ts +1 -1
  140. package/src/cli/plugin-cli.ts +176 -18
  141. package/src/cli/read-cli.ts +2 -1
  142. package/src/cli.ts +1 -0
  143. package/src/commands/deep-interview.ts +29 -9
  144. package/src/commands/harness.ts +35 -12
  145. package/src/commands/read.ts +11 -2
  146. package/src/commands/team.ts +20 -2
  147. package/src/config/file-lock.ts +11 -2
  148. package/src/config/model-registry.ts +21 -2
  149. package/src/config/provider-auth-health.ts +42 -0
  150. package/src/config/provider-ranking.ts +119 -0
  151. package/src/config/settings-schema.ts +74 -1
  152. package/src/config/settings.ts +246 -108
  153. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  154. package/src/coordinator-mcp/server.ts +5 -12
  155. package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
  156. package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
  157. package/src/defaults/gjc/skills/team/SKILL.md +3 -1
  158. package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
  159. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  160. package/src/defaults/gjc-defaults.ts +4 -4
  161. package/src/eval/py/executor.ts +7 -2
  162. package/src/exa/mcp-client.ts +12 -3
  163. package/src/exec/bash-executor.ts +144 -27
  164. package/src/extensibility/extensions/runner.ts +15 -0
  165. package/src/extensibility/extensions/types.ts +46 -5
  166. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  167. package/src/extensibility/gjc-plugins/index.ts +19 -2
  168. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  169. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  170. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  171. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  172. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  173. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  174. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  175. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  176. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  177. package/src/extensibility/gjc-plugins/types.ts +141 -0
  178. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  179. package/src/extensibility/shared-events.ts +5 -0
  180. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
  181. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  182. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  183. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  184. package/src/gjc-runtime/deep-interview-state.ts +12 -943
  185. package/src/gjc-runtime/gc-render.ts +1 -0
  186. package/src/gjc-runtime/gc-runtime.ts +11 -1
  187. package/src/gjc-runtime/launch-tmux.ts +25 -6
  188. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  189. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  190. package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
  191. package/src/gjc-runtime/state-runtime.ts +2 -1
  192. package/src/gjc-runtime/state-writer.ts +70 -222
  193. package/src/gjc-runtime/team-launch.ts +42 -5
  194. package/src/gjc-runtime/team-runtime.ts +1085 -40
  195. package/src/gjc-runtime/team-store.ts +32 -0
  196. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  197. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  198. package/src/gjc-runtime/team-workers.ts +1 -0
  199. package/src/gjc-runtime/tmux-owner-isolation.ts +28 -0
  200. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  201. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  202. package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
  203. package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
  204. package/src/gjc-runtime/workflow-manifest.ts +38 -247
  205. package/src/hooks/native-skill-hook.ts +17 -4
  206. package/src/hooks/skill-state.ts +1 -23
  207. package/src/internal-urls/docs-index.generated.ts +21 -22
  208. package/src/internal-urls/local-root-gc.ts +127 -0
  209. package/src/main.ts +9 -5
  210. package/src/memories/index.ts +10 -0
  211. package/src/modes/DESIGN.md +98 -0
  212. package/src/modes/acp/acp-agent.ts +339 -191
  213. package/src/modes/acp/acp-event-mapper.ts +14 -7
  214. package/src/modes/acp/acp-mode.ts +39 -3
  215. package/src/modes/components/assistant-message.ts +123 -22
  216. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  217. package/src/modes/components/irc-sidebar.ts +176 -62
  218. package/src/modes/components/model-selector.ts +61 -4
  219. package/src/modes/components/notifications-settings-editor.ts +12 -1
  220. package/src/modes/components/oauth-selector.ts +96 -28
  221. package/src/modes/components/read-tool-group.ts +82 -14
  222. package/src/modes/components/session-observer-overlay.ts +457 -83
  223. package/src/modes/components/settings-selector.ts +116 -23
  224. package/src/modes/components/status-line/gh.ts +16 -0
  225. package/src/modes/components/tool-execution.ts +138 -27
  226. package/src/modes/components/tool-status-header.ts +3 -14
  227. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  228. package/src/modes/controllers/command-controller.ts +4 -0
  229. package/src/modes/controllers/event-controller.ts +109 -27
  230. package/src/modes/controllers/extension-ui-controller.ts +26 -2
  231. package/src/modes/controllers/input-controller.ts +18 -3
  232. package/src/modes/controllers/selector-controller.ts +127 -36
  233. package/src/modes/interactive-mode.ts +62 -3
  234. package/src/modes/irc-observation-ledger.ts +71 -15
  235. package/src/modes/runtime-init.ts +22 -2
  236. package/src/modes/types.ts +2 -0
  237. package/src/modes/utils/ui-helpers.ts +94 -1
  238. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  239. package/src/prompts/agents/architect.md +9 -1
  240. package/src/prompts/agents/critic.md +9 -0
  241. package/src/prompts/system/eager-todo.md +8 -4
  242. package/src/prompts/system/system-prompt.md +11 -5
  243. package/src/prompts/tools/bash.md +5 -5
  244. package/src/prompts/tools/read.md +4 -2
  245. package/src/prompts/tools/search-tool-bm25.md +5 -0
  246. package/src/prompts/tools/task.md +1 -1
  247. package/src/reminders/star-reminder.ts +4 -42
  248. package/src/runtime-mcp/manager.ts +19 -4
  249. package/src/runtime-mcp/smithery-auth.ts +17 -2
  250. package/src/runtime-mcp/smithery-connect.ts +11 -1
  251. package/src/sdk/acp/adapter.ts +22 -6
  252. package/src/sdk/broker/lifecycle.ts +25 -6
  253. package/src/sdk/broker/transport.ts +35 -3
  254. package/src/sdk/bus/attachment-registry.ts +5 -1
  255. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  256. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  257. package/src/sdk/bus/config.ts +18 -2
  258. package/src/sdk/bus/control-drain-lease.ts +104 -0
  259. package/src/sdk/bus/index.ts +984 -146
  260. package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
  261. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  262. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  263. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  264. package/src/sdk/bus/reconciliation-store.ts +303 -0
  265. package/src/sdk/bus/rich-draft.ts +1 -1
  266. package/src/sdk/bus/rich-render.ts +2 -2
  267. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  268. package/src/sdk/bus/telegram-cli.ts +33 -8
  269. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  270. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  271. package/src/sdk/bus/telegram-daemon.ts +916 -23
  272. package/src/sdk/bus/telegram-reference.ts +40 -3
  273. package/src/sdk/bus/threaded-render.ts +48 -3
  274. package/src/sdk/bus/topic-registry.ts +20 -0
  275. package/src/sdk/host/control/dispatch.ts +5 -1
  276. package/src/sdk/host/control/operations.ts +1 -1
  277. package/src/sdk/host/host.ts +23 -1
  278. package/src/sdk/host/query/handlers.ts +55 -0
  279. package/src/sdk/host/query/revision-store.ts +36 -2
  280. package/src/sdk/host/reverse-leases.ts +44 -7
  281. package/src/sdk/mcp/server.ts +46 -18
  282. package/src/sdk/prompt-status.ts +16 -3
  283. package/src/sdk/protocol/operation-inventory.generated.json +51 -0
  284. package/src/sdk/protocol/operation-registry.ts +7 -2
  285. package/src/sdk/session.ts +101 -10
  286. package/src/session/agent-session.ts +1286 -220
  287. package/src/session/artifacts.ts +22 -0
  288. package/src/session/auth-broker-config.ts +10 -3
  289. package/src/session/blob-store.ts +915 -24
  290. package/src/session/internal/managed-session-scope.ts +44 -6
  291. package/src/session/internal/managed-session-storage.ts +48 -6
  292. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  293. package/src/session/session-manager.ts +2364 -438
  294. package/src/session/session-storage.ts +9 -1
  295. package/src/session/streaming-output.ts +500 -111
  296. package/src/setup/credential-import.ts +75 -13
  297. package/src/setup/provider-onboarding.ts +19 -4
  298. package/src/skill-state/active-state.ts +32 -10
  299. package/src/skill-state/workflow-hud.ts +42 -2
  300. package/src/skill-state/workflow-mutation-guard.ts +560 -8
  301. package/src/task/executor.ts +122 -39
  302. package/src/task/index.ts +23 -14
  303. package/src/task/provider-retry-status.ts +16 -0
  304. package/src/task/receipt.ts +23 -7
  305. package/src/task/render.ts +6 -2
  306. package/src/task/types.ts +108 -0
  307. package/src/tools/ask-answer-registry.ts +12 -6
  308. package/src/tools/ask.ts +169 -118
  309. package/src/tools/ast-edit.ts +1 -0
  310. package/src/tools/ast-grep.ts +1 -0
  311. package/src/tools/bash-interactive.ts +8 -3
  312. package/src/tools/bash.ts +503 -60
  313. package/src/tools/browser/launch.ts +41 -6
  314. package/src/tools/fetch.ts +114 -23
  315. package/src/tools/find.ts +1 -0
  316. package/src/tools/image-gen.ts +29 -5
  317. package/src/tools/index.ts +4 -0
  318. package/src/tools/output-meta.ts +281 -20
  319. package/src/tools/path-utils.ts +8 -3
  320. package/src/tools/read-internals.ts +44 -0
  321. package/src/tools/read.ts +1560 -310
  322. package/src/tools/resource-gc.ts +182 -18
  323. package/src/tools/review.ts +5 -7
  324. package/src/tools/search.ts +1 -0
  325. package/src/tools/subagent-render.ts +6 -2
  326. package/src/tools/subagent.ts +6 -0
  327. package/src/tools/telegram-send.ts +6 -3
  328. package/src/tools/tool-result.ts +14 -1
  329. package/src/utils/gh.ts +39 -0
  330. package/src/utils/image-resize.ts +3 -0
  331. package/src/web/search/providers/anthropic.ts +4 -4
  332. package/src/web/search/providers/codex.ts +1 -1
  333. package/src/web/search/providers/kimi.ts +15 -5
  334. package/src/web/search/providers/searxng.ts +47 -4
  335. package/src/web/search/providers/xai.ts +7 -2
  336. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  337. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  338. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  339. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  340. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  341. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  342. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  343. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
  344. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -32,6 +32,8 @@ export interface AsyncJob {
32
32
  promise: Promise<void>;
33
33
  resultText?: string;
34
34
  errorText?: string;
35
+ /** Safe, bounded cause when session setup failed before the LLM began work. */
36
+ setupFailureSummary?: string;
35
37
  metadata?: AsyncJobMetadata;
36
38
  /**
37
39
  * Registry id of the agent that registered the job (e.g. "0-Main",
@@ -69,9 +71,12 @@ export interface AsyncJobMetadata {
69
71
  * non-terminal and non-delivering: the run suspended at a safe boundary and the
70
72
  * subagent can be resumed from its persisted sessionFile. `completed` always
71
73
  * wins a race with a late pause because the run returns it once it has actually
72
- * finished.
74
+ * finished. A `failed` outcome retains a safe setup diagnostic for receipt rendering.
73
75
  */
74
- export type SubagentRunOutcome = { kind: "completed"; text: string } | { kind: "paused"; note?: string };
76
+ export type SubagentRunOutcome =
77
+ | { kind: "completed"; text: string }
78
+ | { kind: "failed"; text: string; setupFailureSummary?: string }
79
+ | { kind: "paused"; note?: string };
75
80
 
76
81
  /** Canonical lifecycle of a subagent across pause/resume cycles. */
77
82
  export type SubagentLifecycle = "running" | "paused" | "queued" | "completed" | "failed" | "cancelled";
@@ -146,7 +151,11 @@ export interface SubagentRecord {
146
151
  historicalJobIds: string[];
147
152
  status: SubagentLifecycle;
148
153
  sessionFile: string | null;
149
- /** False for ephemeral sessions (no persistent artifacts dir). */
154
+ /**
155
+ * Explicit veto, not a complete availability result. False always denies;
156
+ * true still requires an owner-compatible descriptor or non-blank session
157
+ * file, followed by a separately available runner (`no_runner` otherwise).
158
+ */
150
159
  resumable: boolean;
151
160
  queued?: { ownerId?: string; seq: number; message?: string; createdAt: number };
152
161
  /** Resolved model the subagent was asked to use, e.g. "openai-codex/gpt-5.5". */
@@ -176,6 +185,22 @@ function sessionFileFromResumeDescriptorData(data: unknown): string | null {
176
185
  return typeof sessionFile === "string" && sessionFile.trim().length > 0 ? sessionFile : null;
177
186
  }
178
187
 
188
+ /**
189
+ * Derive retained context from an already owner-compatible descriptor or a
190
+ * legacy session file. A descriptor is sufficient even when `sessionFile` is
191
+ * null because the descriptor is the payload the resume runner consumes.
192
+ */
193
+ function hasRetainedResumeContext(input: {
194
+ resumable: boolean;
195
+ sessionFile: string | null;
196
+ descriptor: ResumeDescriptor | undefined;
197
+ }): boolean {
198
+ if (!input.resumable) return false;
199
+ return (
200
+ input.descriptor !== undefined || (typeof input.sessionFile === "string" && input.sessionFile.trim().length > 0)
201
+ );
202
+ }
203
+
179
204
  /** A pending resume awaiting a free concurrency slot. */
180
205
  interface ResumeQueueEntry {
181
206
  subagentId: string;
@@ -499,7 +524,7 @@ export class AsyncJobManager {
499
524
  typeof result === "string" ? { kind: "completed", text: result } : result;
500
525
 
501
526
  if (job.status === "cancelled") {
502
- job.resultText = outcome.kind === "completed" ? outcome.text : outcome.note;
527
+ job.resultText = outcome.kind === "paused" ? outcome.note : outcome.text;
503
528
  this.#runLifecycle(id, "terminal", job);
504
529
  this.#scheduleEviction(id);
505
530
  this.#markRecordTerminal(id, "cancelled");
@@ -509,7 +534,7 @@ export class AsyncJobManager {
509
534
  if (outcome.kind === "paused") {
510
535
  // Sole canonical writer of the running -> paused transition. No
511
536
  // delivery and no eviction scheduling: a paused subagent stays
512
- // listed and resumable from its sessionFile.
537
+ // listed and resumable from its retained resume context.
513
538
  job.status = "paused";
514
539
  this.#freezeEndTime(job);
515
540
  if (outcome.note) job.resultText = outcome.note;
@@ -517,6 +542,18 @@ export class AsyncJobManager {
517
542
  this.#drainResumeQueue();
518
543
  return;
519
544
  }
545
+ if (outcome.kind === "failed") {
546
+ job.status = "failed";
547
+ job.setupFailureSummary = outcome.setupFailureSummary;
548
+ this.#freezeEndTime(job);
549
+ job.errorText = outcome.text;
550
+ this.#enqueueDelivery(id, outcome.text);
551
+ this.#runLifecycle(id, "terminal", job);
552
+ this.#scheduleEviction(id);
553
+ this.#markRecordTerminal(id, "failed");
554
+ this.#drainResumeQueue();
555
+ return;
556
+ }
520
557
 
521
558
  job.status = "completed";
522
559
  this.#freezeEndTime(job);
@@ -684,7 +721,9 @@ export class AsyncJobManager {
684
721
  historicalJobIds: [],
685
722
  status: "completed",
686
723
  sessionFile,
687
- resumable: sessionFile !== null,
724
+ // The synthesized record copies this descriptor's owner, so the
725
+ // descriptor is owner-compatible with the record by construction.
726
+ resumable: hasRetainedResumeContext({ resumable: true, sessionFile, descriptor }),
688
727
  };
689
728
  this.#subagentRecords.set(record.subagentId, record);
690
729
  return record;
@@ -765,13 +804,24 @@ export class AsyncJobManager {
765
804
  }
766
805
 
767
806
  /**
768
- * Resolve the resume runner for a subagent: prefer the per-descriptor runner
769
- * captured at registration time (the originating parent's execution authority),
770
- * falling back to the process-global runner only for descriptors registered
771
- * without one.
807
+ * Resolve a descriptor only when `ownerId` matches the record by strict
808
+ * equality. Two undefined owners match; a distinguishably foreign descriptor
809
+ * is treated as absent for eligibility, runner selection, and execution.
772
810
  */
773
- #resolveResumeRunner(subagentId: string): ResumeRunner | undefined {
774
- return this.#descriptorResumeRunners.get(subagentId) ?? this.#resumeRunner;
811
+ #descriptorForRecord(rec: SubagentRecord): ResumeDescriptor | undefined {
812
+ const descriptor = this.#resumeDescriptors.get(rec.subagentId);
813
+ return descriptor !== undefined && descriptor.ownerId === rec.ownerId ? descriptor : undefined;
814
+ }
815
+
816
+ /**
817
+ * Resolve the resume runner for a record and its already owner-compatible
818
+ * descriptor. Per-descriptor authority is unavailable when the descriptor is foreign.
819
+ */
820
+ #resolveResumeRunner(rec: SubagentRecord, descriptor: ResumeDescriptor | undefined): ResumeRunner | undefined {
821
+ return (
822
+ (descriptor !== undefined ? this.#descriptorResumeRunners.get(rec.subagentId) : undefined) ??
823
+ this.#resumeRunner
824
+ );
775
825
  }
776
826
 
777
827
  getResumeDescriptor(subagentId: string, filter?: AsyncJobFilter): ResumeDescriptor | undefined {
@@ -1045,7 +1095,11 @@ export class AsyncJobManager {
1045
1095
  return { ok: true, status: rec.status };
1046
1096
  }
1047
1097
 
1048
- /** Resume a non-running subagent from its sessionFile, optionally injecting a message first. */
1098
+ /**
1099
+ * Resume a non-running subagent from retained context: an owner-compatible
1100
+ * descriptor or a legacy session file. Workflow routing keeps `not_found`,
1101
+ * `context_unavailable`, `no_runner`, and `resume_failed` distinct.
1102
+ */
1049
1103
  resumeSubagent(
1050
1104
  subagentId: string,
1051
1105
  filter?: AsyncJobFilter,
@@ -1066,8 +1120,11 @@ export class AsyncJobManager {
1066
1120
  }
1067
1121
  return { ok: false, status: "queued", reason: "already_queued" };
1068
1122
  }
1069
- if (!rec.resumable || !rec.sessionFile) return { ok: false, reason: "context_unavailable" };
1070
- if (!this.#resolveResumeRunner(rec.subagentId)) return { ok: false, reason: "no_runner" };
1123
+ const descriptor = this.#descriptorForRecord(rec);
1124
+ if (!hasRetainedResumeContext({ resumable: rec.resumable, sessionFile: rec.sessionFile, descriptor })) {
1125
+ return { ok: false, reason: "context_unavailable" };
1126
+ }
1127
+ if (!this.#resolveResumeRunner(rec, descriptor)) return { ok: false, reason: "no_runner" };
1071
1128
  if (this.getRunningJobs().length >= this.#maxRunningJobs) {
1072
1129
  const seq = ++this.#resumeSeq;
1073
1130
  rec.status = "queued";
@@ -1081,12 +1138,13 @@ export class AsyncJobManager {
1081
1138
  });
1082
1139
  return { ok: true, queued: true, status: "queued" };
1083
1140
  }
1084
- return this.#startResume(rec, message);
1141
+ return this.#startResume(rec, message, descriptor);
1085
1142
  }
1086
1143
 
1087
1144
  #startResume(
1088
1145
  rec: SubagentRecord,
1089
- message?: string,
1146
+ message: string | undefined,
1147
+ descriptor: ResumeDescriptor | undefined,
1090
1148
  ): { ok: boolean; status?: SubagentLifecycle; jobId?: string; reason?: string } {
1091
1149
  if (this.#isOwnerSubagentShutdownFenced(rec.ownerId)) {
1092
1150
  return { ok: false, status: rec.status, reason: "owner_shutdown_in_progress" };
@@ -1095,8 +1153,8 @@ export class AsyncJobManager {
1095
1153
  // Clear any retained progress from the previous run so a resumed subagent
1096
1154
  // never renders the prior run's tool/output as live before it emits again.
1097
1155
  this.#subagentProgress.delete(rec.subagentId);
1098
- const runner = this.#resolveResumeRunner(rec.subagentId);
1099
- const newJobId = runner?.(rec.subagentId, message, this.#resumeDescriptors.get(rec.subagentId));
1156
+ const runner = this.#resolveResumeRunner(rec, descriptor);
1157
+ const newJobId = runner?.(rec.subagentId, message, descriptor);
1100
1158
  if (!newJobId) return { ok: false, reason: "resume_failed" };
1101
1159
  if (prevJobId && prevJobId !== newJobId) rec.historicalJobIds.push(prevJobId);
1102
1160
  rec.currentJobId = newJobId;
@@ -1122,7 +1180,7 @@ export class AsyncJobManager {
1122
1180
  continue;
1123
1181
  }
1124
1182
  try {
1125
- const result = this.#startResume(rec, entry.message);
1183
+ const result = this.#startResume(rec, entry.message, this.#descriptorForRecord(rec));
1126
1184
  if (result.reason === "owner_shutdown_in_progress") {
1127
1185
  index += 1;
1128
1186
  continue;
@@ -259,10 +259,18 @@ export function initializeWithSettings(activeSettings: Settings): void {
259
259
  for (const id of disabled) disabledProviders.add(id);
260
260
  }
261
261
 
262
+ function assertDisabledProvidersWritable(activeSettings: Settings): void {
263
+ if (!activeSettings.canWriteDurableConfig()) {
264
+ throw new Error(
265
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
266
+ );
267
+ }
268
+ }
262
269
  /**
263
270
  * Persist current disabled providers to settings.
264
271
  */
265
272
  function persistDisabledProviders(activeSettings: Settings, providers: ReadonlySet<string>): void {
273
+ assertDisabledProvidersWritable(activeSettings);
266
274
  activeSettings.set("disabledProviders", Array.from(providers));
267
275
  }
268
276
 
@@ -93,7 +93,7 @@ Available Tools (default-enabled unless noted):
93
93
  bash - Execute bash commands
94
94
  edit - Edit files with find/replace
95
95
  write - Write files (creates/overwrites)
96
- grep - Search file contents
96
+ search - Search file contents
97
97
  find - Find files by glob pattern
98
98
  lsp - Language server protocol (code intelligence)
99
99
  python - Execute Python code (requires: ${APP_NAME} setup python)
@@ -7,7 +7,19 @@
7
7
  import { APP_NAME, getProjectDir } from "@gajae-code/utils";
8
8
  import chalk from "chalk";
9
9
  import { resolveOrDefaultProjectRegistryPath } from "../discovery/helpers";
10
- import { installGjcPluginBundle, isGjcPluginBundleSource, readRegistry } from "../extensibility/gjc-plugins";
10
+ import {
11
+ applyGjcBundleUpdate,
12
+ bundleIdentity,
13
+ type GjcBundleIdentity,
14
+ type GjcBundleSummary,
15
+ GjcPluginLoadError,
16
+ getGjcBundle,
17
+ installGjcBundle,
18
+ isGjcPluginBundleSource,
19
+ isGjcPluginSourceShape,
20
+ listGjcBundles,
21
+ previewGjcBundleUpdate,
22
+ } from "../extensibility/gjc-plugins";
11
23
  import { PluginManager, parseSettingValue, validateSetting } from "../extensibility/plugins";
12
24
  import {
13
25
  getInstalledPluginsRegistryPath,
@@ -312,9 +324,118 @@ async function handleDiscover(args: string[], _flags: PluginCommandArgs["flags"]
312
324
  }
313
325
  }
314
326
 
327
+ /**
328
+ * Scope-qualified GJC bundle upgrade: re-resolve the stored source, review the
329
+ * candidate, then apply it as a compare-and-swap. `--dry-run` stops after the
330
+ * preview. Requires exactly one of `--user` / `--project` because (scope, name)
331
+ * is the canonical target.
332
+ */
333
+ async function handleGjcUpgrade(name: string, flags: PluginCommandArgs["flags"]): Promise<void> {
334
+ if (flags.user === flags.project) {
335
+ console.error(chalk.red(`GJC bundle upgrade requires exactly one of --user or --project for "${name}".`));
336
+ process.exit(1);
337
+ }
338
+ const scope: "user" | "project" = flags.user ? "user" : "project";
339
+ const ctx = { cwd: getProjectDir() };
340
+ const identity = bundleIdentity(scope, name);
341
+
342
+ const emitError = (error: { code: string; message: string; recovery?: string }): never => {
343
+ if (flags.json) console.log(JSON.stringify({ error }, null, 2));
344
+ else {
345
+ console.error(chalk.red(`${theme.status.error} ${error.message}`));
346
+ if (error.recovery) console.error(chalk.dim(` Try: ${error.recovery}`));
347
+ }
348
+ process.exit(3);
349
+ };
350
+
351
+ // Source re-resolution can throw with a cause carrying the raw locator, so
352
+ // the whole flow reports a stable code instead of the underlying error.
353
+ try {
354
+ await runGjcUpgrade(ctx, identity, name, scope, flags, emitError);
355
+ } catch (err) {
356
+ const reason = err instanceof GjcPluginLoadError ? err.code : "upgrade_failed";
357
+ console.error(chalk.red(`${theme.status.error} Failed to upgrade GJC bundle ${name} (${reason})`));
358
+ process.exit(1);
359
+ }
360
+ }
361
+
362
+ async function runGjcUpgrade(
363
+ ctx: { cwd: string },
364
+ identity: GjcBundleIdentity,
365
+ name: string,
366
+ scope: "user" | "project",
367
+ flags: PluginCommandArgs["flags"],
368
+ emitError: (error: { code: string; message: string; recovery?: string }) => never,
369
+ ): Promise<void> {
370
+ const preview = await previewGjcBundleUpdate(ctx, identity);
371
+ if (!preview.ok) emitError(preview.error);
372
+ else if (flags.dryRun || !preview.value.changed) {
373
+ const { changed, candidateVersion, addedSurfaceIds, removedSurfaceIds } = preview.value;
374
+ if (flags.json) {
375
+ console.log(
376
+ JSON.stringify(
377
+ {
378
+ status: changed ? "update-available" : "up-to-date",
379
+ identity,
380
+ currentVersion: preview.value.current.version,
381
+ candidateVersion,
382
+ addedSurfaceIds,
383
+ removedSurfaceIds,
384
+ },
385
+ null,
386
+ 2,
387
+ ),
388
+ );
389
+ } else if (!changed) {
390
+ console.log(chalk.dim(`GJC bundle ${name} (${scope}) is up to date at ${preview.value.current.version}`));
391
+ } else {
392
+ console.log(
393
+ chalk.cyan(`[dry-run] ${name} (${scope}): ${preview.value.current.version} -> ${candidateVersion}`),
394
+ );
395
+ if (addedSurfaceIds.length > 0) console.log(chalk.dim(` + ${addedSurfaceIds.join(", ")}`));
396
+ if (removedSurfaceIds.length > 0) console.log(chalk.dim(` - ${removedSurfaceIds.join(", ")}`));
397
+ }
398
+ } else {
399
+ const applied = await applyGjcBundleUpdate(ctx, preview.value.token);
400
+ if (!applied.ok) emitError(applied.error);
401
+ else if (flags.json) {
402
+ console.log(
403
+ JSON.stringify(
404
+ {
405
+ status: applied.value.status,
406
+ bundle: applied.value.summary,
407
+ remnantCount: applied.value.remnantCount,
408
+ },
409
+ null,
410
+ 2,
411
+ ),
412
+ );
413
+ } else {
414
+ console.log(
415
+ chalk.green(
416
+ `${theme.status.success} ${applied.value.status} GJC bundle ${name}@${applied.value.summary.version} (${scope})`,
417
+ ),
418
+ );
419
+ if (applied.value.remnantCount > 0) {
420
+ console.error(chalk.yellow(` ${applied.value.remnantCount} leftover directory could not be removed`));
421
+ }
422
+ }
423
+ }
424
+ }
425
+
315
426
  async function handleUpgrade(args: string[], flags: PluginCommandArgs["flags"]): Promise<void> {
316
- const manager = await makeMarketplaceManager();
317
427
  const pluginId = args[0];
428
+ // Scope-qualified GJC bundles upgrade through the lifecycle service, never
429
+ // through the marketplace manager.
430
+ if (pluginId && (flags.user || flags.project)) {
431
+ const scope: "user" | "project" = flags.user ? "user" : "project";
432
+ const existing = await getGjcBundle({ cwd: getProjectDir() }, bundleIdentity(scope, pluginId));
433
+ if (existing.ok) {
434
+ await handleGjcUpgrade(pluginId, flags);
435
+ return;
436
+ }
437
+ }
438
+ const manager = await makeMarketplaceManager();
318
439
  try {
319
440
  if (pluginId) {
320
441
  if (flags.scope) {
@@ -349,6 +470,15 @@ async function handleUpgrade(args: string[], flags: PluginCommandArgs["flags"]):
349
470
  }
350
471
  }
351
472
 
473
+ /**
474
+ * Stable, non-identifying reason for an install failure. The raw spec and the
475
+ * underlying cause can both carry credentials, a query string, or an absolute
476
+ * home path, so neither is ever printed.
477
+ */
478
+ function describeInstallFailure(error: unknown): string {
479
+ return error instanceof GjcPluginLoadError ? error.code : "install_failed";
480
+ }
481
+
352
482
  async function handleInstall(
353
483
  manager: PluginManager,
354
484
  packages: string[],
@@ -374,27 +504,52 @@ async function handleInstall(
374
504
  const knownMarketplaces = new Set((await mktMgr.listMarketplaces()).map(m => m.name));
375
505
 
376
506
  for (const spec of packages) {
377
- // GJC plugin bundle classifier: a source containing gajae-plugin.json (or a
378
- // git/tarball source) routes to the bundle installer BEFORE marketplace/npm.
379
- if (await isGjcPluginBundleSource(spec)) {
507
+ // A GJC bundle is identified by the SHAPE of its source: a filesystem path,
508
+ // a git locator, or a tarball. npm and marketplace specs are never any of
509
+ // those, so shape alone separates the two worlds without resolving.
510
+ //
511
+ // Shape is checked BEFORE `isGjcPluginBundleSource`, which resolves the
512
+ // source: a deleted or unreachable GJC source fails that probe and would
513
+ // otherwise fall through to npm, losing the create-only refusal the
514
+ // lifecycle owes for an already-installed target.
515
+ if (isGjcPluginSourceShape(spec) || (await isGjcPluginBundleSource(spec))) {
380
516
  if (flags.user === flags.project) {
381
517
  console.error(
382
- chalk.red(`GJC plugin bundle install requires exactly one of --user or --project for "${spec}".`),
518
+ // The spec can carry credentials or an absolute home path, so name
519
+ // the missing flag instead of echoing it back.
520
+ chalk.red("GJC plugin bundle install requires exactly one of --user or --project."),
383
521
  );
384
522
  process.exit(1);
385
523
  }
386
524
  const scope: "user" | "project" = flags.user ? "user" : "project";
387
525
  try {
388
- const res = await installGjcPluginBundle(spec, { scope, cwd: process.cwd(), force: flags.force });
526
+ const res = await installGjcBundle({ cwd: getProjectDir() }, scope, spec);
527
+ if (!res.ok) {
528
+ const doc = {
529
+ error: { code: res.error.code, message: res.error.message, recovery: res.error.recovery },
530
+ };
531
+ if (flags.json) console.log(JSON.stringify(doc, null, 2));
532
+ else {
533
+ console.error(chalk.red(`${theme.status.error} ${res.error.message}`));
534
+ if (res.error.recovery) console.error(chalk.dim(` Try: ${res.error.recovery}`));
535
+ }
536
+ process.exit(3);
537
+ }
538
+ const { summary } = res.value;
389
539
  if (flags.json) {
390
- console.log(JSON.stringify({ name: res.entry.name, status: res.status, scope }, null, 2));
540
+ console.log(JSON.stringify({ status: res.value.status, bundle: summary }, null, 2));
391
541
  } else {
392
542
  console.log(
393
- chalk.green(`${theme.status.success} ${res.status} GJC plugin ${res.entry.name} (${scope})`),
543
+ chalk.green(
544
+ `${theme.status.success} installed GJC plugin ${summary.identity.name}@${summary.version} (${scope})`,
545
+ ),
394
546
  );
395
547
  }
396
548
  } catch (err) {
397
- console.error(chalk.red(`${theme.status.error} Failed to install GJC plugin ${spec}: ${err}`));
549
+ // Never echo the raw spec or the underlying cause: either can carry
550
+ // credentials, a query string, or an absolute home path.
551
+ const reason = err instanceof GjcPluginLoadError ? err.code : "install_failed";
552
+ console.error(chalk.red(`${theme.status.error} Failed to install GJC bundle (${reason})`));
398
553
  process.exit(1);
399
554
  }
400
555
  continue;
@@ -414,7 +569,9 @@ async function handleInstall(
414
569
  ),
415
570
  );
416
571
  } catch (err) {
417
- console.error(chalk.red(`${theme.status.error} Failed to install ${spec}: ${err}`));
572
+ // The spec can carry credentials, a query string, or an absolute home
573
+ // path, so report the failure without echoing it or the raw cause.
574
+ console.error(chalk.red(`${theme.status.error} Failed to install plugin (${describeInstallFailure(err)})`));
418
575
  process.exit(1);
419
576
  }
420
577
  continue;
@@ -449,7 +606,9 @@ async function handleInstall(
449
606
  }
450
607
  }
451
608
  } catch (err) {
452
- console.error(chalk.red(`${theme.status.error} Failed to install ${spec}: ${err}`));
609
+ // The spec can carry credentials, a query string, or an absolute home
610
+ // path, so report the failure without echoing it or the raw cause.
611
+ console.error(chalk.red(`${theme.status.error} Failed to install plugin (${describeInstallFailure(err)})`));
453
612
  process.exit(1);
454
613
  }
455
614
  }
@@ -503,8 +662,7 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
503
662
  const mktMgr = await makeMarketplaceManager();
504
663
  const mktPlugins = await mktMgr.listInstalledPlugins();
505
664
  const cwd = getProjectDir();
506
- const [gjcUser, gjcProject] = await Promise.all([readRegistry("user", cwd), readRegistry("project", cwd)]);
507
- const gjcBundles = [...gjcUser.plugins, ...gjcProject.plugins];
665
+ const gjcBundles: GjcBundleSummary[] = await listGjcBundles({ cwd });
508
666
 
509
667
  if (flags.json) {
510
668
  console.log(JSON.stringify({ npm: npmPlugins, marketplace: mktPlugins, gjc: gjcBundles }, null, 2));
@@ -559,9 +717,9 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
559
717
  console.log(chalk.bold("GJC Plugin Bundles:\n"));
560
718
  for (const plugin of gjcBundles) {
561
719
  const status = plugin.enabled ? chalk.green(theme.status.enabled) : chalk.dim(theme.status.disabled);
562
- const scopeLabel = chalk.dim(` (${plugin.scope})`);
563
- const disabledCount = plugin.disabledSurfaceIds.length;
564
- const quarantineCount = plugin.quarantine?.length ?? 0;
720
+ const scopeLabel = chalk.dim(` (${plugin.identity.scope})`);
721
+ const disabledCount = plugin.surfaces.filter(s => !s.enabled).length;
722
+ const quarantineCount = plugin.surfaces.filter(s => s.quarantined).length;
565
723
  const detail = [
566
724
  disabledCount > 0 ? `${disabledCount} disabled` : null,
567
725
  quarantineCount > 0 ? `${quarantineCount} quarantined` : null,
@@ -569,7 +727,7 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
569
727
  .filter((v): v is string => Boolean(v))
570
728
  .join(", ");
571
729
  console.log(
572
- `${status} ${plugin.name}@${plugin.version}${scopeLabel}${detail ? chalk.dim(` — ${detail}`) : ""}`,
730
+ `${status} ${plugin.identity.name}@${plugin.version}${scopeLabel}${detail ? chalk.dim(` — ${detail}`) : ""}`,
573
731
  );
574
732
  }
575
733
  }
@@ -15,6 +15,7 @@ import { renderError } from "../tools/tool-errors";
15
15
 
16
16
  export interface ReadCommandArgs {
17
17
  path: string;
18
+ truncation?: "head" | "last" | "both";
18
19
  }
19
20
 
20
21
  export async function runReadCommand(cmd: ReadCommandArgs): Promise<void> {
@@ -37,7 +38,7 @@ export async function runReadCommand(cmd: ReadCommandArgs): Promise<void> {
37
38
  const tool = wrapToolWithMetaNotice(new ReadTool(session));
38
39
 
39
40
  try {
40
- const result = await tool.execute("gjc-read", { path: cmd.path });
41
+ const result = await tool.execute("gjc-read", { path: cmd.path, truncation: cmd.truncation });
41
42
 
42
43
  for (const block of result.content) {
43
44
  if (block.type === "text") {
package/src/cli.ts CHANGED
@@ -65,6 +65,7 @@ export const commands: CommandEntry[] = [
65
65
  { name: "migrate", load: () => import("./commands/migrate").then(m => m.default) },
66
66
  { name: "rlm", load: () => import("./commands/rlm").then(m => m.default) },
67
67
  { name: "update", load: () => import("./commands/update").then(m => m.default) },
68
+ { name: "read", load: () => import("./commands/read").then(m => m.default) },
68
69
  { name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
69
70
  { name: "completion", load: () => import("./commands/completion").then(m => m.default) },
70
71
  { name: "launch", load: () => import("./commands/launch").then(m => m.default) },
@@ -2,8 +2,22 @@ import { Command, Flags } from "@gajae-code/utils/cli";
2
2
  import { runNativeDeepInterviewCommand } from "../gjc-runtime/deep-interview-runtime";
3
3
 
4
4
  export default class DeepInterview extends Command {
5
- static description =
6
- "Run native GJC deep-interview workflow. Use `draft` for editable CLI-owned mutation inputs; JSON repair verbs are compatibility-only.";
5
+ static description = `Run native GJC deep-interview workflow.
6
+
7
+ All deep-interview state operations go through this command — no gjc state needed:
8
+ read Print the persisted envelope, revision, content sha, and any pending draft
9
+ write One-shot incremental JSON merge into state (--reset replaces; the locked
10
+ intent contract survives a reset)
11
+ stage Stage one JSON transition draft (--for <transition> --input '<json>'|@file)
12
+ check Dry-run the staged draft against current state (same merge apply performs)
13
+ apply Commit the staged draft with runtime-owned revision+sha CAS
14
+ discard Remove the pending draft
15
+ clear Clear deep-interview state for the session (lifecycle passthrough)
16
+ handoff Hand off to the next workflow skill (lifecycle passthrough)
17
+
18
+ Ambiguity is runtime-owned: apply/write derive current_ambiguity from the latest valid scored
19
+ round and clamp it to the deterministic floor. Sessions resolve from --session-id, payload
20
+ session_id, or GJC_SESSION_ID.`;
7
21
  static strict = false;
8
22
  static flags = {
9
23
  quick: Flags.boolean({ description: "Seed a quick deep-interview run" }),
@@ -15,6 +29,11 @@ export default class DeepInterview extends Command {
15
29
  "session-id": Flags.string({
16
30
  description: "Route state/spec handoff through a session-scoped .gjc/_session-{sessionid} directory",
17
31
  }),
32
+ input: Flags.string({ description: "JSON payload (or @file) for the write/stage verbs" }),
33
+ for: Flags.string({
34
+ description: "Transition for stage: initialize-context | record-round | update-facts | merge-state",
35
+ }),
36
+ reset: Flags.boolean({ description: "With write: replace state instead of incremental merge" }),
18
37
  write: Flags.boolean({ description: "Persist a final deep-interview spec through the sanctioned GJC CLI/API" }),
19
38
  stage: Flags.string({ description: 'Spec stage for --write (currently "final")' }),
20
39
  slug: Flags.string({ description: "Safe slug for .gjc/_session-{sessionid}/specs/deep-interview-<slug>.md" }),
@@ -27,13 +46,14 @@ export default class DeepInterview extends Command {
27
46
  json: Flags.boolean({ description: "Output JSON" }),
28
47
  };
29
48
  static examples = [
30
- '$ gjc deep-interview --trace --standard "<idea>" --json',
31
- "$ gjc deep-interview draft create --for initialize-context --session-id <id> --json",
32
- "$ gjc deep-interview draft edit --draft-id <id> --expected-draft-revision 1 --op set --path /type --value greenfield --json",
33
- "$ gjc deep-interview draft check --draft-id <id> --json",
34
- "$ gjc deep-interview initialize-context --draft-id <id> --expected-draft-revision <n> --json",
35
- "$ gjc deep-interview --write --stage final --slug my-feature --spec ./final-spec.md --json",
36
- "$ gjc deep-interview inspect --session-id <id> --selector summary --json",
49
+ '$ gjc deep-interview --trace --standard "<idea>"',
50
+ "$ gjc deep-interview read --json",
51
+ '$ gjc deep-interview write --input \'{"state":{"threshold":0.05}}\' --json',
52
+ '$ gjc deep-interview stage --for record-round --input \'{"state":{"rounds":[{"round":1,"round_key":"r1"}]}}\' --json',
53
+ "$ gjc deep-interview check --json",
54
+ "$ gjc deep-interview apply --json",
55
+ "$ gjc deep-interview --write --stage final --slug my-feature --spec ./final-spec.md",
56
+ "$ gjc deep-interview --write --stage final --slug my-feature --spec ./final-spec.md --deliberate",
37
57
  ];
38
58
 
39
59
  async run(): Promise<void> {
@@ -14,6 +14,7 @@ import { existsSync, readFileSync } from "node:fs";
14
14
  import * as fs from "node:fs/promises";
15
15
  import * as path from "node:path";
16
16
  import { Args, Command, Flags } from "@gajae-code/utils/cli";
17
+ import { $credentialEnv } from "@gajae-code/utils/env";
17
18
  import {
18
19
  GJC_TMUX_OWNER_GENERATION_ENV,
19
20
  GJC_TMUX_OWNER_SERVER_KEY_ENV,
@@ -565,20 +566,42 @@ function ownerIsolationPlatform(): NodeJS.Platform {
565
566
  : "linux";
566
567
  }
567
568
 
569
+ /**
570
+ * Operator override for the process-start probe command, resolved from trusted
571
+ * environment sources only.
572
+ *
573
+ * The result is spawned, so whatever can set it chooses which binary runs.
574
+ * `$env` merges the caller's `cwd/.env` into `process.env`, so reading it there
575
+ * would let repository content pick the command; resolve it the same way
576
+ * provider credentials are (launching shell plus GJC/user-owned `.env` files,
577
+ * never the project `.env`). A malformed override stays fatal rather than
578
+ * silently falling back to `ps`, matching the previous behavior.
579
+ */
580
+ type ProcessStartCommandOverride = { kind: "none" } | { kind: "invalid" } | { kind: "command"; command: string[] };
581
+
582
+ function processStartCommandOverride(): ProcessStartCommandOverride {
583
+ const configured = $credentialEnv("GJC_HARNESS_PROCESS_START_COMMAND");
584
+ if (!configured) return { kind: "none" };
585
+ try {
586
+ const parsed = JSON.parse(configured) as unknown;
587
+ if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some(value => typeof value !== "string" || !value))
588
+ return { kind: "invalid" };
589
+ return { kind: "command", command: parsed as string[] };
590
+ } catch {
591
+ return { kind: "invalid" };
592
+ }
593
+ }
594
+
595
+ /** Test seam: the process-start command override as resolved from trusted env. */
596
+ export function processStartCommandOverrideForTest(): ProcessStartCommandOverride {
597
+ return processStartCommandOverride();
598
+ }
599
+
568
600
  function portableProcessStartTime(pid: number): string | null {
569
601
  if (process.platform === "linux") return null;
570
- const configured = process.env.GJC_HARNESS_PROCESS_START_COMMAND;
571
- let command: string[] = ["ps", "-o", "lstart=", "-p"];
572
- if (configured) {
573
- try {
574
- const parsed = JSON.parse(configured) as unknown;
575
- if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some(value => typeof value !== "string" || !value))
576
- return null;
577
- command = parsed;
578
- } catch {
579
- return null;
580
- }
581
- }
602
+ const override = processStartCommandOverride();
603
+ if (override.kind === "invalid") return null;
604
+ const command: string[] = override.kind === "command" ? override.command : ["ps", "-o", "lstart=", "-p"];
582
605
  const result = Bun.spawnSync([...command, String(pid)], {
583
606
  stdout: "pipe",
584
607
  stderr: "ignore",