mixdog 1.4.1 → 1.4.2

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 (326) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/gate-local.mjs +172 -61
  4. package/scripts/lib/gate-scheduler.mjs +138 -32
  5. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  6. package/scripts/lib/run-node-tests.mjs +54 -29
  7. package/scripts/lib/scoped-temp.mjs +1 -0
  8. package/scripts/lib/test-failure-records.mjs +14 -1
  9. package/scripts/lib/test-failure-summary.mjs +16 -4
  10. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  11. package/scripts/pptx-kit-index.mjs +5 -6
  12. package/scripts/suite-health.mjs +110 -28
  13. package/scripts/test.mjs +9 -14
  14. package/src/agents/advisor/AGENT.md +4 -4
  15. package/src/agents/advisor/agent.json +1 -1
  16. package/src/agents/explore/AGENT.md +22 -0
  17. package/src/agents/explore/agent.json +6 -0
  18. package/src/cli.mjs +7 -0
  19. package/src/defaults/agents.json +7 -0
  20. package/src/defaults/shipped-history.json +18 -2
  21. package/src/defaults/skills/browser-use/SKILL.md +4 -1
  22. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  23. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  24. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  25. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  26. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  27. package/src/defaults/skills/computer-use/SKILL.md +12 -2
  28. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  29. package/src/defaults/skills/setup/references/actions.md +12 -6
  30. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  31. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  32. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  33. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  34. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  35. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  36. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  37. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  38. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  39. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  40. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  41. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  42. package/src/defaults/skills/visualize/SKILL.md +6 -2
  43. package/src/lib/rules-builder.cjs +6 -30
  44. package/src/rules/lead/LEAD.md +3 -3
  45. package/src/rules/shared/20-research.md +3 -6
  46. package/src/rules/shared/40-editing.md +1 -0
  47. package/src/rules/shared/60-verification.md +10 -9
  48. package/src/rules/shared/70-delivery.md +4 -3
  49. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  50. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +130 -21
  52. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  53. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  54. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  55. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +7 -7
  56. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  57. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  58. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  59. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  60. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  61. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  62. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  63. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +83 -52
  64. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  65. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +43 -8
  66. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +30 -28
  67. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  69. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  71. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  74. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  75. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  77. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  78. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  79. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  80. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +48 -31
  81. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  82. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  83. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  85. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +26 -28
  88. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +3 -1
  89. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  92. package/src/runtime/agent/orchestrator/providers/registry.mjs +33 -2
  93. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  94. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  95. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  96. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  97. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  98. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +2 -1
  99. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  100. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  102. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  103. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  104. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  105. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +94 -10
  106. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +3 -3
  107. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +10 -5
  108. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +5 -5
  109. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +11 -1
  110. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  111. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  112. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +5 -3
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +21 -2
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  116. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  117. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  118. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  119. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +3 -4
  120. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  123. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -14
  124. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +17 -14
  125. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  126. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  127. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -3
  128. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +47 -29
  129. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  130. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  131. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +1 -1
  132. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +243 -38
  133. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  134. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  135. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  136. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  137. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -26
  138. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +3 -5
  139. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  141. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  142. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  143. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  144. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  145. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  147. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  148. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  149. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  150. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  151. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  154. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  155. package/src/runtime/channels/lib/whisper-port.mjs +19 -10
  156. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  157. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  158. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  159. package/src/runtime/explore/explorer.mjs +93 -0
  160. package/src/runtime/explore/tool-defs.mjs +35 -0
  161. package/src/runtime/explore/tool.mjs +123 -0
  162. package/src/runtime/memory/index.mjs +4 -0
  163. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  164. package/src/runtime/memory/lib/embedding-worker.mjs +4 -0
  165. package/src/runtime/office/quality/assurance-structure-docx.mjs +1 -1
  166. package/src/runtime/shared/agent-route-config.mjs +17 -2
  167. package/src/runtime/shared/atomic-file.mjs +103 -2
  168. package/src/runtime/shared/background-tasks.mjs +15 -6
  169. package/src/runtime/shared/connector-accounts.mjs +5 -5
  170. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  171. package/src/runtime/shared/connector-catalog.mjs +60 -52
  172. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  173. package/src/runtime/shared/file-has-data.mjs +26 -2
  174. package/src/runtime/shared/file-lock.mjs +13 -15
  175. package/src/runtime/shared/git-launch.d.mts +9 -0
  176. package/src/runtime/shared/git-launch.mjs +213 -0
  177. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  178. package/src/runtime/shared/llm/cost.mjs +6 -1
  179. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  180. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  181. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  182. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  183. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  184. package/src/runtime/shared/provider-accounts.mjs +52 -2
  185. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  186. package/src/runtime/shared/staged-update.mjs +47 -2
  187. package/src/runtime/shared/tool-surface.mjs +110 -36
  188. package/src/runtime/shared/tool-work-units.mjs +4 -0
  189. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  190. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  191. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  192. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  193. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  194. package/src/runtime/shared/worktrees/git.mjs +57 -0
  195. package/src/runtime/shared/worktrees/manager.mjs +205 -137
  196. package/src/runtime/tidy/install.mjs +95 -15
  197. package/src/runtime/tidy/languages.mjs +17 -15
  198. package/src/runtime/tidy/process.mjs +7 -4
  199. package/src/runtime/tidy/resolve.mjs +19 -16
  200. package/src/runtime/tidy/runners/biome.mjs +3 -3
  201. package/src/runtime/tidy/tool.mjs +1 -1
  202. package/src/runtime/tidy/worktree.mjs +10 -6
  203. package/src/runtime/visualize-bridge/client.mjs +4 -1
  204. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  205. package/src/runtime/web-search/index.mjs +1 -1
  206. package/src/session-runtime/agent-task-sessions.mjs +410 -353
  207. package/src/session-runtime/boot/tools.mjs +2 -0
  208. package/src/session-runtime/bot-status.mjs +103 -103
  209. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  210. package/src/session-runtime/conversation-import/discovery.mjs +159 -40
  211. package/src/session-runtime/conversation-import/service.mjs +9 -3
  212. package/src/session-runtime/handoff-attachments.mjs +1 -1
  213. package/src/session-runtime/held-requests.mjs +3 -1
  214. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  215. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  216. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +143 -116
  217. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  218. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  219. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  220. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  221. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  222. package/src/session-runtime/provider-auth/login.mjs +2 -2
  223. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  224. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  225. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  226. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  227. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  228. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  229. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  230. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  231. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  232. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  233. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  234. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  235. package/src/session-runtime/services/provider-admin.mjs +281 -10
  236. package/src/session-runtime/session-turn-api.mjs +2 -0
  237. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  238. package/src/session-runtime/settings-system-api.mjs +1 -1
  239. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  240. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  241. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  242. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  243. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  244. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  245. package/src/session-runtime/workflow-agents-api/agent-route.mjs +10 -1
  246. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  247. package/src/session-runtime/workspace/session-view.mjs +149 -0
  248. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  249. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  250. package/src/session-runtime/workspace/transitions.mjs +108 -1066
  251. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  252. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  253. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  254. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  255. package/src/session-runtime/workspace/worktree-tool.mjs +54 -54
  256. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  257. package/src/standalone/daemon-session-control.mjs +151 -136
  258. package/src/standalone/daemon.mjs +7 -0
  259. package/src/standalone/desktop-service-registry.mjs +9 -0
  260. package/src/standalone/local-session-runtime.mjs +5 -1
  261. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  262. package/src/standalone/session-client.mjs +5 -1
  263. package/src/standalone/session-frame-stream.mjs +83 -11
  264. package/src/standalone/session-protocol.mjs +4 -0
  265. package/src/standalone/session-service/agent-tree.mjs +45 -9
  266. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  267. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  268. package/src/standalone/session-service/stored-reader.mjs +127 -40
  269. package/src/tui/App.jsx +154 -164
  270. package/src/tui/app/app-view/floating-panel.jsx +3 -0
  271. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  272. package/src/tui/app/clipboard.mjs +21 -10
  273. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  274. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  275. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  276. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  277. package/src/tui/app/provider-setup/oauth-actions.mjs +25 -6
  278. package/src/tui/app/provider-setup-picker.mjs +20 -13
  279. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  280. package/src/tui/app/slash-commands.mjs +1 -0
  281. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  282. package/src/tui/app/slash-dispatch.mjs +1 -1
  283. package/src/tui/app/use-btw-panel.mjs +29 -0
  284. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  285. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  286. package/src/tui/app/use-mouse-input.mjs +11 -0
  287. package/src/tui/app/use-onboarding-start.mjs +8 -3
  288. package/src/tui/app/use-panel-transition.mjs +10 -1
  289. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  290. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  291. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  292. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  293. package/src/tui/app/use-selection-focus.mjs +11 -2
  294. package/src/tui/app/use-selection-geometry.mjs +18 -9
  295. package/src/tui/app/use-selection-paint.mjs +4 -4
  296. package/src/tui/app/use-selection-stitch.mjs +3 -3
  297. package/src/tui/app/use-slash-palette.mjs +62 -0
  298. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  299. package/src/tui/app/use-transcript-activity.mjs +47 -32
  300. package/src/tui/app/use-transcript-follow.mjs +12 -4
  301. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  302. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  303. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  304. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  305. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  306. package/src/tui/app/use-transcript-window.mjs +21 -11
  307. package/src/tui/app/use-tui-ready.mjs +18 -0
  308. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  309. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  310. package/src/tui/components/Picker.jsx +27 -3
  311. package/src/tui/components/SignInUrl.jsx +68 -0
  312. package/src/tui/components/TextEntryPanel.jsx +76 -46
  313. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  314. package/src/tui/dist/index.mjs +1896 -1341
  315. package/src/tui/session/goal-turn-state.mjs +6 -2
  316. package/src/tui/session/session-api/controls.mjs +4 -0
  317. package/src/tui/session/session-api/extensions.mjs +8 -0
  318. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  319. package/src/tui/session/session-api/intake.mjs +2 -0
  320. package/src/tui/session/session-api/integrations.mjs +2 -2
  321. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  322. package/src/tui/session/session-api/settings.mjs +2 -2
  323. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  324. package/src/tui/session/transcript-spill.mjs +92 -4
  325. package/src/tui/session/transcript-store.mjs +2 -0
  326. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
package/src/tui/App.jsx CHANGED
@@ -16,13 +16,15 @@
16
16
  * layout, which <Static> collapses. The terminal handles scrollback itself as
17
17
  * the transcript column grows past the screen height.
18
18
  */
19
- import { useState, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
19
+ import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
20
20
  import { useApp, useStdin, useStdout } from 'ink';
21
21
  import { useSession } from './hooks/useSession.mjs';
22
22
  import { pickFolder } from '../standalone/folder-dialog.mjs';
23
- import { deriveSlashPalette } from './app/slash-palette.mjs';
24
- import { createBtwController } from './app/btw-controller.mjs';
25
- import { skillMentionQuery, useSkillMentionSkills } from './app/skill-mention-palette.mjs';
23
+ import { useSlashPalette, useSlashPaletteState } from './app/use-slash-palette.mjs';
24
+ import { useBtwPanel } from './app/use-btw-panel.mjs';
25
+ import { useTuiReady } from './app/use-tui-ready.mjs';
26
+ import { useTranscriptSwapReset } from './app/use-transcript-swap-reset.mjs';
27
+ import { useContextPanelRefresh } from './app/use-context-panel-refresh.mjs';
26
28
  import { shouldSupersedePanelEpoch, supersedePanelEpoch } from './app/panel-epoch.mjs';
27
29
  import { createPanelSurface } from './app/panel-surface.mjs';
28
30
  import { resolvePickerState } from './app/picker-index-mode.mjs';
@@ -50,7 +52,6 @@ import { usePromptQueueHistory } from './app/use-prompt-queue-history.mjs';
50
52
  import { useMessageSelector } from './app/message-selector.mjs';
51
53
  import { useTerminalChrome } from './app/use-terminal-chrome.mjs';
52
54
  import { useTranscriptWindow } from './app/use-transcript-window.mjs';
53
- import { transcriptSwapReturnsToTail } from './app/transcript-window.mjs';
54
55
  import { terminalSize, projectNameFromPath } from './app/app-format.mjs';
55
56
  import { useWorkflowTabCycle } from './app/use-workflow-tab-cycle.mjs';
56
57
  import { createProjectPicker } from './app/project-picker.mjs';
@@ -59,67 +60,25 @@ import { useModelCatalogCache } from './app/use-model-catalog-cache.mjs';
59
60
  import { useDisabledSkills } from './app/use-disabled-skills.mjs';
60
61
  import { resolveOnboardingCompleted, useOnboardingStart } from './app/use-onboarding-start.mjs';
61
62
 
62
- export function App({ store, initialStatusLine = '', forceOnboarding = false, onboardingCompleted = undefined }) {
63
- const state = useSession(store);
64
- const [toolOutputExpanded, setToolOutputExpanded] = useState(false);
65
- // True for the entire first-run onboarding wizard (every step + nested depth)
66
- // so the welcome banner stays reserved and the layout doesn't jump when the
67
- // step pickers mount. Cleared on finish/cancel.
68
- const [onboardingActive, setOnboardingActive] = useState(false);
69
- const { exit } = useApp();
70
- // internal_eventEmitter is ink's parsed-input bus. ink 7 consumes stdin via
71
- // the 'readable' event + stdin.read() (see ink's App.js), draining the buffer
72
- // so a plain stdin.on('data') listener of ours never sees mouse bytes. Instead
73
- // we subscribe to ink's 'input' events, which carry every parsed sequence —
74
- // including raw SGR mouse sequences (\x1b[<…M/m), since ink's input-parser
75
- // passes CSI sequences through untouched and emitInput forwards them verbatim.
76
- const { isRawModeSupported, internal_eventEmitter: inkInput } = useStdin();
77
- const { stdout } = useStdout();
78
- const [exiting, setExiting] = useState(false);
79
- // tuiReady stays false across the first render + commit. A setTimeout(0) in
80
- // the first effect defers the flip until one event-loop poll has drained any
81
- // keystrokes that the OS buffered during terminal setup / initial mount.
82
- const [tuiReady, setTuiReady] = useState(false);
83
- const exitRequestedRef = useRef(false);
84
- const [resizeState, setResizeState] = useState(() => ({ ...terminalSize(stdout), epoch: 0 }));
85
- const [panelTransitionEpoch, setPanelTransitionEpoch] = useState(0);
86
- const [panelInkMaskEpoch, setPanelInkMaskEpoch] = useState(0);
87
- // Keep a universal one-cell margin at the right edge: terminals may clip or
88
- // wrap their final cell, including macOS terminals rendering rounded borders.
89
- const rightSafetyColumns = 1;
90
- const frameColumns = Math.max(1, resizeState.columns - rightSafetyColumns);
91
- const {
92
- scrollOffset,
93
- setScrollOffset,
94
- scrollPositionRef,
95
- scrollTargetRef,
96
- maxScrollRowsRef,
97
- transcriptBottomSlackRowsRef,
98
- transcriptAnchorRef,
99
- transcriptAnchorDirtyRef,
100
- transcriptGeomRef,
101
- measuredRowsVersion,
102
- setMeasuredRowsVersion,
103
- followingRef,
104
- lastItemsCountRef,
105
- lastFirstItemIdRef,
106
- } = useTranscriptScrollState();
63
+ // Statusline band height (bottom rows); mirrors the layout reserve.
64
+ const STATUSLINE_BAND_ROWS = 3;
65
+
66
+ // OWNING LAYER for the raw panel sinks (with app/panel-surface.mjs): the picker
67
+ // and the context/usage panel setters are handed to the surface exactly once
68
+ // and never leave this file. Every panel factory gets `surface` instead, so
69
+ // painting or delegating without a claim that proves ownership is not
70
+ // expressible elsewhere. The surface is stable across renders — the usage
71
+ // generation lives inside it.
72
+ function usePanelOwnership() {
107
73
  // picker = null | { type, title, items, onSelect }
108
74
  // Rendered as an option panel attached directly above the bottom prompt.
109
75
  const pickerOpenedFromEnterRef = useRef(false);
110
76
  const pickerOpenedFromEnterTimerRef = useRef(null);
111
- // Late-bound handle to the project-picker cluster (created after the picker
112
- // and prompt setters exist, below). Referencing it via a ref lets the
113
- // `useState` initializer build the first-mount picker state before the
114
- // factory is instantiated: the factory's onSelect/onKey/onCancel closures
115
- // resolve `projectPicker.current` at call time, not at build time.
116
- const projectPickerRef = useRef(null);
117
- // NOTE: the initial project-picker state CANNOT be built inside this
118
- // useState initializer — it runs before projectPickerRef is populated
119
- // (createProjectPicker below), so buildProjectPickerState would deref null.
120
- // The first-mount build happens as a render-phase update right after the
121
- // factory is instantiated (see initialPickerBuiltRef below), which React
122
- // applies before the first commit — no picker-less flash frame.
77
+ // NOTE: the initial project-picker state is NOT built in this useState
78
+ // initializer — the project-picker factory does not exist yet. The
79
+ // first-mount build happens as a render-phase update right after the factory
80
+ // is instantiated (useProjectPickerBoot), which React applies before the
81
+ // first commit — no picker-less flash frame.
123
82
  const [picker, setPickerState] = useState(null);
124
83
  // Live handle to the current picker state so async callbacks (e.g. the MCP
125
84
  // toggle settle guard in extension-pickers) read the picker actually on
@@ -153,52 +112,41 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
153
112
  livePickerRef.current = picker;
154
113
  const [contextPanel, setContextPanel] = useState(null);
155
114
  const [usagePanel, setUsagePanel] = useState(null);
156
- const [btwPanel, setBtwPanel] = useState(null);
157
- const btwRef = useRef(null);
158
- const sessionIdRef = useRef('');
159
- sessionIdRef.current = state.sessionId || '';
160
- if (!btwRef.current) {
161
- btwRef.current = createBtwController({
162
- getStore: () => store,
163
- setPanel: setBtwPanel,
164
- getSessionId: () => sessionIdRef.current,
165
- });
166
- }
167
- const btw = btwRef.current;
168
- // A side answer belongs to the conversation it was asked in: drop it on a
169
- // session change (new/resume/clear/switch) and cancel it on unmount.
170
- useEffect(() => {
171
- btw.sync();
172
- }, [btw, state.sessionId]);
173
- useEffect(() => () => btw.dispose(), [btw]);
174
- // OWNING LAYER (app/panel-surface.mjs): the three sinks above are handed over
175
- // exactly once and never leave that module. Every panel factory below gets
176
- // `surface` instead, so painting or delegating without a claim that proves
177
- // ownership is not expressible outside this file. Stable across renders — the
178
- // usage generation lives inside it.
179
115
  const surfaceRef = useRef(null);
180
116
  if (!surfaceRef.current) {
181
117
  surfaceRef.current = createPanelSurface({ setPicker, setContextPanel, setUsagePanel });
182
118
  }
183
119
  const surface = surfaceRef.current;
184
- // Cache of the last computed heavy settings-picker status objects (MCP,
185
- // plugins, skills, channel provider). ←/→ cycle/toggle handlers in
186
- // openSettingsPicker() pass { light: true } to reuse this cache instead of
187
- // re-querying these heavy getters on every keystroke; only a full open
188
- // (initial /config or Esc-return) recomputes them.
189
- const settingsHeavyCacheRef = useRef(null);
190
- // Settings build generation: every open/refresh takes a ticket and Esc bumps
191
- // it, so a slow (daemon) snapshot from a superseded build cannot re-open the
192
- // panel after it was closed.
193
- const settingsRequestRef = useRef(0);
194
120
  const closeUsagePanel = useCallback(() => surface.closeUsage(), [surface]);
195
- const [providerPrompt, setProviderPrompt] = useState(null);
196
- const oauthSubmitRef = useRef(false);
197
- const [settingsPrompt, setSettingsPrompt] = useState(null);
198
- // Instantiate the project-picker cluster now that the surface + every prompt
199
- // setter and the usage-panel closer exist. projectPickerRef (declared above,
200
- // before the picker useState) is populated here so first-mount build and all
201
- // later callers resolve the same set of builders.
121
+ return {
122
+ picker,
123
+ setPicker,
124
+ livePickerRef,
125
+ pickerOpenedFromEnterRef,
126
+ pickerOpenedFromEnterTimerRef,
127
+ contextPanel,
128
+ usagePanel,
129
+ surface,
130
+ closeUsagePanel,
131
+ };
132
+ }
133
+
134
+ // Project-picker cluster plus its first-mount entry. The factory's
135
+ // onSelect/onKey/onCancel closures resolve the builders at call time; the
136
+ // initial open goes through projectPickerRef so it reaches the latest ones.
137
+ function useProjectPickerBoot({
138
+ state,
139
+ store,
140
+ surface,
141
+ setProviderPrompt,
142
+ setSettingsPrompt,
143
+ closeUsagePanel,
144
+ setPicker,
145
+ livePickerRef,
146
+ forceOnboarding,
147
+ onboardingCompleted,
148
+ }) {
149
+ const projectPickerRef = useRef(null);
202
150
  const projectPicker = createProjectPicker({
203
151
  state,
204
152
  store,
@@ -221,11 +169,96 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
221
169
  setPicker(projectPicker.buildProjectPickerState({ initialEntry: true, loading: true }));
222
170
  }
223
171
  }
172
+ // biome-ignore lint/correctness/useExhaustiveDependencies(store): a new store re-runs the pending initial open
224
173
  useEffect(() => {
225
174
  if (livePickerRef.current?._projectInitialPending !== true) return;
226
175
  void projectPickerRef.current?.openProjectPicker({ initialEntry: true });
227
- }, [store]);
228
- const { registerProject, enterProject, openProjectPicker } = projectPicker;
176
+ }, [store, livePickerRef]);
177
+ return projectPicker;
178
+ }
179
+
180
+ export function App({ store, initialStatusLine = '', forceOnboarding = false, onboardingCompleted = undefined }) {
181
+ const state = useSession(store);
182
+ const [toolOutputExpanded, setToolOutputExpanded] = useState(false);
183
+ // True for the entire first-run onboarding wizard (every step + nested depth)
184
+ // so the welcome banner stays reserved and the layout doesn't jump when the
185
+ // step pickers mount. Cleared on finish/cancel.
186
+ const [onboardingActive, setOnboardingActive] = useState(false);
187
+ const { exit } = useApp();
188
+ // internal_eventEmitter is ink's parsed-input bus. ink 7 consumes stdin via
189
+ // the 'readable' event + stdin.read() (see ink's App.js), draining the buffer
190
+ // so a plain stdin.on('data') listener of ours never sees mouse bytes. Instead
191
+ // we subscribe to ink's 'input' events, which carry every parsed sequence —
192
+ // including raw SGR mouse sequences (\x1b[<…M/m), since ink's input-parser
193
+ // passes CSI sequences through untouched and emitInput forwards them verbatim.
194
+ const { isRawModeSupported, internal_eventEmitter: inkInput } = useStdin();
195
+ const { stdout } = useStdout();
196
+ const [exiting, setExiting] = useState(false);
197
+ const exitRequestedRef = useRef(false);
198
+ const [resizeState, setResizeState] = useState(() => ({ ...terminalSize(stdout), epoch: 0 }));
199
+ const [panelTransitionEpoch, setPanelTransitionEpoch] = useState(0);
200
+ const [panelInkMaskEpoch, setPanelInkMaskEpoch] = useState(0);
201
+ // Keep a universal one-cell margin at the right edge: terminals may clip or
202
+ // wrap their final cell, including macOS terminals rendering rounded borders.
203
+ const rightSafetyColumns = 1;
204
+ const frameColumns = Math.max(1, resizeState.columns - rightSafetyColumns);
205
+ const {
206
+ scrollOffset,
207
+ setScrollOffset,
208
+ scrollPositionRef,
209
+ scrollTargetRef,
210
+ maxScrollRowsRef,
211
+ transcriptBottomSlackRowsRef,
212
+ transcriptAnchorRef,
213
+ transcriptAnchorDirtyRef,
214
+ transcriptGeomRef,
215
+ measuredRowsVersion,
216
+ setMeasuredRowsVersion,
217
+ followingRef,
218
+ lastItemsCountRef,
219
+ lastFirstItemIdRef,
220
+ } = useTranscriptScrollState();
221
+ // Picker + context/usage panels and the surface that owns them (owning layer).
222
+ const {
223
+ picker,
224
+ setPicker,
225
+ livePickerRef,
226
+ pickerOpenedFromEnterRef,
227
+ pickerOpenedFromEnterTimerRef,
228
+ contextPanel,
229
+ usagePanel,
230
+ surface,
231
+ closeUsagePanel,
232
+ } = usePanelOwnership();
233
+ // /btw side-question panel + controller lifecycle: app/use-btw-panel.mjs.
234
+ const { btwPanel, btw } = useBtwPanel({ store, sessionId: state.sessionId });
235
+ // Cache of the last computed heavy settings-picker status objects (MCP,
236
+ // plugins, skills, channel provider). ←/→ cycle/toggle handlers in
237
+ // openSettingsPicker() pass { light: true } to reuse this cache instead of
238
+ // re-querying these heavy getters on every keystroke; only a full open
239
+ // (initial /config or Esc-return) recomputes them.
240
+ const settingsHeavyCacheRef = useRef(null);
241
+ // Settings build generation: every open/refresh takes a ticket and Esc bumps
242
+ // it, so a slow (daemon) snapshot from a superseded build cannot re-open the
243
+ // panel after it was closed.
244
+ const settingsRequestRef = useRef(0);
245
+ const [providerPrompt, setProviderPrompt] = useState(null);
246
+ const oauthSubmitRef = useRef(false);
247
+ const [settingsPrompt, setSettingsPrompt] = useState(null);
248
+ // Project-picker cluster, instantiated now that the surface + every prompt
249
+ // setter and the usage-panel closer exist, plus its first-mount entry.
250
+ const { registerProject, enterProject, openProjectPicker } = useProjectPickerBoot({
251
+ state,
252
+ store,
253
+ surface,
254
+ setProviderPrompt,
255
+ setSettingsPrompt,
256
+ closeUsagePanel,
257
+ setPicker,
258
+ livePickerRef,
259
+ forceOnboarding,
260
+ onboardingCompleted,
261
+ });
229
262
  // Disabled-Skill set (async load + persist on change):
230
263
  // app/use-disabled-skills.mjs.
231
264
  const { disabledSkills, setDisabledSkills } = useDisabledSkills({ store });
@@ -287,11 +320,10 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
287
320
  welcomePromptHintVisibleRef,
288
321
  dismissWelcomePromptHint,
289
322
  } = useWelcomePromptHint({ store, state, toastErrorSignature });
290
- const [slashIndex, setSlashIndex] = useState(0);
291
- const [slashDismissedFor, setSlashDismissedFor] = useState('');
292
- const slashPaletteRef = useRef({ open: false, count: 0 });
323
+ // Slash palette index / dismissal state: app/use-slash-palette.mjs.
324
+ const { slashIndex, setSlashIndex, slashDismissedFor, setSlashDismissedFor, slashPaletteRef } =
325
+ useSlashPaletteState();
293
326
  const scrollFocusRef = useRef({});
294
- const onboardingStartedRef = useRef(false);
295
327
  const onboardingRef = useRef({
296
328
  defaultRoute: null,
297
329
  webSearchRoute: null,
@@ -365,7 +397,6 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
365
397
  selectionTextRef,
366
398
  lastClickRef,
367
399
  } = useInteractionRefs();
368
- const STATUSLINE_BAND_ROWS = 3;
369
400
  const promptContentColumns = Math.max(1, frameColumns - 4);
370
401
  // Prompt + text-entry layout rows: app/use-prompt-layout-rows.mjs.
371
402
  const syncPromptLayoutRows = usePromptLayoutRows({
@@ -377,14 +408,8 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
377
408
  setTextEntryLayoutRows,
378
409
  });
379
410
 
380
- // ── Post-mount input gate ──────────────────────────────────────────────
381
- // Let one event-loop poll pass so Ink processes (and discards, because
382
- // PromptInput is still disabled) any keystrokes queued during boot/first
383
- // render. After the tick, enable the input — new keystrokes land normally.
384
- useEffect(() => {
385
- const timer = setTimeout(() => setTuiReady(true), 0);
386
- return () => clearTimeout(timer);
387
- }, []);
411
+ // Post-mount input gate: app/use-tui-ready.mjs.
412
+ const tuiReady = useTuiReady();
388
413
 
389
414
  // Resize debounce + extended-keyboard enable: app/use-terminal-chrome.mjs.
390
415
  useTerminalChrome({ stdout, isRawModeSupported, setResizeState });
@@ -471,23 +496,8 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
471
496
  clearStitchBuffer,
472
497
  });
473
498
 
474
- // Item-count changes never infer follow permission from scrollTarget=0. A
475
- // first read-back input can be waiting one frame for committed row geometry;
476
- // only prompt submit, session reset, or an explicit return to bottom arms
477
- // follow. Pure streaming height growth is handled in the row-delta effect.
478
- useLayoutEffect(() => {
479
- const count = state.items.length;
480
- const previousCount = lastItemsCountRef.current;
481
- lastItemsCountRef.current = count;
482
- const firstId = count > 0 ? (state.items[0]?.id ?? null) : null;
483
- const previousFirstId = lastFirstItemIdRef.current;
484
- lastFirstItemIdRef.current = firstId;
485
- // A bulk swap (compaction included) is an explicit return to the tail: the
486
- // rows the reader was anchored to no longer exist.
487
- if (transcriptSwapReturnsToTail({ count, previousCount, firstId, previousFirstId })) {
488
- resetTranscriptScroll();
489
- }
490
- }, [state.items, resetTranscriptScroll]);
499
+ // Bulk transcript swap → back to the tail: app/use-transcript-swap-reset.mjs.
500
+ useTranscriptSwapReset({ items: state.items, lastItemsCountRef, lastFirstItemIdRef, resetTranscriptScroll });
491
501
 
492
502
  // Exit + queued-restore + prompt history: app/use-prompt-queue-history.mjs.
493
503
  const { requestExit, restoreQueuedToPrompt, recentPromptHistory, resetPromptHistoryNav } = usePromptQueueHistory({
@@ -594,31 +604,14 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
594
604
  closeUsagePanel,
595
605
  });
596
606
 
597
- useEffect(() => {
598
- if (contextPanel?.kind === 'context') openContextPicker();
599
- }, [
600
- contextPanel?.kind,
601
- state.stats,
602
- state.contextWindow,
603
- state.rawContextWindow,
604
- state.sessionId,
605
- state.toolMode,
606
- state.agentWorkers,
607
- state.agentJobs,
608
- state.provider,
609
- state.model,
610
- state.effort,
611
- state.fast,
612
- state.cwd,
613
- state.clientHostPid,
614
- ]);
607
+ // Open /context breakdown stays current: app/use-context-panel-refresh.mjs.
608
+ useContextPanelRefresh({ contextPanelKind: contextPanel?.kind, state, openContextPicker });
615
609
 
616
610
  // First-run onboarding wizard launch: app/use-onboarding-start.mjs.
617
611
  useOnboardingStart({
618
612
  store,
619
613
  forceOnboarding,
620
614
  onboardingCompleted,
621
- onboardingStartedRef,
622
615
  setOnboardingActive,
623
616
  openOnboardingAuthStep,
624
617
  });
@@ -652,8 +645,14 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
652
645
  pastedTextsRef,
653
646
  });
654
647
 
655
- const mentionSkills = useSkillMentionSkills(store, skillMentionQuery(promptDraft, promptCaret) !== null);
656
- const { activeSlashQuery, slashCommands, slashPaletteOpen, paletteKind } = deriveSlashPalette({
648
+ // Slash / `$skill` palette for this render: app/use-slash-palette.mjs.
649
+ const { activeSlashQuery, slashCommands, slashPaletteOpen, paletteKind } = useSlashPalette({
650
+ store,
651
+ slashPaletteRef,
652
+ setSlashIndex,
653
+ slashDismissedFor,
654
+ promptDraft,
655
+ promptCaret,
657
656
  providerPrompt,
658
657
  settingsPrompt,
659
658
  toolApproval,
@@ -662,12 +661,7 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
662
661
  picker,
663
662
  exiting,
664
663
  commandBusy: state.commandBusy,
665
- promptDraft,
666
- slashDismissedFor,
667
- skills: mentionSkills,
668
- promptCaret,
669
664
  });
670
- slashPaletteRef.current = { open: slashPaletteOpen, count: slashCommands.length };
671
665
  scrollFocusRef.current = {
672
666
  slashPaletteOpen,
673
667
  picker: !!picker,
@@ -679,10 +673,6 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false, on
679
673
  settingsPrompt: !!settingsPrompt,
680
674
  };
681
675
 
682
- useEffect(() => {
683
- setSlashIndex((index) => Math.min(index, Math.max(0, slashCommands.length - 1)));
684
- }, [slashCommands.length, activeSlashQuery]);
685
-
686
676
  // Draft/slash flow (draft sync, prompt cancels, palette accept/cancel):
687
677
  // app/use-prompt-draft-flow.mjs.
688
678
  const {
@@ -124,6 +124,7 @@ function renderPicker({
124
124
  onHighlight={picker.onHighlight}
125
125
  title={picker.title}
126
126
  description={picker.description}
127
+ detail={picker.detail}
127
128
  footer={picker.footer}
128
129
  footerGapRows={picker.footerGapRows}
129
130
  help={picker.help}
@@ -159,6 +160,7 @@ export function renderFloatingPanel(ctx) {
159
160
  slashCommands,
160
161
  slashIndex,
161
162
  slashPaletteOpen,
163
+ textEntryUrlMaxRows,
162
164
  toolApproval,
163
165
  usagePanel,
164
166
  btwPanel,
@@ -212,6 +214,7 @@ export function renderFloatingPanel(ctx) {
212
214
  title={title}
213
215
  hint={hint}
214
216
  detail={providerPrompt.detail || ''}
217
+ urlMaxRows={textEntryUrlMaxRows}
215
218
  mask={providerPrompt.kind === 'api-key' || providerPrompt.kind === 'openai-usage-session'}
216
219
  columns={frameColumns}
217
220
  // Restored after a REJECTED daemon save so the entered secret
@@ -17,6 +17,7 @@ export function providerPromptFields(providerPrompt) {
17
17
  providerPrompt.envName ? `Env: ${providerPrompt.envName}` : '',
18
18
  providerPrompt.source ? `Current: ${providerPrompt.source}` : '',
19
19
  providerPrompt.keyUrl ? `Get a key: ${providerPrompt.keyUrl}` : '',
20
+ providerPrompt.hint || '',
20
21
  'Stored in the OS keychain.',
21
22
  ]
22
23
  .filter(Boolean)
@@ -71,11 +71,21 @@ function nativeClipboardCommand(text) {
71
71
  };
72
72
  }
73
73
  if (process.platform === 'darwin') return { cmd: 'pbcopy', args: [], input: value };
74
+ // WAYLAND_DISPLAY is the variable a Wayland client connects by; set means a
75
+ // Wayland session. A helper that is not installed fails with ENOENT, which
76
+ // clipboardHelperError turns into the install hint.
74
77
  if (process.env.WAYLAND_DISPLAY) return { cmd: 'wl-copy', args: [], input: value };
75
78
  return { cmd: 'xclip', args: ['-selection', 'clipboard'], input: value };
76
79
  }
77
80
 
78
- export function copyToClipboard(text) {
81
+ /**
82
+ * Resolves 'copied' once a native helper confirmed the write, or 'terminal' when
83
+ * only OSC 52 was emitted (the terminal may ignore it, so it is unconfirmed).
84
+ * Rejects when no path was available. By default a successful OSC 52 resolves
85
+ * at once without awaiting the helper; `confirm` waits for the helper so the
86
+ * caller can tell which of the two happened.
87
+ */
88
+ export function copyToClipboard(text, { confirm = false } = {}) {
79
89
  const value = String(text ?? '');
80
90
  const wroteOsc52 = writeOsc52Clipboard(value);
81
91
  // When OSC 52 already wrote the clipboard, fire-and-forget the native helper
@@ -84,24 +94,25 @@ export function copyToClipboard(text) {
84
94
  // skipped (payload too large), the native helper is the ONLY writer, so we
85
95
  // must await its exit to report real success/failure instead of a false
86
96
  // "copied" hint.
97
+ const waitForHelper = !wroteOsc52 || confirm;
87
98
  return new Promise((resolve, reject) => {
99
+ // A helper failure only matters when OSC 52 did not cover us.
100
+ const helperFailed = (error) => (wroteOsc52 ? resolve('terminal') : reject(error));
88
101
  const { cmd, args, input } = nativeClipboardCommand(value);
89
102
  let child;
90
103
  try {
91
104
  child = spawn(cmd, args, { stdio: ['pipe', 'ignore', 'ignore'], windowsHide: true });
92
105
  } catch (e) {
93
- if (wroteOsc52) resolve();
94
- else reject(clipboardHelperError(cmd, e));
106
+ helperFailed(clipboardHelperError(cmd, e));
95
107
  return;
96
108
  }
97
109
  child.on('error', (e) => {
98
- // Surface only if OSC 52 didn't cover us; otherwise the copy still landed.
99
- if (!wroteOsc52) reject(clipboardHelperError(cmd, e));
110
+ if (waitForHelper) helperFailed(clipboardHelperError(cmd, e));
100
111
  });
101
- if (!wroteOsc52) {
112
+ if (waitForHelper) {
102
113
  child.on('close', (code) => {
103
- if (code === 0) resolve();
104
- else reject(new Error(`${cmd} exited with code ${code}`));
114
+ if (code === 0) resolve('copied');
115
+ else helperFailed(new Error(`${cmd} exited with code ${code}`));
105
116
  });
106
117
  }
107
118
  child.stdin.on('error', () => {
@@ -109,9 +120,9 @@ export function copyToClipboard(text) {
109
120
  });
110
121
  child.stdin.end(input);
111
122
  // OSC 52 covered us: resolve now, don't await the child's exit.
112
- if (wroteOsc52) {
123
+ if (!waitForHelper) {
113
124
  child.unref?.();
114
- resolve();
125
+ resolve('terminal');
115
126
  }
116
127
  });
117
128
  }
@@ -26,6 +26,6 @@ export function usePromptHistoryNavigation({
26
26
  if (outcome.draftChanged) promptHistoryDraftChangeRef.current = true;
27
27
  return outcome.value;
28
28
  },
29
- [recentPromptHistory, resetPromptHistoryNav, clearPromptHint]
29
+ [promptHistoryNavRef, promptHistoryDraftChangeRef, recentPromptHistory, resetPromptHistoryNav, clearPromptHint]
30
30
  );
31
31
  }
@@ -47,6 +47,9 @@ export function usePromptInterrupt({
47
47
  [store, promptValueRef, restoreAttachments, clearPromptHint, syncPromptLayoutRows, setPromptDraftOverride]
48
48
  );
49
49
 
50
+ // `busy` is the trigger, not a read: a parked restore re-checks the store
51
+ // each time the turn's busy flag flips.
52
+ // biome-ignore lint/correctness/useExhaustiveDependencies(busy): busy-flip trigger for the parked restore
50
53
  useEffect(() => {
51
54
  const pending = pendingRestoreRef.current;
52
55
  if (!pending) return;
@@ -36,7 +36,9 @@ function saveApiKey(ctx, target, commandText) {
36
36
  requiredNotice: `API key is required for ${target.providerId}`,
37
37
  busyNotice: 'API key is already being saved',
38
38
  method: 'saveProviderApiKey',
39
- args: [target.providerId, commandText],
39
+ args: target.authMode
40
+ ? [target.providerId, commandText, { authMode: target.authMode }]
41
+ : [target.providerId, commandText],
40
42
  onSaved: () => ctx.clearModelCaches('all'),
41
43
  failurePrefix: 'api key save failed',
42
44
  });
@@ -6,9 +6,13 @@ import { openInBrowser } from '../../../runtime/shared/open-url.mjs';
6
6
  import { providerDetailText } from '../app-format.mjs';
7
7
  import { forgetProviderAuth, keyConsoleUrl, providerStatusFooter } from './provider-items.mjs';
8
8
 
9
- const setApiKeyPrompt = (flow, providerItem) => {
9
+ const VERTEX_EXPRESS_HINT =
10
+ 'Express mode API keys serve Gemini models on the global location only. Claude and MaaS models need a service-account key or the gcloud login.';
11
+
12
+ const setApiKeyPrompt = (flow, providerItem, extra = {}) => {
10
13
  if (!flow.ownsSurface()) return;
11
14
  flow.setProviderPrompt({
15
+ ...extra,
12
16
  kind: 'api-key',
13
17
  providerId: providerItem._providerId,
14
18
  label: providerItem._providerName,
@@ -51,6 +55,28 @@ function apiKeyActions(providerItem, provider) {
51
55
  return { apiActions, keyUrl };
52
56
  }
53
57
 
58
+ // Vertex AI takes either a service-account key or an express-mode API key.
59
+ function openVertexModePicker(flow, providerItem) {
60
+ flow.paint({
61
+ title: `Provider · ${providerItem._providerName}`,
62
+ description: 'Choose the Vertex AI key type.',
63
+ help: '↑/↓ Select · Enter Choose · Esc Back',
64
+ indexMode: 'always',
65
+ labelWidth: 22,
66
+ pickerKey: `providers-vertex-mode:${providerItem.value}`,
67
+ initialIndex: 0,
68
+ items: [
69
+ { value: 'service-account', label: 'Service account key', description: 'service-account JSON' },
70
+ { value: 'api-key', label: 'API key (express mode)', description: 'Gemini only · global location' },
71
+ ],
72
+ onSelect: (mode) => {
73
+ flow.releaseSurface();
74
+ setApiKeyPrompt(flow, providerItem, mode === 'api-key' ? { authMode: 'api-key', hint: VERTEX_EXPRESS_HINT } : {});
75
+ },
76
+ onCancel: () => openApiProviderActions(flow, providerItem),
77
+ });
78
+ }
79
+
54
80
  export function openApiProviderActions(flow, providerItem) {
55
81
  // Reached from acks (a failed forget-key) as well as key presses: prove
56
82
  // ownership at the sink so every caller is covered.
@@ -69,6 +95,10 @@ export function openApiProviderActions(flow, providerItem) {
69
95
  initialIndex: 0,
70
96
  items: apiActions,
71
97
  onSelect: (_detailValue, detail) => {
98
+ if (detail._action === 'set-key' && providerItem._providerId === 'vertex-ai') {
99
+ openVertexModePicker(flow, providerItem);
100
+ return;
101
+ }
72
102
  flow.releaseSurface();
73
103
  if (detail._action === 'set-key') {
74
104
  setApiKeyPrompt(flow, providerItem);