@gajae-code/coding-agent 0.18.11 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (462) hide show
  1. package/CHANGELOG.md +760 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/capability/rule.d.ts +7 -2
  7. package/dist/types/cli/accounts-cli.d.ts +2 -2
  8. package/dist/types/cli/append-system-prompt.d.ts +6 -0
  9. package/dist/types/cli/args.d.ts +7 -2
  10. package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
  11. package/dist/types/cli/customize-doctor.d.ts +4 -3
  12. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  13. package/dist/types/cli/install-activation.d.ts +10 -4
  14. package/dist/types/cli/public-command-errors.d.ts +2 -0
  15. package/dist/types/cli/root-flags.d.ts +0 -12
  16. package/dist/types/cli/update-cli.d.ts +21 -3
  17. package/dist/types/cli-main.d.ts +0 -6
  18. package/dist/types/cli-ordinary.d.ts +2 -2
  19. package/dist/types/commands/accounts.d.ts +2 -1
  20. package/dist/types/commands/launch.d.ts +0 -6
  21. package/dist/types/commands/migrate.d.ts +1 -1
  22. package/dist/types/commands/sdk.d.ts +13 -0
  23. package/dist/types/commit/changelog/index.d.ts +1 -0
  24. package/dist/types/commit/map-reduce/index.d.ts +4 -4
  25. package/dist/types/config/autorouting-tier-map.d.ts +53 -1
  26. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  27. package/dist/types/config/config-hot-reload.d.ts +44 -0
  28. package/dist/types/config/file-lock.d.ts +52 -3
  29. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  30. package/dist/types/config/model-profile-activation.d.ts +79 -9
  31. package/dist/types/config/model-registry.d.ts +72 -8
  32. package/dist/types/config/model-resolver.d.ts +7 -4
  33. package/dist/types/config/models-config-schema.d.ts +10 -0
  34. package/dist/types/config/settings-schema.d.ts +57 -4
  35. package/dist/types/config/settings.d.ts +33 -1
  36. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  37. package/dist/types/customization/import.d.ts +2 -0
  38. package/dist/types/customization/types.d.ts +1 -1
  39. package/dist/types/dap/config.d.ts +8 -4
  40. package/dist/types/dap/session.d.ts +11 -2
  41. package/dist/types/debug/system-info.d.ts +6 -1
  42. package/dist/types/discovery/helpers.d.ts +7 -2
  43. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  44. package/dist/types/edit/index.d.ts +5 -0
  45. package/dist/types/edit/modes/patch.d.ts +14 -0
  46. package/dist/types/edit/modes/replace.d.ts +3 -0
  47. package/dist/types/eval/py/runtime.d.ts +9 -0
  48. package/dist/types/exec/bash-executor.d.ts +12 -0
  49. package/dist/types/export/ttsr.d.ts +19 -0
  50. package/dist/types/extensibility/extensions/types.d.ts +5 -1
  51. package/dist/types/extensibility/slash-commands.d.ts +1 -0
  52. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  53. package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
  54. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
  55. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  56. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  57. package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
  58. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
  59. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  60. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  61. package/dist/types/hindsight/content.d.ts +5 -6
  62. package/dist/types/internal-urls/types.d.ts +2 -0
  63. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
  64. package/dist/types/main.d.ts +23 -18
  65. package/dist/types/migrate/action-planner.d.ts +10 -1
  66. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  67. package/dist/types/migrate/adapters/index.d.ts +42 -1
  68. package/dist/types/migrate/executor.d.ts +3 -1
  69. package/dist/types/migrate/hook-module.d.ts +23 -0
  70. package/dist/types/migrate/report.d.ts +2 -1
  71. package/dist/types/migrate/types.d.ts +82 -3
  72. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  73. package/dist/types/modes/components/model-selector.d.ts +43 -0
  74. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  75. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  76. package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
  77. package/dist/types/modes/theme/theme.d.ts +1 -0
  78. package/dist/types/modes/utils/host-status.d.ts +15 -11
  79. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  80. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  81. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  82. package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
  83. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  84. package/dist/types/sdk/broker/broker-exit.d.ts +3 -3
  85. package/dist/types/sdk/broker/broker.d.ts +29 -2
  86. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  87. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  88. package/dist/types/sdk/broker/ensure.d.ts +141 -9
  89. package/dist/types/sdk/broker/hop.d.ts +45 -0
  90. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  91. package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
  92. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  93. package/dist/types/sdk/broker/session-index.d.ts +6 -1
  94. package/dist/types/sdk/broker/transport.d.ts +2 -0
  95. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  96. package/dist/types/sdk/bus/index.d.ts +44 -0
  97. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  98. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -1
  99. package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
  100. package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
  101. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  102. package/dist/types/sdk/host/control/operations.d.ts +5 -1
  103. package/dist/types/sdk/host/events.d.ts +4 -0
  104. package/dist/types/sdk/host/host.d.ts +2 -1
  105. package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
  106. package/dist/types/sdk/host/session-runtime.d.ts +40 -8
  107. package/dist/types/sdk/host/surface-policy.d.ts +2 -1
  108. package/dist/types/sdk/lifecycle/service.d.ts +12 -0
  109. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  110. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  111. package/dist/types/sdk/prompt-status.d.ts +11 -4
  112. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  113. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  114. package/dist/types/sdk/session-list.d.ts +1 -1
  115. package/dist/types/session/agent-session.d.ts +107 -9
  116. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  117. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  118. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  119. package/dist/types/session/messages.d.ts +5 -1
  120. package/dist/types/session/session-manager.d.ts +29 -3
  121. package/dist/types/session/startup-auth-config.d.ts +24 -2
  122. package/dist/types/session/streaming-output.d.ts +11 -0
  123. package/dist/types/session/terminal-abort.d.ts +3 -1
  124. package/dist/types/session-import/service.d.ts +1 -1
  125. package/dist/types/skill-state/workflow-mutation-guard.d.ts +20 -4
  126. package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
  127. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  128. package/dist/types/ssh/ssh-executor.d.ts +8 -1
  129. package/dist/types/system-prompt.d.ts +3 -2
  130. package/dist/types/task/executor.d.ts +19 -2
  131. package/dist/types/task/role-body.d.ts +11 -0
  132. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  133. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  134. package/dist/types/tools/awaiting-input.d.ts +28 -0
  135. package/dist/types/tools/bash-allowed-prefixes.d.ts +17 -0
  136. package/dist/types/tools/bash-skill-urls.d.ts +4 -0
  137. package/dist/types/tools/bash.d.ts +7 -0
  138. package/dist/types/tools/descriptors.d.ts +15 -2
  139. package/dist/types/tools/fetch.d.ts +7 -1
  140. package/dist/types/tools/index.d.ts +2 -0
  141. package/dist/types/tools/irc.d.ts +5 -3
  142. package/dist/types/tools/python.d.ts +1 -1
  143. package/dist/types/tools/report-issue.d.ts +41 -0
  144. package/dist/types/tools/subagent.d.ts +23 -0
  145. package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
  146. package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
  147. package/dist/types/tools/write.d.ts +2 -0
  148. package/dist/types/utils/file-mentions.d.ts +5 -0
  149. package/dist/types/utils/image-limits.d.ts +7 -0
  150. package/dist/types/utils/image-loading.d.ts +0 -1
  151. package/dist/types/utils/open.d.ts +0 -1
  152. package/dist/types/utils/pasted-image-loading.d.ts +1 -5
  153. package/dist/types/utils/prompt-suggestion.d.ts +2 -2
  154. package/dist/types/utils/shell-snapshot.d.ts +2 -0
  155. package/dist/types/utils/title-generator.d.ts +13 -3
  156. package/dist/types/web/scrapers/types.d.ts +8 -1
  157. package/dist/types/web/search/providers/codex.d.ts +6 -7
  158. package/dist/types/web/search/providers/insane.d.ts +7 -1
  159. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  160. package/package.json +10 -9
  161. package/scripts/acp-conformance-agent.ts +12 -2
  162. package/scripts/build-binary.ts +2 -1
  163. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  164. package/scripts/generate-docs-index.ts +8 -1
  165. package/scripts/generate-sdk-operation-inventory.ts +38 -4
  166. package/src/async/job-manager.ts +356 -56
  167. package/src/async/result-formatting.ts +40 -0
  168. package/src/autoresearch/git.ts +9 -2
  169. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  170. package/src/capability/fs.ts +58 -22
  171. package/src/capability/index.ts +2 -2
  172. package/src/capability/rule.ts +11 -2
  173. package/src/cli/accounts-cli.ts +176 -8
  174. package/src/cli/append-system-prompt.ts +12 -0
  175. package/src/cli/args.ts +11 -7
  176. package/src/cli/auth-broker-cli.ts +21 -11
  177. package/src/cli/customize-doctor-precedence.ts +31 -0
  178. package/src/cli/customize-doctor.ts +49 -39
  179. package/src/cli/doctor/install-repairs.ts +35 -5
  180. package/src/cli/fast-help.ts +1 -3
  181. package/src/cli/install-activation.ts +47 -4
  182. package/src/cli/migrate-cli.ts +19 -6
  183. package/src/cli/public-command-errors.ts +6 -0
  184. package/src/cli/root-flags.ts +2 -4
  185. package/src/cli/stats-cli.ts +9 -5
  186. package/src/cli/update-cli.ts +471 -13
  187. package/src/cli-ordinary.ts +7 -2
  188. package/src/commands/accounts.ts +10 -5
  189. package/src/commands/harness.ts +25 -15
  190. package/src/commands/migrate.ts +3 -3
  191. package/src/commands/sdk.ts +277 -50
  192. package/src/commands/ultragoal.ts +16 -8
  193. package/src/commit/agentic/agent.ts +6 -6
  194. package/src/commit/analysis/conventional.ts +14 -5
  195. package/src/commit/changelog/index.ts +39 -1
  196. package/src/commit/map-reduce/index.ts +7 -7
  197. package/src/commit/message.ts +10 -3
  198. package/src/commit/pipeline.ts +12 -12
  199. package/src/config/autorouting-tier-map.ts +50 -1
  200. package/src/config/config-file.ts +106 -3
  201. package/src/config/config-hot-reload-trust.ts +148 -0
  202. package/src/config/config-hot-reload.ts +742 -0
  203. package/src/config/file-lock.ts +1615 -328
  204. package/src/config/keybindings.ts +154 -7
  205. package/src/config/model-bindings-applier.ts +85 -47
  206. package/src/config/model-discovery-manager.ts +10 -0
  207. package/src/config/model-profile-activation.ts +889 -148
  208. package/src/config/model-registry.ts +1706 -205
  209. package/src/config/model-resolver.ts +52 -22
  210. package/src/config/models-config-schema.ts +6 -0
  211. package/src/config/settings-schema.ts +73 -4
  212. package/src/config/settings.ts +460 -24
  213. package/src/coordinator-mcp/policy.ts +26 -1
  214. package/src/coordinator-mcp/server.ts +37 -2
  215. package/src/crash/sanitize.ts +7 -1
  216. package/src/customization/import.ts +279 -8
  217. package/src/customization/inventory.ts +24 -8
  218. package/src/customization/types.ts +1 -0
  219. package/src/dap/client.ts +3 -2
  220. package/src/dap/config.ts +31 -11
  221. package/src/dap/defaults.json +1 -1
  222. package/src/dap/session.ts +161 -36
  223. package/src/debug/crash-diagnostics.ts +89 -6
  224. package/src/debug/report-bundle.ts +90 -2
  225. package/src/debug/system-info.ts +8 -3
  226. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  227. package/src/discovery/agents-md.ts +1 -1
  228. package/src/discovery/builtin.ts +4 -3
  229. package/src/discovery/helpers.ts +25 -16
  230. package/src/discovery/mcp-compat.ts +120 -17
  231. package/src/edit/apply-patch/index.ts +12 -1
  232. package/src/edit/index.ts +204 -22
  233. package/src/edit/modes/patch.ts +214 -8
  234. package/src/edit/modes/replace.ts +136 -0
  235. package/src/eval/js/context-manager.ts +29 -3
  236. package/src/eval/js/worker-entry.ts +51 -18
  237. package/src/eval/py/executor.ts +14 -4
  238. package/src/eval/py/runner.py +144 -5
  239. package/src/eval/py/runtime.ts +44 -4
  240. package/src/exec/bash-executor.ts +81 -4
  241. package/src/exec/bash-shell-guardian.ts +4 -1
  242. package/src/exec/bash-shell-supervisor.ts +8 -4
  243. package/src/exec/isolated-shell.ts +5 -1
  244. package/src/exec/non-interactive-env.ts +0 -1
  245. package/src/export/html/index.ts +86 -6
  246. package/src/export/ttsr.ts +176 -1
  247. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  248. package/src/extensibility/extensions/loader.ts +10 -1
  249. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
  250. package/src/extensibility/extensions/types.ts +5 -1
  251. package/src/extensibility/hooks/loader.ts +10 -2
  252. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  253. package/src/extensibility/plugins/marketplace/manager.ts +36 -10
  254. package/src/extensibility/slash-commands.ts +5 -0
  255. package/src/gjc-runtime/gc-runtime.ts +37 -7
  256. package/src/gjc-runtime/launch-tmux.ts +30 -15
  257. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  258. package/src/gjc-runtime/launch-worktree.ts +101 -14
  259. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  260. package/src/gjc-runtime/ralplan-current-final.ts +294 -0
  261. package/src/gjc-runtime/ralplan-runtime.ts +95 -1
  262. package/src/gjc-runtime/session-resolution.ts +38 -6
  263. package/src/gjc-runtime/session-state-lock.ts +59 -14
  264. package/src/gjc-runtime/state-runtime.ts +23 -19
  265. package/src/gjc-runtime/state-writer.ts +109 -5
  266. package/src/gjc-runtime/tmux-sessions.ts +93 -13
  267. package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
  268. package/src/gjc-runtime/ultragoal-guard.ts +9 -1
  269. package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
  270. package/src/harness-control-plane/finalize.ts +11 -0
  271. package/src/harness-control-plane/receipt-spool.ts +105 -28
  272. package/src/harness-control-plane/storage.ts +124 -6
  273. package/src/hindsight/content.ts +116 -16
  274. package/src/hindsight/mental-models.ts +22 -10
  275. package/src/hindsight/state.ts +7 -4
  276. package/src/hooks/native-skill-hook.ts +17 -12
  277. package/src/internal-urls/artifact-protocol.ts +3 -1
  278. package/src/internal-urls/docs-index.generated.ts +23 -22
  279. package/src/internal-urls/local-protocol.ts +167 -8
  280. package/src/internal-urls/types.ts +2 -0
  281. package/src/lsp/client.ts +2 -1
  282. package/src/lsp/clients/biome-client.ts +2 -0
  283. package/src/lsp/clients/swiftlint-client.ts +2 -0
  284. package/src/lsp/index.ts +29 -0
  285. package/src/lsp/workspace-path.ts +23 -0
  286. package/src/main.ts +330 -92
  287. package/src/memories/index.ts +25 -2
  288. package/src/migrate/action-planner.ts +574 -2
  289. package/src/migrate/adapters/claude-code.ts +162 -5
  290. package/src/migrate/adapters/codex.ts +77 -7
  291. package/src/migrate/adapters/index.ts +258 -5
  292. package/src/migrate/adapters/opencode.ts +10 -1
  293. package/src/migrate/executor.ts +222 -7
  294. package/src/migrate/hook-module.ts +193 -0
  295. package/src/migrate/report.ts +63 -2
  296. package/src/migrate/types.ts +84 -4
  297. package/src/modes/DESIGN.md +51 -0
  298. package/src/modes/acp/acp-agent.ts +10 -1
  299. package/src/modes/acp/permission-mode.ts +18 -1
  300. package/src/modes/components/agent-dashboard.ts +20 -4
  301. package/src/modes/components/assistant-message.ts +55 -1
  302. package/src/modes/components/customization/customization-dashboard.ts +7 -0
  303. package/src/modes/components/customization/import-wizard.ts +29 -3
  304. package/src/modes/components/model-selector.ts +157 -103
  305. package/src/modes/components/settings-selector.ts +103 -37
  306. package/src/modes/components/welcome.ts +3 -1
  307. package/src/modes/controllers/command-controller.ts +300 -9
  308. package/src/modes/controllers/event-controller.ts +1 -1
  309. package/src/modes/controllers/extension-ui-controller.ts +31 -1
  310. package/src/modes/controllers/goal-mode-controller.ts +79 -10
  311. package/src/modes/controllers/input-controller.ts +2 -0
  312. package/src/modes/controllers/plan-mode-controller.ts +66 -11
  313. package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
  314. package/src/modes/controllers/selector-controller.ts +76 -7
  315. package/src/modes/interactive-mode.ts +123 -4
  316. package/src/modes/prompt-suggestion-controller.ts +1 -1
  317. package/src/modes/runtime-init.ts +5 -1
  318. package/src/modes/theme/theme.ts +15 -0
  319. package/src/modes/utils/host-status.ts +21 -10
  320. package/src/prompts/agent-fragments/browser-aside.md +2 -2
  321. package/src/prompts/system/irc-incoming.md +3 -1
  322. package/src/prompts/tools/debug.md +1 -1
  323. package/src/prompts/tools/irc.md +1 -1
  324. package/src/prompts/tools/read.md +1 -1
  325. package/src/prompts/tools/report-issue.md +13 -0
  326. package/src/prompts/tools/task.md +1 -1
  327. package/src/runtime-credential-selector.ts +2 -2
  328. package/src/runtime-mcp/content-limits.ts +7 -0
  329. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  330. package/src/runtime-mcp/manager.ts +40 -3
  331. package/src/runtime-mcp/oauth-flow.ts +32 -6
  332. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  333. package/src/runtime-mcp/project-host-values.ts +12 -8
  334. package/src/runtime-mcp/render.ts +16 -1
  335. package/src/runtime-mcp/tool-bridge.ts +194 -21
  336. package/src/runtime-mcp/transports/stdio.ts +2 -1
  337. package/src/sdk/broker/broker-exit.ts +10 -5
  338. package/src/sdk/broker/broker.ts +484 -85
  339. package/src/sdk/broker/daemon-entry.ts +338 -116
  340. package/src/sdk/broker/doctor-restart.ts +2 -1
  341. package/src/sdk/broker/ensure.ts +1214 -268
  342. package/src/sdk/broker/hop.ts +286 -0
  343. package/src/sdk/broker/lifecycle-ledger.ts +38 -11
  344. package/src/sdk/broker/lifecycle.ts +102 -6
  345. package/src/sdk/broker/lock-host-identity.ts +29 -0
  346. package/src/sdk/broker/session-index.ts +91 -16
  347. package/src/sdk/broker/transport.ts +17 -1
  348. package/src/sdk/bus/chat-command-policy.ts +16 -3
  349. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  350. package/src/sdk/bus/index.ts +460 -35
  351. package/src/sdk/bus/reconciliation-store.ts +201 -8
  352. package/src/sdk/bus/session-control.ts +2 -0
  353. package/src/sdk/bus/slack-daemon.ts +105 -9
  354. package/src/sdk/bus/telegram-daemon-contract.ts +9 -1
  355. package/src/sdk/bus/telegram-daemon.ts +10 -2
  356. package/src/sdk/bus/threaded-render.ts +20 -1
  357. package/src/sdk/cli/session-cli.ts +23 -6
  358. package/src/sdk/host/control/dispatch.ts +13 -1
  359. package/src/sdk/host/control/operations.ts +10 -1
  360. package/src/sdk/host/events.ts +67 -4
  361. package/src/sdk/host/host.ts +8 -2
  362. package/src/sdk/host/prompt-image-upload.ts +438 -0
  363. package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
  364. package/src/sdk/host/session-runtime.test.ts +1737 -129
  365. package/src/sdk/host/session-runtime.ts +684 -83
  366. package/src/sdk/host/surface-policy.ts +4 -0
  367. package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
  368. package/src/sdk/host/websocket-transport.ts +19 -7
  369. package/src/sdk/lifecycle/service.ts +15 -1
  370. package/src/sdk/prompt-deadline-flush.ts +78 -5
  371. package/src/sdk/prompt-deadline-manager.ts +68 -14
  372. package/src/sdk/prompt-status.ts +6 -4
  373. package/src/sdk/protocol/adapter-validation.ts +81 -12
  374. package/src/sdk/protocol/operation-inventory.generated.json +261 -2
  375. package/src/sdk/protocol/operation-registry.ts +33 -3
  376. package/src/sdk/reconciliation-extensions.ts +1 -0
  377. package/src/sdk/session-list.ts +1 -0
  378. package/src/sdk/session.ts +237 -97
  379. package/src/session/agent-session.ts +2726 -479
  380. package/src/session/history-storage.ts +182 -12
  381. package/src/session/internal/managed-session-scope.ts +173 -57
  382. package/src/session/internal/managed-session-storage.ts +85 -12
  383. package/src/session/internal/native-publish-outcome.ts +5 -1
  384. package/src/session/messages.ts +47 -6
  385. package/src/session/session-manager.ts +326 -19
  386. package/src/session/startup-auth-config.ts +116 -8
  387. package/src/session/streaming-output.ts +262 -4
  388. package/src/session/terminal-abort.ts +14 -2
  389. package/src/session-import/redact.ts +53 -2
  390. package/src/session-import/service.ts +1 -0
  391. package/src/skill-state/workflow-mutation-guard.ts +2145 -206
  392. package/src/slash-commands/builtin-registry.ts +21 -6
  393. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  394. package/src/slash-commands/helpers/mcp.ts +11 -3
  395. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  396. package/src/ssh/ssh-executor.ts +9 -2
  397. package/src/stt/recorder.ts +26 -9
  398. package/src/system-prompt.ts +61 -16
  399. package/src/task/executor.ts +115 -20
  400. package/src/task/index.ts +389 -175
  401. package/src/task/role-body.ts +84 -0
  402. package/src/task/token-log-mode.test.ts +162 -0
  403. package/src/task/token-log.ts +91 -2
  404. package/src/tools/archive-reader.ts +650 -37
  405. package/src/tools/ask.ts +1 -0
  406. package/src/tools/ast-edit-write-path.ts +64 -0
  407. package/src/tools/ast-edit.ts +18 -1
  408. package/src/tools/ast-grep.ts +7 -2
  409. package/src/tools/atomic-file-write.ts +46 -15
  410. package/src/tools/auto-generated-guard.ts +15 -3
  411. package/src/tools/awaiting-input.ts +114 -0
  412. package/src/tools/bash-allowed-prefixes.ts +214 -6
  413. package/src/tools/bash-interactive.ts +14 -2
  414. package/src/tools/bash-skill-urls.ts +58 -5
  415. package/src/tools/bash.ts +113 -26
  416. package/src/tools/browser/tab-worker.ts +39 -13
  417. package/src/tools/browser.ts +17 -7
  418. package/src/tools/cron.ts +10 -1
  419. package/src/tools/debug.ts +16 -8
  420. package/src/tools/descriptors.test.ts +44 -0
  421. package/src/tools/descriptors.ts +50 -6
  422. package/src/tools/eval.ts +15 -0
  423. package/src/tools/fetch.ts +93 -4
  424. package/src/tools/gh.ts +27 -12
  425. package/src/tools/image-gen.ts +20 -6
  426. package/src/tools/index.ts +2 -0
  427. package/src/tools/irc.ts +5 -3
  428. package/src/tools/monitor.ts +9 -1
  429. package/src/tools/output-meta.ts +74 -5
  430. package/src/tools/python.ts +1 -1
  431. package/src/tools/read.ts +26 -37
  432. package/src/tools/recipe/runner.ts +23 -12
  433. package/src/tools/render-utils.ts +2 -2
  434. package/src/tools/report-issue.ts +184 -0
  435. package/src/tools/search.ts +3 -2
  436. package/src/tools/ssh.ts +274 -8
  437. package/src/tools/subagent-render.ts +114 -12
  438. package/src/tools/subagent.ts +118 -5
  439. package/src/tools/tmux-self-injection-guard.ts +3301 -122
  440. package/src/tools/todo-write.ts +41 -3
  441. package/src/tools/tool-catalog.generated.ts +32 -4
  442. package/src/tools/ultragoal-ask-guard.ts +13 -1
  443. package/src/tools/write.ts +42 -3
  444. package/src/tui/tree-list.ts +15 -1
  445. package/src/utils/commit-message-generator.ts +12 -4
  446. package/src/utils/external-editor.ts +21 -7
  447. package/src/utils/file-mentions.ts +75 -7
  448. package/src/utils/git.ts +38 -7
  449. package/src/utils/image-limits.ts +8 -0
  450. package/src/utils/image-loading.ts +1 -1
  451. package/src/utils/open.ts +73 -4
  452. package/src/utils/pasted-image-loading.ts +9 -8
  453. package/src/utils/prompt-suggestion.ts +5 -4
  454. package/src/utils/shell-snapshot.ts +9 -1
  455. package/src/utils/title-generator.ts +72 -22
  456. package/src/web/scrapers/index.ts +3 -1
  457. package/src/web/scrapers/types.ts +8 -0
  458. package/src/web/scrapers/youtube.ts +3 -2
  459. package/src/web/search/index.ts +2 -1
  460. package/src/web/search/providers/codex.ts +29 -32
  461. package/src/web/search/providers/insane.ts +91 -28
  462. package/src/web/search/providers/searxng.ts +62 -10
@@ -1,3 +1,5 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
1
3
  import {
2
4
  classifyStateArgv,
3
5
  STATE_ACTION_NAMES,
@@ -13,8 +15,20 @@ export interface BashAllowedPrefixesCheck {
13
15
 
14
16
  export type BashRestrictionProfile = "workflow" | "read-only";
15
17
 
18
+ /** Session identities and workspace the restricted `gjc` command may name. */
19
+ export interface GjcSessionBinding {
20
+ sessionId?: string | null;
21
+ /** Owner identity already on the tool session. Role-agent ralplan writes use it. */
22
+ credentialSessionId?: string | null;
23
+ /** Session workspace. A `--worktree-root` must be this repo, not a foreign one. */
24
+ worktreeRoot: string;
25
+ /** Child cwd. Relative `--worktree-root` resolves against it, as ralplan does. */
26
+ invocationCwd: string;
27
+ }
28
+
16
29
  export interface BashRestrictionOptions {
17
30
  profile?: BashRestrictionProfile;
31
+ gjcSessionBinding?: GjcSessionBinding;
18
32
  }
19
33
 
20
34
  const SHELL_CONTROL_CHARS = new Set([";", "|", "&", "<", ">", "(", ")"]);
@@ -231,6 +245,33 @@ function isLongOption(word: string, option: string): boolean {
231
245
  return word === option || word.startsWith(`${option}=`);
232
246
  }
233
247
 
248
+ function fileCompilesMagic(words: readonly string[]): boolean {
249
+ const valueOptions = ["--exclude", "--exclude-quiet", "--files-from", "--separator", "--magic-file", "--parameter"];
250
+ for (let index = 1; index < words.length; index += 1) {
251
+ const word = words[index]!;
252
+ if (word === "--") break;
253
+ if (word.startsWith("--")) {
254
+ const equalsAt = word.indexOf("=");
255
+ const name = equalsAt === -1 ? word : word.slice(0, equalsAt);
256
+ if (name.length > 2 && "--compile".startsWith(name)) return true;
257
+ if (equalsAt === -1 && valueOptions.some(option => name.length > 2 && option.startsWith(name))) {
258
+ index += 1;
259
+ }
260
+ } else if (word.startsWith("-")) {
261
+ for (let offset = 1; offset < word.length; offset += 1) {
262
+ const flag = word[offset]!;
263
+ if (flag === "C") return true;
264
+ // Required arguments consume the rest of the cluster, or the next word (even `--`).
265
+ if ("eFfmMP".includes(flag)) {
266
+ if (offset === word.length - 1) index += 1;
267
+ break;
268
+ }
269
+ }
270
+ }
271
+ }
272
+ return false;
273
+ }
274
+
234
275
  function hasShortOption(word: string, option: string): boolean {
235
276
  return word.startsWith("-") && !word.startsWith("--") && word.slice(1).includes(option);
236
277
  }
@@ -239,7 +280,9 @@ function shellQuote(value: string): string {
239
280
  }
240
281
 
241
282
  function resolvedExternalCommand(command: string): string | undefined {
242
- return Bun.which(command) ?? undefined;
283
+ // Bun.which caches a command name until PATH is passed explicitly, so a test
284
+ // or a later session that changes PATH must be visible here.
285
+ return Bun.which(command, { PATH: process.env.PATH ?? "" }) ?? undefined;
243
286
  }
244
287
 
245
288
  function validateReadOnlyCommand(words: readonly string[]): BashAllowedPrefixesCheck {
@@ -254,6 +297,9 @@ function validateReadOnlyCommand(words: readonly string[]): BashAllowedPrefixesC
254
297
  if (isLongOption(option, "--pre") || isLongOption(option, "--pre-glob")) {
255
298
  return { allowed: false, reason: "read-only bash does not allow ripgrep preprocessors" };
256
299
  }
300
+ if (isLongOption(option, "--hostname-bin")) {
301
+ return { allowed: false, reason: "read-only bash does not allow ripgrep hostname commands" };
302
+ }
257
303
  if (isLongOption(option, "--search-zip") || hasShortOption(option, "z")) {
258
304
  return { allowed: false, reason: "read-only bash does not allow ripgrep compressed-file subprocesses" };
259
305
  }
@@ -268,23 +314,161 @@ function validateReadOnlyCommand(words: readonly string[]): BashAllowedPrefixesC
268
314
  }
269
315
  }
270
316
 
317
+ if (command === "file" && fileCompilesMagic(words)) {
318
+ return { allowed: false, reason: "read-only bash does not allow file magic compilation" };
319
+ }
320
+
271
321
  const gitWrite = rejectGitHostAccess(words);
272
322
  if (gitWrite) return gitWrite;
273
323
 
274
324
  return { allowed: true };
275
325
  }
276
326
 
327
+ const GIT_DIFF_FAMILY_SUBCOMMANDS = new Set(["blame", "diff", "log", "show"]);
328
+
329
+ function gitOptionName(word: string): string {
330
+ const equalsAt = word.indexOf("=");
331
+ return equalsAt === -1 ? word : word.slice(0, equalsAt);
332
+ }
333
+
334
+ /** Enabling forms only. `--no-ext-diff` and `--no-textconv` stay allowed. */
335
+ function gitEnablesExternalHelper(word: string): boolean {
336
+ if (!word.startsWith("--")) return false;
337
+ const name = gitOptionName(word);
338
+ return name === "--ext-diff" || name === "--textconv";
339
+ }
340
+
277
341
  function rejectGitHostAccess(words: readonly string[]): BashAllowedPrefixesCheck | undefined {
278
342
  if (words[0] !== "git") return undefined;
279
343
  for (const option of optionWords(words)) {
280
344
  if (isLongOption(option, "--output") || isLongOption(option, "--no-index")) {
281
345
  return { allowed: false, reason: "restricted bash does not allow git --output or --no-index" };
282
346
  }
347
+ if (gitEnablesExternalHelper(option)) {
348
+ return {
349
+ allowed: false,
350
+ reason: "restricted bash does not allow git external diff or textconv helpers",
351
+ };
352
+ }
283
353
  }
284
354
  return undefined;
285
355
  }
286
356
 
287
- function validateMatchedGjcCommand(words: readonly string[]): BashAllowedPrefixesCheck {
357
+ /**
358
+ * Diff-family git commands run `diff.external`, `GIT_EXTERNAL_DIFF`, and textconv
359
+ * filters unless these flags are present. Place them immediately after the
360
+ * subcommand so a later value-taking option cannot consume them and they stay
361
+ * before a `--` pathspec. The gate rejects a later `--ext-diff` or `--textconv`,
362
+ * which would otherwise win.
363
+ */
364
+ function disableGitExternalHelpers(commandName: string, argv: readonly string[]): string[] {
365
+ if (commandName !== "git" || !GIT_DIFF_FAMILY_SUBCOMMANDS.has(argv[1] ?? "")) return [...argv];
366
+ const next = [...argv];
367
+ next.splice(2, 0, "--no-ext-diff", "--no-textconv");
368
+ return next;
369
+ }
370
+
371
+ const GJC_SESSION_BIND_REASON = "restricted role-agent bash binds gjc to the current session";
372
+ const GJC_WORKTREE_BIND_REASON = "restricted role-agent bash binds gjc --worktree-root to the current repository";
373
+
374
+ function separateFlagValues(words: readonly string[], flag: string): string[] {
375
+ const values: string[] = [];
376
+ for (let index = 0; index < words.length; index += 1) {
377
+ if (words[index] !== flag) continue;
378
+ values.push(words[index + 1] ?? "");
379
+ index += 1;
380
+ }
381
+ return values;
382
+ }
383
+
384
+ function boundSessionIds(binding: GjcSessionBinding): ReadonlySet<string> {
385
+ const ids = new Set<string>();
386
+ for (const value of [binding.sessionId, binding.credentialSessionId]) {
387
+ if (typeof value !== "string") continue;
388
+ const trimmed = value.trim();
389
+ if (trimmed) ids.add(trimmed);
390
+ }
391
+ return ids;
392
+ }
393
+
394
+ function inlineStateSessionIds(words: readonly string[]): string[] {
395
+ if (words[1] !== "state") return [];
396
+ const ids: string[] = [];
397
+ for (const input of classifyStateArgv(words.slice(2)).inputs) {
398
+ if (input.kind !== "inline") continue;
399
+ let payload: unknown;
400
+ try {
401
+ payload = JSON.parse(input.raw);
402
+ } catch {
403
+ continue;
404
+ }
405
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) continue;
406
+ const sessionId = (payload as Record<string, unknown>).session_id;
407
+ if (typeof sessionId === "string") ids.push(sessionId);
408
+ }
409
+ return ids;
410
+ }
411
+
412
+ function gitCommonDir(directory: string): string | undefined {
413
+ const result = Bun.spawnSync(["git", "-C", directory, "rev-parse", "--git-common-dir"], {
414
+ stdout: "pipe",
415
+ stderr: "ignore",
416
+ });
417
+ if (result.exitCode !== 0) return undefined;
418
+ const text = result.stdout.toString().trim();
419
+ if (!text) return undefined;
420
+ const absolute = path.isAbsolute(text) ? text : path.resolve(directory, text);
421
+ try {
422
+ return fs.realpathSync(absolute);
423
+ } catch {
424
+ return undefined;
425
+ }
426
+ }
427
+
428
+ /** Same directory, or another worktree of the same git common dir (isolated tasks). */
429
+ function sameRepository(workspace: string, target: string): boolean {
430
+ let workspaceReal: string;
431
+ let targetReal: string;
432
+ try {
433
+ workspaceReal = fs.realpathSync(workspace);
434
+ targetReal = fs.realpathSync(target);
435
+ } catch {
436
+ return false;
437
+ }
438
+ if (workspaceReal === targetReal) return true;
439
+ const workspaceCommon = gitCommonDir(workspaceReal);
440
+ const targetCommon = gitCommonDir(targetReal);
441
+ return workspaceCommon !== undefined && workspaceCommon === targetCommon;
442
+ }
443
+
444
+ function worktreeSelectorAllowed(binding: GjcSessionBinding, rawTarget: string): boolean {
445
+ const trimmed = rawTarget.trim();
446
+ if (!trimmed) return false;
447
+ const resolved = path.isAbsolute(trimmed) ? trimmed : path.resolve(binding.invocationCwd, trimmed);
448
+ return sameRepository(binding.worktreeRoot, resolved);
449
+ }
450
+
451
+ function rejectUnboundGjcCommand(
452
+ words: readonly string[],
453
+ options: BashRestrictionOptions,
454
+ ): BashAllowedPrefixesCheck | undefined {
455
+ const binding = options.gjcSessionBinding;
456
+ if (!binding) return undefined;
457
+ const allowed = boundSessionIds(binding);
458
+ const sessionSelectors = [...separateFlagValues(words, "--session-id"), ...inlineStateSessionIds(words)];
459
+ if (sessionSelectors.some(value => !allowed.has(value.trim()))) {
460
+ return { allowed: false, reason: GJC_SESSION_BIND_REASON };
461
+ }
462
+ if (separateFlagValues(words, "--worktree-root").some(value => !worktreeSelectorAllowed(binding, value))) {
463
+ return { allowed: false, reason: GJC_WORKTREE_BIND_REASON };
464
+ }
465
+ return undefined;
466
+ }
467
+
468
+ function validateMatchedGjcCommand(
469
+ words: readonly string[],
470
+ options: BashRestrictionOptions,
471
+ ): BashAllowedPrefixesCheck {
288
472
  const gitWrite = rejectGitHostAccess(words);
289
473
  if (gitWrite) return gitWrite;
290
474
  if (words[0] !== "gjc") return { allowed: true };
@@ -293,7 +477,7 @@ function validateMatchedGjcCommand(words: readonly string[]): BashAllowedPrefixe
293
477
  if (!words.includes("--write")) {
294
478
  return { allowed: false, reason: "restricted role-agent bash only allows `gjc ralplan --write ...`" };
295
479
  }
296
- return { allowed: true };
480
+ return rejectUnboundGjcCommand(words, options) ?? { allowed: true };
297
481
  }
298
482
 
299
483
  if (words[1] === "state") {
@@ -311,10 +495,10 @@ function validateMatchedGjcCommand(words: readonly string[]): BashAllowedPrefixe
311
495
  reason: `restricted role-agent bash does not allow \`gjc state ${classification.parsed.action}\``,
312
496
  };
313
497
  }
314
- return { allowed: true };
498
+ return rejectUnboundGjcCommand(words, options) ?? { allowed: true };
315
499
  }
316
500
 
317
- return { allowed: true };
501
+ return rejectUnboundGjcCommand(words, options) ?? { allowed: true };
318
502
  }
319
503
 
320
504
  function commandAllowedPrefixesReason(normalizedPrefixes: readonly string[], options: BashRestrictionOptions): string {
@@ -324,6 +508,30 @@ function commandAllowedPrefixesReason(normalizedPrefixes: readonly string[], opt
324
508
  : `restricted role-agent bash only allows commands starting with: ${prefixList}`;
325
509
  }
326
510
 
511
+ /**
512
+ * Workflow restricted bash keeps the lexical prefix check, then runs the resolved
513
+ * executable. Callers also skip the shell snapshot and shell prefix, because a
514
+ * snapshot function can be named for that absolute path.
515
+ */
516
+ export function normalizeWorkflowBashCommand(
517
+ command: string,
518
+ allowedPrefixes: readonly string[] | undefined,
519
+ options: BashRestrictionOptions = {},
520
+ ): string | undefined {
521
+ if (options.profile === "read-only") return undefined;
522
+ const gate = checkBashAllowedPrefixes(command, allowedPrefixes, { ...options, profile: "workflow" });
523
+ if (!gate.allowed) return undefined;
524
+ const parsed = parseShellWords(command.trim());
525
+ if (parsed.reason || parsed.words.length === 0) return undefined;
526
+ const [head, ...rest] = parsed.words;
527
+ if (!head) return undefined;
528
+ const resolvedHead = resolvedExternalCommand(head);
529
+ if (!resolvedHead) return undefined;
530
+ return disableGitExternalHelpers(head, [resolvedHead, ...rest])
531
+ .map(shellQuote)
532
+ .join(" ");
533
+ }
534
+
327
535
  export function normalizeReadOnlyBashCommand(command: string): string | undefined {
328
536
  const parsed = parseShellWords(command.trim());
329
537
  if (parsed.reason || parsed.words.length === 0) return undefined;
@@ -360,5 +568,5 @@ export function checkBashAllowedPrefixes(
360
568
  if (options.profile === "read-only") {
361
569
  return validateReadOnlyCommand(parsed.words);
362
570
  }
363
- return validateMatchedGjcCommand(parsed.words);
571
+ return validateMatchedGjcCommand(parsed.words, options);
364
572
  }
@@ -12,6 +12,7 @@ import {
12
12
  visibleWidth,
13
13
  } from "@gajae-code/tui";
14
14
  import { logger, sanitizeText } from "@gajae-code/utils";
15
+ import { projectDeclaredSpawnHookNames, projectSafeHomeOverride } from "@gajae-code/utils/shell-config";
15
16
  import type { Terminal as XtermTerminalType } from "@xterm/headless";
16
17
  import { Settings } from "../config/settings";
17
18
  import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
@@ -323,6 +324,14 @@ export interface InteractivePtyControls {
323
324
  detachForegroundCancellation: () => void;
324
325
  }
325
326
 
327
+ function ptyUnsetHookNames(explicit: Record<string, string> | undefined): string[] {
328
+ return projectDeclaredSpawnHookNames().filter(name => !Object.hasOwn(explicit ?? {}, name));
329
+ }
330
+
331
+ function ptyHomeOverride(explicit: Record<string, string> | undefined): Record<string, string> {
332
+ return projectSafeHomeOverride(explicit);
333
+ }
334
+
326
335
  export async function runInteractiveBashPty(
327
336
  ui: NonNullable<AgentToolContext["ui"]>,
328
337
  options: {
@@ -422,8 +431,11 @@ export async function runInteractiveBashPty(
422
431
  command: options.command,
423
432
  cwd: options.cwd,
424
433
  timeoutMs: options.timeoutMs,
425
- env: { ...NON_INTERACTIVE_ENV, ...options.env },
426
- unsetEnv: options.unsetEnv,
434
+ // The PTY child inherits the process env; drop project hook variables
435
+ // unless this call sets them explicitly. A dropped HOME is replaced by
436
+ // the trusted account home so the shell and git still have one.
437
+ env: { ...ptyHomeOverride(options.env), ...NON_INTERACTIVE_ENV, ...options.env },
438
+ unsetEnv: [...(options.unsetEnv ?? []), ...ptyUnsetHookNames(options.env)],
427
439
  signal: processAbortController.signal,
428
440
  cols: initialPtySize.cols,
429
441
  rows: initialPtySize.rows,
@@ -1,3 +1,4 @@
1
+ import type * as fsTypes from "node:fs";
1
2
  import * as fs from "node:fs/promises";
2
3
  import * as path from "node:path";
3
4
  import { isEnoent } from "@gajae-code/utils";
@@ -40,7 +41,7 @@ export interface InternalUrlExpansionOptions {
40
41
  * Resolve a single skill:// URL to its absolute filesystem path.
41
42
  * Does NOT read file content or verify existence.
42
43
  */
43
- export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): string {
44
+ export function resolveSkillUrl(url: string, skills: readonly Skill[]): { skill: Skill; lexicalPath: string } {
44
45
  const parsed = /^skill:\/\/([^/?#]+)(\/[^?#]*)?(?:[?#].*)?$/.exec(url);
45
46
  if (!parsed) {
46
47
  throw new ToolError(`Invalid skill:// URL: ${url}`);
@@ -72,7 +73,7 @@ export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): st
72
73
  const hasRelativePath = rawPath !== "" && rawPath !== "/";
73
74
 
74
75
  if (!hasRelativePath) {
75
- return path.resolve(skill.filePath);
76
+ return { skill, lexicalPath: path.resolve(skill.filePath) };
76
77
  }
77
78
 
78
79
  let relativePath: string;
@@ -95,7 +96,11 @@ export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): st
95
96
  throw new ToolError("Path traversal is not allowed in skill:// URLs");
96
97
  }
97
98
 
98
- return resolvedPath;
99
+ return { skill, lexicalPath: resolvedPath };
100
+ }
101
+
102
+ export function resolveSkillUrlToPath(url: string, skills: readonly Skill[]): string {
103
+ return resolveSkillUrl(url, skills).lexicalPath;
99
104
  }
100
105
 
101
106
  /**
@@ -150,7 +155,7 @@ async function canonicalLocalPath(localRoot: string, lexicalPath: string): Promi
150
155
  const missing: string[] = [];
151
156
  let cursor = lexicalPath;
152
157
  for (;;) {
153
- let stat: Awaited<ReturnType<typeof fs.lstat>> | undefined;
158
+ let stat: fsTypes.Stats | undefined;
154
159
  try {
155
160
  stat = await fs.lstat(cursor);
156
161
  } catch (error) {
@@ -177,6 +182,53 @@ async function canonicalLocalPath(localRoot: string, lexicalPath: string): Promi
177
182
  }
178
183
  }
179
184
 
185
+ const SKILL_ESCAPE = "Path traversal is not allowed in skill:// URLs";
186
+
187
+ /** Real path of `target`, or the real ancestor plus the missing suffix. */
188
+ async function canonicalPathOrAncestor(target: string, escapeMessage: string): Promise<string> {
189
+ try {
190
+ return await fs.realpath(target);
191
+ } catch (error) {
192
+ if (!isEnoent(error)) throw error;
193
+ }
194
+
195
+ const missing: string[] = [];
196
+ let cursor = target;
197
+ for (;;) {
198
+ let stat: fsTypes.Stats | undefined;
199
+ try {
200
+ stat = await fs.lstat(cursor);
201
+ } catch (error) {
202
+ if (!isEnoent(error)) throw error;
203
+ }
204
+ if (stat) {
205
+ try {
206
+ return path.join(await fs.realpath(cursor), ...missing);
207
+ } catch (error) {
208
+ if (isEnoent(error)) throw new ToolError(escapeMessage);
209
+ throw error;
210
+ }
211
+ }
212
+ const parent = path.dirname(cursor);
213
+ if (parent === cursor) throw new ToolError(escapeMessage);
214
+ missing.unshift(path.basename(cursor));
215
+ cursor = parent;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Keep the lexical skill path, then bind the shell to the real path inside the skill directory.
221
+ * A missing file under a real in-skill ancestor still expands.
222
+ */
223
+ async function canonicalSkillExpansionPath(baseDir: string, lexicalPath: string): Promise<string> {
224
+ const realBase = await canonicalPathOrAncestor(baseDir, SKILL_ESCAPE);
225
+ const realTarget = await canonicalPathOrAncestor(lexicalPath, SKILL_ESCAPE);
226
+ if (localPathEscapes(realBase, realTarget)) {
227
+ throw new ToolError(SKILL_ESCAPE);
228
+ }
229
+ return realTarget;
230
+ }
231
+
180
232
  function extractScheme(url: string): SupportedInternalScheme | undefined {
181
233
  const match = /^([a-z][a-z0-9+.-]*):\/\//i.exec(url);
182
234
  if (!match) return undefined;
@@ -211,7 +263,8 @@ async function resolveInternalUrlToPath(
211
263
  }
212
264
 
213
265
  if (scheme === "skill") {
214
- return resolveSkillUrlToPath(url, skills);
266
+ const { skill, lexicalPath } = resolveSkillUrl(url, skills);
267
+ return canonicalSkillExpansionPath(skill.baseDir, lexicalPath);
215
268
  }
216
269
 
217
270
  // Managed artifact and agent resources carry session authority. Expanding
package/src/tools/bash.ts CHANGED
@@ -64,7 +64,12 @@ import { CachedOutputBlock, getOutputBlockContentWidth } from "../tui/output-blo
64
64
  import { truncateToWidth } from "../tui/utils";
65
65
  import { getSixelLineMask } from "../utils/sixel";
66
66
  import type { ToolSession } from ".";
67
- import { checkBashAllowedPrefixes, normalizeReadOnlyBashCommand } from "./bash-allowed-prefixes";
67
+ import { AwaitingInputDetector, type AwaitingInputSignal } from "./awaiting-input";
68
+ import {
69
+ checkBashAllowedPrefixes,
70
+ normalizeReadOnlyBashCommand,
71
+ normalizeWorkflowBashCommand,
72
+ } from "./bash-allowed-prefixes";
68
73
  import { applyBashFixups } from "./bash-command-fixup";
69
74
  import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
70
75
  import { checkBashInterception } from "./bash-interceptor";
@@ -578,6 +583,12 @@ export interface BashToolDetails {
578
583
  foldReason?: FoldReason;
579
584
  /** The accepted model-declared activity declaration, when the call carried one. */
580
585
  activity?: BashActivityDeclaration;
586
+ /**
587
+ * Set on a running (partial) update when the foreground command printed an
588
+ * interactive auth prompt and then went quiet. Carries only the prompt kind,
589
+ * never the URL or code.
590
+ */
591
+ awaitingInput?: AwaitingInputSignal;
581
592
  async?: {
582
593
  state: "running" | "completed" | "failed";
583
594
  jobId: string;
@@ -1125,6 +1136,17 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1125
1136
  const spillThreshold = resolveBashOutputSinkTailBytes(this.session.settings);
1126
1137
  const headBytes = resolveBashOutputSinkHeadBytes(this.session.settings);
1127
1138
  const tailBuffer = new TailBuffer(spillThreshold);
1139
+ // Only a foreground wait blocks the turn; once folded or started in the
1140
+ // background the agent is free and this tool call has already settled.
1141
+ const awaitingInput = new AwaitingInputDetector({
1142
+ onDetect: signal => {
1143
+ if (backgrounded) return;
1144
+ void options.onUpdate?.({
1145
+ content: [{ type: "text", text: latestText }],
1146
+ details: { ...activityDetails, awaitingInput: signal },
1147
+ });
1148
+ },
1149
+ });
1128
1150
 
1129
1151
  let executionResult: BashResult | BashInteractiveResult | undefined;
1130
1152
  try {
@@ -1148,18 +1170,24 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1148
1170
  spillThreshold,
1149
1171
  headBytes,
1150
1172
  oneShot: true,
1151
- ignoreShellPrefix: this.session.bashRestrictionProfile === "read-only",
1152
- disableShellSnapshot: this.session.bashRestrictionProfile === "read-only",
1173
+ ignoreShellPrefix: this.#isolateRestrictedShell(),
1174
+ disableShellSnapshot: this.#isolateRestrictedShell(),
1153
1175
  onChunk: chunk => {
1154
1176
  tailBuffer.append(chunk);
1155
1177
  latestText = tailBuffer.text();
1156
1178
  void reportProgress(latestText, runningDetails(jobId));
1157
1179
  },
1158
- onRawChunk: chunk => manager.appendOutput(jobId, chunk),
1180
+ // onChunk is throttled and drops chunks inside the window; the
1181
+ // detector must see every chunk, so it reads the raw stream.
1182
+ onRawChunk: chunk => {
1183
+ manager.appendOutput(jobId, chunk);
1184
+ awaitingInput.push(chunk);
1185
+ },
1159
1186
  onMinimizedSave: async originalText =>
1160
1187
  saveBashOriginalArtifact(options.artifactPublication, originalText),
1161
1188
  });
1162
1189
  executionResult = result;
1190
+ awaitingInput.dispose();
1163
1191
  const finalResult = this.#buildCompletedResult(result, options.timeoutSec, {
1164
1192
  requestedTimeoutSec: options.requestedTimeoutSec,
1165
1193
  notices: options.notices,
@@ -1173,6 +1201,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1173
1201
  await reportProgress(finalText, completedDetails(jobId));
1174
1202
  return finalText;
1175
1203
  } catch (error) {
1204
+ awaitingInput.dispose();
1176
1205
  const message = error instanceof Error ? error.message : String(error);
1177
1206
  latestText = message;
1178
1207
  options.onCompletion?.();
@@ -1323,6 +1352,11 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1323
1352
  * helper after `AgentSession` has applied the public-tool permission gate, so
1324
1353
  * Monitor inherits Bash's cwd / env / artifact / interceptor pipeline 1:1.
1325
1354
  */
1355
+ /** Read-only and workflow allowlists must not source a snapshot or a shell prefix. */
1356
+ #isolateRestrictedShell(): boolean {
1357
+ return this.session.bashRestrictionProfile === "read-only" || (this.session.bashAllowedPrefixes?.length ?? 0) > 0;
1358
+ }
1359
+
1326
1360
  async #prepareBashExecution(
1327
1361
  input: { command: string; env?: Record<string, string>; timeout?: number; cwd?: string },
1328
1362
  ctx?: AgentToolContext,
@@ -1379,9 +1413,23 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1379
1413
  }
1380
1414
  if (allowedPrefixes && allowedPrefixes.length > 0) {
1381
1415
  const commandsToCheck = rawCommand === command ? [command] : [rawCommand, command];
1416
+ const invocationCwd =
1417
+ cwd && !cwd.includes("://") && !cwd.includes("local:/")
1418
+ ? resolveToCwd(cwd, this.session.cwd)
1419
+ : this.session.cwd;
1382
1420
  for (const commandToCheck of commandsToCheck) {
1383
1421
  const allowlist = checkBashAllowedPrefixes(commandToCheck, allowedPrefixes, {
1384
1422
  profile: this.session.bashRestrictionProfile,
1423
+ ...(this.session.bashRestrictionProfile === "read-only"
1424
+ ? {}
1425
+ : {
1426
+ gjcSessionBinding: {
1427
+ sessionId: this.session.getSessionId?.() ?? null,
1428
+ credentialSessionId: this.session.getCredentialSessionId?.() ?? null,
1429
+ worktreeRoot: this.session.cwd,
1430
+ invocationCwd,
1431
+ },
1432
+ }),
1385
1433
  });
1386
1434
  if (!allowlist.allowed) {
1387
1435
  throw new ToolError(allowlist.reason ?? "Command blocked by restricted role-agent bash allowlist.");
@@ -1433,19 +1481,6 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1433
1481
  ...internalUrlOptions,
1434
1482
  ensureLocalParentDirs: this.session.bashRestrictionProfile !== "read-only",
1435
1483
  });
1436
- // Issue #5039: refuse tmux write verbs whose target resolves to this
1437
- // agent's own tmux pane — such bytes forge a human `user` turn in the
1438
- // transcript. This is after command expansion but before any spawn, so
1439
- // blocked attempts inject no bytes and create no transcript turn.
1440
- if (this.session.bashRestrictionProfile !== "read-only") {
1441
- const injection = await checkTmuxSelfInjection(command, {
1442
- env: process.env,
1443
- cwd: this.session.cwd,
1444
- });
1445
- if (injection.block) {
1446
- throw new ToolError(injection.reason ?? "Blocked: tmux self-pane injection.");
1447
- }
1448
- }
1449
1484
  const expandedEnv = env
1450
1485
  ? Object.fromEntries(
1451
1486
  await Promise.all(
@@ -1527,6 +1562,45 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1527
1562
  if (this.session.bashRestrictionProfile === "read-only" || (allowedPrefixes?.length ?? 0) > 0) {
1528
1563
  await assertCwdInsideWorkspace(this.session.cwd, commandCwd);
1529
1564
  }
1565
+ // Issue #5039: refuse tmux write verbs whose target resolves to this
1566
+ // agent's own tmux pane — such bytes forge a human `user` turn in the
1567
+ // transcript. This is after command/env/cwd preparation but before any
1568
+ // spawn, so guard discovery matches execution without trusting caller env
1569
+ // for the agent's tmux identity.
1570
+ if (this.session.bashRestrictionProfile !== "read-only") {
1571
+ const shellConfigEnv = this.session.settings.getShellConfig().env;
1572
+ const injection = await checkTmuxSelfInjection(command, {
1573
+ env: process.env,
1574
+ cwd: commandCwd,
1575
+ executionEnv: { ...process.env, ...shellConfigEnv, ...resolvedEnv },
1576
+ });
1577
+ if (injection.block) {
1578
+ throw new ToolError(injection.reason ?? "Blocked: tmux self-pane injection.");
1579
+ }
1580
+ }
1581
+ // Direct master spawn parses the original command itself and does not source
1582
+ // a shell snapshot. Every other restricted workflow command is rebound here,
1583
+ // after the allowlist and interceptor have seen the caller's text.
1584
+ if (
1585
+ !directMasterSpawn &&
1586
+ this.session.bashRestrictionProfile !== "read-only" &&
1587
+ allowedPrefixes &&
1588
+ allowedPrefixes.length > 0
1589
+ ) {
1590
+ const normalizedWorkflowCommand = normalizeWorkflowBashCommand(command, allowedPrefixes, {
1591
+ profile: "workflow",
1592
+ gjcSessionBinding: {
1593
+ sessionId: this.session.getSessionId?.() ?? null,
1594
+ credentialSessionId: this.session.getCredentialSessionId?.() ?? null,
1595
+ worktreeRoot: this.session.cwd,
1596
+ invocationCwd: commandCwd,
1597
+ },
1598
+ });
1599
+ if (!normalizedWorkflowCommand) {
1600
+ throw new ToolError("Restricted role-agent bash command could not be normalized safely.");
1601
+ }
1602
+ command = normalizedWorkflowCommand;
1603
+ }
1530
1604
 
1531
1605
  const requestedTimeoutSec = input.timeout ?? 300;
1532
1606
  const timeoutSec = clampTimeout("bash", requestedTimeoutSec);
@@ -1710,9 +1784,8 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
1710
1784
  spillThreshold,
1711
1785
  headBytes,
1712
1786
  oneShot: true,
1713
- ignoreShellPrefix: this.session.bashRestrictionProfile === "read-only" || prepared.directMasterSpawn,
1714
- disableShellSnapshot:
1715
- this.session.bashRestrictionProfile === "read-only" || prepared.directMasterSpawn,
1787
+ ignoreShellPrefix: this.#isolateRestrictedShell() || prepared.directMasterSpawn,
1788
+ disableShellSnapshot: this.#isolateRestrictedShell() || prepared.directMasterSpawn,
1716
1789
  onChunk: chunk => {
1717
1790
  tailBuffer.append(chunk);
1718
1791
  void reportProgress(tailBuffer.text(), {
@@ -2637,6 +2710,14 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
2637
2710
 
2638
2711
  // Track output for streaming updates (tail only)
2639
2712
  const tailBuffer = new TailBuffer(spillThreshold);
2713
+ const awaitingInput = new AwaitingInputDetector({
2714
+ onDetect: signal => {
2715
+ void onUpdate?.({
2716
+ content: [{ type: "text", text: tailBuffer.text() }],
2717
+ details: { ...(activity === undefined ? {} : { activity }), awaitingInput: signal },
2718
+ });
2719
+ },
2720
+ });
2640
2721
 
2641
2722
  const artifactPublisher = createBashArtifactPublisher(artifactPublication);
2642
2723
 
@@ -2819,11 +2900,15 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
2819
2900
  onOutput: chunk => {
2820
2901
  ptyOutputSeen = true;
2821
2902
  if (ptyJobId) ownedManager?.appendOutput(ptyJobId, chunk);
2903
+ awaitingInput.push(chunk);
2822
2904
  },
2823
- }).catch(error => {
2824
- if (ptyAdmission) ownedManager?.releaseCapacity(ptyAdmission);
2825
- throw error;
2826
2905
  })
2906
+ .catch(error => {
2907
+ if (ptyAdmission) ownedManager?.releaseCapacity(ptyAdmission);
2908
+ throw error;
2909
+ })
2910
+ // Completion, error, and a fold all settle this foreground wait.
2911
+ .finally(() => awaitingInput.dispose())
2827
2912
  : await executeBash(command, {
2828
2913
  cwd: commandCwd,
2829
2914
  settings: this.session.settings,
@@ -2837,10 +2922,12 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
2837
2922
  spillThreshold,
2838
2923
  headBytes,
2839
2924
  onChunk: streamTailUpdates(tailBuffer, onUpdate),
2925
+ // onChunk is throttled and drops chunks; the detector needs all of them.
2926
+ onRawChunk: chunk => awaitingInput.push(chunk),
2840
2927
  onMinimizedSave: async originalText => saveBashOriginalArtifact(artifactPublication, originalText),
2841
- ignoreShellPrefix: this.session.bashRestrictionProfile === "read-only" || directMasterSpawn,
2842
- disableShellSnapshot: this.session.bashRestrictionProfile === "read-only" || directMasterSpawn,
2843
- });
2928
+ ignoreShellPrefix: this.#isolateRestrictedShell() || directMasterSpawn,
2929
+ disableShellSnapshot: this.#isolateRestrictedShell() || directMasterSpawn,
2930
+ }).finally(() => awaitingInput.dispose());
2844
2931
  ptyFoldUnregister?.();
2845
2932
  // Folded: the foreground was settled by the fold, so return the background
2846
2933
  // -start result verbatim instead of presenting a half-finished command.