@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
@@ -0,0 +1,40 @@
1
+ import { logger } from "@gajae-code/utils";
2
+ import type { SessionManager } from "../session/session-manager";
3
+ import { DEFAULT_ARTIFACT_MAX_BYTES, truncateHeadBytes } from "../session/streaming-output";
4
+
5
+ const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
6
+ const ASYNC_PREVIEW_MAX_CHARS = 4_000;
7
+
8
+ export async function formatAsyncResultForFollowUp(
9
+ sessionManager: SessionManager,
10
+ result: string,
11
+ allowArtifact = true,
12
+ ): Promise<string> {
13
+ if (result.length <= ASYNC_INLINE_RESULT_MAX_CHARS) return result;
14
+ const preview = `${result.slice(0, ASYNC_PREVIEW_MAX_CHARS)}\n\n[Output truncated. Showing first ${ASYNC_PREVIEW_MAX_CHARS.toLocaleString()} characters.]`;
15
+ // Denied owned deliveries must not leave unreferenced output artifacts.
16
+ if (!allowArtifact) return preview;
17
+ try {
18
+ const artifactId = await sessionManager.saveArtifact(result, "async");
19
+ if (artifactId) {
20
+ const bytes = Buffer.byteLength(result, "utf8");
21
+ const omittedBytes =
22
+ bytes > DEFAULT_ARTIFACT_MAX_BYTES
23
+ ? bytes - truncateHeadBytes(result, DEFAULT_ARTIFACT_MAX_BYTES).bytes
24
+ : 0;
25
+ const notice = omittedBytes
26
+ ? `Saved completion output is incomplete; omitted ${omittedBytes} UTF-8 bytes`
27
+ : "Saved completion output";
28
+ return `${preview}\n${notice}: artifact://${artifactId}`;
29
+ }
30
+ } catch (error) {
31
+ try {
32
+ logger.warn("Failed to persist async follow-up artifact", {
33
+ error: error instanceof Error ? error.message : String(error),
34
+ });
35
+ } catch {
36
+ // Diagnostics must not discard a completed job's preview.
37
+ }
38
+ }
39
+ return `${preview}\n[Completion output storage unavailable; only the preview is available here.]`;
40
+ }
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import * as fs from "node:fs";
15
15
  import * as path from "node:path";
16
+ import { isEnoent } from "@gajae-code/utils";
16
17
  import * as git from "../utils/git";
17
18
  import { HARNESS_FILENAME, normalizePathSpec } from "./harness";
18
19
 
@@ -49,10 +50,16 @@ export function getCurrentAutoresearchBranch(workDir: string): Promise<string |
49
50
  * Mission state under `.gjc/**` is intentionally NOT listed here: it is
50
51
  * runtime-owned and only the sanctioned `gjc autoresearch` CLI writes it.
51
52
  */
52
- export function isAutoresearchAuthorizedResearchPath(_cwd: string, rawPath: string): boolean {
53
+ export function isAutoresearchAuthorizedResearchPath(cwd: string, rawPath: string): boolean {
53
54
  const normalized = normalizePathSpec(rawPath);
54
55
  if (normalized === ".") return false;
55
- return normalized === HARNESS_FILENAME;
56
+ if (normalized !== HARNESS_FILENAME) return false;
57
+ try {
58
+ // Writes follow an existing harness symlink into its referent.
59
+ return !fs.lstatSync(path.join(cwd, HARNESS_FILENAME)).isSymbolicLink();
60
+ } catch (error) {
61
+ return isEnoent(error);
62
+ }
56
63
  }
57
64
 
58
65
  /**
@@ -32,7 +32,7 @@ Do not edit product source, manifests, or dependencies. The harness must use exi
32
32
  ### Steps
33
33
 
34
34
  1. Inspect the target. Read source, identify what to measure, decide on the workload.
35
- 2. Write `autoresearch.sh` only when it is an explicitly approved research artifact; otherwise use an existing benchmark command.
35
+ 2. Write or edit `autoresearch.sh` with a direct file tool only when it is an explicitly approved research artifact; do not use Bash, Python, or eval to change the harness. Otherwise use an existing benchmark command.
36
36
  3. Validate it: invoke `bash autoresearch.sh` through the regular `bash` tool. Confirm it exits 0 and emits at least one `METRIC` line. Iterate on the harness until it does.
37
37
  4. Start the mission with `gjc autoresearch` (cold intake: goal, constraints, deliverables, and an explicit mode) or `gjc autoresearch --spec <spec>` (handoff intake). Record the primary metric name and its direction (lower/higher is better) with the mission. The harness baseline is snapshotted at mission start and Phase 2 (the iteration loop) begins.
38
38
 
@@ -9,8 +9,8 @@ const dirCache = new Map<string, fs.Dirent[]>();
9
9
  export type ReadScope = "project" | "user" | "native";
10
10
 
11
11
  export interface FileIdentity {
12
- readonly dev: number;
13
- readonly ino: number;
12
+ readonly dev: bigint;
13
+ readonly ino: bigint;
14
14
  }
15
15
 
16
16
  export interface ReadFileOptions {
@@ -45,7 +45,7 @@ function isWithinOrEqual(root: string, candidate: string): boolean {
45
45
  async function isHomeIdentityStable(options?: ReadFileOptions): Promise<boolean> {
46
46
  if (!options?.isolatedHome || !options.home || !options.homeIdentity) return true;
47
47
  try {
48
- const current = await fs.promises.stat(options.home);
48
+ const current = await fs.promises.stat(options.home, { bigint: true });
49
49
  return current.dev === options.homeIdentity.dev && current.ino === options.homeIdentity.ino;
50
50
  } catch {
51
51
  return false;
@@ -69,7 +69,7 @@ async function isUserAgentIdentityStable(options?: ReadFileOptions): Promise<boo
69
69
  }
70
70
  }
71
71
  try {
72
- const current = await fs.promises.stat(options.userAgentDir);
72
+ const current = await fs.promises.stat(options.userAgentDir, { bigint: true });
73
73
  return current.dev === options.userAgentIdentity.dev && current.ino === options.userAgentIdentity.ino;
74
74
  } catch {
75
75
  return false;
@@ -79,7 +79,7 @@ async function isUserAgentIdentityStable(options?: ReadFileOptions): Promise<boo
79
79
  async function isContainmentRootIdentityStable(options?: ReadFileOptions): Promise<boolean> {
80
80
  if (!options?.isolatedHome || !options.containmentRoot || !options.containmentRootIdentity) return true;
81
81
  try {
82
- const current = await fs.promises.stat(options.containmentRoot);
82
+ const current = await fs.promises.stat(options.containmentRoot, { bigint: true });
83
83
  return sameFileIdentity(current, options.containmentRootIdentity);
84
84
  } catch {
85
85
  return false;
@@ -152,8 +152,8 @@ async function isCurrentIsolatedPath(abs: string, options?: ReadFileOptions): Pr
152
152
  }
153
153
  }
154
154
 
155
- function isSingleLinkRegularFile(stat: fs.Stats): boolean {
156
- return stat.isFile() && !stat.isSymbolicLink() && stat.nlink === 1;
155
+ function isSingleLinkRegularFile(stat: fs.Stats | fs.BigIntStats): boolean {
156
+ return stat.isFile() && !stat.isSymbolicLink() && (stat.nlink === 1n || stat.nlink === 1);
157
157
  }
158
158
 
159
159
  /** Reject hard-linked executable or configuration files in isolated discovery. */
@@ -165,8 +165,16 @@ export async function isSingleLinkRegularFileAt(filePath: string): Promise<boole
165
165
  }
166
166
  }
167
167
 
168
- function sameFileIdentity(left: Pick<fs.Stats, "dev" | "ino">, right: Pick<fs.Stats, "dev" | "ino">): boolean {
169
- return left.dev === right.dev && left.ino === right.ino;
168
+ function sameFileIdentity(
169
+ left: Pick<fs.Stats, "dev" | "ino"> | Pick<fs.BigIntStats, "dev" | "ino">,
170
+ right: Pick<fs.Stats, "dev" | "ino"> | Pick<fs.BigIntStats, "dev" | "ino"> | FileIdentity,
171
+ ): boolean {
172
+ // Convert to bigint if needed for comparison
173
+ const leftDev = typeof left.dev === "bigint" ? left.dev : BigInt(left.dev);
174
+ const leftIno = typeof left.ino === "bigint" ? left.ino : BigInt(left.ino);
175
+ const rightDev = typeof right.dev === "bigint" ? right.dev : BigInt(right.dev);
176
+ const rightIno = typeof right.ino === "bigint" ? right.ino : BigInt(right.ino);
177
+ return leftDev === rightDev && leftIno === rightIno;
170
178
  }
171
179
 
172
180
  /**
@@ -174,13 +182,13 @@ function sameFileIdentity(left: Pick<fs.Stats, "dev" | "ino">, right: Pick<fs.St
174
182
  * Stat first and canonicalize second so a parent replacement in either seam
175
183
  * is detected by the descriptor identity check below.
176
184
  */
177
- async function statAuthorizedPath(abs: string, options?: ReadFileOptions): Promise<fs.Stats | null> {
185
+ async function statAuthorizedPath(abs: string, options?: ReadFileOptions): Promise<fs.Stats | fs.BigIntStats | null> {
178
186
  if (!options?.isolatedHome) return null;
179
187
  if (!(await isHomeIdentityStable(options))) return null;
180
188
  if (!(await isUserAgentIdentityStable(options))) return null;
181
- let authorized: fs.Stats;
189
+ let authorized: fs.Stats | fs.BigIntStats;
182
190
  try {
183
- authorized = await fs.promises.stat(abs);
191
+ authorized = await fs.promises.stat(abs, { bigint: true });
184
192
  } catch {
185
193
  return null;
186
194
  }
@@ -191,18 +199,18 @@ async function validateOpenedPath(
191
199
  handle: fsPromises.FileHandle,
192
200
  abs: string,
193
201
  options: ReadFileOptions | undefined,
194
- authorized: fs.Stats | null,
202
+ authorized: fs.Stats | fs.BigIntStats | null,
195
203
  kind: "file" | "directory",
196
204
  ): Promise<boolean> {
197
205
  if (!options?.isolatedHome) return true;
198
206
  if (!(await isHomeIdentityStable(options))) return false;
199
207
  if (!(await isUserAgentIdentityStable(options))) return false;
200
208
  try {
201
- const opened = await handle.stat();
209
+ const opened = await handle.stat({ bigint: true });
202
210
  const validKind =
203
211
  kind === "file" ? isSingleLinkRegularFile(opened) : opened.isDirectory() && !opened.isSymbolicLink();
204
212
  if (!validKind || authorized === null || !sameFileIdentity(opened, authorized)) return false;
205
- const current = await fs.promises.stat(abs);
213
+ const current = await fs.promises.stat(abs, { bigint: true });
206
214
  if (!sameFileIdentity(opened, current)) return false;
207
215
  return await isCurrentIsolatedPath(abs, options);
208
216
  } catch {
@@ -212,7 +220,7 @@ async function validateOpenedPath(
212
220
 
213
221
  interface OpenedPath {
214
222
  handle: fsPromises.FileHandle;
215
- authorized: fs.Stats | null;
223
+ authorized: fs.Stats | fs.BigIntStats | null;
216
224
  }
217
225
 
218
226
  async function openIsolatedFile(abs: string, options?: ReadFileOptions): Promise<OpenedPath | null> {
@@ -233,6 +241,28 @@ async function openIsolatedFile(abs: string, options?: ReadFileOptions): Promise
233
241
  }
234
242
  }
235
243
 
244
+ /**
245
+ * A lexical path outside the boundary keeps the historical ancestor read.
246
+ * A path inside the boundary must still be inside after realpath, so a
247
+ * repository symlink cannot be opened as its external target.
248
+ */
249
+ async function containedReadPath(abs: string, options?: ReadFileOptions): Promise<string | null> {
250
+ const root = options?.containmentRoot;
251
+ if (!root || options?.isolatedHome) return abs;
252
+ const lexical = path.resolve(abs);
253
+ const boundary = path.resolve(root);
254
+ if (!isWithinOrEqual(boundary, lexical)) return lexical;
255
+ try {
256
+ const [boundaryReal, targetReal] = await Promise.all([
257
+ fs.promises.realpath(boundary),
258
+ fs.promises.realpath(lexical),
259
+ ]);
260
+ return isWithinOrEqual(boundaryReal, targetReal) ? targetReal : null;
261
+ } catch {
262
+ return null;
263
+ }
264
+ }
265
+
236
266
  export async function readFile(filePath: string, options?: ReadFileOptions): Promise<string | null> {
237
267
  let abs: string | null;
238
268
  try {
@@ -242,13 +272,19 @@ export async function readFile(filePath: string, options?: ReadFileOptions): Pro
242
272
  }
243
273
  if (abs === null) return null;
244
274
  if (!(await isCurrentIsolatedPath(abs, options))) return null;
245
- const useCache = !options?.bypassCache && !options?.isolatedHome;
275
+ // A contained read must not share the lexical cache with a different boundary.
276
+ const useCache = !options?.bypassCache && !options?.isolatedHome && !options?.containmentRoot;
246
277
  if (useCache && contentCache.has(abs)) {
247
278
  return contentCache.get(abs) ?? null;
248
279
  }
249
280
  if (!options?.isolatedHome) {
281
+ const readable = await containedReadPath(abs, options);
282
+ if (readable === null) {
283
+ if (useCache) contentCache.set(abs, null);
284
+ return null;
285
+ }
250
286
  try {
251
- const content = await Bun.file(abs).text();
287
+ const content = await Bun.file(readable).text();
252
288
  if (useCache) contentCache.set(abs, content);
253
289
  return content;
254
290
  } catch {
@@ -279,7 +315,7 @@ export async function readFile(filePath: string, options?: ReadFileOptions): Pro
279
315
  /** Capture a stable physical identity for an authorized containment root. */
280
316
  export async function capturePathIdentity(filePath: string): Promise<FileIdentity | null> {
281
317
  try {
282
- const stat = await fs.promises.stat(path.resolve(filePath));
318
+ const stat = await fs.promises.stat(path.resolve(filePath), { bigint: true });
283
319
  return { dev: stat.dev, ino: stat.ino };
284
320
  } catch {
285
321
  return null;
@@ -342,14 +378,14 @@ export async function readFileSize(filePath: string, options?: ReadFileOptions):
342
378
  async function validateDirectoryPath(
343
379
  abs: string,
344
380
  options: ReadFileOptions | undefined,
345
- authorized: fs.Stats | null,
381
+ authorized: fs.Stats | fs.BigIntStats | null,
346
382
  ): Promise<boolean> {
347
383
  if (!options?.isolatedHome) return true;
348
384
  if (authorized === null) return false;
349
385
  if (!(await isHomeIdentityStable(options))) return false;
350
386
  if (!(await isUserAgentIdentityStable(options))) return false;
351
387
  try {
352
- const current = await fs.promises.stat(abs);
388
+ const current = await fs.promises.stat(abs, { bigint: true });
353
389
  return (
354
390
  current.isDirectory() && sameFileIdentity(current, authorized) && (await isCurrentIsolatedPath(abs, options))
355
391
  );
@@ -452,7 +488,7 @@ export async function findRepoRoot(
452
488
  let current = resolvePath(startDir);
453
489
  const stop = stopAt ? resolvePath(stopAt) : undefined;
454
490
  while (true) {
455
- const entries = await readDirEntries(current, options);
491
+ const entries = await readDirEntries(current, { ...options, bypassCache: true });
456
492
  if (entries.some(e => e.name === ".git")) {
457
493
  return current;
458
494
  }
@@ -482,7 +482,7 @@ export async function loadCapabilityForHome<T>(
482
482
  // read another profile's SYSTEM/RULES/AGENTS, skills, commands, hooks,
483
483
  // settings, or executable descriptors.
484
484
  const canonicalHome = await canonicalizeThroughExistingAncestor(resolvedHome);
485
- const homeStats = await fs.stat(canonicalHome);
485
+ const homeStats = await fs.stat(canonicalHome, { bigint: true });
486
486
  const homeIdentity = { dev: homeStats.dev, ino: homeStats.ino };
487
487
  const trustedHome = await canonicalizeThroughExistingAncestor(getTrustedHomeDir());
488
488
  const isolatedHome = canonicalHome !== trustedHome;
@@ -510,7 +510,7 @@ export async function loadCapabilityForHome<T>(
510
510
  "user agent directory",
511
511
  )
512
512
  : path.join(canonicalHome, getConfigDirName(), "agent");
513
- const userAgentStats = await fs.stat(userAgentDir).catch(() => null);
513
+ const userAgentStats = await fs.stat(userAgentDir, { bigint: true }).catch(() => null);
514
514
  const userAgentIdentity = userAgentStats ? { dev: userAgentStats.dev, ino: userAgentStats.ino } : null;
515
515
  await assertExplicitHomeRoots(canonicalHome, cwd, Boolean(options.agentDir), effectiveProviderIds, isolatedHome);
516
516
  const repoRootCandidate = await findRepoRoot(cwd, canonicalHome, {
@@ -18,6 +18,8 @@ export interface RuleFrontmatter {
18
18
  alwaysApply?: boolean;
19
19
  /** New key for TTSR match conditions. */
20
20
  condition?: string | string[];
21
+ /** AST-grep pattern(s) for TTSR AST matching (evaluated on completed tool args). */
22
+ astCondition?: string | string[];
21
23
  /** New key for TTSR stream scope. */
22
24
  scope?: string | string[];
23
25
  /** Per-rule TTSR interrupt mode override. */
@@ -48,6 +50,8 @@ export interface Rule {
48
50
  description?: string;
49
51
  /** Regex condition(s) that can trigger TTSR interruption. */
50
52
  condition?: string[];
53
+ /** AST-grep pattern(s) for TTSR AST matching (evaluated on completed tool args). */
54
+ astCondition?: string[];
51
55
  /** Optional stream scope tokens (for example: text, thinking, tool:edit(*.ts)). */
52
56
  scope?: string[];
53
57
  /** Per-rule TTSR interrupt mode override (falls back to global ttsr.interruptMode). */
@@ -182,17 +186,21 @@ function isLikelyFileGlob(value: string): boolean {
182
186
  }
183
187
 
184
188
  /**
185
- * Parse `condition` + `scope` from rule frontmatter.
189
+ * Parse `condition`, `astCondition`, and `scope` from rule frontmatter.
186
190
  *
187
191
  * - `condition` accepts string or string[]
192
+ * - `astCondition` accepts string or string[] (ast-grep patterns)
188
193
  * - `scope` accepts string or string[]
189
194
  * - legacy `ttsr_trigger` / `ttsrTrigger` are accepted as a `condition` fallback
190
195
  * - condition tokens that look like file globs become scope shorthands:
191
196
  * `*.rs` => `tool:edit(*.rs)`, `tool:write(*.rs)` and a catch-all condition `.*`
192
197
  */
193
- export function parseRuleConditionAndScope(frontmatter: RuleFrontmatter): Pick<Rule, "condition" | "scope"> {
198
+ export function parseRuleConditionAndScope(
199
+ frontmatter: RuleFrontmatter,
200
+ ): Pick<Rule, "condition" | "astCondition" | "scope"> {
194
201
  const rawCondition = frontmatter.condition ?? frontmatter.ttsr_trigger ?? frontmatter.ttsrTrigger;
195
202
  const parsedCondition = normalizeRuleField(rawCondition);
203
+ const parsedAstCondition = normalizeRuleField(frontmatter.astCondition);
196
204
  const parsedScope = normalizeScopeField(frontmatter.scope);
197
205
 
198
206
  const inferredScope: string[] = [];
@@ -214,6 +222,7 @@ export function parseRuleConditionAndScope(frontmatter: RuleFrontmatter): Pick<R
214
222
  const scope = [...(parsedScope ?? []), ...inferredScope];
215
223
  return {
216
224
  condition: condition.length > 0 ? Array.from(new Set(condition)) : undefined,
225
+ astCondition: parsedAstCondition ? Array.from(new Set(parsedAstCondition)) : undefined,
217
226
  scope: scope.length > 0 ? Array.from(new Set(scope)) : undefined,
218
227
  };
219
228
  }
@@ -14,6 +14,7 @@ import {
14
14
  OAuthCredentialSelectorError,
15
15
  type OAuthPinTarget,
16
16
  resolveOAuthStorageProvider,
17
+ selectOrganizationMatches,
17
18
  } from "@gajae-code/ai/core";
18
19
  import { getAgentDir } from "@gajae-code/utils";
19
20
  import { ModelRegistry } from "../config/model-registry";
@@ -25,15 +26,21 @@ import {
25
26
  clearPersistentPinForRemovedRows,
26
27
  } from "../session/account-inventory";
27
28
  import { CREDENTIAL_STORE_UNREADABLE_MESSAGE } from "../session/credential-store-errors";
28
- import { resolveStartupAuthConfig, type StartupAuthConfigSnapshot } from "../session/startup-auth-config";
29
+ import {
30
+ type CredentialPreferenceEntryValue,
31
+ isValidCredentialPreferenceCap,
32
+ parsePreferenceAccount,
33
+ resolveStartupAuthConfig,
34
+ type StartupAuthConfigSnapshot,
35
+ } from "../session/startup-auth-config";
29
36
 
30
- export const ACCOUNTS_ACTIONS = ["list", "check", "pin", "logout"] as const;
37
+ export const ACCOUNTS_ACTIONS = ["list", "check", "pin", "prefer", "logout"] as const;
31
38
  export type AccountsAction = (typeof ACCOUNTS_ACTIONS)[number];
32
39
 
33
40
  export interface AccountsCommandArgs {
34
41
  action: AccountsAction;
35
42
  provider?: string;
36
- selector?: string;
43
+ selectors?: readonly string[];
37
44
  flags: {
38
45
  json?: boolean;
39
46
  persistent?: boolean;
@@ -92,9 +99,10 @@ function selectorFromInput(raw: string | undefined): AuthCredentialSelector {
92
99
  if (/^id:[1-9]\d*$/.test(value)) return { kind: "id", value: value.slice(3) };
93
100
  if (/^email:[^@\s]+@[^@\s]+$/.test(value)) return { kind: "email", value: value.slice(6) };
94
101
  if (/^account:\S+$/.test(value)) return { kind: "account", value: value.slice(8) };
102
+ if (/^org:\S/.test(value)) return { kind: "org", value: value.slice(4).trim() };
95
103
  if (/^[^@\s]+@[^@\s]+$/.test(value)) return { kind: "email", value };
96
104
  throw new AccountsCommandError(
97
- "Pin target must be a bare email or an id:<row-id>, email:<email>, or account:<account-id> selector.",
105
+ "Pin target must be a bare email or an id:<row-id>, email:<email>, account:<account-id>, or org:<organization id or name> selector.",
98
106
  );
99
107
  }
100
108
 
@@ -286,15 +294,171 @@ async function runPin(
286
294
  });
287
295
  }
288
296
 
297
+ /**
298
+ * Split `<account>=<percent>` into the account and its usage cap. The cap follows the whole account, so an `=` before
299
+ * the last `@` belongs to an email local part (`user=2@example.com`), and a suffix that does not start with a digit is
300
+ * part of the account (account ids may end in `=`); a digit-led suffix must be a valid cap.
301
+ */
302
+ function parsePreferArgument(raw: string): { account: string; cap: number | undefined } {
303
+ const separator = raw.lastIndexOf("=");
304
+ const rawCap = separator < 0 || separator < raw.lastIndexOf("@") ? "" : raw.slice(separator + 1).trim();
305
+ if (!/^\d/.test(rawCap)) return { account: raw.trim(), cap: undefined };
306
+ const cap = /^\d+(\.\d+)?$/.test(rawCap) ? Number(rawCap) : Number.NaN;
307
+ if (!isValidCredentialPreferenceCap(cap)) {
308
+ throw new AccountsCommandError(
309
+ `Usage cap in ${JSON.stringify(raw)} must be a percent above 0 and at most 100, e.g. me@example.com=75; no preference was written.`,
310
+ );
311
+ }
312
+ return { account: raw.slice(0, separator).trim(), cap };
313
+ }
314
+
315
+ /**
316
+ * Portable config text for a resolved OAuth row: its email, else its account id, whichever no other OAuth row of the
317
+ * provider shares. Numeric row ids are never persisted as preferences; a row with neither unique value is refused.
318
+ * Each candidate text is written only when {@link parsePreferenceAccount} reads it back as the same selector, so a
319
+ * successful write neither fails the next startup nor retargets the row: the email is written bare when that
320
+ * round-trips, else as `email:<address>` (an address such as `account:team@example.com` would otherwise parse as an
321
+ * account id, and `email:team@example.com` would lose its prefix).
322
+ */
323
+ function portablePreferenceAccount(
324
+ row: CredentialInventoryRecord,
325
+ providerRows: readonly CredentialInventoryRecord[],
326
+ ): string | undefined {
327
+ const oauthRows = providerRows.filter(other => other.credentialKind === "oauth");
328
+ const email = row.email?.trim().toLowerCase();
329
+ if (email && oauthRows.filter(other => other.email?.trim().toLowerCase() === email).length === 1) {
330
+ const selector: AuthCredentialSelector = { kind: "email", value: row.email!.trim() };
331
+ const written = [selector.value, `email:${selector.value}`].find(text => readsBackAs(text, selector));
332
+ if (written) return written;
333
+ }
334
+ const accountId = row.accountId?.trim();
335
+ if (accountId && oauthRows.filter(other => other.accountId?.trim() === accountId).length === 1) {
336
+ const written = `account:${accountId}`;
337
+ if (readsBackAs(written, { kind: "account", value: accountId })) return written;
338
+ }
339
+ return undefined;
340
+ }
341
+
342
+ /** Whether startup parses `text` as exactly `selector`, kind and value. */
343
+ function readsBackAs(text: string, selector: AuthCredentialSelector): boolean {
344
+ const parsed = parsePreferenceAccount(text);
345
+ return parsed?.kind === selector.kind && parsed.value === selector.value;
346
+ }
347
+
348
+ async function writePersistentPreferences(
349
+ provider: string,
350
+ entries: readonly CredentialPreferenceEntryValue[] | undefined,
351
+ ): Promise<void> {
352
+ const settings = await Settings.init();
353
+ await settings.commitAtomicBatchWithCurrent(current => {
354
+ const currentAuth = current.auth === undefined ? {} : asRecord(current.auth);
355
+ const rawPreferences = currentAuth?.credentialPreferences;
356
+ const preferences = rawPreferences === undefined ? {} : asRecord(rawPreferences);
357
+ if (!currentAuth || !preferences) {
358
+ throw new AccountsCommandError("Global auth.credentialPreferences is malformed; no preference was written.");
359
+ }
360
+ const next: Record<string, unknown> = { ...preferences };
361
+ if (entries) next[provider] = entries;
362
+ else delete next[provider];
363
+ return [{ path: "auth.credentialPreferences", op: "set", value: next }];
364
+ });
365
+ }
366
+
367
+ async function runPrefer(
368
+ providerArg: string | undefined,
369
+ accountArgs: readonly string[] | undefined,
370
+ flags: AccountsCommandArgs["flags"],
371
+ ): Promise<void> {
372
+ const provider = canonicalProvider(providerArg);
373
+ const accounts = accountArgs ?? [];
374
+ if (flags.clear && accounts.length > 0) {
375
+ throw new AccountsCommandError("Use either preferred accounts or --clear, not both.");
376
+ }
377
+ if (!flags.clear && accounts.length === 0) {
378
+ throw new AccountsCommandError(
379
+ "Prefer requires one or more accounts in order (e.g. me@example.com shared@example.com=75), or --clear.",
380
+ );
381
+ }
382
+ if (flags.clear) {
383
+ await writePersistentPreferences(provider, undefined);
384
+ if (flags.json) writeJson({ ok: true, provider, cleared: true });
385
+ else writeText([`Cleared preferred accounts for ${provider}; selection uses normal ranking.`]);
386
+ return;
387
+ }
388
+
389
+ const requested = accounts.map(parsePreferArgument);
390
+ await withAuthStorageAndModels(async (storage, modelRegistry, startupAuth) => {
391
+ if (
392
+ !modelRegistry.getConfiguredProviderIds().includes(provider) &&
393
+ modelRegistry.getAll().every(model => model.provider !== provider)
394
+ ) {
395
+ throw new AccountsCommandError(`Provider ${provider} is not configured; no preference was written.`);
396
+ }
397
+ const providerRows = storage.listCredentialInventory(provider);
398
+ const seenIds = new Set<number>();
399
+ const resolved = requested.map(({ account, cap }) => {
400
+ let target: OAuthPinTarget;
401
+ try {
402
+ target = storage.resolveOAuthPinTarget(provider, selectorFromInput(account));
403
+ } catch (error) {
404
+ throw toAccountsCommandError(error) ?? error;
405
+ }
406
+ if (seenIds.has(target.credentialId)) {
407
+ throw new AccountsCommandError(`${account} is listed more than once; no preference was written.`);
408
+ }
409
+ seenIds.add(target.credentialId);
410
+ const row = providerRows.find(candidate => candidate.id === target.credentialId);
411
+ const portable = row ? portablePreferenceAccount(row, providerRows) : undefined;
412
+ if (!portable) {
413
+ throw new AccountsCommandError(
414
+ `${account} has no unique email or account id to store as a preference; no preference was written.`,
415
+ );
416
+ }
417
+ return { account: portable, cap: cap === 100 ? undefined : cap };
418
+ });
419
+ const entries: CredentialPreferenceEntryValue[] = resolved.map(({ account, cap }) =>
420
+ cap === undefined ? account : { [account]: cap },
421
+ );
422
+ await writePersistentPreferences(provider, entries);
423
+ if (flags.json) {
424
+ writeJson({ ok: true, provider, preferences: entries });
425
+ return;
426
+ }
427
+ writeText([
428
+ `Preferred accounts for ${provider}, tried in order:`,
429
+ ...resolved.map(
430
+ ({ account, cap }, index) =>
431
+ ` ${index + 1}. ${account}${cap === undefined ? " (until rate-limited)" : ` (until ${cap}% used)`}`,
432
+ ),
433
+ `Then: remaining accounts by ${startupAuth.credentialRankingMode} ranking. Takes effect for new sessions.`,
434
+ ]);
435
+ });
436
+ }
437
+
289
438
  function accountMatches(row: CredentialInventoryRecord, rawAccount: string): boolean {
290
439
  const value = rawAccount.trim();
291
440
  if (value.length === 0) return false;
292
441
  if (/^[1-9]\d*$/.test(value) && row.id === Number(value)) return true;
293
442
  if (/^id:[1-9]\d*$/i.test(value) && row.id === Number(value.slice(3))) return true;
443
+ // Labels carry the organization name, so a bare email matches every organization of that account.
444
+ if (row.email?.toLowerCase() === value.toLowerCase()) return true;
294
445
  const identity = inventoryIdentity(row);
295
446
  return identity?.toLowerCase() === value.toLowerCase();
296
447
  }
297
448
 
449
+ /**
450
+ * Rows a `--account` value designates. `org:` prefers an exact organization-id match over display-name
451
+ * matches; more than one result is reported as ambiguous by the caller, never resolved by guessing.
452
+ */
453
+ function selectLogoutTargets<T extends { row: CredentialInventoryRecord }>(
454
+ targets: readonly T[],
455
+ rawAccount: string,
456
+ ): T[] {
457
+ const value = rawAccount.trim();
458
+ if (/^org:/i.test(value)) return selectOrganizationMatches(targets, value.slice(4), target => target.row);
459
+ return targets.filter(target => accountMatches(target.row, value));
460
+ }
461
+
298
462
  function removalCandidates(
299
463
  inventory: readonly CredentialInventoryRecord[],
300
464
  targets: readonly CredentialRemovalTarget[],
@@ -335,9 +499,7 @@ async function runLogout(providerArg: string | undefined, flags: AccountsCommand
335
499
  try {
336
500
  const inventory = storage.listCredentialInventory(provider);
337
501
  const targets = removalCandidates(inventory, storage.listCredentialRemovalTargets(provider));
338
- const selected = flags.all
339
- ? targets
340
- : targets.filter(candidate => accountMatches(candidate.row, flags.account ?? ""));
502
+ const selected = flags.all ? targets : selectLogoutTargets(targets, flags.account ?? "");
341
503
  if (selected.length !== 1 && !flags.all) throwLogoutResolutionError(provider, flags.account, targets);
342
504
  if (selected.length === 0) throwLogoutResolutionError(provider, flags.account, targets);
343
505
  const result = storage.removeAuthCredentialsHard(
@@ -383,7 +545,13 @@ export async function runAccountsCommand(cmd: AccountsCommandArgs): Promise<void
383
545
  await runCheck(cmd.provider, cmd.flags);
384
546
  return;
385
547
  case "pin":
386
- await runPin(cmd.provider, cmd.selector, cmd.flags);
548
+ if ((cmd.selectors?.length ?? 0) > 1) {
549
+ throw new AccountsCommandError("Pin takes one selector; use `gjc accounts prefer` for an ordered list.");
550
+ }
551
+ await runPin(cmd.provider, cmd.selectors?.[0], cmd.flags);
552
+ return;
553
+ case "prefer":
554
+ await runPrefer(cmd.provider, cmd.selectors, cmd.flags);
387
555
  return;
388
556
  case "logout":
389
557
  await runLogout(cmd.provider, cmd.flags);
@@ -0,0 +1,12 @@
1
+ export interface AppendSystemPromptCandidate {
2
+ level: "user" | "project";
3
+ path: string;
4
+ }
5
+
6
+ /** First user file. A repository copy is untrusted project text and is never selected. */
7
+ export function selectAppendSystemPromptFile(candidates: readonly AppendSystemPromptCandidate[]): string | undefined {
8
+ for (const candidate of candidates) {
9
+ if (candidate.level === "user") return candidate.path;
10
+ }
11
+ return undefined;
12
+ }
package/src/cli/args.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import * as path from "node:path";
5
5
  import { ThinkingLevel } from "@gajae-code/agent-core";
6
- import { logger } from "@gajae-code/utils";
6
+ import { $pickenv, logger } from "@gajae-code/utils";
7
7
  import { CliParseError } from "@gajae-code/utils/cli";
8
8
  import { parseThinkingLevel } from "../thinking";
9
9
  import { BUILTIN_TOOLS } from "../tools";
@@ -46,8 +46,6 @@ export interface Args {
46
46
  allowHome?: boolean;
47
47
  provider?: string;
48
48
  model?: string;
49
- smol?: string;
50
- slow?: string;
51
49
  plan?: string;
52
50
  mpreset?: string;
53
51
  default?: boolean;
@@ -228,10 +226,6 @@ export function parseArgs(args: string[], authority: ParseArgsAuthority = "local
228
226
  result.provider = takeFlagValue(args, i++, "--provider");
229
227
  } else if (arg === "--model") {
230
228
  result.model = takeFlagValue(args, i++, "--model");
231
- } else if (arg === "--smol") {
232
- result.smol = takeFlagValue(args, i++, "--smol");
233
- } else if (arg === "--slow") {
234
- result.slow = takeFlagValue(args, i++, "--slow");
235
229
  } else if (arg === "--plan") {
236
230
  result.plan = takeFlagValue(args, i++, "--plan");
237
231
  } else if (arg === "--mpreset") {
@@ -427,3 +421,13 @@ export function assertLocalLaunchArgs(parsed: Args): void {
427
421
  if (!launchFlagIsOwnedBy(name, "local")) throw new CliParseError(`Unknown option: ${token}`);
428
422
  }
429
423
  }
424
+
425
+ /**
426
+ * Resolve the ephemeral `plan` model-role override that plan mode switches to.
427
+ *
428
+ * Precedence is CLI `--plan` > `GJC_PLAN_MODEL` > legacy `PI_PLAN_MODEL`.
429
+ * `$pickenv` trims values and treats empty/whitespace as unset.
430
+ */
431
+ export function resolvePlanModelOverride(parsed: Pick<Args, "plan">): string | undefined {
432
+ return parsed.plan ?? $pickenv("GJC_PLAN_MODEL", "PI_PLAN_MODEL");
433
+ }