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
@@ -16,11 +16,21 @@ const panelFrame = (providerItem, provider, pickerKey) => ({
16
16
  initialIndex: 0,
17
17
  });
18
18
 
19
- function showOAuthProgress(flow, providerItem, message, onBack) {
19
+ // Shown with every device code: a code someone else hands you signs THEM in to your account.
20
+ // One row that fits 80 columns: it takes the row the status footer did, so the URL
21
+ // keeps its copy-hint row and the "Enter code" line is never replaced by the copy note.
22
+ const DEVICE_CODE_WARNING = [
23
+ { glyph: '!', text: 'Continue only if you started this. Never enter a code someone sent you.' },
24
+ ];
25
+
26
+ function showOAuthProgress(flow, providerItem, message, onBack, detail = '', footer = null) {
20
27
  const provider = providerItem._provider || {};
28
+ const frame = panelFrame(providerItem, provider, `providers-oauth-progress:${providerItem.value}`);
21
29
  flow.paint({
22
- ...panelFrame(providerItem, provider, `providers-oauth-progress:${providerItem.value}`),
30
+ ...frame,
31
+ ...(footer ? { footer } : {}),
23
32
  description: message,
33
+ detail,
24
34
  help: '↑/↓ Select · Enter Choose · Esc Providers',
25
35
  indexMode: 'never',
26
36
  items: [
@@ -80,10 +90,11 @@ function oauthCodeHint(manualUrl, loginUrl, providerName) {
80
90
  return `Paste the authorization code or full redirect URL for ${providerName}.`;
81
91
  }
82
92
 
93
+ // The URL goes in the picker's wrapped `detail` block, never the one-line description.
83
94
  function pollingLoginMessage(userCode, loginUrl, opened) {
84
- if (userCode) return `Enter code ${userCode} at:\n${loginUrl}${opened}`;
85
- if (loginUrl) return `Open this URL to sign in:\n${loginUrl}${opened}`;
86
- return 'Finish signing in for this provider.';
95
+ if (userCode) return { message: `Enter code ${userCode} at${opened}:`, detail: loginUrl };
96
+ if (loginUrl) return { message: `Open this URL to sign in${opened}:`, detail: loginUrl };
97
+ return { message: 'Finish signing in for this provider.', detail: '' };
87
98
  }
88
99
 
89
100
  /** Login through store.beginOAuthProviderLogin: a code-completing login hands
@@ -148,7 +159,15 @@ function beginInteractiveOAuthLogin(flow, providerItem, login) {
148
159
  // Device-code and URL-only polling logins stay in the progress panel,
149
160
  // with the code and URL, until the result panel replaces it or Back.
150
161
  const opened = openBrowser ? ' (browser opened)' : '';
151
- showOAuthProgress(flow, providerItem, pollingLoginMessage(started?.userCode, loginUrl, opened), login.onBack);
162
+ const { message, detail } = pollingLoginMessage(started?.userCode, loginUrl, opened);
163
+ showOAuthProgress(
164
+ flow,
165
+ providerItem,
166
+ message,
167
+ login.onBack,
168
+ detail,
169
+ started?.userCode ? () => DEVICE_CODE_WARNING : null
170
+ );
152
171
  }
153
172
  started.waitForCallback
154
173
  ?.then((result) => {
@@ -18,6 +18,25 @@ import {
18
18
 
19
19
  const providerFooter = (item) => providerStatusFooter(item?._provider);
20
20
 
21
+ /** The rows of the main provider list: the continue row, one row per provider,
22
+ * and the client refresh row. */
23
+ function mainListItems(setup, options, canContinue) {
24
+ const items = [];
25
+ if (canContinue && !options.confirmBar) {
26
+ items.push({
27
+ value: 'continue-setup',
28
+ label: options.continueLabel || 'Continue setup',
29
+ description: options.continueDescription || 'return to setup',
30
+ _type: 'continue',
31
+ });
32
+ }
33
+ items.push(...buildProviderItems(setup));
34
+ if (setup.managedByClient === true) {
35
+ items.push({ value: 'refresh-client-providers', label: 'Refresh', _type: 'refresh-client-providers' });
36
+ }
37
+ return items;
38
+ }
39
+
21
40
  export function createProviderSetupPicker({
22
41
  store,
23
42
  surface,
@@ -87,19 +106,7 @@ export function createProviderSetupPicker({
87
106
  if (!setup) return;
88
107
  }
89
108
 
90
- const items = [];
91
- if ((returnTo || onContinue) && !options.confirmBar) {
92
- items.push({
93
- value: 'continue-setup',
94
- label: options.continueLabel || 'Continue setup',
95
- description: options.continueDescription || 'return to setup',
96
- _type: 'continue',
97
- });
98
- }
99
- items.push(...buildProviderItems(setup));
100
- if (setup.managedByClient === true) {
101
- items.push({ value: 'refresh-client-providers', label: 'Refresh', _type: 'refresh-client-providers' });
102
- }
109
+ const items = mainListItems(setup, options, Boolean(returnTo || onContinue));
103
110
 
104
111
  const flow = {
105
112
  store,
@@ -4,8 +4,9 @@
4
4
  * welcome banner, floating panel, and the layout signature that names them.
5
5
  */
6
6
  import { isLiveSpinnerMetaVisible } from '../live-spinner-visibility.mjs';
7
- import { promptContentRows, wrappedDetailRows, queuedBandRows } from '../text-layout.mjs';
7
+ import { promptContentRows, queuedBandRows } from '../text-layout.mjs';
8
8
  import { questionPanelRows, questionsOf } from '../../components/question-panel-model.mjs';
9
+ import { signInUrlLayout } from '../../components/sign-in-url-layout.mjs';
9
10
  import { CORE_MULTILINE_TEXT_ENTRY_KINDS } from '../panel-signature.mjs';
10
11
  import { btwPanelRows } from '../btw-controller.mjs';
11
12
 
@@ -49,24 +50,25 @@ function promptBoxBudget(inputBoxHidden, promptLayoutValueRef, promptContentColu
49
50
 
50
51
  // TextEntryPanel content is one prompt line (chrome + 1) for single-line
51
52
  // prompts, or up to PANEL_MAX_VISIBLE wrapped rows for core memory add/edit.
52
- // PLUS an optional wrapped detail block (blank spacer + N wrapped rows — e.g.
53
- // the manual OAuth URL). The floating container clips from the TOP (flex-end +
53
+ // PLUS an optional borderless sign-in URL block under the panel (the manual
54
+ // OAuth URL). The floating container clips from the TOP (flex-end +
54
55
  // overflow hidden), so under-reserving here pushed the bordered title off
55
- // the top of the panel. Width matches the panel interior: frame − 2 border
56
- // − 2 paddingX, same wrap-ansi math ink uses for wrap="wrap".
57
- function textEntryBudget(textEntryPrompt, textEntryLayoutRows, frameColumns) {
56
+ // the top of the panel. The block gets only the rows the terminal leaves
57
+ // (maxFloatingPanelRows minus chrome and content) and TextEntryPanel degrades
58
+ // it to fit, using the same sign-in-url-layout math.
59
+ function textEntryBudget(textEntryPrompt, textEntryLayoutRows, frameColumns, maxFloatingPanelRows) {
58
60
  const textEntryKind = String(textEntryPrompt?.kind || '');
59
61
  const textEntryMultiline = CORE_MULTILINE_TEXT_ENTRY_KINDS.has(textEntryKind);
60
62
  const textEntryDetailText = String(textEntryPrompt?.detail || '').trim();
61
- const textEntryDetailRows = textEntryDetailText
62
- ? 1 + wrappedDetailRows(textEntryDetailText, Math.max(1, frameColumns - 4))
63
- : 0;
64
63
  const textEntryContentRows = textEntryMultiline ? textEntryLayoutRows : 1;
64
+ const textEntryUrlMaxRows = Math.max(0, maxFloatingPanelRows - PANEL_CHROME_ROWS - textEntryContentRows);
65
+ const textEntryDetailRows = signInUrlLayout(textEntryDetailText, frameColumns, textEntryUrlMaxRows).rows;
65
66
  return {
66
67
  textEntryKind,
67
68
  textEntryMultiline,
68
69
  textEntryDetailText,
69
70
  textEntryDetailRows,
71
+ textEntryUrlMaxRows,
70
72
  textEntryContentRows,
71
73
  TEXT_ENTRY_ROWS: PANEL_CHROME_ROWS + textEntryContentRows + textEntryDetailRows,
72
74
  };
@@ -187,7 +189,6 @@ export function computeRowBudgets({
187
189
  const { textEntryPrompt, hasTextEntryPrompt, hasFloatingPanel, expandedOptionPanel, inputBoxHidden } = surfaces;
188
190
  const { liveSpinner, liveSpinnerIsCommand, latestTranscriptItem, inputHint } = hints;
189
191
  const prompt = promptBoxBudget(inputBoxHidden, promptLayoutValueRef, promptContentColumns);
190
- const textEntry = textEntryBudget(textEntryPrompt, textEntryLayoutRows, frameColumns);
191
192
  const OPTION_PANEL_EXTRA_ROWS = expandedOptionPanel ? 3 : 0;
192
193
  const queuedVisible = !hasFloatingPanel && !inputBoxHidden && state.queued?.length > 0;
193
194
  // While the slash palette is open it owns the area above the prompt, so the
@@ -214,6 +215,7 @@ export function computeRowBudgets({
214
215
  const baseReserve =
215
216
  welcome.WELCOME_ROWS + SCROLL_HINT_ROWS + LIVE_STATUS_ROWS + INPUT_BOX_ROWS + STATUSLINE_ROWS + queued.queuedRows;
216
217
  const maxFloatingPanelRows = Math.max(0, resizeState.rows - baseReserve - 1);
218
+ const textEntry = textEntryBudget(textEntryPrompt, textEntryLayoutRows, frameColumns, maxFloatingPanelRows);
217
219
  const panel = floatingPanelBudget({
218
220
  toolApproval,
219
221
  picker,
@@ -41,6 +41,7 @@ export const SLASH_COMMANDS = [
41
41
  params: '[release <task_id> [--confirm]]',
42
42
  description: 'List shells; release one whose termination is unconfirmed',
43
43
  },
44
+ { name: 'stop', usage: '/stop', description: 'Stop every background shell of this session' },
44
45
  {
45
46
  name: 'fork',
46
47
  usage: '/fork',
@@ -4,6 +4,7 @@
4
4
  // while a turn or session command is running.
5
5
  import { presentErrorText } from '../../../runtime/shared/err-text.mjs';
6
6
  import { BTW_USAGE_HINT } from '../btw-controller.mjs';
7
+ import { theme } from '../../theme.mjs';
7
8
 
8
9
  function compactFailureNotice(error) {
9
10
  if (error == null || String(error).trim() === '') return 'Compact failed.';
@@ -168,6 +169,51 @@ export const sessionCommands = {
168
169
  return true;
169
170
  },
170
171
 
172
+ // /stop — stop every background shell of this session (never the running
173
+ // foreground command, agents or other sessions) after one confirmation.
174
+ stop(ctx) {
175
+ const { store, surface } = ctx;
176
+ const stopAll = () =>
177
+ Promise.resolve(store.stopShellJobs?.())
178
+ .then((result) => {
179
+ const stopped = result?.stopped || 0;
180
+ const stopping = result?.stopping || 0;
181
+ store.pushNotice(
182
+ stopping
183
+ ? `Stopped background shells: ${stopped} · still stopping: ${stopping}`
184
+ : `Stopped background shells: ${stopped}`,
185
+ stopping ? 'warn' : 'info'
186
+ );
187
+ })
188
+ .catch((error) => store.pushNotice(String(error?.message || error), 'error'));
189
+ void Promise.resolve(store.listStoppableShellJobs?.())
190
+ .then((result) => {
191
+ const jobs = Array.isArray(result?.jobs) ? result.jobs : [];
192
+ if (!jobs.length) {
193
+ store.pushNotice('No background shells are running.', 'info');
194
+ return;
195
+ }
196
+ const own = surface.claim();
197
+ own.paint({
198
+ title: `Stop background shells (${jobs.length})?`,
199
+ indexMode: 'always',
200
+ labelWidth: 12,
201
+ items: [
202
+ { value: 'cancel', label: 'Cancel' },
203
+ { value: 'stop', label: 'Stop' },
204
+ ],
205
+ footer: jobs.map((job) => ({ glyph: '·', color: theme.warning, text: job.command })),
206
+ onSelect: (value) => {
207
+ own.close();
208
+ if (value === 'stop') void stopAll();
209
+ },
210
+ onCancel: () => own.close(),
211
+ });
212
+ })
213
+ .catch((error) => store.pushNotice(String(error?.message || error), 'error'));
214
+ return true;
215
+ },
216
+
171
217
  doctor(ctx) {
172
218
  const { state, store, deps } = ctx;
173
219
  if (state.commandBusy) {
@@ -17,7 +17,7 @@ import { createSlashPanelOpener } from './slash-dispatch/slash-panel.mjs';
17
17
  const commands = { ...routeCommands, ...sessionCommands, ...panelCommands };
18
18
 
19
19
  export function createSlashDispatch({ state, store, normalizeSlashCommandName, surface, closeUsagePanel, ...deps }) {
20
- const ctx = { state, store, deps, openSlashPanel: createSlashPanelOpener({ surface, store }) };
20
+ const ctx = { state, store, deps, surface, openSlashPanel: createSlashPanelOpener({ surface, store }) };
21
21
  const runSlashCommand = (cmd, arg = '') => {
22
22
  const rawName = String(cmd || '').toLowerCase();
23
23
  cmd = normalizeSlashCommandName(cmd);
@@ -0,0 +1,29 @@
1
+ /*
2
+ * app/use-btw-panel.mjs — the /btw side-question panel: its state, the
3
+ * controller that fills it, and the controller's session/unmount lifecycle.
4
+ */
5
+ import { useEffect, useRef, useState } from 'react';
6
+ import { createBtwController } from './btw-controller.mjs';
7
+
8
+ export function useBtwPanel({ store, sessionId }) {
9
+ const [btwPanel, setBtwPanel] = useState(null);
10
+ const btwRef = useRef(null);
11
+ const sessionIdRef = useRef('');
12
+ sessionIdRef.current = sessionId || '';
13
+ if (!btwRef.current) {
14
+ btwRef.current = createBtwController({
15
+ getStore: () => store,
16
+ setPanel: setBtwPanel,
17
+ getSessionId: () => sessionIdRef.current,
18
+ });
19
+ }
20
+ const btw = btwRef.current;
21
+ // A side answer belongs to the conversation it was asked in: drop it on a
22
+ // session change (new/resume/clear/switch) and cancel it on unmount.
23
+ // biome-ignore lint/correctness/useExhaustiveDependencies(sessionId): session-change trigger; the controller reads the id through its getter
24
+ useEffect(() => {
25
+ btw.sync();
26
+ }, [btw, sessionId]);
27
+ useEffect(() => () => btw.dispose(), [btw]);
28
+ return { btwPanel, btw };
29
+ }
@@ -0,0 +1,31 @@
1
+ /*
2
+ * app/use-context-panel-refresh.mjs — keeps an open /context breakdown panel
3
+ * current: it is rebuilt whenever one of the session fields it summarizes
4
+ * changes.
5
+ */
6
+ import { useEffect } from 'react';
7
+
8
+ export function useContextPanelRefresh({ contextPanelKind, state, openContextPicker }) {
9
+ // The deps are the refresh triggers (the fields the breakdown summarizes),
10
+ // not the reads; openContextPicker is rebuilt by its factory every render
11
+ // and must not re-trigger the rebuild.
12
+ // biome-ignore lint/correctness/useExhaustiveDependencies: trigger-keyed refresh over the summarized session fields
13
+ useEffect(() => {
14
+ if (contextPanelKind === 'context') openContextPicker();
15
+ }, [
16
+ contextPanelKind,
17
+ state.stats,
18
+ state.contextWindow,
19
+ state.rawContextWindow,
20
+ state.sessionId,
21
+ state.toolMode,
22
+ state.agentWorkers,
23
+ state.agentJobs,
24
+ state.provider,
25
+ state.model,
26
+ state.effort,
27
+ state.fast,
28
+ state.cwd,
29
+ state.clientHostPid,
30
+ ]);
31
+ }
@@ -20,16 +20,19 @@ export function useModelCatalogCache({ store, onboardingRef }) {
20
20
  // provider-scope cache clear (e.g. after auth) so a stale in-flight
21
21
  // listProviderModels() cannot repopulate the ref after invalidation.
22
22
  const onboardingPrefetchSeqRef = useRef(0);
23
- const clearModelCaches = useCallback((scope = 'all') => {
24
- if (scope === 'all' || scope === 'provider') {
25
- providerModelsCacheRef.current = { models: null, at: 0 };
26
- onboardingRef.current.providerModels = [];
27
- onboardingPrefetchSeqRef.current += 1;
28
- }
29
- if (scope === 'all' || scope === 'webSearch') {
30
- webSearchModelsCacheRef.current = { models: null, at: 0 };
31
- }
32
- }, []);
23
+ const clearModelCaches = useCallback(
24
+ (scope = 'all') => {
25
+ if (scope === 'all' || scope === 'provider') {
26
+ providerModelsCacheRef.current = { models: null, at: 0 };
27
+ onboardingRef.current.providerModels = [];
28
+ onboardingPrefetchSeqRef.current += 1;
29
+ }
30
+ if (scope === 'all' || scope === 'webSearch') {
31
+ webSearchModelsCacheRef.current = { models: null, at: 0 };
32
+ }
33
+ },
34
+ [onboardingRef]
35
+ );
33
36
  // Boot-time catalog prefetch: warm the /model & /agents provider catalog and
34
37
  // the /websearch catalog once at startup so those pickers open instantly from
35
38
  // cache (openModelPicker still TTL-refreshes stale rows in the background).
@@ -316,6 +316,17 @@ export function useMouseInput({
316
316
  isRawModeSupported,
317
317
  store,
318
318
  stdout,
319
+ dragRef,
320
+ lastClickRef,
321
+ slashPaletteRef,
322
+ scrollFocusRef,
323
+ promptMouseSelectionRef,
324
+ frameRowsRef,
325
+ promptBoxRectRef,
326
+ transcriptViewportRef,
327
+ scrollTargetRef,
328
+ setSlashIndex,
329
+ setMeasuredRowsVersion,
319
330
  passthroughCtrlWheelZoom,
320
331
  frameColumns,
321
332
  statuslineBandRows,
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * app/use-onboarding-start.mjs — first-run onboarding gate + wizard launch.
3
3
  */
4
- import { useEffect } from 'react';
4
+ import { useEffect, useRef } from 'react';
5
5
 
6
6
  // First-run gate. The daemon-backed engine store answers any method it does
7
7
  // not implement locally with an async remote call, so a synchronous
@@ -25,10 +25,15 @@ export function useOnboardingStart({
25
25
  store,
26
26
  forceOnboarding,
27
27
  onboardingCompleted,
28
- onboardingStartedRef,
29
28
  setOnboardingActive,
30
29
  openOnboardingAuthStep,
31
30
  }) {
31
+ // Latches the launch so the wizard opens at most once per mount.
32
+ const onboardingStartedRef = useRef(false);
33
+ // openOnboardingAuthStep is rebuilt by the picker factory every render;
34
+ // depending on it would re-run this launch (and cancel its deferred open)
35
+ // on every commit. The launch keys on the gate inputs only.
36
+ // biome-ignore lint/correctness/useExhaustiveDependencies(openOnboardingAuthStep): per-render factory callback; launch keys on gate inputs
32
37
  useEffect(() => {
33
38
  if (onboardingStartedRef.current) return undefined;
34
39
  if (resolveOnboardingCompleted(store, onboardingCompleted) && !forceOnboarding) return undefined;
@@ -41,5 +46,5 @@ export function useOnboardingStart({
41
46
  return () => {
42
47
  canceled = true;
43
48
  };
44
- }, [store, forceOnboarding, onboardingCompleted]);
49
+ }, [store, forceOnboarding, onboardingCompleted, setOnboardingActive]);
45
50
  }
@@ -30,7 +30,16 @@ export function usePanelTransition({
30
30
  if (!hadTransitionClearance) return undefined;
31
31
  const timer = setTimeout(() => setPanelTransitionEpoch((epoch) => epoch + 1), 0);
32
32
  return () => clearTimeout(timer);
33
- }, [panelLayoutSignature, bottomClusterRows, panelTransitionClearRows, panelTransitionGuardRows]);
33
+ }, [
34
+ panelTransitionRef,
35
+ panelCloseInkMaskRowsRef,
36
+ panelLayoutSignature,
37
+ bottomClusterRows,
38
+ panelTransitionClearRows,
39
+ panelTransitionGuardRows,
40
+ setPanelInkMaskEpoch,
41
+ setPanelTransitionEpoch,
42
+ ]);
34
43
  // Record the transcript tail id AFTER every commit so the next render's
35
44
  // spinner-meta-collapse gate (doneTailAppendedThisCommit) can tell a freshly
36
45
  // appended done row from a stale one that was already at the tail.
@@ -48,31 +48,39 @@ function usePastedBufferSlice({ toEntry, formatRef }) {
48
48
  setBuffers(next);
49
49
  }, []);
50
50
 
51
- const register = useCallback((input) => {
52
- const id = nextIdRef.current;
53
- const entry = toEntry(input, id);
54
- if (!entry) return '';
55
- nextIdRef.current = id + 1;
56
- buffersRef.current = { ...buffersRef.current, [id]: entry };
57
- setBuffers(buffersRef.current);
58
- return formatRef(entry);
59
- }, []);
51
+ const register = useCallback(
52
+ (input) => {
53
+ const id = nextIdRef.current;
54
+ const entry = toEntry(input, id);
55
+ if (!entry) return '';
56
+ nextIdRef.current = id + 1;
57
+ buffersRef.current = { ...buffersRef.current, [id]: entry };
58
+ setBuffers(buffersRef.current);
59
+ return formatRef(entry);
60
+ },
61
+ [toEntry, formatRef]
62
+ );
60
63
 
61
64
  return { buffersRef, nextIdRef, install, clearSnapshot, register };
62
65
  }
63
66
 
67
+ // Module-level slice specs keep toEntry/formatRef identities stable, so
68
+ // `register` is created once per mount.
69
+ const IMAGE_SLICE = {
70
+ toEntry: (image, id) => (image?.type === 'image' && image.content ? { ...image, id } : null),
71
+ formatRef: (entry) => formatImageRef(entry.id),
72
+ };
73
+ const TEXT_SLICE = {
74
+ toEntry: (text, id) => {
75
+ const value = String(text ?? '');
76
+ return value ? { id, text: value } : null;
77
+ },
78
+ formatRef: (entry) => formatPastedTextRef(entry.id, entry.text),
79
+ };
80
+
64
81
  export function usePastedBuffers() {
65
- const images = usePastedBufferSlice({
66
- toEntry: (image, id) => (image?.type === 'image' && image.content ? { ...image, id } : null),
67
- formatRef: (entry) => formatImageRef(entry.id),
68
- });
69
- const texts = usePastedBufferSlice({
70
- toEntry: (text, id) => {
71
- const value = String(text ?? '');
72
- return value ? { id, text: value } : null;
73
- },
74
- formatRef: (entry) => formatPastedTextRef(entry.id, entry.text),
75
- });
82
+ const images = usePastedBufferSlice(IMAGE_SLICE);
83
+ const texts = usePastedBufferSlice(TEXT_SLICE);
76
84
 
77
85
  return {
78
86
  pastedImagesRef: images.buffersRef,
@@ -65,6 +65,13 @@ export function usePromptDraftFlow({
65
65
  showPromptHint,
66
66
  slashDismissedFor,
67
67
  syncPromptLayoutRows,
68
+ promptHistoryDraftChangeRef,
69
+ promptHistoryNavRef,
70
+ setPromptDraft,
71
+ setPromptDraftOverride,
72
+ promptHintActiveRef,
73
+ promptHintTimerRef,
74
+ setSlashDismissedFor,
68
75
  ]
69
76
  );
70
77
 
@@ -21,11 +21,11 @@ export function usePromptLayoutRows({
21
21
  const nextRows = promptContentRows(text, promptContentColumns);
22
22
  setPromptLayoutRows((prev) => (prev === nextRows ? prev : nextRows));
23
23
  },
24
- [promptContentColumns]
24
+ [promptContentColumns, promptLayoutValueRef, setPromptLayoutRows]
25
25
  );
26
26
  useEffect(() => {
27
27
  syncPromptLayoutRows(promptLayoutValueRef.current);
28
- }, [syncPromptLayoutRows]);
28
+ }, [syncPromptLayoutRows, promptLayoutValueRef]);
29
29
  useEffect(() => {
30
30
  const kind = String(settingsPrompt?.kind || '');
31
31
  if (!CORE_MULTILINE_TEXT_ENTRY_KINDS.has(kind)) {
@@ -34,6 +34,6 @@ export function usePromptLayoutRows({
34
34
  }
35
35
  const cols = Math.max(1, frameColumns - 4 - stringWidth('Sentence > '));
36
36
  setTextEntryLayoutRows(textEntryReservedRows(settingsPrompt?.initialValue, cols, 8));
37
- }, [settingsPrompt?.kind, settingsPrompt?.initialValue, frameColumns]);
37
+ }, [settingsPrompt?.kind, settingsPrompt?.initialValue, frameColumns, setTextEntryLayoutRows]);
38
38
  return syncPromptLayoutRows;
39
39
  }
@@ -53,7 +53,7 @@ export function usePromptQueueHistory({
53
53
  exit();
54
54
  });
55
55
  }, 60);
56
- }, [store, exit]);
56
+ }, [store, exit, exitRequestedRef, setExiting]);
57
57
 
58
58
  const restoreQueuedToPrompt = useCallback(
59
59
  (options = {}) =>
@@ -74,6 +74,7 @@ export function usePromptQueueHistory({
74
74
  store,
75
75
  state?.queued,
76
76
  promptDraft,
77
+ promptValueRef,
77
78
  showPromptHint,
78
79
  clearPromptHint,
79
80
  installPastedImages,
@@ -94,7 +95,7 @@ export function usePromptQueueHistory({
94
95
 
95
96
  const resetPromptHistoryNav = useCallback(() => {
96
97
  promptHistoryNavRef.current = { active: false, index: -1, seed: '', lastValue: '' };
97
- }, []);
98
+ }, [promptHistoryNavRef]);
98
99
 
99
100
  return { requestExit, restoreQueuedToPrompt, recentPromptHistory, resetPromptHistoryNav };
100
101
  }
@@ -31,7 +31,7 @@ export function useSelectionFocusMove({
31
31
  const shiftedRect = dragRef.current.rect;
32
32
  return { anchor: shiftedRect ? { x: shiftedRect.x1, y: shiftedRect.y1 } : null };
33
33
  },
34
- [scrollTranscriptRows]
34
+ [dragRef, scrollTargetRef, scrollTranscriptRows]
35
35
  );
36
36
 
37
37
  const moveSelectionFocus = useCallback(
@@ -120,7 +120,16 @@ export function useSelectionFocusMove({
120
120
  dragNow.last = { x: col, y: row };
121
121
  return true;
122
122
  },
123
- [applySelectionRect, statusBandRows, transcriptViewportRows, selectionMaxColAtRow, scrollFocusPastEdge]
123
+ [
124
+ dragRef,
125
+ scrollTargetRef,
126
+ transcriptBottomSlackRowsRef,
127
+ applySelectionRect,
128
+ statusBandRows,
129
+ transcriptViewportRows,
130
+ selectionMaxColAtRow,
131
+ scrollFocusPastEdge,
132
+ ]
124
133
  );
125
134
 
126
135
  return moveSelectionFocus;
@@ -22,13 +22,16 @@ export function useSelectionGeometry({
22
22
  transcriptViewportRef,
23
23
  dragRef,
24
24
  }) {
25
- const selectionPointAtCurrentScroll = useCallback((point, pointScroll = 0) => {
26
- if (!point) return null;
27
- return {
28
- x: point.x,
29
- y: point.y + (Number(scrollTargetRef.current) || 0) - (Number(pointScroll) || 0),
30
- };
31
- }, []);
25
+ const selectionPointAtCurrentScroll = useCallback(
26
+ (point, pointScroll = 0) => {
27
+ if (!point) return null;
28
+ return {
29
+ x: point.x,
30
+ y: point.y + (Number(scrollTargetRef.current) || 0) - (Number(pointScroll) || 0),
31
+ };
32
+ },
33
+ [scrollTargetRef]
34
+ );
32
35
 
33
36
  // Grow a word/line multi-click selection from its anchor span out to the
34
37
  // word/line under the cursor. Shared by the mouse handler (motion/release)
@@ -68,9 +71,15 @@ export function useSelectionGeometry({
68
71
  [store, frameColumns, selectionPointAtCurrentScroll]
69
72
  );
70
73
 
71
- const transcriptViewportRows = useCallback(() => transcriptViewportRowRange(transcriptViewportRef.current), []);
74
+ const transcriptViewportRows = useCallback(
75
+ () => transcriptViewportRowRange(transcriptViewportRef.current),
76
+ [transcriptViewportRef]
77
+ );
72
78
 
73
- const statusBandRows = useCallback(() => statusBandRowRange(frameRowsRef.current, statuslineBandRows), []);
79
+ const statusBandRows = useCallback(
80
+ () => statusBandRowRange(frameRowsRef.current, statuslineBandRows),
81
+ [frameRowsRef, statuslineBandRows]
82
+ );
74
83
 
75
84
  const selectionMaxColAtRow = useCallback(
76
85
  (row) => {
@@ -54,12 +54,12 @@ export function useSelectionPaint({
54
54
  .catch(() => {
55
55
  /* best-effort capture: Ctrl+C falls back to the render-time text */
56
56
  });
57
- }, [store]);
57
+ }, [store, selectionTextRef]);
58
58
 
59
59
  // Clip band + theming: use-selection-paint/selection-clip.mjs.
60
60
  const selectionClip = useCallback(
61
61
  () => selectionClipBand({ dragRef, frameRowsRef, transcriptViewportRef, statuslineBandRows }),
62
- [statuslineBandRows]
62
+ [dragRef, frameRowsRef, transcriptViewportRef, statuslineBandRows]
63
63
  );
64
64
 
65
65
  const withSelectionClip = useCallback(
@@ -98,7 +98,7 @@ export function useSelectionPaint({
98
98
  cancelPendingSelectionPaint();
99
99
  paintSelectionRect(clippedRect, { rememberText: true });
100
100
  },
101
- [paintSelectionRect, withSelectionClip, clearStitchBuffer, cancelPendingSelectionPaint]
101
+ [dragRef, selectionTextRef, paintSelectionRect, withSelectionClip, clearStitchBuffer, cancelPendingSelectionPaint]
102
102
  );
103
103
 
104
104
  const applySelectionRectThrottled = useCallback(
@@ -111,7 +111,7 @@ export function useSelectionPaint({
111
111
  cancelPending: cancelPendingSelectionPaint,
112
112
  });
113
113
  },
114
- [paintSelectionRect, withSelectionClip, cancelPendingSelectionPaint]
114
+ [dragRef, paintSelectionRect, withSelectionClip, cancelPendingSelectionPaint]
115
115
  );
116
116
 
117
117
  return {