@gajae-code/coding-agent 0.18.7 → 0.18.9

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 +377 -1
  2. package/dist/types/async/endpoint-id.d.ts +16 -0
  3. package/dist/types/async/job-manager.d.ts +1 -1
  4. package/dist/types/async/support.d.ts +0 -14
  5. package/dist/types/capability/mcp.d.ts +2 -0
  6. package/dist/types/capability/skill.d.ts +2 -0
  7. package/dist/types/capability/types.d.ts +2 -0
  8. package/dist/types/cli/public-command-errors.d.ts +3 -1
  9. package/dist/types/cli/setup-cli.d.ts +1 -1
  10. package/dist/types/commands/deep-interview.d.ts +3 -0
  11. package/dist/types/commands/mcp-serve.d.ts +4 -0
  12. package/dist/types/config/autorouting-tier-map.d.ts +348 -9
  13. package/dist/types/config/file-lock.d.ts +1 -0
  14. package/dist/types/config/model-bindings-applier.d.ts +2 -0
  15. package/dist/types/config/model-profile-activation.d.ts +11 -3
  16. package/dist/types/config/model-registry.d.ts +4 -9
  17. package/dist/types/config/skill-settings-defaults.d.ts +2 -0
  18. package/dist/types/cursor-delete-path.d.ts +1 -0
  19. package/dist/types/cursor-mcp-name.d.ts +8 -0
  20. package/dist/types/customization/inventory.d.ts +2 -0
  21. package/dist/types/debug/report-bundle-parent.d.ts +2 -0
  22. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
  23. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
  24. package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
  25. package/dist/types/discovery/helpers.d.ts +6 -1
  26. package/dist/types/discovery/mcp-json.d.ts +1 -0
  27. package/dist/types/eval/py/kernel.d.ts +18 -0
  28. package/dist/types/export/html/css-color.d.ts +2 -0
  29. package/dist/types/export/html/html-export-destination.d.ts +2 -0
  30. package/dist/types/export/html/template.generated.d.ts +1 -1
  31. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +5 -0
  32. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  33. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
  34. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
  35. package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
  36. package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
  37. package/dist/types/extensibility/skill-management.d.ts +4 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
  40. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
  41. package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
  42. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -3
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
  44. package/dist/types/gjc-runtime/sidecar-bootstrap-env.d.ts +6 -0
  45. package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
  46. package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
  47. package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
  48. package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
  49. package/dist/types/gjc-runtime/tmux-common.d.ts +8 -5
  50. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
  51. package/dist/types/internal-urls/skill-path.d.ts +1 -0
  52. package/dist/types/lsp/config.d.ts +8 -5
  53. package/dist/types/lsp/workspace-path.d.ts +1 -0
  54. package/dist/types/modes/acp/acp-agent.d.ts +7 -0
  55. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  56. package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
  57. package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
  58. package/dist/types/modes/controllers/input-controller.d.ts +9 -0
  59. package/dist/types/modes/interactive-mode.d.ts +1 -0
  60. package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
  61. package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
  62. package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
  63. package/dist/types/progress/collect-project-progress.d.ts +38 -0
  64. package/dist/types/progress/progress-contract.d.ts +154 -0
  65. package/dist/types/progress/progress-snapshot.d.ts +11 -0
  66. package/dist/types/progress/project-progress.d.ts +141 -0
  67. package/dist/types/progress/render-progress.d.ts +28 -0
  68. package/dist/types/runtime-mcp/manager.d.ts +2 -1
  69. package/dist/types/runtime-mcp/mcp-redirect.d.ts +9 -0
  70. package/dist/types/runtime-mcp/oauth-public-url.d.ts +3 -0
  71. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
  72. package/dist/types/runtime-mcp/project-host-values.d.ts +27 -0
  73. package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
  74. package/dist/types/runtime-mcp/types.d.ts +6 -0
  75. package/dist/types/sdk/acp/adapter.d.ts +1 -1
  76. package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
  77. package/dist/types/sdk/broker/broker.d.ts +24 -2
  78. package/dist/types/sdk/broker/doctor-restart.d.ts +5 -0
  79. package/dist/types/sdk/broker/ensure.d.ts +14 -1
  80. package/dist/types/sdk/broker/session-index.d.ts +12 -6
  81. package/dist/types/sdk/bus/index.d.ts +12 -1
  82. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
  83. package/dist/types/sdk/client/client.d.ts +2 -0
  84. package/dist/types/sdk/host/query/handlers.d.ts +3 -0
  85. package/dist/types/sdk/index.d.ts +1 -0
  86. package/dist/types/sdk/router/session-router.d.ts +2 -0
  87. package/dist/types/sdk/session-list.d.ts +1 -1
  88. package/dist/types/sdk/session.d.ts +8 -0
  89. package/dist/types/session/agent-session.d.ts +38 -9
  90. package/dist/types/session/artifacts.d.ts +2 -2
  91. package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
  92. package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
  93. package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
  94. package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
  95. package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
  96. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
  97. package/dist/types/session/session-manager.d.ts +31 -5
  98. package/dist/types/session/session-retirement.d.ts +31 -7
  99. package/dist/types/session/session-storage.d.ts +69 -2
  100. package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
  101. package/dist/types/setup/credential-auto-import.d.ts +1 -1
  102. package/dist/types/setup/credential-import.d.ts +6 -4
  103. package/dist/types/skill-state/active-state.d.ts +38 -0
  104. package/dist/types/skill-state/workflow-mutation-guard.d.ts +3 -0
  105. package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
  106. package/dist/types/task/executor.d.ts +3 -0
  107. package/dist/types/task/isolation-dir.d.ts +26 -0
  108. package/dist/types/tools/ast-edit-write-path.d.ts +13 -0
  109. package/dist/types/tools/bash-skill-urls.d.ts +1 -1
  110. package/dist/types/tools/browser/cdp-url.d.ts +2 -0
  111. package/dist/types/tools/github-side-effect.d.ts +22 -0
  112. package/dist/types/tools/index.d.ts +3 -0
  113. package/dist/types/tools/monitor-mutation.d.ts +9 -0
  114. package/dist/types/tools/python.d.ts +9 -1
  115. package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
  116. package/dist/types/tools/restricted-cwd.d.ts +1 -0
  117. package/dist/types/tools/todo-write.d.ts +6 -0
  118. package/dist/types/utils/tools-checksum.d.ts +7 -0
  119. package/package.json +7 -7
  120. package/scripts/generate-sdk-operation-inventory.ts +21 -0
  121. package/scripts/verify-managed-empty-stop.ts +28 -0
  122. package/src/async/endpoint-id.ts +26 -0
  123. package/src/async/job-manager.ts +247 -132
  124. package/src/async/support.ts +0 -25
  125. package/src/capability/index.ts +15 -0
  126. package/src/capability/mcp.ts +2 -0
  127. package/src/capability/skill.ts +2 -0
  128. package/src/capability/types.ts +2 -0
  129. package/src/cli/auth-broker-cli.ts +1 -1
  130. package/src/cli/customize-doctor.ts +15 -2
  131. package/src/cli/doctor/repairs.ts +4 -1
  132. package/src/cli/doctor-supervisor.ts +3 -0
  133. package/src/cli/public-command-errors.ts +21 -1
  134. package/src/cli/public-command-registry.ts +28 -6
  135. package/src/cli/setup-cli.ts +4 -4
  136. package/src/cli/skills-cli.ts +5 -0
  137. package/src/commands/deep-interview.ts +47 -8
  138. package/src/commands/mcp-serve.ts +7 -0
  139. package/src/commands/sdk.ts +455 -397
  140. package/src/config/autorouting-tier-map.ts +117 -7
  141. package/src/config/file-lock.ts +316 -79
  142. package/src/config/mcp-schema.json +4 -0
  143. package/src/config/model-bindings-applier.ts +20 -0
  144. package/src/config/model-profile-activation.ts +240 -46
  145. package/src/config/model-registry.ts +47 -153
  146. package/src/config/skill-settings-defaults.ts +7 -0
  147. package/src/coordinator-mcp/policy.ts +3 -2
  148. package/src/coordinator-mcp/server.ts +2 -0
  149. package/src/cursor-delete-path.ts +10 -0
  150. package/src/cursor-mcp-name.ts +15 -0
  151. package/src/cursor.ts +13 -2
  152. package/src/customization/inventory.ts +4 -13
  153. package/src/dap/config.ts +2 -2
  154. package/src/debug/report-bundle-parent.ts +22 -0
  155. package/src/debug/report-bundle.ts +3 -0
  156. package/src/debug/system-info.ts +13 -1
  157. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +10 -6
  158. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
  159. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
  160. package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
  161. package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
  162. package/src/defaults/gjc-defaults.test.ts +18 -1
  163. package/src/defaults/gjc-defaults.ts +3 -0
  164. package/src/defaults/gjc-skills.generated.ts +58 -36
  165. package/src/discovery/agents.ts +6 -1
  166. package/src/discovery/builtin.ts +13 -1
  167. package/src/discovery/helpers.ts +171 -29
  168. package/src/discovery/mcp-json.ts +26 -12
  169. package/src/discovery/ssh.ts +1 -1
  170. package/src/discovery/substitute-plugin-root.ts +3 -1
  171. package/src/edit/file-read-cache.ts +1 -1
  172. package/src/eval/py/executor.ts +796 -154
  173. package/src/eval/py/kernel.ts +79 -13
  174. package/src/eval/py/tool-bridge.ts +4 -0
  175. package/src/export/html/css-color.ts +9 -0
  176. package/src/export/html/html-export-destination.ts +16 -0
  177. package/src/export/html/index.ts +7 -10
  178. package/src/export/html/template.generated.ts +1 -1
  179. package/src/extensibility/extensions/function-hooks-internal.ts +27 -0
  180. package/src/extensibility/extensions/runner.ts +15 -1
  181. package/src/extensibility/extensions/types.ts +7 -0
  182. package/src/extensibility/gjc-plugins/mcp-policy.ts +2 -1
  183. package/src/extensibility/gjc-plugins/runtime-adapters.ts +85 -8
  184. package/src/extensibility/plugins/manager.ts +3 -2
  185. package/src/extensibility/plugins/marketplace/cache.ts +15 -0
  186. package/src/extensibility/plugins/marketplace/manager.ts +11 -3
  187. package/src/extensibility/plugins/plugin-link-path.ts +12 -0
  188. package/src/extensibility/runtime-skill-discovery.ts +20 -2
  189. package/src/extensibility/skill-management.ts +9 -2
  190. package/src/extensibility/skills.ts +6 -1
  191. package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
  192. package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
  193. package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
  194. package/src/gjc-runtime/deep-interview-stage.ts +98 -21
  195. package/src/gjc-runtime/gc-runtime.ts +206 -9
  196. package/src/gjc-runtime/launch-worktree.ts +70 -9
  197. package/src/gjc-runtime/psmux-detect.ts +28 -4
  198. package/src/gjc-runtime/ralplan-runtime.ts +447 -52
  199. package/src/gjc-runtime/session-state-sidecar.ts +3 -2
  200. package/src/gjc-runtime/sidecar-bootstrap-env.ts +19 -0
  201. package/src/gjc-runtime/state-argv.ts +2 -0
  202. package/src/gjc-runtime/state-migrations.ts +91 -29
  203. package/src/gjc-runtime/state-runtime.ts +3716 -135
  204. package/src/gjc-runtime/state-writer.ts +105 -16
  205. package/src/gjc-runtime/tmux-common.ts +8 -5
  206. package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
  207. package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
  208. package/src/gjc-runtime/workflow-manifest.ts +54 -21
  209. package/src/harness-control-plane/operate.ts +12 -4
  210. package/src/harness-control-plane/owner.ts +5 -1
  211. package/src/hashline/parser.ts +48 -9
  212. package/src/hashline/recovery.ts +13 -4
  213. package/src/internal-urls/docs-index.generated.ts +20 -19
  214. package/src/internal-urls/skill-path.ts +19 -0
  215. package/src/internal-urls/skill-protocol.ts +2 -0
  216. package/src/lsp/config.ts +12 -63
  217. package/src/lsp/edits.ts +6 -0
  218. package/src/lsp/workspace-path.ts +51 -0
  219. package/src/main.ts +195 -31
  220. package/src/memories/index.ts +88 -7
  221. package/src/modes/acp/acp-agent.ts +88 -4
  222. package/src/modes/components/custom-editor.ts +14 -0
  223. package/src/modes/components/customization/customization-dashboard.ts +12 -0
  224. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  225. package/src/modes/components/extensions/state-manager.ts +15 -5
  226. package/src/modes/components/hook-editor.ts +1 -1
  227. package/src/modes/components/hook-selector.ts +1 -1
  228. package/src/modes/components/model-selector.ts +1 -4
  229. package/src/modes/components/provider-onboarding-selector.ts +1 -1
  230. package/src/modes/components/status-line/segments.ts +4 -0
  231. package/src/modes/controllers/event-controller.ts +11 -4
  232. package/src/modes/controllers/input-controller.ts +76 -31
  233. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -16
  234. package/src/modes/controllers/selector-controller.ts +24 -15
  235. package/src/modes/controllers/todo-command-controller.ts +1 -1
  236. package/src/modes/interactive-mode.ts +5 -1
  237. package/src/modes/runtime-init.ts +4 -0
  238. package/src/modes/utils/abort-message.ts +3 -1
  239. package/src/modes/utils/interrupt-ownership.ts +12 -0
  240. package/src/modes/utils/keybinding-matchers.ts +4 -0
  241. package/src/modes/utils/macos-option-forwarding.ts +80 -0
  242. package/src/progress/collect-project-progress.ts +153 -0
  243. package/src/progress/progress-contract.ts +172 -0
  244. package/src/progress/progress-snapshot.ts +140 -0
  245. package/src/progress/project-progress.ts +446 -0
  246. package/src/progress/render-progress.ts +209 -0
  247. package/src/prompts/system/master-mode.md +1 -0
  248. package/src/prompts/tools/skill-discovery.md +1 -1
  249. package/src/runtime-mcp/client.ts +4 -2
  250. package/src/runtime-mcp/config-writer.ts +10 -6
  251. package/src/runtime-mcp/config.ts +1 -0
  252. package/src/runtime-mcp/json-rpc.ts +2 -1
  253. package/src/runtime-mcp/manager.ts +29 -11
  254. package/src/runtime-mcp/mcp-redirect.ts +73 -0
  255. package/src/runtime-mcp/oauth-flow.ts +14 -2
  256. package/src/runtime-mcp/oauth-public-url.ts +14 -0
  257. package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
  258. package/src/runtime-mcp/pool.ts +6 -0
  259. package/src/runtime-mcp/project-host-values.ts +46 -0
  260. package/src/runtime-mcp/smithery-registry.ts +22 -3
  261. package/src/runtime-mcp/transports/http.ts +4 -3
  262. package/src/runtime-mcp/types.ts +6 -0
  263. package/src/sdk/acp/adapter.ts +95 -8
  264. package/src/sdk/broker/broker-exit.ts +52 -23
  265. package/src/sdk/broker/broker.ts +81 -6
  266. package/src/sdk/broker/doctor-restart.ts +4 -0
  267. package/src/sdk/broker/ensure.ts +31 -2
  268. package/src/sdk/broker/lifecycle.ts +711 -66
  269. package/src/sdk/broker/session-index.ts +191 -81
  270. package/src/sdk/bus/chat-daemon-cli.ts +38 -1
  271. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  272. package/src/sdk/bus/index.ts +238 -43
  273. package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
  274. package/src/sdk/cli/session-cli.ts +17 -3
  275. package/src/sdk/client/client.ts +28 -8
  276. package/src/sdk/host/query/handlers.ts +9 -1
  277. package/src/sdk/host/session-runtime.test.ts +2705 -303
  278. package/src/sdk/host/session-runtime.ts +490 -143
  279. package/src/sdk/host/surface-policy.ts +1 -0
  280. package/src/sdk/index.ts +8 -0
  281. package/src/sdk/lifecycle-session.ts +3 -1
  282. package/src/sdk/prompt-deadline-manager.ts +15 -0
  283. package/src/sdk/protocol/operation-inventory.generated.json +146 -0
  284. package/src/sdk/protocol/operation-registry.ts +9 -2
  285. package/src/sdk/router/session-router.ts +82 -18
  286. package/src/sdk/session-list.ts +1 -0
  287. package/src/sdk/session.ts +69 -45
  288. package/src/session/agent-session.ts +679 -238
  289. package/src/session/artifacts.ts +176 -71
  290. package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
  291. package/src/session/internal/managed-session-scope.ts +3779 -462
  292. package/src/session/internal/managed-session-storage.ts +501 -78
  293. package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
  294. package/src/session/internal/task-artifact-owner-access.ts +19 -3
  295. package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
  296. package/src/session/session-manager.ts +951 -133
  297. package/src/session/session-retirement.ts +573 -14
  298. package/src/session/session-storage.ts +447 -15
  299. package/src/session/task-artifact-owner-codec.ts +71 -8
  300. package/src/session/task-artifact-owner-retirement.ts +21 -19
  301. package/src/setup/credential-auto-import.ts +12 -3
  302. package/src/setup/credential-import.ts +176 -5
  303. package/src/setup/paseo/announce.ts +6 -1
  304. package/src/skill-state/active-state.ts +145 -2
  305. package/src/skill-state/workflow-mutation-guard.ts +102 -10
  306. package/src/slash-commands/builtin-registry.ts +39 -9
  307. package/src/slash-commands/helpers/mcp.ts +8 -4
  308. package/src/slash-commands/helpers/progress-report.ts +23 -0
  309. package/src/stt/setup.ts +3 -5
  310. package/src/task/commands.ts +3 -1
  311. package/src/task/executor.ts +4 -0
  312. package/src/task/index.ts +6 -0
  313. package/src/task/isolation-dir.ts +143 -0
  314. package/src/task/worktree.ts +16 -7
  315. package/src/tools/ask.ts +205 -6
  316. package/src/tools/ast-edit-write-path.ts +68 -0
  317. package/src/tools/ast-edit.ts +10 -1
  318. package/src/tools/bash-allowed-prefixes.ts +15 -0
  319. package/src/tools/bash-skill-urls.ts +56 -3
  320. package/src/tools/bash.ts +48 -79
  321. package/src/tools/browser/cdp-url.ts +18 -0
  322. package/src/tools/browser.ts +8 -1
  323. package/src/tools/descriptors.ts +5 -1
  324. package/src/tools/eval.ts +56 -36
  325. package/src/tools/gh.ts +10 -0
  326. package/src/tools/github-side-effect.ts +112 -0
  327. package/src/tools/image-gen.ts +1 -1
  328. package/src/tools/index.ts +3 -0
  329. package/src/tools/monitor-mutation.ts +27 -0
  330. package/src/tools/monitor.ts +7 -0
  331. package/src/tools/python.ts +188 -66
  332. package/src/tools/recipe/index.ts +9 -0
  333. package/src/tools/recipe/recipe-guard.ts +19 -0
  334. package/src/tools/restricted-cwd.ts +10 -0
  335. package/src/tools/skill-discovery.ts +5 -0
  336. package/src/tools/skill.ts +28 -1
  337. package/src/tools/todo-write.ts +10 -1
  338. package/src/tools/tool-catalog.generated.ts +1 -1
  339. package/src/tools/write.ts +1 -0
  340. package/src/utils/external-editor.ts +3 -2
  341. package/src/utils/tools-checksum.ts +34 -0
  342. package/src/utils/tools-manager.ts +24 -6
  343. package/src/web/insane/url-guard.ts +2 -1
  344. package/src/web/search/providers/codex.ts +3 -2
@@ -11,10 +11,13 @@ import { resolveGjcSessionForRead, SessionResolutionError } from "../gjc-runtime
11
11
  import {
12
12
  type ActiveSessionScope,
13
13
  type GuardedWriteResult,
14
+ persistedStateRevision,
14
15
  readActiveEntries,
16
+ readActiveEntriesStrict,
15
17
  rebuildActiveSnapshot,
16
18
  removeActiveEntry,
17
19
  setActiveStateCacheInvalidator,
20
+ workflowEnvelopeChecksumStatus,
18
21
  writeActiveEntry,
19
22
  } from "../gjc-runtime/state-writer";
20
23
  import { getSkillManifest } from "../gjc-runtime/workflow-manifest";
@@ -517,6 +520,18 @@ async function readModeStatePhase(
517
520
  const phase = safeString(record.current_phase).trim();
518
521
  if (!phase) return undefined;
519
522
  if (record.active === false && !getSkillManifest("ralplan").canonicalOverrides.includes(phase)) return undefined;
523
+ const claimedSession = safeString(record.session_id).trim();
524
+ if (claimedSession && claimedSession !== sessionId) return undefined;
525
+ // A mode-state phase may replace the active entry only when the writer
526
+ // stamp still matches. A checksum mismatch is an out-of-band edit. An
527
+ // unsigned releasing phase is not a seal and must not paint "complete"
528
+ // onto a planning entry. Non-releasing canonical phases (final, handoff)
529
+ // still overlay, and a matching stamp still overlays a releasing phase.
530
+ const checksum = workflowEnvelopeChecksumStatus(record);
531
+ if (checksum === "mismatch") return undefined;
532
+ if (checksum === "absent" && getSkillManifest(skill).stopReleasingPhases.includes(phase.trim().toLowerCase())) {
533
+ return undefined;
534
+ }
520
535
  return phase;
521
536
  } catch {
522
537
  return undefined;
@@ -667,13 +682,17 @@ async function mergeVisibleEntries(
667
682
  cwd: string,
668
683
  sessionState: SkillActiveState | null,
669
684
  sessionId: string,
685
+ perSkillEntries?: readonly SkillActiveEntry[],
670
686
  ): Promise<SkillActiveEntry[]> {
671
687
  // Use the raw (active + inactive) rows so a handoff demotion stays visible
672
688
  // long enough to supersede a stale same-skill row before the active filter.
673
689
  // Per-skill files in active/<skill>.json are authoritative and are merged
674
690
  // after the derived snapshot cache, so a stale skill-active-state.json row
675
691
  // cannot override the latest entry file.
676
- const entries = [...rawActiveEntries(sessionState), ...(await readActiveEntries(cwd, { sessionId }))];
692
+ const entries = [
693
+ ...rawActiveEntries(sessionState),
694
+ ...(perSkillEntries ?? (await readActiveEntries(cwd, { sessionId }))),
695
+ ];
677
696
  const merged = new Map(entries.map(entry => [entryKey(entry), entry]));
678
697
  const canonicalRalplanPhase = await readModeStatePhase(cwd, sessionId, "ralplan");
679
698
  const visibleEntries = dedupeVisibleBySkill([...merged.values()], sessionId)
@@ -788,7 +807,16 @@ async function readVisibleSkillActiveStateUncached(
788
807
  ): Promise<SkillActiveState | null> {
789
808
  const { sessionPath } = getSkillActiveStatePaths(cwd, resolvedSessionId);
790
809
  const sessionState = await readRawActiveStateForHandoff(sessionPath, false);
791
- const activeSkills = await mergeVisibleEntries(cwd, sessionState, resolvedSessionId);
810
+ return await buildVisibleSkillActiveState(cwd, sessionState, resolvedSessionId);
811
+ }
812
+
813
+ async function buildVisibleSkillActiveState(
814
+ cwd: string,
815
+ sessionState: SkillActiveState | null,
816
+ resolvedSessionId: string,
817
+ perSkillEntries?: readonly SkillActiveEntry[],
818
+ ): Promise<SkillActiveState | null> {
819
+ const activeSkills = await mergeVisibleEntries(cwd, sessionState, resolvedSessionId, perSkillEntries);
792
820
  if (activeSkills.length === 0) return null;
793
821
  const primary = activeSkills[0];
794
822
  return {
@@ -805,6 +833,121 @@ async function readVisibleSkillActiveStateUncached(
805
833
 
806
834
  setActiveStateCacheInvalidator(invalidateVisibleSkillActiveStateCache);
807
835
 
836
+ /**
837
+ * Outcome of a failure-observable workflow-state read:
838
+ * - `absent`: neither a session snapshot nor a per-skill entry is recorded.
839
+ * - `read`: every recorded source was read and no snapshot row lacks authority.
840
+ * - `recovered`: the session snapshot was unreadable, but authoritative per-skill
841
+ * entries (which the snapshot is rebuilt from) supplied the visible state.
842
+ * - `discarded`: the snapshot is a derived cache that still lists active workflows
843
+ * whose per-skill entry is gone or inactive (e.g. a deactivation whose snapshot
844
+ * rebuild never ran); those stale rows were dropped and the visible state comes
845
+ * from the per-skill entries alone.
846
+ * - `unresolved`: the snapshot lists active workflows without an active per-skill
847
+ * entry, but it cannot be shown to be a derived cache, so those rows may be legacy
848
+ * authority or stale cache; they are excluded without confirming their absence.
849
+ * - `failed`: the session snapshot was unreadable and no per-skill entry could
850
+ * replace it (e.g. a corrupt legacy root-only record), so the state is unknown.
851
+ */
852
+ export type VisibleSkillActiveStateReadStatus = "absent" | "read" | "recovered" | "discarded" | "unresolved" | "failed";
853
+
854
+ export interface VisibleSkillActiveStateRead {
855
+ status: VisibleSkillActiveStateReadStatus;
856
+ /** Visible active state; `null` when nothing is active or the read failed. */
857
+ state: SkillActiveState | null;
858
+ }
859
+
860
+ /** Strict snapshot read: `undefined` when absent; throws on I/O errors, invalid JSON, or a non-object record. */
861
+ async function readSessionSnapshotStrict(filePath: string): Promise<SkillActiveState | undefined> {
862
+ let raw: string;
863
+ try {
864
+ raw = await Bun.file(filePath).text();
865
+ } catch (error) {
866
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
867
+ throw error;
868
+ }
869
+ const parsed: unknown = JSON.parse(raw);
870
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
871
+ throw new Error(`skill-active-state at ${filePath} is not a JSON object`);
872
+ return parsed as SkillActiveState;
873
+ }
874
+
875
+ /**
876
+ * Whether a readable session snapshot can carry workflow authority of its own.
877
+ *
878
+ * - `legacy`: a root-only record (no `active_skills` array). The derived-cache
879
+ * writers (`rebuildActiveSnapshot` and the activation seed) always persist an
880
+ * `active_skills` array, so this shape predates per-skill entries and its row
881
+ * stays visible.
882
+ * - `derived`: an `active_skills` snapshot stamped by the guarded state writer.
883
+ * Every stamped producer persists the per-skill entry before the snapshot, so
884
+ * the per-skill entries are the only authority for its rows.
885
+ * - `indeterminate`: an unstamped `active_skills` snapshot. Unstamped derived
886
+ * rebuilds and unstamped authoritative activation seeds were both written before
887
+ * the guarded writer, so the record cannot establish which one it is.
888
+ */
889
+ function classifySnapshotAuthority(snapshot: SkillActiveState): "legacy" | "derived" | "indeterminate" {
890
+ if (!Array.isArray(snapshot.active_skills)) return "legacy";
891
+ return persistedStateRevision(snapshot) > 0 ? "derived" : "indeterminate";
892
+ }
893
+
894
+ /**
895
+ * Uncached, failure-observable counterpart of `readVisibleSkillActiveState` for
896
+ * read-only projections that must not present unreadable state as absence. The
897
+ * tolerant reader keeps mapping an unreadable session snapshot to `null` for
898
+ * HUD and guard callers; this variant reports `failed` or `recovered` instead.
899
+ *
900
+ * Unlike the tolerant reader, a snapshot row never survives without authority: an
901
+ * active row with no active per-skill entry is dropped as stale (`discarded`) when
902
+ * the snapshot is provably derived, or excluded as `unresolved` when it is not.
903
+ * Only a legacy root-only record keeps its own row. Per-skill entry read failures
904
+ * propagate, as they do from the tolerant reader, and so does an entry file that
905
+ * exists but holds no usable skill entry (e.g. `{}`): the tolerant reader skips
906
+ * it, which would make it indistinguishable from a deleted entry and let it pass
907
+ * as proof that a snapshot row is stale. The read never repairs state.
908
+ */
909
+ export async function readVisibleSkillActiveStateWithStatus(
910
+ cwd: string,
911
+ sessionId: string,
912
+ ): Promise<VisibleSkillActiveStateRead> {
913
+ const resolvedCwd = path.resolve(cwd);
914
+ const { sessionPath } = getSkillActiveStatePaths(resolvedCwd, sessionId);
915
+ let sessionState: SkillActiveState | undefined;
916
+ let snapshotUnreadable = false;
917
+ try {
918
+ sessionState = await readSessionSnapshotStrict(sessionPath);
919
+ } catch {
920
+ snapshotUnreadable = true;
921
+ }
922
+ const perSkillEntries = await readActiveEntriesStrict(resolvedCwd, { sessionId });
923
+ const fromEntries = () => buildVisibleSkillActiveState(resolvedCwd, null, sessionId, perSkillEntries);
924
+ if (snapshotUnreadable) {
925
+ if (perSkillEntries.length === 0) return { status: "failed", state: null };
926
+ return { status: "recovered", state: await fromEntries() };
927
+ }
928
+ if (sessionState === undefined) {
929
+ return perSkillEntries.length === 0
930
+ ? { status: "absent", state: null }
931
+ : { status: "read", state: await fromEntries() };
932
+ }
933
+ const authority = classifySnapshotAuthority(sessionState);
934
+ if (authority === "legacy") {
935
+ return {
936
+ status: "read",
937
+ state: await buildVisibleSkillActiveState(resolvedCwd, sessionState, sessionId, perSkillEntries),
938
+ };
939
+ }
940
+ const activeEntrySkills = new Set(
941
+ perSkillEntries.filter(entry => entry.active !== false).map(entry => safeString(entry.skill).trim()),
942
+ );
943
+ const hasUnbackedRow = rawActiveEntries(sessionState).some(
944
+ entry => entry.active !== false && !activeEntrySkills.has(entry.skill),
945
+ );
946
+ const state = await fromEntries();
947
+ if (!hasUnbackedRow) return { status: "read", state };
948
+ return { status: authority === "derived" ? "discarded" : "unresolved", state };
949
+ }
950
+
808
951
  export async function readVisibleSkillActiveState(
809
952
  cwd: string,
810
953
  sessionId?: string,
@@ -8,8 +8,10 @@ import { expandApplyPatchToEntries } from "../edit/modes/apply-patch";
8
8
  import { GJC_SESSION_PREFIX, modeStatePath as sessionModeStatePath } from "../gjc-runtime/session-layout";
9
9
  import { resolveGjcSessionForRead } from "../gjc-runtime/session-resolution";
10
10
  import { ModeStateSchema } from "../gjc-runtime/state-schema";
11
+ import { workflowEnvelopeChecksumStatus } from "../gjc-runtime/state-writer";
11
12
  import { getSkillManifest } from "../gjc-runtime/workflow-manifest";
12
13
  import { LocalProtocolHandler, resolveLocalUrlToPath } from "../internal-urls/local-protocol";
14
+ import { isGithubReadOnlyArgs } from "../tools/github-side-effect";
13
15
  import { resolveToCwd } from "../tools/path-utils";
14
16
  import { ToolError } from "../tools/tool-errors";
15
17
  import { listActiveSkills, readVisibleSkillActiveState, type SkillActiveEntry } from "./active-state";
@@ -38,7 +40,14 @@ function planningPhaseBlockMessage(skill: CanonicalGjcWorkflowSkill): string {
38
40
  return DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE;
39
41
  }
40
42
 
41
- const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit", "bash"]);
43
+ /** Tools whose execute path can mutate product state. `github` is included so side-effect ops share this guard. */
44
+ export const WORKFLOW_MUTATION_TOOL_NAMES = ["edit", "write", "ast_edit", "bash", "github"] as const;
45
+
46
+ const BLOCKED_TOOL_NAMES = new Set<string>(WORKFLOW_MUTATION_TOOL_NAMES);
47
+
48
+ export function isWorkflowMutationTool(name: string): boolean {
49
+ return BLOCKED_TOOL_NAMES.has(name);
50
+ }
42
51
  /**
43
52
  * Only `/dev/null` is exempt. `/dev/stdout`, `/dev/stderr`, and `/dev/fd/<n>` are descriptor
44
53
  * aliases: `exec 1<>src/product.ts; printf x >/dev/stdout` reaches a real repository file
@@ -317,10 +326,20 @@ function resolveCurrentWorkflowEntry(entries: SkillActiveEntry[], topLevelSkill:
317
326
  * governs, so a stale planning entry can never block while an executor runs and
318
327
  * a resumed planning phase reliably re-blocks.
319
328
  *
320
- * Fail-open contract: a missing or invalid durable mode-state releases the block
321
- * (a corrupt state file must not lock all mutation), matching the guard's
322
- * historical behavior — this is intentionally looser than the Stop hook, which
323
- * fails closed for handoff-required skills.
329
+ * Fail-open contract: a missing or schema-invalid durable mode-state releases
330
+ * the block (a corrupt state file must not lock all mutation), matching the
331
+ * guard's historical behavior — this is intentionally looser than the Stop
332
+ * hook, which fails closed for handoff-required skills. A checksum mismatch is
333
+ * not that case: the file is still schema-valid, and the CLI already refuses
334
+ * it as an out-of-band edit. The guard keeps the active entry's phase instead
335
+ * of letting the forged `current_phase` or `active: false` release the block.
336
+ * An unsigned envelope may restate that entry, but it may not release a block
337
+ * the entry still holds. It may still name a stricter blocking phase: the
338
+ * pre-fix guard honored that phase, and dropping it would allow an edit the
339
+ * base rejected. A `session_id` or `thread_id` that does not match this
340
+ * session is the same kind of claim: it does not make the file someone else's,
341
+ * and it does not open the block. A matching `content_sha256` whose context
342
+ * also matches is the existing writer stamp and is trusted.
324
343
  */
325
344
  async function getActivePlanningSkill(
326
345
  cwd: string,
@@ -343,11 +362,73 @@ async function getActivePlanningSkill(
343
362
  const modeState = guardContext.modeStates.get(current.skill) ?? null;
344
363
 
345
364
  if (!modeState) return null;
346
- if (modeState.active !== true) return null;
347
- if (!modeStateMatchesContext(modeState, resolvedSessionId, threadId)) return null;
348
- const phase = String(modeState.current_phase ?? current.phase ?? "").trim();
349
- if (!isBlockingPlanningPhase(current.skill, phase)) return null;
350
- return { skill: current.skill, phase };
365
+ const activePhase = String(current.phase ?? "").trim();
366
+ const posture = planningPostureFromModeState(current.skill, activePhase, modeState, resolvedSessionId, threadId);
367
+ if (posture.tampered) {
368
+ warnInvalidModeState(
369
+ modeStatePath(cwd, current.skill, resolvedSessionId),
370
+ "out-of-band edit detected (content_sha256 mismatch)",
371
+ );
372
+ }
373
+ if (!posture.blocking) return null;
374
+ return { skill: current.skill, phase: posture.phase };
375
+ }
376
+
377
+ /**
378
+ * Decide whether `modeState` may release the planning block.
379
+ *
380
+ * The active entry is the seal. A mode-state file releases that seal only when
381
+ * its writer-stamped `content_sha256` matches and its session context matches,
382
+ * or when an unsigned file in the same context does not claim a looser posture
383
+ * than the entry. An unsigned file may still impose a stricter blocking phase.
384
+ * Checksum mismatch ignores the file. A context mismatch does not release the
385
+ * entry's block: the file was read from this session's path.
386
+ */
387
+ function planningPostureFromModeState(
388
+ skill: MutationGatedSkill,
389
+ activePhase: string,
390
+ modeState: ModeState,
391
+ sessionId?: string,
392
+ threadId?: string,
393
+ ): { blocking: boolean; phase: string; tampered: boolean } {
394
+ const status = workflowEnvelopeChecksumStatus(modeState);
395
+ if (status === "mismatch") {
396
+ return {
397
+ blocking: isBlockingPlanningPhase(skill, activePhase),
398
+ phase: activePhase,
399
+ tampered: true,
400
+ };
401
+ }
402
+ if (!modeStateMatchesContext(modeState, sessionId, threadId)) {
403
+ return {
404
+ blocking: isBlockingPlanningPhase(skill, activePhase),
405
+ phase: activePhase,
406
+ tampered: false,
407
+ };
408
+ }
409
+ if (status === "match") {
410
+ if (modeState.active !== true) return { blocking: false, phase: activePhase, tampered: false };
411
+ const phase = String(modeState.current_phase ?? activePhase).trim();
412
+ return { blocking: isBlockingPlanningPhase(skill, phase), phase, tampered: false };
413
+ }
414
+ if (!isBlockingPlanningPhase(skill, activePhase)) {
415
+ const stricterPhase = String(modeState.current_phase ?? "").trim();
416
+ if (modeState.active === true && stricterPhase && isBlockingPlanningPhase(skill, stricterPhase)) {
417
+ return { blocking: true, phase: stricterPhase, tampered: false };
418
+ }
419
+ return { blocking: false, phase: activePhase, tampered: false };
420
+ }
421
+ if (modeState.active !== true) return { blocking: true, phase: activePhase, tampered: false };
422
+ const modePhase = String(modeState.current_phase ?? "").trim();
423
+ if (
424
+ modePhase &&
425
+ !isBlockingPlanningPhase(skill, modePhase) &&
426
+ modePhase.trim().toLowerCase() !== activePhase.trim().toLowerCase()
427
+ ) {
428
+ return { blocking: true, phase: activePhase, tampered: false };
429
+ }
430
+ const phase = modePhase || activePhase;
431
+ return { blocking: isBlockingPlanningPhase(skill, phase), phase, tampered: false };
351
432
  }
352
433
 
353
434
  function normalizePosix(value: string): string {
@@ -1607,11 +1688,22 @@ function extractBashTargets(args: unknown, depth = 0): ExtractedTargets {
1607
1688
  }
1608
1689
  return targets;
1609
1690
  }
1691
+ /**
1692
+ * Read-only github ops extract no mutation. Every other op fails closed during
1693
+ * planning: remote push, PR creation, and worktree/config writes are not a
1694
+ * filesystem path the planning allowlist can clear.
1695
+ */
1696
+ function extractGithubTargets(args: unknown): ExtractedTargets {
1697
+ if (isGithubReadOnlyArgs(args)) return { paths: [], unknown: false };
1698
+ return { paths: [], unknown: true, explicitMutation: true };
1699
+ }
1700
+
1610
1701
  function extractTargets(tool: ToolWithEditMode, args: unknown): ExtractedTargets {
1611
1702
  if (tool.name === "write") return extractWriteTargets(args);
1612
1703
  if (tool.name === "ast_edit") return extractAstEditTargets(args);
1613
1704
  if (tool.name === "edit") return extractEditTargets(args, tool);
1614
1705
  if (tool.name === "bash") return extractBashTargets(args);
1706
+ if (tool.name === "github") return extractGithubTargets(args);
1615
1707
  return { paths: [], unknown: true };
1616
1708
  }
1617
1709
 
@@ -31,6 +31,8 @@ import {
31
31
  type InteractiveModeContext,
32
32
  } from "../modes/types";
33
33
  import { parseUiLanguage, resolveUiLanguage, UI_LANGUAGE_LABELS, UI_LANGUAGES, uiString } from "../modes/ui-language";
34
+ import { buildSessionProjectProgress } from "../progress/collect-project-progress";
35
+ import { PROGRESS_COMMAND_ACP_DESCRIPTION } from "../progress/render-progress";
34
36
  // W1b/W5b: notification-service and daemon controllers stay off the static
35
37
  // import graph; the /notify handlers import them lazily at first use.
36
38
  import type { NotificationProvider } from "../sdk/bus/config";
@@ -63,6 +65,7 @@ import { switchSessionCredentialCommand } from "./helpers/credential-switch";
63
65
  import { buildFastStatusReport } from "./helpers/fast-status-report";
64
66
  import { formatDuration } from "./helpers/format";
65
67
  import { commandConsumed, errorMessage, parseSlashCommand, parseSubcommand, usage } from "./helpers/parse";
68
+ import { renderProgressReportLines, renderProgressReportText } from "./helpers/progress-report";
66
69
  import { handleSshAcp } from "./helpers/ssh";
67
70
  import { buildUsageReportText, collectCachedUsageReports } from "./helpers/usage-report";
68
71
  import type {
@@ -1059,14 +1062,17 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1059
1062
  const profileLabel = formatModelProfileDisplayLabel(
1060
1063
  runtime.session.modelRegistry.getModelProfile(presetName) ?? { name: presetName },
1061
1064
  );
1062
- await activateModelProfile(
1063
- {
1064
- session: runtime.session,
1065
- modelRegistry: runtime.session.modelRegistry,
1066
- settings: runtime.settings,
1067
- profileName: presetName,
1068
- },
1069
- { persistDefault: false },
1065
+ runtime.session.markUserModelSelection();
1066
+ await runtime.session.withSdkControlMutation(() =>
1067
+ activateModelProfile(
1068
+ {
1069
+ session: runtime.session,
1070
+ modelRegistry: runtime.session.modelRegistry,
1071
+ settings: runtime.settings,
1072
+ profileName: presetName,
1073
+ },
1074
+ { persistDefault: false },
1075
+ ),
1070
1076
  );
1071
1077
  await runtime.output(`Model profile: ${profileLabel}`);
1072
1078
  await runtime.notifyTitleChanged?.();
@@ -1106,6 +1112,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1106
1112
  throw new Error(`No API key for ${selection.model.provider}/${selection.model.id}`);
1107
1113
  }
1108
1114
  }
1115
+ if (includesRoleAgent && !includesDefault) runtime.session.markUserModelSelection();
1109
1116
 
1110
1117
  const overrides = runtime.settings.get("task.agentModelOverrides");
1111
1118
  const assignments = new Map<GjcModelAssignmentTargetId, string>();
@@ -1136,7 +1143,6 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1136
1143
  runtime.session.setThinkingLevel(existingDefaultThinkingLevel);
1137
1144
  }
1138
1145
  }
1139
-
1140
1146
  const materializedProfile = materializeActiveModelProfileAssignments({
1141
1147
  session: runtime.session,
1142
1148
  settings: runtime.settings,
@@ -1584,6 +1590,30 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1584
1590
  runtime.ctx.editor.setText("");
1585
1591
  },
1586
1592
  },
1593
+ {
1594
+ name: "progress",
1595
+ description:
1596
+ "Show a read-only project progress overview from durable goal, todo, workflow, agent, and verification state",
1597
+ // Production ACP advertises and dispatches `/progress` itself over the
1598
+ // `session.progress` SDK query (modes/acp/acp-agent.ts) with the same copy.
1599
+ acpDescription: PROGRESS_COMMAND_ACP_DESCRIPTION,
1600
+ allowArgs: false,
1601
+ handle: async (_command, runtime) => {
1602
+ const report = await buildSessionProjectProgress(runtime.session, runtime.sessionManager);
1603
+ await runtime.output(renderProgressReportText(report));
1604
+ return commandConsumed();
1605
+ },
1606
+ handleTui: async (_command, runtime) => {
1607
+ const ctx = runtime.ctx;
1608
+ const report = await buildSessionProjectProgress(ctx.session, ctx.sessionManager);
1609
+ ctx.chatContainer.addChild(new Spacer(1));
1610
+ ctx.chatContainer.addChild(new DynamicBorder());
1611
+ ctx.chatContainer.addChild(new Text(renderProgressReportLines(report, theme).join("\n"), 1, 0));
1612
+ ctx.chatContainer.addChild(new DynamicBorder());
1613
+ ctx.ui.requestRender();
1614
+ ctx.editor.setText("");
1615
+ },
1616
+ },
1587
1617
  {
1588
1618
  name: "usage",
1589
1619
  description: "Show provider usage and limits",
@@ -9,6 +9,7 @@ import {
9
9
  updateMCPServer,
10
10
  } from "../../runtime-mcp/config-writer";
11
11
  import { MCPManager } from "../../runtime-mcp/manager";
12
+ import { nativeMcpSource } from "../../runtime-mcp/project-host-values";
12
13
  import { getSmitheryApiKey } from "../../runtime-mcp/smithery-auth";
13
14
  import { searchSmitheryRegistry } from "../../runtime-mcp/smithery-registry";
14
15
  import type { MCPServerConfig, MCPServerConnection } from "../../runtime-mcp/types";
@@ -199,6 +200,7 @@ async function withPreparedMcpConnection<T>(
199
200
  runtime: SlashCommandRuntime,
200
201
  name: string,
201
202
  config: MCPServerConfig,
203
+ scope: AcpMcpScope,
202
204
  fn: (connection: MCPServerConnection) => Promise<T>,
203
205
  ): Promise<T> {
204
206
  const manager = new MCPManager(runtime.cwd, null, {
@@ -207,7 +209,9 @@ async function withPreparedMcpConnection<T>(
207
209
  // Auth storage must be wired in before the prepared lease so OAuth-backed
208
210
  // servers can refresh credentials and inject Authorization headers.
209
211
  manager.setAuthStorage(runtime.session.modelRegistry.authStorage);
210
- return manager.withPreparedLease(name, config, lease => fn(lease.connectionForLease()));
212
+ return manager.withPreparedLease(name, config, lease => fn(lease.connectionForLease()), {
213
+ source: nativeMcpSource(scope, getMCPConfigPath(scope, runtime.cwd)),
214
+ });
211
215
  }
212
216
 
213
217
  async function collectConnectedMcpLines(
@@ -218,9 +222,9 @@ async function collectConnectedMcpLines(
218
222
  if (servers.length === 0) return undefined;
219
223
 
220
224
  const lines: string[] = [];
221
- for (const { name, config } of servers) {
225
+ for (const { name, config, scope } of servers) {
222
226
  try {
223
- const collected = await withPreparedMcpConnection(runtime, name, config, connection =>
227
+ const collected = await withPreparedMcpConnection(runtime, name, config, scope, connection =>
224
228
  collect(name, connection),
225
229
  );
226
230
  lines.push(...collected);
@@ -265,7 +269,7 @@ async function handleTestCommand(rest: string, runtime: SlashCommandRuntime): Pr
265
269
  if (!server) return usage(`Server "${name}" not found. Run /mcp list to see configured servers.`, runtime);
266
270
 
267
271
  try {
268
- return await withPreparedMcpConnection(runtime, name, server.config, async connection => {
272
+ return await withPreparedMcpConnection(runtime, name, server.config, server.scope, async connection => {
269
273
  const tools = await listTools(connection);
270
274
  const lines = [`Server "${name}" connected (${tools.length} tools).`];
271
275
  for (const tool of tools) lines.push(` - ${tool.name}`);
@@ -0,0 +1,23 @@
1
+ import { replaceTabs, truncateToWidth } from "@gajae-code/tui";
2
+ import { sanitizeDisplayLine } from "@gajae-code/utils";
3
+ import { toProjectProgressSnapshot } from "../../progress/progress-snapshot";
4
+ import type { ProjectProgressReport } from "../../progress/project-progress";
5
+ import { type ProgressRenderStyle, renderProgressSnapshot } from "../../progress/render-progress";
6
+
7
+ /** Bound untrusted durable text (objectives, titles, HUD values) to one sanitized display line of `max` columns. */
8
+ export function clipProgressText(text: string, max: number): string {
9
+ return truncateToWidth(sanitizeDisplayLine(replaceTabs(text)).trim(), max);
10
+ }
11
+
12
+ /** TUI view: the `session.progress` snapshot rendered with column-aware clipping and theme colours. */
13
+ export function renderProgressReportLines(report: ProjectProgressReport, style?: ProgressRenderStyle): string[] {
14
+ return renderProgressSnapshot(toProjectProgressSnapshot(report), {
15
+ clip: clipProgressText,
16
+ ...(style ? { style } : {}),
17
+ });
18
+ }
19
+
20
+ /** Plain text view: identical to what production ACP renders from the `session.progress` query. */
21
+ export function renderProgressReportText(report: ProjectProgressReport): string {
22
+ return renderProgressSnapshot(toProjectProgressSnapshot(report)).join("\n");
23
+ }
package/src/stt/setup.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { macosOptionForwardingInstruction } from "../modes/utils/macos-option-forwarding";
1
2
  import { detectRecordingTools } from "./recorder";
2
3
  import { resolvePython } from "./transcriber";
3
4
 
@@ -20,11 +21,8 @@ export function formatSTTUsage(
20
21
  ];
21
22
  if (platform === "darwin") {
22
23
  lines.push("On macOS, Alt+H is Option+H. Your terminal must forward Option as Meta/Esc.");
23
- if (terminalProgram?.toLowerCase().includes("ghostty")) {
24
- lines.push(
25
- "Ghostty: set macos-option-as-alt = true in its config, then reload the config or restart Ghostty.",
26
- );
27
- }
24
+ const instruction = macosOptionForwardingInstruction(terminalProgram);
25
+ if (instruction) lines.push(instruction);
28
26
  }
29
27
  lines.push("Run /hotkeys inside GJC to confirm the active shortcut.");
30
28
  return lines.join("\n");
@@ -120,7 +120,9 @@ export function getCommand(commands: WorkflowCommand[], name: string): WorkflowC
120
120
  * Replaces $@ with the provided input.
121
121
  */
122
122
  export function expandCommand(command: WorkflowCommand, input: string): string {
123
- return command.instructions.replace(/\$@/g, input);
123
+ // Function replacer: `input` is user text and must be inserted verbatim, not
124
+ // parsed for `$&`, `$$`, `` $` `` or `$'` replacement patterns.
125
+ return command.instructions.replace(/\$@/g, () => input);
124
126
  }
125
127
 
126
128
  /**
@@ -33,6 +33,7 @@ import * as canonicalSdk from "@gajae-code/coding-agent/sdk";
33
33
  import { logger, prompt, untilAborted } from "@gajae-code/utils";
34
34
  import { AsyncJobManager } from "../async";
35
35
  import { AUTOROUTING_SELECTOR_MAX_LENGTH, type AutoroutingReasonCode } from "../config/autorouting-contract";
36
+ import type { ModelProfileOwnershipMarker } from "../config/model-profile-ownership";
36
37
  import { ModelRegistry } from "../config/model-registry";
37
38
  import {
38
39
  formatModelString,
@@ -262,6 +263,8 @@ export interface ExecutorOptions {
262
263
  * Manual/direct parents (no active profile) keep exact resolution.
263
264
  */
264
265
  parentActiveModelProfile?: string;
266
+ /** Parent session's model profile ownership marker for propagating to subagent sessions. */
267
+ parentModelProfileOwnershipMarker?: ModelProfileOwnershipMarker;
265
268
  parentSessionId?: string;
266
269
  parentCredentialSessionId?: string;
267
270
  thinkingLevel?: ThinkingLevel;
@@ -2026,6 +2029,7 @@ export async function runSubprocessOnce(options: ExecutorOptions): Promise<Singl
2026
2029
  model,
2027
2030
  thinkingLevel: effectiveThinkingLevel,
2028
2031
  activeModelProfile: options.parentActiveModelProfile,
2032
+ modelProfileOwnershipMarker: options.parentModelProfileOwnershipMarker,
2029
2033
  credentialSessionId: options.parentCredentialSessionId ?? options.parentSessionId,
2030
2034
  modelSubstitution:
2031
2035
  modelSubstitutionWarning?.reason === "auth_unavailable" && requestedModel
package/src/task/index.ts CHANGED
@@ -1738,6 +1738,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1738
1738
  const parentActiveModelProfile = (
1739
1739
  this.session as { getActiveModelProfile?: () => string | undefined }
1740
1740
  ).getActiveModelProfile?.();
1741
+ // Propagate the parent session's ownership marker when a session-local profile is active.
1742
+ const parentModelProfileOwnershipMarker = (
1743
+ this.session as { sessionManager?: { getModelProfileOwnershipMarker?: () => unknown } }
1744
+ ).sessionManager?.getModelProfileOwnershipMarker?.();
1741
1745
  const parentProfileDefinition = parentActiveModelProfile
1742
1746
  ? this.session.modelRegistry?.getModelProfile?.(parentActiveModelProfile)
1743
1747
  : undefined;
@@ -2273,6 +2277,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2273
2277
  modelOverride: effectivePatterns(index),
2274
2278
  parentActiveModelPattern,
2275
2279
  parentActiveModelProfile: parentOwnedModelProfile,
2280
+ parentModelProfileOwnershipMarker: parentModelProfileOwnershipMarker as any,
2276
2281
  parentSessionId: this.session.getSessionId?.() ?? undefined,
2277
2282
  parentCredentialSessionId:
2278
2283
  this.session.getCredentialSessionId?.() ?? this.session.getSessionId?.() ?? undefined,
@@ -2360,6 +2365,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
2360
2365
  modelOverride: effectivePatterns(index),
2361
2366
  parentActiveModelPattern,
2362
2367
  parentActiveModelProfile: parentOwnedModelProfile,
2368
+ parentModelProfileOwnershipMarker: parentModelProfileOwnershipMarker as any,
2363
2369
  parentSessionId: this.session.getSessionId?.() ?? undefined,
2364
2370
  parentCredentialSessionId:
2365
2371
  this.session.getCredentialSessionId?.() ?? this.session.getSessionId?.() ?? undefined,