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
@@ -64,7 +64,7 @@ export function useSelectionStitchBuffer({ store, dragRef, scrollTargetRef }) {
64
64
  if (!Array.isArray(rows)) return;
65
65
  storeStitchRows(stitchBufferRef.current, rows, stitchHarvestScrollRef.current);
66
66
  }, 0);
67
- }, [store]);
67
+ }, [store, dragRef, scrollTargetRef]);
68
68
 
69
69
  // Synchronous sibling: snapshot the rows CURRENTLY under the selection
70
70
  // immediately, keyed by the given (pre-scroll) offset. Called right before
@@ -80,7 +80,7 @@ export function useSelectionStitchBuffer({ store, dragRef, scrollTargetRef }) {
80
80
  if (!Array.isArray(rows)) return;
81
81
  storeStitchRows(stitchBufferRef.current, rows, Number(scroll) || 0);
82
82
  },
83
- [store]
83
+ [store, dragRef]
84
84
  );
85
85
 
86
86
  // Map the CURRENT rect + current scrollTarget onto the content-key range and
@@ -122,7 +122,7 @@ export function useSelectionStitchBuffer({ store, dragRef, scrollTargetRef }) {
122
122
  }
123
123
  const text = logical.map((l) => l.replace(/\s+$/u, '')).join('\n');
124
124
  return text.trim() ? { text, complete } : empty;
125
- }, []);
125
+ }, [dragRef, scrollTargetRef]);
126
126
 
127
127
  return { clearStitchBuffer, harvestStitchRowsSoon, harvestStitchRowsNow, getStitchedSelectionText };
128
128
  }
@@ -0,0 +1,62 @@
1
+ /*
2
+ * app/use-slash-palette.mjs — the slash / `$skill` palette above the prompt.
3
+ *
4
+ * useSlashPaletteState owns the selection index, the dismissed-draft marker
5
+ * and the live open/count ref the mouse handler reads; it is declared early
6
+ * because the mouse and draft hooks need the setters. useSlashPalette derives
7
+ * the palette for this render from the prompt and the surfaces that suppress
8
+ * it, publishes it to that ref, and keeps the index inside the list.
9
+ */
10
+ import { useEffect, useRef, useState } from 'react';
11
+ import { deriveSlashPalette } from './slash-palette.mjs';
12
+ import { skillMentionQuery, useSkillMentionSkills } from './skill-mention-palette.mjs';
13
+
14
+ export function useSlashPaletteState() {
15
+ const [slashIndex, setSlashIndex] = useState(0);
16
+ const [slashDismissedFor, setSlashDismissedFor] = useState('');
17
+ const slashPaletteRef = useRef({ open: false, count: 0 });
18
+ return { slashIndex, setSlashIndex, slashDismissedFor, setSlashDismissedFor, slashPaletteRef };
19
+ }
20
+
21
+ export function useSlashPalette({
22
+ store,
23
+ slashPaletteRef,
24
+ setSlashIndex,
25
+ slashDismissedFor,
26
+ promptDraft,
27
+ promptCaret,
28
+ providerPrompt,
29
+ settingsPrompt,
30
+ toolApproval,
31
+ contextPanel,
32
+ usagePanel,
33
+ picker,
34
+ exiting,
35
+ commandBusy,
36
+ }) {
37
+ const mentionSkills = useSkillMentionSkills(store, skillMentionQuery(promptDraft, promptCaret) !== null);
38
+ const palette = deriveSlashPalette({
39
+ providerPrompt,
40
+ settingsPrompt,
41
+ toolApproval,
42
+ contextPanel,
43
+ usagePanel,
44
+ picker,
45
+ exiting,
46
+ commandBusy,
47
+ promptDraft,
48
+ slashDismissedFor,
49
+ skills: mentionSkills,
50
+ promptCaret,
51
+ });
52
+ const { activeSlashQuery, slashCommands, slashPaletteOpen } = palette;
53
+ slashPaletteRef.current = { open: slashPaletteOpen, count: slashCommands.length };
54
+
55
+ // A query change re-clamps even when the list length stays the same.
56
+ // biome-ignore lint/correctness/useExhaustiveDependencies(activeSlashQuery): query-change re-clamp trigger
57
+ useEffect(() => {
58
+ setSlashIndex((index) => Math.min(index, Math.max(0, slashCommands.length - 1)));
59
+ }, [slashCommands.length, activeSlashQuery, setSlashIndex]);
60
+
61
+ return palette;
62
+ }
@@ -47,7 +47,7 @@ export function useTerminalChrome({ stdout, isRawModeSupported, setResizeState }
47
47
  if (trailing) clearTimeout(trailing);
48
48
  stdout.off('resize', onResize);
49
49
  };
50
- }, [stdout]);
50
+ }, [stdout, setResizeState]);
51
51
 
52
52
  // Enable extended keyboard reporting (kitty + xterm modifyOtherKeys)
53
53
  // SYNCHRONOUSLY, ONCE, with NO query/round-trip. ink
@@ -61,7 +61,7 @@ export function useTerminalChrome({ stdout, isRawModeSupported, setResizeState }
61
61
  // \x1b[27;5;13~ (or kitty \x1b[13;5u) instead of a bare \r — fixing the old
62
62
  // "first Ctrl+Enter submits, second works" race. Teardown lives in index.jsx's
63
63
  // restoreTerminal(). The empty dep array makes this run exactly once on mount.
64
- // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ // biome-ignore lint/correctness/useExhaustiveDependencies: one-shot mount enable; teardown is owned by restoreTerminal()
65
65
  useEffect(() => {
66
66
  if (!isRawModeSupported || !stdout?.write) return;
67
67
  if (!supportsExtendedKeys()) return;
@@ -105,38 +105,53 @@ export function useTranscriptActivity({ state }) {
105
105
  // StatusLine only reads a small stats subset; engine clones the full stats
106
106
  // object on many updates. Memoize by field value so identical usage keeps
107
107
  // the same object reference and React.memo / effect deps stay quiet.
108
- const statuslineStats = useMemo(() => {
109
- const s = state.stats || {};
110
- return {
111
- currentContextSource: s.currentContextSource ?? null,
112
- currentEstimatedContextTokens: s.currentEstimatedContextTokens ?? 0,
113
- currentContextTokens: s.currentContextTokens ?? null,
114
- contextTokens: s.contextTokens ?? 0,
115
- latestPromptTokens: s.latestPromptTokens ?? 0,
116
- latestInputTokens: s.latestInputTokens ?? 0,
117
- latestCachedTokens: s.latestCachedTokens ?? 0,
118
- latestCacheWriteTokens: s.latestCacheWriteTokens ?? 0,
119
- inputTokens: s.inputTokens ?? 0,
120
- cachedTokens: s.cachedTokens ?? 0,
121
- cacheWriteTokens: s.cacheWriteTokens ?? 0,
122
- promptTokens: s.promptTokens ?? 0,
123
- turns: s.turns ?? 0,
124
- };
125
- }, [
126
- state.stats?.currentContextSource,
127
- state.stats?.currentEstimatedContextTokens,
128
- state.stats?.currentContextTokens,
129
- state.stats?.contextTokens,
130
- state.stats?.latestPromptTokens,
131
- state.stats?.latestInputTokens,
132
- state.stats?.latestCachedTokens,
133
- state.stats?.latestCacheWriteTokens,
134
- state.stats?.inputTokens,
135
- state.stats?.cachedTokens,
136
- state.stats?.cacheWriteTokens,
137
- state.stats?.promptTokens,
138
- state.stats?.turns,
139
- ]);
108
+ const {
109
+ currentContextSource,
110
+ currentEstimatedContextTokens,
111
+ currentContextTokens,
112
+ contextTokens,
113
+ latestPromptTokens,
114
+ latestInputTokens,
115
+ latestCachedTokens,
116
+ latestCacheWriteTokens,
117
+ inputTokens,
118
+ cachedTokens,
119
+ cacheWriteTokens,
120
+ promptTokens,
121
+ turns,
122
+ } = state.stats || {};
123
+ const statuslineStats = useMemo(
124
+ () => ({
125
+ currentContextSource: currentContextSource ?? null,
126
+ currentEstimatedContextTokens: currentEstimatedContextTokens ?? 0,
127
+ currentContextTokens: currentContextTokens ?? null,
128
+ contextTokens: contextTokens ?? 0,
129
+ latestPromptTokens: latestPromptTokens ?? 0,
130
+ latestInputTokens: latestInputTokens ?? 0,
131
+ latestCachedTokens: latestCachedTokens ?? 0,
132
+ latestCacheWriteTokens: latestCacheWriteTokens ?? 0,
133
+ inputTokens: inputTokens ?? 0,
134
+ cachedTokens: cachedTokens ?? 0,
135
+ cacheWriteTokens: cacheWriteTokens ?? 0,
136
+ promptTokens: promptTokens ?? 0,
137
+ turns: turns ?? 0,
138
+ }),
139
+ [
140
+ currentContextSource,
141
+ currentEstimatedContextTokens,
142
+ currentContextTokens,
143
+ contextTokens,
144
+ latestPromptTokens,
145
+ latestInputTokens,
146
+ latestCachedTokens,
147
+ latestCacheWriteTokens,
148
+ inputTokens,
149
+ cachedTokens,
150
+ cacheWriteTokens,
151
+ promptTokens,
152
+ turns,
153
+ ]
154
+ );
140
155
 
141
156
  return { agentRevision, activeTools, statuslineStats };
142
157
  }
@@ -44,11 +44,11 @@ export function useTranscriptFollow({
44
44
  setScrollOffset(Math.max(0, Math.round(scrollPositionRef.current)));
45
45
  }, 16);
46
46
  scrollAnimationRef.current.unref?.();
47
- }, [stopSmoothScroll]);
47
+ }, [scrollPositionRef, scrollTargetRef, followingRef, setScrollOffset, stopSmoothScroll]);
48
48
 
49
49
  const cancelTranscriptFollow = useCallback(() => {
50
50
  followingRef.current = false;
51
- }, []);
51
+ }, [followingRef]);
52
52
 
53
53
  const resetTranscriptScroll = useCallback(() => {
54
54
  stopSmoothScroll();
@@ -65,7 +65,15 @@ export function useTranscriptFollow({
65
65
  // and every new row piled up below the fold.
66
66
  followingRef.current = true;
67
67
  setScrollOffset(0);
68
- }, [stopSmoothScroll]);
68
+ }, [
69
+ scrollPositionRef,
70
+ scrollTargetRef,
71
+ followingRef,
72
+ transcriptAnchorRef,
73
+ transcriptAnchorDirtyRef,
74
+ setScrollOffset,
75
+ stopSmoothScroll,
76
+ ]);
69
77
 
70
78
  const armTranscriptFollow = useCallback(() => {
71
79
  // Do not mutate scrollOffset here. During prompt submit the transcript rows
@@ -78,7 +86,7 @@ export function useTranscriptFollow({
78
86
  pendingReadbackRowsRef.current = 0;
79
87
  followingRef.current = true;
80
88
  stopSmoothScroll();
81
- }, [stopSmoothScroll]);
89
+ }, [followingRef, transcriptAnchorRef, transcriptAnchorDirtyRef, stopSmoothScroll]);
82
90
 
83
91
  return {
84
92
  pendingReadbackRowsRef,
@@ -141,7 +141,7 @@ export function useTranscriptScroll({
141
141
  if (!anchor) return;
142
142
  transcriptAnchorRef.current = anchor;
143
143
  transcriptAnchorDirtyRef.current = false;
144
- }, []);
144
+ }, [transcriptGeomRef, scrollTargetRef, transcriptAnchorRef, transcriptAnchorDirtyRef]);
145
145
 
146
146
  // A manual scroll moves the reading position. Capture the new anchor
147
147
  // SYNCHRONOUSLY from the latest published geometry so the very next render
@@ -149,20 +149,23 @@ export function useTranscriptScroll({
149
149
  // streaming growth could lurch the view. Only at the true bottom drop the
150
150
  // anchor so the bottom-follow path owns the viewport again: a positive
151
151
  // wheel offset is an explicit reading position, even inside the old slack.
152
- const captureReadingAnchor = useCallback((target) => {
153
- if (target === 0) {
154
- transcriptAnchorRef.current = null;
155
- transcriptAnchorDirtyRef.current = false;
156
- return;
157
- }
158
- const anchor = readingAnchorAt(transcriptGeomRef.current || {}, target);
159
- if (anchor) {
160
- transcriptAnchorRef.current = anchor;
161
- transcriptAnchorDirtyRef.current = false;
162
- } else {
163
- transcriptAnchorDirtyRef.current = true;
164
- }
165
- }, []);
152
+ const captureReadingAnchor = useCallback(
153
+ (target) => {
154
+ if (target === 0) {
155
+ transcriptAnchorRef.current = null;
156
+ transcriptAnchorDirtyRef.current = false;
157
+ return;
158
+ }
159
+ const anchor = readingAnchorAt(transcriptGeomRef.current || {}, target);
160
+ if (anchor) {
161
+ transcriptAnchorRef.current = anchor;
162
+ transcriptAnchorDirtyRef.current = false;
163
+ } else {
164
+ transcriptAnchorDirtyRef.current = true;
165
+ }
166
+ },
167
+ [transcriptGeomRef, transcriptAnchorRef, transcriptAnchorDirtyRef]
168
+ );
166
169
 
167
170
  // Scrolling past the oldest/newest loaded edge pages transcript history
168
171
  // instead of moving the target. Returns true when the edge owned the
@@ -190,7 +193,15 @@ export function useTranscriptScroll({
190
193
  }
191
194
  return true;
192
195
  },
193
- [store, captureRestoreAnchor, stopSmoothScroll, cancelTranscriptFollow]
196
+ [
197
+ store,
198
+ scrollTargetRef,
199
+ scrollPositionRef,
200
+ setScrollOffset,
201
+ captureRestoreAnchor,
202
+ stopSmoothScroll,
203
+ cancelTranscriptFollow,
204
+ ]
194
205
  );
195
206
 
196
207
  // Keep a live selection on its content after the viewport moved. An active
@@ -229,7 +240,14 @@ export function useTranscriptScroll({
229
240
  // (Ctrl+C after scrolling then copied just that fragment).
230
241
  paintSelectionRect(clippedRect, { rememberText: false });
231
242
  },
232
- [buildSpanRect, selectionPointAtCurrentScroll, withSelectionClip, cancelPendingSelectionPaint, paintSelectionRect]
243
+ [
244
+ dragRef,
245
+ buildSpanRect,
246
+ selectionPointAtCurrentScroll,
247
+ withSelectionClip,
248
+ cancelPendingSelectionPaint,
249
+ paintSelectionRect,
250
+ ]
233
251
  );
234
252
 
235
253
  const scrollTranscriptRows = useCallback(
@@ -294,6 +312,17 @@ export function useTranscriptScroll({
294
312
  setScrollOffset(Math.round(target));
295
313
  },
296
314
  [
315
+ maxScrollRowsRef,
316
+ transcriptGeomRef,
317
+ scrollTargetRef,
318
+ scrollPositionRef,
319
+ pendingReadbackRowsRef,
320
+ dragRef,
321
+ transcriptAnchorRef,
322
+ transcriptAnchorDirtyRef,
323
+ followingRef,
324
+ selectionLayoutRef,
325
+ setScrollOffset,
297
326
  pageTranscriptHistory,
298
327
  captureReadingAnchor,
299
328
  rebuildSelectionAfterScroll,
@@ -0,0 +1,27 @@
1
+ /*
2
+ * app/use-transcript-swap-reset.mjs — returns the viewport to the live tail
3
+ * when the transcript items are bulk-swapped.
4
+ *
5
+ * Item-count changes never infer follow permission from scrollTarget=0. A
6
+ * first read-back input can be waiting one frame for committed row geometry;
7
+ * only prompt submit, session reset, or an explicit return to bottom arms
8
+ * follow. Pure streaming height growth is handled in the row-delta effect.
9
+ */
10
+ import { useLayoutEffect } from 'react';
11
+ import { transcriptSwapReturnsToTail } from './transcript-window.mjs';
12
+
13
+ export function useTranscriptSwapReset({ items, lastItemsCountRef, lastFirstItemIdRef, resetTranscriptScroll }) {
14
+ useLayoutEffect(() => {
15
+ const count = items.length;
16
+ const previousCount = lastItemsCountRef.current;
17
+ lastItemsCountRef.current = count;
18
+ const firstId = count > 0 ? (items[0]?.id ?? null) : null;
19
+ const previousFirstId = lastFirstItemIdRef.current;
20
+ lastFirstItemIdRef.current = firstId;
21
+ // A bulk swap (compaction included) is an explicit return to the tail: the
22
+ // rows the reader was anchored to no longer exist.
23
+ if (transcriptSwapReturnsToTail({ count, previousCount, firstId, previousFirstId })) {
24
+ resetTranscriptScroll();
25
+ }
26
+ }, [items, lastItemsCountRef, lastFirstItemIdRef, resetTranscriptScroll]);
27
+ }
@@ -81,7 +81,12 @@ export function useTranscriptGeometry(state, props) {
81
81
  // settled-prefix row-index instead of re-walking all N items. Any structural
82
82
  // change misses the cache and falls back to a full rebuild, so the prefix
83
83
  // table is byte-identical to a full rebuild for the settled prefix. All those
84
- // invalidators are folded into the memo deps.
84
+ // invalidators are folded into the memo deps: revision + tailSig stand in
85
+ // for the items/tail identities, and measuredRowsVersion folds in measured
86
+ // corrections.
87
+ // biome-ignore lint/correctness/useExhaustiveDependencies(transcriptItems): keyed by revision + tailSig
88
+ // biome-ignore lint/correctness/useExhaustiveDependencies(streamingTailItem): keyed by tailSig
89
+ // biome-ignore lint/correctness/useExhaustiveDependencies(tailSig): tail id + height recompute key
85
90
  const transcriptRowIndex = useMemo(
86
91
  () =>
87
92
  buildTranscriptRowIndexIncremental(transcriptItems, {
@@ -92,9 +97,8 @@ export function useTranscriptGeometry(state, props) {
92
97
  cacheRef: state.incrementalRowIndexCacheRef,
93
98
  prefixRevision: revision,
94
99
  streamingTailItem,
95
- // eslint-disable-next-line react-hooks/exhaustive-deps -- revision/tail height capture structural geometry; measuredRowsVersion folds in measured corrections
96
100
  }),
97
- [revision, tailSig, frameColumns, toolOutputExpanded, measuredRowsVersion]
101
+ [revision, tailSig, frameColumns, toolOutputExpanded, measuredRowsVersion, state.incrementalRowIndexCacheRef]
98
102
  );
99
103
  const renderScrollOffset = resolveRenderScrollOffset({
100
104
  scrollOffset,
@@ -109,6 +113,10 @@ export function useTranscriptGeometry(state, props) {
109
113
  geomRef: transcriptGeomRef,
110
114
  prevViewportRef: state.prevViewportGeomRef,
111
115
  });
116
+ // transcriptStructureSig stands in for the items identity (it also encodes
117
+ // frameColumns and toolOutputExpanded); scroll/viewport capture the rest.
118
+ // biome-ignore lint/correctness/useExhaustiveDependencies(transcriptItems): keyed by transcriptStructureSig
119
+ // biome-ignore lint/correctness/useExhaustiveDependencies(transcriptStructureSig): items recompute key
112
120
  const transcriptWindow = useMemo(
113
121
  () =>
114
122
  transcriptRenderWindow(transcriptItems, {
@@ -117,9 +125,15 @@ export function useTranscriptGeometry(state, props) {
117
125
  columns: frameColumns,
118
126
  toolOutputExpanded,
119
127
  rowIndex: transcriptRowIndex,
120
- // eslint-disable-next-line react-hooks/exhaustive-deps -- intentional: sig+scroll/viewport capture the relevant changes
121
128
  }),
122
- [transcriptStructureSig, renderScrollOffset, transcriptContentHeight, transcriptRowIndex]
129
+ [
130
+ transcriptStructureSig,
131
+ renderScrollOffset,
132
+ transcriptContentHeight,
133
+ transcriptRowIndex,
134
+ frameColumns,
135
+ toolOutputExpanded,
136
+ ]
123
137
  );
124
138
  publishMaxScrollRows(state, transcriptWindow, { maxScrollRowsRef, frameColumns, toolOutputExpanded });
125
139
  // Publish this frame's geometry so a manual scroll can capture the reading
@@ -65,6 +65,10 @@ export function useTranscriptInstanceState({ sessionKey, scrollOffset, setScroll
65
65
  followingRef.current = true;
66
66
  selectionLayoutRef.current = null;
67
67
  }
68
+ // Runs on a session switch only: normalizedSessionKey is the trigger, and
69
+ // scrollOffset is read (not tracked) so scrolling never re-runs the reset.
70
+ // biome-ignore lint/correctness/useExhaustiveDependencies(normalizedSessionKey): session-switch trigger
71
+ // biome-ignore lint/correctness/useExhaustiveDependencies(scrollOffset): read at switch time only
68
72
  useLayoutEffect(() => {
69
73
  if (scrollOffset !== 0) setScrollOffset(0);
70
74
  }, [normalizedSessionKey, setScrollOffset]);
@@ -40,12 +40,16 @@ export function useSelectionLayoutShift({
40
40
  // with only the still-visible fragment (partial Ctrl+C after scrolling).
41
41
  paintSelectionRect(clippedRect, { rememberText: false });
42
42
  }, [
43
+ transcriptViewportRef,
44
+ selectionLayoutRef,
45
+ dragRef,
43
46
  transcriptContentHeight,
44
47
  transcriptWindow.totalRows,
45
48
  transcriptWindow.effectiveScrollOffset,
46
49
  withSelectionClip,
47
50
  paintSelectionRect,
48
51
  ]);
52
+ // biome-ignore lint/correctness/useExhaustiveDependencies(themeEpoch): theme-change repaint trigger
49
53
  useEffect(() => {
50
54
  if (!dragRef.current.rect) return;
51
55
  const clippedRect = withSelectionClip(dragRef.current.rect);
@@ -53,5 +57,5 @@ export function useSelectionLayoutShift({
53
57
  // Theme repaint: same cells, same text — no need to re-harvest (and a
54
58
  // clipped rect would clobber the remembered full text with a fragment).
55
59
  paintSelectionRect(clippedRect, { rememberText: false });
56
- }, [themeEpoch, withSelectionClip, paintSelectionRect]);
60
+ }, [themeEpoch, dragRef, withSelectionClip, paintSelectionRect]);
57
61
  }
@@ -13,7 +13,7 @@
13
13
  * use-transcript-window/visible-items.mjs — on-screen slice, overlay hint placement
14
14
  * use-transcript-window/selection-shift.mjs — selection rect follows geometry/theme
15
15
  */
16
- import { useCallback, useEffect, useLayoutEffect } from 'react';
16
+ import { useCallback, useEffect, useLayoutEffect, useMemo } from 'react';
17
17
  import { harvestMeasuredRows, measureRefFor } from './transcript-measure.mjs';
18
18
  import {
19
19
  clampScrollToOverflow,
@@ -66,7 +66,10 @@ export function useTranscriptWindow({
66
66
  selectionLayoutRef,
67
67
  };
68
68
  const state = useTranscriptInstanceState({ sessionKey, scrollOffset, setScrollOffset, refs });
69
- const transcriptMeasureRef = useCallback((item) => measureRefFor(state.measureStateRef.current, item), []);
69
+ const transcriptMeasureRef = useCallback(
70
+ (item) => measureRefFor(state.measureStateRef.current, item),
71
+ [state.measureStateRef]
72
+ );
70
73
  const { revision, streamingTailItem, transcriptItems, transcriptRowIndex, transcriptWindow } = useTranscriptGeometry(
71
74
  state,
72
75
  {
@@ -102,13 +105,12 @@ export function useTranscriptWindow({
102
105
  transcriptGuardRows,
103
106
  themeEpoch,
104
107
  };
105
- const scrollRefs = {
106
- transcriptAnchorRef,
107
- transcriptAnchorDirtyRef,
108
- scrollTargetRef,
109
- scrollPositionRef,
110
- followingRef,
111
- };
108
+ // Memoized over the App-owned refs (stable identities), so the effects below
109
+ // can depend on the bundle without re-running.
110
+ const scrollRefs = useMemo(
111
+ () => ({ transcriptAnchorRef, transcriptAnchorDirtyRef, scrollTargetRef, scrollPositionRef, followingRef }),
112
+ [transcriptAnchorRef, transcriptAnchorDirtyRef, scrollTargetRef, scrollPositionRef, followingRef]
113
+ );
112
114
  // ── Measured height harvest (every commit, no deps) ─────────────────────
113
115
  useLayoutEffect(() => {
114
116
  harvestMeasuredRows(state.measureStateRef.current, {
@@ -120,6 +122,10 @@ export function useTranscriptWindow({
120
122
  });
121
123
  });
122
124
  // ── Bottom-follow / anchor post-commit sync ─────────────────────────────
125
+ // transcriptItems is read alongside transcriptRowIndex, which is derived
126
+ // from it; the sync keys on the row index so a same-geometry items swap
127
+ // does not re-run it.
128
+ // biome-ignore lint/correctness/useExhaustiveDependencies(transcriptItems): keyed on the derived row index
123
129
  useLayoutEffect(() => {
124
130
  const totalRows = Math.max(0, Number(transcriptWindow.totalRows) || 0);
125
131
  const previousTotalRows = Math.max(0, Number(state.transcriptTotalRowsRef.current) || 0);
@@ -144,10 +150,14 @@ export function useTranscriptWindow({
144
150
  transcriptContentHeight,
145
151
  scrollOffset,
146
152
  stopSmoothScroll,
153
+ scrollRefs,
154
+ state.transcriptTotalRowsRef,
155
+ dragRef,
156
+ setScrollOffset,
147
157
  ]);
148
158
  useLayoutEffect(() => {
149
159
  resetScrollWhenUnanchored(scrollRefs, { scrollOffset, setScrollOffset, stopSmoothScroll });
150
- }, [scrollOffset, stopSmoothScroll]);
160
+ }, [scrollRefs, scrollOffset, setScrollOffset, stopSmoothScroll]);
151
161
  // ── Selection layout shift ──────────────────────────────────────────────
152
162
  useSelectionLayoutShift({
153
163
  transcriptWindow,
@@ -166,7 +176,7 @@ export function useTranscriptWindow({
166
176
  setScrollOffset,
167
177
  stopSmoothScroll,
168
178
  });
169
- }, [transcriptWindow.maxScrollRows, scrollOffset, stopSmoothScroll]);
179
+ }, [scrollRefs, transcriptWindow.maxScrollRows, scrollOffset, setScrollOffset, stopSmoothScroll]);
170
180
 
171
181
  return {
172
182
  transcriptWindow,
@@ -0,0 +1,18 @@
1
+ /*
2
+ * app/use-tui-ready.mjs — post-mount input gate.
3
+ *
4
+ * tuiReady stays false across the first render + commit. Let one event-loop
5
+ * poll pass so Ink processes (and discards, because PromptInput is still
6
+ * disabled) any keystrokes the OS buffered during terminal setup / initial
7
+ * mount. After the tick, enable the input — new keystrokes land normally.
8
+ */
9
+ import { useEffect, useState } from 'react';
10
+
11
+ export function useTuiReady() {
12
+ const [tuiReady, setTuiReady] = useState(false);
13
+ useEffect(() => {
14
+ const timer = setTimeout(() => setTuiReady(true), 0);
15
+ return () => clearTimeout(timer);
16
+ }, []);
17
+ return tuiReady;
18
+ }
@@ -26,6 +26,11 @@ export function useWelcomePromptHint({ store, state, toastErrorSignature }) {
26
26
  setWelcomePromptHintDismissed(true);
27
27
  }, []);
28
28
 
29
+ // The probe re-runs when the workflow id changes; the workflow snapshot it
30
+ // hands to the summary probe is read, not tracked, so an identity-only
31
+ // workflow refresh does not re-probe.
32
+ // biome-ignore lint/correctness/useExhaustiveDependencies(state.workflow): keyed by workflow id
33
+ // biome-ignore lint/correctness/useExhaustiveDependencies(state.workflow?.id): workflow id re-probe key
29
34
  useEffect(() => {
30
35
  let alive = true;
31
36
  const refreshConditionalWelcomeHint = async () => {
@@ -20,10 +20,12 @@ export function useWorkflowTabCycle({
20
20
  // async switch and pushes a toast, producing a rapid bottom-layout repaint
21
21
  // storm that can visually tear the prompt box in Windows Terminal.
22
22
  const workflowTabCycleRef = useRef({ pending: false, lastAt: 0 });
23
+ // The cycle reads only these two state fields.
24
+ const { commandBusy, workflow } = state;
23
25
  return useCallback(() => {
24
26
  if (slashPaletteOpen || toolApproval || picker || settingsPrompt || providerPrompt || contextPanel || usagePanel)
25
27
  return true;
26
- return cycleWorkflow({ store, state, cycleGuard: workflowTabCycleRef.current });
28
+ return cycleWorkflow({ store, state: { commandBusy, workflow }, cycleGuard: workflowTabCycleRef.current });
27
29
  }, [
28
30
  slashPaletteOpen,
29
31
  toolApproval,
@@ -32,8 +34,8 @@ export function useWorkflowTabCycle({
32
34
  providerPrompt,
33
35
  contextPanel,
34
36
  usagePanel,
35
- state.commandBusy,
36
- state.workflow,
37
+ commandBusy,
38
+ workflow,
37
39
  store,
38
40
  ]);
39
41
  }