@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
package/src/tools/ask.ts CHANGED
@@ -953,6 +953,7 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
953
953
  this.session.cwd,
954
954
  { sessionId: this.session.getSessionId?.() ?? null },
955
955
  this.session.getSessionAgentDir?.() ?? this.session.settings.getAgentDir(),
956
+ params,
956
957
  );
957
958
  assertDeepInterviewStructuredResponseWithinLimit(params);
958
959
  // Recheck at execution time as well: direct SDK callers and a phase change
@@ -66,3 +66,67 @@ export async function resolveAstEditPreviewWritePaths(
66
66
  }
67
67
  return resolved;
68
68
  }
69
+
70
+ /** Refusal prefix when a single-file real path leaves its reference root. */
71
+ export const AST_EDIT_OUTSIDE_ROOT_MESSAGE = "AST edit target resolves outside its root";
72
+
73
+ function isEnoent(error: unknown): boolean {
74
+ return (error as NodeJS.ErrnoException | undefined)?.code === "ENOENT";
75
+ }
76
+
77
+ function staysInside(root: string, target: string): boolean {
78
+ const relative = path.relative(root, target);
79
+ return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
80
+ }
81
+
82
+ async function realOrLexical(target: string): Promise<string> {
83
+ try {
84
+ return await fs.realpath(target);
85
+ } catch (error) {
86
+ if (isEnoent(error)) return path.resolve(target);
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Native astEdit canonicalizes one file and writes that real path. Directory
93
+ * scans do not follow links. A lexical path inside cwd uses the real cwd as
94
+ * the reference root; an explicit path outside cwd uses its real parent, so a
95
+ * direct file there still applies. Returns the lexical path that would leave
96
+ * that root, if any.
97
+ */
98
+ export async function astEditSingleFileOutsideReferenceRoot(
99
+ cwd: string,
100
+ candidatePaths: readonly string[],
101
+ signal?: AbortSignal,
102
+ ): Promise<string | undefined> {
103
+ signal?.throwIfAborted();
104
+ const resolvedCwd = path.resolve(cwd);
105
+ const realCwd = await realOrLexical(resolvedCwd);
106
+ for (const candidate of candidatePaths) {
107
+ signal?.throwIfAborted();
108
+ const lexical = path.resolve(resolvedCwd, candidate);
109
+ let followed: string;
110
+ try {
111
+ followed = await fs.realpath(lexical);
112
+ } catch (error) {
113
+ if (isEnoent(error)) continue;
114
+ throw error;
115
+ }
116
+ let stat: Awaited<ReturnType<typeof fs.stat>>;
117
+ try {
118
+ stat = await fs.stat(followed);
119
+ } catch (error) {
120
+ if (isEnoent(error)) continue;
121
+ throw error;
122
+ }
123
+ if (!stat.isFile()) continue;
124
+ const root =
125
+ staysInside(resolvedCwd, lexical) || staysInside(realCwd, lexical)
126
+ ? realCwd
127
+ : await realOrLexical(path.dirname(lexical));
128
+ signal?.throwIfAborted();
129
+ if (!staysInside(root, followed)) return lexical;
130
+ }
131
+ return undefined;
132
+ }
@@ -13,7 +13,11 @@ import { assertWorkflowMutationRawPathsAllowed } from "../skill-state/workflow-m
13
13
  import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
14
14
  import { resolveFileDisplayMode } from "../utils/file-display-mode";
15
15
  import type { ToolSession } from ".";
16
- import { resolveAstEditPreviewWritePaths } from "./ast-edit-write-path";
16
+ import {
17
+ AST_EDIT_OUTSIDE_ROOT_MESSAGE,
18
+ astEditSingleFileOutsideReferenceRoot,
19
+ resolveAstEditPreviewWritePaths,
20
+ } from "./ast-edit-write-path";
17
21
  import { assertEditableFile } from "./auto-generated-guard";
18
22
  import { createFileRecorder, formatResultPath } from "./file-recorder";
19
23
  import { formatGroupedFiles } from "./grouped-file-output";
@@ -220,6 +224,18 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
220
224
  });
221
225
  const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
222
226
 
227
+ const assertSingleFileReferenceRoot = async (flowSignal?: AbortSignal): Promise<void> => {
228
+ const nativeWritePaths = multiTargets ? multiTargets.map(target => target.basePath) : [resolvedSearchPath];
229
+ const outsideRoot = await astEditSingleFileOutsideReferenceRoot(
230
+ this.session.cwd,
231
+ nativeWritePaths,
232
+ flowSignal,
233
+ );
234
+ if (outsideRoot) {
235
+ throw new ToolError(`${AST_EDIT_OUTSIDE_ROOT_MESSAGE}: ${outsideRoot}`);
236
+ }
237
+ };
238
+
223
239
  const result = await runAstEditOnce(multiTargets, resolvedSearchPath, globFilter, {
224
240
  rewrites: normalizedRewrites,
225
241
  dryRun: true,
@@ -351,6 +367,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
351
367
  for (const filePath of previewedFiles) {
352
368
  await assertEditableFile(resolvePlanPath(this.session, filePath), filePath, this.session.settings);
353
369
  }
370
+ await assertSingleFileReferenceRoot();
354
371
  const applyResult = await runAstEditOnce(multiTargets, resolvedSearchPath, globFilter, {
355
372
  rewrites: normalizedRewrites,
356
373
  dryRun: false,
@@ -11,6 +11,7 @@ import astGrepDescription from "../prompts/tools/ast-grep.md" with { type: "text
11
11
  import { Ellipsis, fileHyperlink, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
12
12
  import { resolveFileDisplayMode } from "../utils/file-display-mode";
13
13
  import type { ToolSession } from ".";
14
+ import { wrapUntrustedContent } from "./fetch";
14
15
  import { createFileRecorder, formatResultPath } from "./file-recorder";
15
16
  import { formatGroupedFiles } from "./grouped-file-output";
16
17
  import { formatMatchLine } from "./match-line-format";
@@ -221,7 +222,9 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
221
222
  const parseMessage = cappedParseErrors.length
222
223
  ? `\n${formatParseErrors(cappedParseErrors, parseErrorsTotal).join("\n")}`
223
224
  : "";
224
- return toolResult(baseDetails).text(`${noMatchMessage}${parseMessage}`).done();
225
+ return toolResult(baseDetails)
226
+ .text(wrapUntrustedContent(`${noMatchMessage}${parseMessage}`))
227
+ .done();
225
228
  }
226
229
 
227
230
  const useHashLines = resolveFileDisplayMode(this.session).hashLines;
@@ -291,7 +294,9 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
291
294
  outputLines.push("", ...formatParseErrors(cappedParseErrors, parseErrorsTotal));
292
295
  }
293
296
 
294
- return toolResult(details).text(outputLines.join("\n")).done();
297
+ return toolResult(details)
298
+ .text(wrapUntrustedContent(outputLines.join("\n")))
299
+ .done();
295
300
  });
296
301
  }
297
302
  }
@@ -25,10 +25,11 @@
25
25
  *
26
26
  * Destination symlinks are followed: the referent is replaced, the link stays.
27
27
  * The referent is re-resolved immediately before publication so a retargeted
28
- * link cannot silently repoint the write, and when the lexical destination
29
- * sits inside a session-scoped `gjc-local` root the resolved referent and its
30
- * parent must remain inside that root (a link there must not redirect the
31
- * write out of the trust boundary).
28
+ * link cannot silently repoint the write. When the lexical destination sits
29
+ * inside the active explicit artifacts local root (`<artifacts>/local`) or a
30
+ * session-scoped `gjc-local` root, the resolved referent and its parent must
31
+ * remain inside that root (a link there must not redirect the write out of
32
+ * the trust boundary).
32
33
  *
33
34
  * Staging uses exclusive create (`wx`) so a colliding leftover temp is never
34
35
  * truncated or unlinked; only the temp this call created is cleaned on failure.
@@ -44,6 +45,7 @@ import * as fs from "node:fs/promises";
44
45
  import * as os from "node:os";
45
46
  import * as path from "node:path";
46
47
  import { hasFsCode, isEacces, isEisdir, isEnoent, isFsError } from "@gajae-code/utils";
48
+ import { LocalProtocolHandler } from "../internal-urls/local-protocol";
47
49
 
48
50
  const TEMP_CREATE_ATTEMPTS = 8;
49
51
  const DEFAULT_FILE_MODE = 0o666;
@@ -111,9 +113,10 @@ export function formatFileWriteError(error: unknown, dest: string, options: { de
111
113
  export interface WriteFileAtomicallyOptions {
112
114
  /**
113
115
  * Trusted root that a resolved symlink referent and its parent must not
114
- * leave. When omitted, the helper still enforces the session-scoped
115
- * `gjc-local` boundary implied by a lexical destination under
116
- * `<tmpdir>/gjc-local/<session-id>`.
116
+ * leave. When omitted, a destination inside the active explicit artifacts
117
+ * local root (`<artifacts>/local`) uses that root. Otherwise the helper
118
+ * still enforces the session-scoped `gjc-local` boundary implied by a
119
+ * lexical destination under `<tmpdir>/gjc-local/<session-id>`.
117
120
  */
118
121
  trustBoundary?: string;
119
122
  /** Platform override used by deterministic retry tests. */
@@ -170,11 +173,20 @@ function pathIsWithin(target: string, root: string): boolean {
170
173
  }
171
174
 
172
175
  async function realpathOrSelf(p: string): Promise<string> {
173
- try {
174
- return await fs.realpath(p);
175
- } catch (error) {
176
- if (!isEnoent(error)) throw error;
177
- return path.resolve(p);
176
+ const resolved = path.resolve(p);
177
+ const missing: string[] = [];
178
+ let cursor = resolved;
179
+ for (;;) {
180
+ try {
181
+ const real = await fs.realpath(cursor);
182
+ return missing.length === 0 ? real : path.join(real, ...missing.reverse());
183
+ } catch (error) {
184
+ if (!isEnoent(error)) throw error;
185
+ const parent = path.dirname(cursor);
186
+ if (parent === cursor) return resolved;
187
+ missing.push(path.basename(cursor));
188
+ cursor = parent;
189
+ }
178
190
  }
179
191
  }
180
192
 
@@ -193,6 +205,25 @@ function sessionLocalRootFor(lexicalDest: string): string | undefined {
193
205
  return path.join(localParent, sessionSegment);
194
206
  }
195
207
 
208
+ /**
209
+ * Active explicit artifacts root (`<artifacts>/local`) when `lexicalDest` is
210
+ * inside it. That root is not under `<tmpdir>/gjc-local`, so the tmp helper
211
+ * above would leave the write unbounded. Managed sessions keep the tmp root.
212
+ */
213
+ function explicitLocalRootFor(lexicalDest: string): string | undefined {
214
+ const options = LocalProtocolHandler.resolveOptions();
215
+ if (!options || options.isManagedDestination?.()) return undefined;
216
+ const artifactsDir = options.getArtifactsDir?.();
217
+ if (!artifactsDir) return undefined;
218
+ const root = path.resolve(artifactsDir, "local");
219
+ if (!pathIsWithin(path.resolve(lexicalDest), root)) return undefined;
220
+ return root;
221
+ }
222
+
223
+ function trustBoundaryForDestination(lexicalDest: string): string | undefined {
224
+ return explicitLocalRootFor(lexicalDest) ?? sessionLocalRootFor(lexicalDest);
225
+ }
226
+
196
227
  /**
197
228
  * Reject publication when the resolved referent's real parent (and therefore
198
229
  * the referent itself) leaves the trust boundary. The boundary root is
@@ -201,8 +232,8 @@ function sessionLocalRootFor(lexicalDest: string): string | undefined {
201
232
  * This must run before any directory is created. A dangling symlink inside a
202
233
  * trusted root resolves to a path outside it, so creating parents first would
203
234
  * let an attacker-selected directory tree be materialized outside the sandbox
204
- * before publication is refused. Missing ancestors therefore resolve
205
- * lexically (`realpathOrSelf` tolerates ENOENT) and are still boundary-checked.
235
+ * before publication is refused. A missing path is resolved through the
236
+ * nearest existing ancestor's realpath, then the missing suffix is appended.
206
237
  */
207
238
  async function assertWithinTrustBoundary(publishPath: string, trustBoundary: string): Promise<void> {
208
239
  const boundary = path.resolve(trustBoundary);
@@ -502,7 +533,7 @@ export async function writeFileAtomically(
502
533
  try {
503
534
  const platform = options.platform ?? process.platform;
504
535
  const sleep = options.sleep ?? (async (delayMs: number): Promise<void> => await Bun.sleep(delayMs));
505
- const trustBoundary = options.trustBoundary ?? sessionLocalRootFor(dest);
536
+ const trustBoundary = options.trustBoundary ?? trustBoundaryForDestination(dest);
506
537
  const resolved = await resolvePublishPath(dest);
507
538
  publishPath = resolved.publishPath;
508
539
  // Boundary first, then create parents. A dangling symlink inside a trusted
@@ -36,6 +36,8 @@ const AUTO_GENERATED_HEADER_MARKERS: readonly RegExp[] = [
36
36
  /\bcode\s+generated\s+by\s+[a-z0-9_.-]+/i,
37
37
  /\bthis\s+file\s+was\s+automatically\s+generated\b/i,
38
38
  new RegExp(`\\bgenerated\\s+by\\s+${KNOWN_GENERATOR_PATTERN}\\b`, "i"),
39
+ /\bgenerated\s+by\s+scripts\/[a-z0-9_./-]+/i,
40
+ /\bauto-generated\s+by\s+[a-z0-9_./-]+/i,
39
41
  ];
40
42
 
41
43
  type CommentStyle = "slash" | "hash" | "sql" | "html";
@@ -100,6 +102,7 @@ const AUTO_GENERATED_FILENAME_PATTERNS = [
100
102
  /_pb2\.py$/,
101
103
  /_pb2_grpc\.py$/,
102
104
  /\.gen\.(go|ts|js|py)$/,
105
+ /\.generated\.ts$/,
103
106
  /^generated\.(go|ts|js|py)$/,
104
107
  /\.swagger\.json$/,
105
108
  /\.openapi\.json$/,
@@ -209,8 +212,14 @@ function extractLeadingHeaderCommentText(content: string, commentStyles: readonl
209
212
  * Check if a file name indicates it might be auto-generated.
210
213
  * This is a quick pre-check before reading file content.
211
214
  */
215
+ function guardFileName(filePath: string): string {
216
+ // resolvePlanPath keeps the platform separator. Anchored patterns such as
217
+ // zz_generated.* and generated.ts must see that basename on Windows too.
218
+ return path.win32.basename(filePath);
219
+ }
220
+
212
221
  function isAutoGeneratedFileName(filePath: string): boolean {
213
- const fileName = filePath.split("/").pop() ?? "";
222
+ const fileName = guardFileName(filePath);
214
223
  return AUTO_GENERATED_FILENAME_PATTERNS.some(pattern => pattern.test(fileName));
215
224
  }
216
225
 
@@ -251,11 +260,14 @@ const autoGeneratedMap = new LRUCache<string, { marker: string | undefined }>({
251
260
 
252
261
  async function getAutoGeneratedMarker(filePath: string): Promise<string | undefined> {
253
262
  if (isAutoGeneratedFileName(filePath)) {
254
- return filePath.split("/").pop() ?? "";
263
+ return guardFileName(filePath);
255
264
  }
256
265
 
257
266
  const cached = autoGeneratedMap.get(filePath);
258
- if (cached) return cached.marker;
267
+ // A cached miss only means the file was handwritten at the last read.
268
+ // An earlier patch entry can write a generated header before a later
269
+ // rename checks the same destination, so do not trust that miss.
270
+ if (cached && cached.marker !== undefined) return cached.marker;
259
271
 
260
272
  let marker: string | undefined;
261
273
  try {
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Detects a running command that printed an interactive auth prompt and is now
3
+ * waiting for a human (browser sign-in URL or device code).
4
+ *
5
+ * The agent cannot see this on its own: the tool call is simply still running.
6
+ * The detector watches the streamed output tail and fires once per command when
7
+ * all of these hold:
8
+ * - a known auth-prompt signature is present in the recent output tail;
9
+ * - the command is still running (the caller stops feeding it on exit);
10
+ * - no new output arrived for `quietMs` after the match.
11
+ *
12
+ * Requiring the quiet window keeps `cat README.md` or `grep` over docs that
13
+ * merely mention "enter the code" from firing: those commands exit or keep
14
+ * printing. The signal carries only the prompt kind, never the URL or code,
15
+ * because both are short-lived credential-equivalent values.
16
+ */
17
+ import { stripVTControlCharacters } from "node:util";
18
+
19
+ export type AwaitingInputKind = "browser_auth" | "device_code";
20
+
21
+ export interface AwaitingInputSignal {
22
+ kind: AwaitingInputKind;
23
+ /** Epoch milliseconds when the quiet window elapsed after the match. */
24
+ detectedAt: number;
25
+ }
26
+
27
+ /** Cancels a scheduled callback. */
28
+ export type CancelScheduled = () => void;
29
+
30
+ export interface AwaitingInputDetectorOptions {
31
+ onDetect: (signal: AwaitingInputSignal) => void;
32
+ /** Quiet window after a match before the signal fires. */
33
+ quietMs?: number;
34
+ /** Injectable scheduler; defaults to `setTimeout`. */
35
+ schedule?: (callback: () => void, ms: number) => CancelScheduled;
36
+ /** Injectable clock; defaults to `Date.now`. */
37
+ now?: () => number;
38
+ }
39
+
40
+ export const AWAITING_INPUT_QUIET_MS = 3_000;
41
+
42
+ /** Only the most recent output can be the prompt the process is blocked on. */
43
+ const TAIL_CHARS = 2_048;
44
+
45
+ const URL_PATTERN = /\bhttps?:\/\/\S+/i;
46
+
47
+ const DEVICE_CODE_PATTERNS: readonly RegExp[] = [
48
+ /\bone-time code\b/i,
49
+ /\benter the code\b/i,
50
+ /\benter code\b/i,
51
+ /\buser_code=/i,
52
+ ];
53
+
54
+ const BROWSER_AUTH_PATTERNS: readonly RegExp[] = [
55
+ /\bto authenticate,? visit\b/i,
56
+ /\blogin\.tailscale\.com\/a\//i,
57
+ /\bgo to the following link in your browser\b/i,
58
+ /\bopen the following url\b/i,
59
+ /\bvisit this url to authori[sz]e\b/i,
60
+ /\bopen (?:this|the) (?:url|link) in (?:a|your) browser\b/i,
61
+ ];
62
+
63
+ /** Classify an output tail; `undefined` when it does not look like an auth prompt. */
64
+ export function classifyAwaitingInput(tail: string): AwaitingInputKind | undefined {
65
+ const text = stripVTControlCharacters(tail);
66
+ if (DEVICE_CODE_PATTERNS.some(pattern => pattern.test(text))) return "device_code";
67
+ if (URL_PATTERN.test(text) && BROWSER_AUTH_PATTERNS.some(pattern => pattern.test(text))) return "browser_auth";
68
+ return undefined;
69
+ }
70
+
71
+ function defaultSchedule(callback: () => void, ms: number): CancelScheduled {
72
+ const timer = setTimeout(callback, ms);
73
+ return () => clearTimeout(timer);
74
+ }
75
+
76
+ export class AwaitingInputDetector {
77
+ readonly #onDetect: (signal: AwaitingInputSignal) => void;
78
+ readonly #quietMs: number;
79
+ readonly #schedule: (callback: () => void, ms: number) => CancelScheduled;
80
+ readonly #now: () => number;
81
+ #tail = "";
82
+ #cancel: CancelScheduled | undefined;
83
+ #done = false;
84
+
85
+ constructor(options: AwaitingInputDetectorOptions) {
86
+ this.#onDetect = options.onDetect;
87
+ this.#quietMs = options.quietMs ?? AWAITING_INPUT_QUIET_MS;
88
+ this.#schedule = options.schedule ?? defaultSchedule;
89
+ this.#now = options.now ?? Date.now;
90
+ }
91
+
92
+ /** Feed one streamed output chunk. Any new output restarts the quiet window. */
93
+ push(chunk: string): void {
94
+ if (this.#done || chunk.length === 0) return;
95
+ this.#cancel?.();
96
+ this.#cancel = undefined;
97
+ this.#tail = (this.#tail + chunk).slice(-TAIL_CHARS);
98
+ const kind = classifyAwaitingInput(this.#tail);
99
+ if (!kind) return;
100
+ this.#cancel = this.#schedule(() => {
101
+ this.#cancel = undefined;
102
+ if (this.#done) return;
103
+ this.#done = true;
104
+ this.#onDetect({ kind, detectedAt: this.#now() });
105
+ }, this.#quietMs);
106
+ }
107
+
108
+ /** Stop watching; the command exited or was folded. Never fires afterwards. */
109
+ dispose(): void {
110
+ this.#done = true;
111
+ this.#cancel?.();
112
+ this.#cancel = undefined;
113
+ }
114
+ }