mixdog 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/README.md +47 -23
  2. package/package.json +33 -27
  3. package/scripts/_test-folder-dialog.mjs +30 -0
  4. package/scripts/agent-parallel-smoke.mjs +388 -0
  5. package/scripts/agent-tag-reuse-smoke.mjs +183 -0
  6. package/scripts/background-task-meta-smoke.mjs +38 -0
  7. package/scripts/boot-smoke.mjs +52 -9
  8. package/scripts/build-runtime-linux.sh +348 -0
  9. package/scripts/build-runtime-macos.sh +217 -0
  10. package/scripts/build-runtime-windows.ps1 +242 -0
  11. package/scripts/compact-active-turn-test.mjs +68 -0
  12. package/scripts/compact-smoke.mjs +859 -129
  13. package/scripts/compact-trigger-migration-smoke.mjs +187 -0
  14. package/scripts/fix-brief-fn.mjs +35 -0
  15. package/scripts/fix-format-tool-surface.mjs +24 -0
  16. package/scripts/fix-tool-exec-visible.mjs +42 -0
  17. package/scripts/generate-runtime-manifest.mjs +166 -0
  18. package/scripts/hook-bus-test.mjs +330 -0
  19. package/scripts/lead-workflow-smoke.mjs +33 -39
  20. package/scripts/live-worker-smoke.mjs +43 -37
  21. package/scripts/llm-trace-summary.mjs +315 -0
  22. package/scripts/memory-meta-concurrency-test.mjs +20 -0
  23. package/scripts/output-style-smoke.mjs +56 -15
  24. package/scripts/parent-abort-link-test.mjs +44 -0
  25. package/scripts/patch-agent-brief.mjs +48 -0
  26. package/scripts/patch-app.mjs +21 -0
  27. package/scripts/patch-app2.mjs +18 -0
  28. package/scripts/patch-dist-brief.mjs +96 -0
  29. package/scripts/patch-tool-exec.mjs +70 -0
  30. package/scripts/pretool-ask-runtime-test.mjs +54 -0
  31. package/scripts/provider-toolcall-test.mjs +376 -0
  32. package/scripts/reactive-compact-persist-smoke.mjs +124 -0
  33. package/scripts/sanitize-tool-pairs-test.mjs +260 -0
  34. package/scripts/session-context-bench.mjs +344 -0
  35. package/scripts/session-ingest-smoke.mjs +177 -0
  36. package/scripts/set-effort-config-test.mjs +41 -0
  37. package/scripts/smoke-runtime-negative.ps1 +106 -0
  38. package/scripts/smoke-runtime-negative.sh +97 -0
  39. package/scripts/smoke.mjs +25 -0
  40. package/scripts/tool-result-hook-test.mjs +48 -0
  41. package/scripts/tool-smoke.mjs +1223 -95
  42. package/scripts/toolcall-args-test.mjs +150 -0
  43. package/scripts/tui-background-failure-smoke.mjs +73 -0
  44. package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
  45. package/src/agents/debugger/AGENT.md +8 -0
  46. package/src/agents/explore/AGENT.md +4 -0
  47. package/src/agents/heavy-worker/AGENT.md +9 -3
  48. package/src/agents/maintainer/AGENT.md +4 -0
  49. package/src/agents/reviewer/AGENT.md +8 -0
  50. package/src/agents/scheduler-task/AGENT.md +12 -0
  51. package/src/agents/scheduler-task/agent.json +6 -0
  52. package/src/agents/webhook-handler/AGENT.md +12 -0
  53. package/src/agents/webhook-handler/agent.json +6 -0
  54. package/src/agents/worker/AGENT.md +9 -3
  55. package/src/app.mjs +77 -3
  56. package/src/defaults/hidden-roles.json +17 -12
  57. package/src/headless-role.mjs +117 -0
  58. package/src/help.mjs +30 -0
  59. package/src/hooks/lib/permission-evaluator.cjs +11 -475
  60. package/src/lib/keychain-cjs.cjs +9 -1
  61. package/src/lib/mixdog-debug.cjs +0 -7
  62. package/src/lib/rules-builder.cjs +240 -96
  63. package/src/lib/text-utils.cjs +1 -1
  64. package/src/mixdog-session-runtime.mjs +2325 -450
  65. package/src/output-styles/default.md +12 -28
  66. package/src/output-styles/extreme-simple.md +9 -6
  67. package/src/output-styles/simple.md +22 -9
  68. package/src/repl.mjs +118 -59
  69. package/src/rules/agent/00-common.md +15 -0
  70. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  71. package/src/rules/agent/30-explorer.md +22 -0
  72. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  73. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  75. package/src/rules/lead/01-general.md +9 -5
  76. package/src/rules/lead/04-workflow.md +51 -12
  77. package/src/rules/lead/lead-tool.md +6 -0
  78. package/src/rules/shared/01-tool.md +12 -1
  79. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  80. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  81. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  82. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  83. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  85. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  86. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  87. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  88. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  89. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  91. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  92. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  93. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
  95. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
  96. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  97. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  99. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  100. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  101. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  102. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  103. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  106. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
  109. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  110. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  111. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
  112. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  113. package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
  114. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
  115. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  116. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  117. package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
  118. package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
  119. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  120. package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
  121. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  123. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  124. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
  125. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
  126. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
  128. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  129. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  130. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  131. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  132. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  139. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  140. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  141. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  142. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  143. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  144. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
  145. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  146. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  147. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  148. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  149. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  150. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  151. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  152. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  154. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  155. package/src/runtime/channels/index.mjs +152 -24
  156. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  157. package/src/runtime/channels/lib/session-discovery.mjs +3 -2
  158. package/src/runtime/channels/lib/tool-format.mjs +0 -2
  159. package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
  160. package/src/runtime/channels/lib/webhook.mjs +1 -1
  161. package/src/runtime/channels/tool-defs.mjs +29 -29
  162. package/src/runtime/memory/index.mjs +635 -107
  163. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  164. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  165. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  166. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  167. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  168. package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
  169. package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
  170. package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
  171. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
  172. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  173. package/src/runtime/memory/lib/memory.mjs +88 -9
  174. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  175. package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  177. package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
  178. package/src/runtime/memory/lib/session-ingest.mjs +194 -0
  179. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  180. package/src/runtime/memory/tool-defs.mjs +46 -37
  181. package/src/runtime/search/index.mjs +102 -466
  182. package/src/runtime/search/lib/web-tools.mjs +45 -25
  183. package/src/runtime/search/tool-defs.mjs +16 -23
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/atomic-file.mjs +4 -3
  186. package/src/runtime/shared/background-tasks.mjs +122 -11
  187. package/src/runtime/shared/child-spawn-gate.mjs +145 -0
  188. package/src/runtime/shared/config.mjs +7 -4
  189. package/src/runtime/shared/err-text.mjs +131 -4
  190. package/src/runtime/shared/llm/cost.mjs +2 -2
  191. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  192. package/src/runtime/shared/llm/index.mjs +34 -11
  193. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  194. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  195. package/src/runtime/shared/singleton-owner.mjs +104 -0
  196. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  197. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  198. package/src/runtime/shared/tool-surface.mjs +624 -98
  199. package/src/runtime/shared/user-data-guard.mjs +0 -2
  200. package/src/standalone/agent-task-status.mjs +203 -0
  201. package/src/standalone/agent-task-status.test.mjs +76 -0
  202. package/src/standalone/agent-tool.mjs +1913 -0
  203. package/src/standalone/channel-worker.mjs +370 -14
  204. package/src/standalone/explore-tool.mjs +165 -70
  205. package/src/standalone/folder-dialog.mjs +314 -0
  206. package/src/standalone/hook-bus.mjs +898 -22
  207. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  208. package/src/standalone/projects.mjs +226 -0
  209. package/src/standalone/provider-admin.mjs +41 -24
  210. package/src/standalone/seeds.mjs +2 -69
  211. package/src/standalone/usage-dashboard.mjs +96 -8
  212. package/src/tui/App.jsx +4798 -2153
  213. package/src/tui/components/AnsiText.jsx +39 -28
  214. package/src/tui/components/ContextPanel.jsx +87 -29
  215. package/src/tui/components/Markdown.jsx +43 -77
  216. package/src/tui/components/MarkdownTable.jsx +9 -184
  217. package/src/tui/components/Message.jsx +28 -11
  218. package/src/tui/components/Picker.jsx +95 -56
  219. package/src/tui/components/PromptInput.jsx +367 -239
  220. package/src/tui/components/QueuedCommands.jsx +1 -1
  221. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  222. package/src/tui/components/Spinner.jsx +67 -38
  223. package/src/tui/components/StatusLine.jsx +606 -38
  224. package/src/tui/components/TextEntryPanel.jsx +128 -9
  225. package/src/tui/components/ToolExecution.jsx +617 -368
  226. package/src/tui/components/TurnDone.jsx +3 -3
  227. package/src/tui/components/UsagePanel.jsx +3 -5
  228. package/src/tui/components/tool-output-format.mjs +365 -0
  229. package/src/tui/components/tool-output-format.test.mjs +220 -0
  230. package/src/tui/dist/index.mjs +8915 -2418
  231. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  232. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  233. package/src/tui/engine.mjs +1455 -279
  234. package/src/tui/figures.mjs +21 -40
  235. package/src/tui/index.jsx +75 -31
  236. package/src/tui/input-editing.mjs +25 -0
  237. package/src/tui/markdown/format-token.mjs +511 -68
  238. package/src/tui/markdown/format-token.test.mjs +216 -0
  239. package/src/tui/markdown/render-ansi.mjs +94 -0
  240. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  241. package/src/tui/markdown/stream-fence.mjs +34 -0
  242. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  243. package/src/tui/markdown/table-layout.mjs +250 -0
  244. package/src/tui/paste-attachments.mjs +0 -7
  245. package/src/tui/spinner-verbs.mjs +1 -2
  246. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  247. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  248. package/src/tui/theme.mjs +746 -24
  249. package/src/tui/time-format.mjs +1 -1
  250. package/src/tui/transcript-tool-failures.mjs +67 -0
  251. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  252. package/src/ui/ansi.mjs +1 -2
  253. package/src/ui/markdown.mjs +85 -26
  254. package/src/ui/markdown.test.mjs +70 -0
  255. package/src/ui/model-display.mjs +121 -0
  256. package/src/ui/session-stats.mjs +44 -0
  257. package/src/ui/statusline-context-label.test.mjs +15 -0
  258. package/src/ui/statusline.mjs +386 -178
  259. package/src/ui/tool-card.mjs +3 -16
  260. package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
  261. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  262. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  263. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  264. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  265. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  266. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  267. package/src/workflows/default/WORKFLOW.md +12 -5
  268. package/src/workflows/default/workflow.json +0 -1
  269. package/src/workflows/solo/WORKFLOW.md +15 -0
  270. package/src/workflows/solo/workflow.json +7 -0
  271. package/vendor/ink/build/output.js +6 -1
  272. package/src/agents/scheduler-task.md +0 -3
  273. package/src/agents/web-researcher/AGENT.md +0 -3
  274. package/src/agents/web-researcher/agent.json +0 -6
  275. package/src/agents/webhook-handler.md +0 -3
  276. package/src/rules/bridge/00-common.md +0 -5
  277. package/src/rules/bridge/30-explorer.md +0 -4
  278. package/src/rules/lead/00-tool-lead.md +0 -5
  279. package/src/rules/shared/00-language.md +0 -3
  280. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  281. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  282. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  283. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  284. package/src/standalone/bridge-tool.mjs +0 -1414
  285. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -6,7 +6,6 @@ import { Box, Text, useInput, usePaste, useStdin } from 'ink';
6
6
  import stringWidth from 'string-width';
7
7
  import { theme } from '../theme.mjs';
8
8
  import {
9
- caretPosition,
10
9
  clearSelection,
11
10
  deleteBackwardWord,
12
11
  deleteForwardWord,
@@ -38,7 +37,7 @@ function renderSelectedText(displayValue, range, trailingSpace = false) {
38
37
  <>
39
38
  {start > 0 ? displayValue.slice(0, start) : null}
40
39
  {end > start ? (
41
- <Text color={theme.inverseText} backgroundColor="rgb(245,245,245)">{displayValue.slice(start, end)}</Text>
40
+ <Text color={theme.selectionText} backgroundColor={theme.selectionBackground}>{displayValue.slice(start, end)}</Text>
42
41
  ) : null}
43
42
  {displayValue.slice(end)}
44
43
  {trailingSpace ? ' ' : ''}
@@ -50,6 +49,76 @@ function normalizeInput(text) {
50
49
  return String(text ?? '').replace(/\r\n?/g, '\n');
51
50
  }
52
51
 
52
+ // Collapse newlines to a single visible glyph so multiline pasted input stays a
53
+ // single visual row (the draft itself is unchanged for editing/submit).
54
+ const NEWLINE_GLYPH = '⏎';
55
+ function flattenForSingleLine(text) {
56
+ return String(text ?? '').replace(/\n/g, NEWLINE_GLYPH);
57
+ }
58
+
59
+ // Horizontal viewport over a single (flattened) line. Keeps the caret visible
60
+ // inside `width` cells and returns the visible slice plus the caret column so
61
+ // the content box can be hard-bounded to ONE row: long/multiline pasted input
62
+ // scrolls horizontally instead of wrapping and growing the panel. Offsets are
63
+ // code-unit offsets, which map 1:1 to the flattened string (newline → 1 glyph,
64
+ // mask → 1 char), so selection/cursor offsets carry over unchanged.
65
+ function windowSingleLine(flat, cursor, width) {
66
+ const w = Math.max(1, Math.floor(Number(width) || 1));
67
+ const chars = Array.from(flat);
68
+ const cells = chars.map((ch) => stringWidth(ch));
69
+ // Map the code-unit cursor to a char index.
70
+ let cuIndex = 0;
71
+ let cursorCharIdx = chars.length;
72
+ for (let i = 0; i < chars.length; i += 1) {
73
+ if (cuIndex >= cursor) { cursorCharIdx = i; break; }
74
+ cuIndex += chars[i].length;
75
+ }
76
+ let cursorCell = 0;
77
+ for (let i = 0; i < cursorCharIdx; i += 1) cursorCell += cells[i];
78
+ const totalCell = cells.reduce((a, b) => a + b, 0);
79
+ let startCell = cursorCell > w - 1 ? cursorCell - (w - 1) : 0;
80
+ startCell = Math.min(startCell, Math.max(0, totalCell - w));
81
+ startCell = Math.max(0, startCell);
82
+ let acc = 0;
83
+ let a = 0;
84
+ while (a < chars.length && acc + cells[a] <= startCell) { acc += cells[a]; a += 1; }
85
+ const alignedStart = acc;
86
+ let b = a;
87
+ let bAcc = 0;
88
+ while (b < chars.length && bAcc + cells[b] <= w) { bAcc += cells[b]; b += 1; }
89
+ let cuStart = 0;
90
+ for (let i = 0; i < a; i += 1) cuStart += chars[i].length;
91
+ let cuEnd = cuStart;
92
+ for (let i = a; i < b; i += 1) cuEnd += chars[i].length;
93
+ return {
94
+ text: chars.slice(a, b).join(''),
95
+ cuStart,
96
+ cuEnd,
97
+ startCell: alignedStart,
98
+ caretCol: Math.max(0, cursorCell - alignedStart),
99
+ };
100
+ }
101
+
102
+ // Collapse any whitespace/newlines so a hint is always a single visual line.
103
+ function singleLine(text) {
104
+ return String(text ?? '').replace(/\s+/g, ' ').trim();
105
+ }
106
+
107
+ // Width-aware single-line truncation with an ellipsis so a long hint (e.g. an
108
+ // OAuth manual URL) can never wrap into extra rows and overflow the panel.
109
+ function truncateText(value, width) {
110
+ const text = String(value || '');
111
+ if (!(width > 0)) return '';
112
+ if (stringWidth(text) <= width) return text;
113
+ if (width <= 1) return '…'.repeat(Math.max(0, width));
114
+ let out = '';
115
+ for (const ch of text) {
116
+ if (stringWidth(`${out}${ch}…`) > width) break;
117
+ out += ch;
118
+ }
119
+ return `${out}…`;
120
+ }
121
+
53
122
  function singleTrailingLineBreakPrefix(text) {
54
123
  const normalized = normalizeInput(text);
55
124
  if (!normalized.endsWith('\n')) return null;
@@ -57,6 +126,16 @@ function singleTrailingLineBreakPrefix(text) {
57
126
  return prefix.includes('\n') ? null : prefix;
58
127
  }
59
128
 
129
+ function isCtrlEnterSequence(input) {
130
+ const text = String(input ?? '');
131
+ const body = text.startsWith('\x1b[') ? text.slice(2) : text.startsWith('[') ? text.slice(1) : '';
132
+ if (!body) return false;
133
+ const kitty = /^13;(\d+)(?::\d+)?(?:;[\d:]+)?u$/.exec(body);
134
+ if (kitty) return ((Number(kitty[1]) - 1) & 4) !== 0;
135
+ const modifyOtherKeys = /^27;(\d+);13~$/.exec(body);
136
+ return Boolean(modifyOtherKeys && (((Number(modifyOtherKeys[1]) - 1) & 4) !== 0));
137
+ }
138
+
60
139
  export function TextEntryPanel({
61
140
  title,
62
141
  hint = '',
@@ -145,8 +224,9 @@ export function TextEntryPanel({
145
224
  }, { isActive: isRawModeSupported });
146
225
 
147
226
  useInput((input, key) => {
227
+ const rawSource = String(input ?? '');
148
228
  const rawInput = normalizeInput(input);
149
- if (/(?:\x1b)?\[<\d+;\d+;\d+[Mm]/.test(String(input ?? ''))) return;
229
+ if (/(?:\x1b)?\[<\d+;\d+;\d+[Mm]/.test(rawSource)) return;
150
230
 
151
231
  if (key.escape) {
152
232
  if (selectionRange(draftRef.current)) {
@@ -157,20 +237,30 @@ export function TextEntryPanel({
157
237
  return;
158
238
  }
159
239
  const trailingEnterPrefix = singleTrailingLineBreakPrefix(rawInput);
240
+ const rawCtrlEnter = isCtrlEnterSequence(rawSource) || isCtrlEnterSequence(rawInput);
241
+ const modifiedLineBreak = key.shift || key.meta || key.ctrl || rawCtrlEnter;
160
242
  const pasteFallback = rawInput.includes('\n') && trailingEnterPrefix === null && (rawInput.length > 1 || !key.return);
161
243
  if (pasteFallback) {
162
244
  updateDraft((d) => insertText(d, rawInput));
163
245
  return;
164
246
  }
165
247
  if (trailingEnterPrefix !== null) {
166
- if (key.shift || key.meta) {
248
+ if (modifiedLineBreak) {
167
249
  updateDraft((d) => insertText(d, `${trailingEnterPrefix}\n`));
168
250
  return;
169
251
  }
170
252
  submitEnterChunk(trailingEnterPrefix);
171
253
  return;
172
254
  }
255
+ if (rawCtrlEnter) {
256
+ updateDraft((d) => replaceSelection(d, '\n'));
257
+ return;
258
+ }
173
259
  if (key.return || rawInput === '\n') {
260
+ if (modifiedLineBreak) {
261
+ updateDraft((d) => replaceSelection(d, '\n'));
262
+ return;
263
+ }
174
264
  submit();
175
265
  return;
176
266
  }
@@ -280,8 +370,13 @@ export function TextEntryPanel({
280
370
  const labelWidth = stringWidth(String(promptLabel || ''));
281
371
  const w = Math.max(1, (yogaNode?.getComputedWidth?.() ?? columns) - labelWidth);
282
372
  contentWidthRef.current = w;
283
- const pos = caretPosition(d.value, d.cursor, w);
284
- return pos.row === 0 ? { row: 0, col: labelWidth + pos.col } : pos;
373
+ // The content area is hard-bounded to ONE row (see render): newlines are
374
+ // flattened to a glyph and the line scrolls horizontally. So the caret is
375
+ // always on row 0, at the windowed caret column past the prompt label.
376
+ const visible = mask ? d.value.replace(/[^\n]/g, '*') : d.value;
377
+ const flat = flattenForSingleLine(visible);
378
+ const win = windowSingleLine(flat, d.cursor, w);
379
+ return { row: 0, col: labelWidth + win.caretCol };
285
380
  };
286
381
  return true;
287
382
  };
@@ -301,9 +396,31 @@ export function TextEntryPanel({
301
396
  }, [isRawModeSupported, title]);
302
397
 
303
398
  const visibleValue = mask ? draft.value.replace(/[^\n]/g, '*') : draft.value;
304
- const renderedValue = renderSelectedText(visibleValue, selectionRange(draft), draft.cursor === draft.value.length);
399
+ // Hard-bound the content to the ONE reserved row: flatten newlines to a glyph
400
+ // and take a horizontal window around the caret so long/multiline pasted
401
+ // input scrolls sideways instead of wrapping and growing the panel (which
402
+ // would top-clip the title). Offsets map 1:1 to the flattened string, so the
403
+ // selection highlight stays aligned. The full draft value is preserved for
404
+ // editing and submit; only the rendered slice is clipped.
405
+ const labelCells = stringWidth(String(promptLabel || ''));
406
+ const contentCells = Math.max(1, columns - 2 - labelCells);
407
+ const flatValue = flattenForSingleLine(visibleValue);
408
+ const win = windowSingleLine(flatValue, draft.cursor, contentCells);
409
+ const windowSelection = (() => {
410
+ const range = selectionRange(draft);
411
+ if (!range) return null;
412
+ const start = Math.max(win.cuStart, Math.min(win.cuEnd, range.start)) - win.cuStart;
413
+ const end = Math.max(win.cuStart, Math.min(win.cuEnd, range.end)) - win.cuStart;
414
+ return end > start ? { start, end } : null;
415
+ })();
416
+ const trailingCaret = draft.cursor === draft.value.length && win.cuEnd >= flatValue.length;
417
+ const renderedValue = renderSelectedText(win.text, windowSelection, trailingCaret);
305
418
  const action = String(actionLabel || 'save').trim() || 'save';
306
419
  const helpText = `Enter to ${action} · Esc to cancel`;
420
+ // Standard panel rhythm: title row, blank, single-line hint, blank, content.
421
+ // The hint is collapsed to one line and width-truncated so a long manual
422
+ // OAuth URL can never wrap and push the bordered title off the top.
423
+ const hintText = truncateText(singleLine(hint), Math.max(0, columns - 4));
307
424
 
308
425
  return (
309
426
  <Box flexDirection="column" flexShrink={0} width="100%">
@@ -312,10 +429,12 @@ export function TextEntryPanel({
312
429
  <Text color={theme.panelTitle}>{title}</Text>
313
430
  <Text color={theme.subtle}>{helpText}</Text>
314
431
  </Box>
315
- {hint ? <Text color={theme.subtle}>{hint}</Text> : <Text color={theme.subtle}> </Text>}
432
+ <Text> </Text>
433
+ <Text color={theme.subtle}>{hintText || ' '}</Text>
434
+ <Text> </Text>
316
435
  <Box ref={boxRef} flexDirection="row" width="100%" backgroundColor={theme.background}>
317
436
  <Text color={theme.inactive}>{promptLabel}</Text>
318
- <Text color={theme.text} wrap="hard">{renderedValue}</Text>
437
+ <Text color={theme.text} wrap="truncate">{renderedValue}</Text>
319
438
  </Box>
320
439
  </Box>
321
440
  </Box>