@bahulam/code 0.1.12 → 0.1.14

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.
@@ -71,6 +71,7 @@ import { PluginRegistry } from '../plugins/registry.mjs';
71
71
  import { SessionManager } from '../core/session-manager.mjs';
72
72
  import { parseArgs } from '../config/cli-args.mjs';
73
73
  import { pickModelOverridesForm } from './repl-model-form.mjs';
74
+ import { isRawMultilinePasteChunk, normalizePastedText, pastedTextLabel } from './paste-input.mjs';
74
75
  import {
75
76
  MODEL_CATEGORY_ORDER,
76
77
  formatCategoryBadge,
@@ -183,6 +184,7 @@ import {
183
184
  moveToContent,
184
185
  prepareInputPrompt,
185
186
  redrawDockFrame,
187
+ redrawDockInput,
186
188
  renderDockInput,
187
189
  unmountInputDock,
188
190
  } from '../ui/input-dock.mjs';
@@ -1558,11 +1560,35 @@ function subAgentRunId(data = {}) {
1558
1560
  return data?.run_id || data?.sub_agent_run_id || null;
1559
1561
  }
1560
1562
 
1563
+ function activeSubAgentRunsMap() {
1564
+ const runs = session.activeSubAgentRuns;
1565
+ if (runs instanceof Map) return runs;
1566
+
1567
+ const restored = new Map();
1568
+ if (Array.isArray(runs)) {
1569
+ for (const item of runs) {
1570
+ if (Array.isArray(item) && item.length >= 2) {
1571
+ restored.set(item[0], item[1]);
1572
+ } else if (item && typeof item === 'object') {
1573
+ const runId = item.runId || item.run_id || item.id;
1574
+ if (runId) restored.set(runId, item);
1575
+ }
1576
+ }
1577
+ } else if (runs && typeof runs === 'object') {
1578
+ for (const [key, value] of Object.entries(runs)) {
1579
+ if (value && typeof value === 'object') restored.set(key, value);
1580
+ }
1581
+ }
1582
+
1583
+ session.activeSubAgentRuns = restored;
1584
+ return restored;
1585
+ }
1586
+
1561
1587
  function normalizeSubAgentRunData(data = {}) {
1562
1588
  if (!data || typeof data !== 'object') return data;
1563
1589
  const runId = subAgentRunId(data);
1564
1590
  if (!runId) return data;
1565
- const laneRun = session.activeSubAgentRuns?.get(runId);
1591
+ const laneRun = activeSubAgentRunsMap().get(runId);
1566
1592
  const patch = {};
1567
1593
  if (!data.run_id) patch.run_id = runId;
1568
1594
  if (laneRun?.type && !data.sub_agent) patch.sub_agent = laneRun.type;
@@ -1590,7 +1616,7 @@ function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
1590
1616
  let fold = runtime.foldedSubAgentToolMap.get(key);
1591
1617
  if (!fold) {
1592
1618
  const runId = subAgentRunId(data);
1593
- const laneRun = runId ? session.activeSubAgentRuns?.get(runId) : null;
1619
+ const laneRun = runId ? activeSubAgentRunsMap().get(runId) : null;
1594
1620
  fold = {
1595
1621
  key,
1596
1622
  runId,
@@ -1603,7 +1629,7 @@ function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
1603
1629
  runtime.foldedSubAgentToolMap.set(key, fold);
1604
1630
  } else {
1605
1631
  const runId = subAgentRunId(data);
1606
- const laneRun = runId ? session.activeSubAgentRuns?.get(runId) : null;
1632
+ const laneRun = runId ? activeSubAgentRunsMap().get(runId) : null;
1607
1633
  if (runId && !fold.runId) fold.runId = runId;
1608
1634
  if (laneRun) fold.label = subAgentLaneLabel(laneRun);
1609
1635
  if (laneRun?.query && !fold.query) fold.query = laneRun.query;
@@ -1613,8 +1639,8 @@ function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
1613
1639
  }
1614
1640
 
1615
1641
  function createSubAgentLane(agentType, query, runId, data = {}) {
1616
- session.activeSubAgentRuns = session.activeSubAgentRuns || new Map();
1617
- const sameTypeOrdinals = [...session.activeSubAgentRuns.values()]
1642
+ const activeRuns = activeSubAgentRunsMap();
1643
+ const sameTypeOrdinals = [...activeRuns.values()]
1618
1644
  .filter(run => run.type === agentType)
1619
1645
  .map(run => Number(run.ordinal || 1));
1620
1646
  const ordinal = sameTypeOrdinals.length ? Math.max(...sameTypeOrdinals) + 1 : 1;
@@ -1630,16 +1656,14 @@ function createSubAgentLane(agentType, query, runId, data = {}) {
1630
1656
  // the close line agree even for the first run of a batch.
1631
1657
  forceOrdinal: Number(data?.parallel_batch) > 1,
1632
1658
  };
1633
- session.activeSubAgentRuns.set(runId, lane);
1659
+ activeRuns.set(runId, lane);
1634
1660
  ensureFoldedSubAgentTools(agentType, runId, { type: agentType, query, run_id: runId });
1635
1661
  _syncSubAgentWindow();
1636
1662
  return lane;
1637
1663
  }
1638
1664
 
1639
1665
  function activeSubAgentLanes() {
1640
- return session.activeSubAgentRuns instanceof Map
1641
- ? [...session.activeSubAgentRuns.values()]
1642
- : [];
1666
+ return [...activeSubAgentRunsMap().values()];
1643
1667
  }
1644
1668
 
1645
1669
  function subAgentLaneLabel(lane, lanes = activeSubAgentLanes()) {
@@ -2321,8 +2345,8 @@ function renderEvent(event) {
2321
2345
  // concurrent runs of the same type are only distinguishable by the
2322
2346
  // backend-issued run_id; label lanes explore#1 / explore#2 when
2323
2347
  // more than one run is active. Solo runs render exactly as before.
2324
- session.activeSubAgentRuns = session.activeSubAgentRuns || new Map();
2325
- const hadActiveRuns = session.activeSubAgentRuns.size > 0;
2348
+ const activeRuns = activeSubAgentRunsMap();
2349
+ const hadActiveRuns = activeRuns.size > 0;
2326
2350
  if (!hadActiveRuns) {
2327
2351
  stopSpinner();
2328
2352
  clearPendingHead();
@@ -2330,7 +2354,8 @@ function renderEvent(event) {
2330
2354
  }
2331
2355
  const runId = data?.run_id || `${agentType}:${Date.now().toString(36)}`;
2332
2356
  const lane = createSubAgentLane(agentType, query, runId, data);
2333
- const parallel = session.activeSubAgentRuns.size > 1 || lane.forceOrdinal;
2357
+ const activeRunsAfterStart = activeSubAgentRunsMap();
2358
+ const parallel = activeRunsAfterStart.size > 1 || lane.forceOrdinal;
2334
2359
  const label = subAgentLaneLabel(lane);
2335
2360
  renderBlockBoundary('subagent');
2336
2361
  process.stderr.write(renderSubAgentOpen({ id: runId, type: label, query, parentDepth: parallel ? 0 : undefined }).replace(/^\n/, '') + '\n');
@@ -2341,7 +2366,7 @@ function renderEvent(event) {
2341
2366
  // inner tool calls stream here instead of flooding the transcript.
2342
2367
  setSubAgentWindowActive(true);
2343
2368
  if (hadActiveRuns) {
2344
- updateSpinner(`${session.activeSubAgentRuns.size} agents running`);
2369
+ updateSpinner(`${activeRunsAfterStart.size} agents running`);
2345
2370
  } else {
2346
2371
  // Phase per sub-agent run: the status line counts elapsed time and
2347
2372
  // tool calls live ("plan agent · 4 calls · 32s") for the whole run.
@@ -2366,16 +2391,17 @@ function renderEvent(event) {
2366
2391
  if (!eventRunId && hasParallelSubAgentRuns()) {
2367
2392
  break;
2368
2393
  }
2369
- const laneRun = session.activeSubAgentRuns?.get(eventRunId);
2394
+ const laneRun = activeSubAgentRunsMap().get(eventRunId);
2370
2395
  if (laneRun) laneRun.tools++;
2371
2396
  foldSubAgentToolProgress(eventData);
2372
- const laneParallel = (session.activeSubAgentRuns?.size || 0) > 1;
2397
+ const activeRuns = activeSubAgentRunsMap();
2398
+ const laneParallel = activeRuns.size > 1;
2373
2399
  if (laneParallel) {
2374
2400
  bumpSpinnerProgress();
2375
2401
  const activeLanes = activeSubAgentLanes();
2376
2402
  const lanes = activeLanes
2377
2403
  .map(r => `${subAgentLaneLabel(r, activeLanes)} ${r.tools}`).join(' · ');
2378
- updateSpinner(`${session.activeSubAgentRuns.size} agents · ${lanes}`);
2404
+ updateSpinner(`${activeRuns.size} agents · ${lanes}`);
2379
2405
  break;
2380
2406
  }
2381
2407
  // Feed the live window from THIS event — it always fires (55/55 in
@@ -2432,16 +2458,17 @@ function renderEvent(event) {
2432
2458
  // Retire this run's display lane; while sibling runs are still
2433
2459
  // active keep the shared window/spinner alive for them.
2434
2460
  const eventRunId = subAgentRunId(eventData);
2435
- const doneRun = session.activeSubAgentRuns?.get(eventRunId);
2461
+ const activeRuns = activeSubAgentRunsMap();
2462
+ const doneRun = activeRuns.get(eventRunId);
2436
2463
  const doneLabel = doneRun ? subAgentLaneLabel(doneRun) : agentType;
2437
2464
  const doneFold = eventRunId ? removeFoldedSubAgentTools(eventRunId) : null;
2438
2465
  if (doneFold) {
2439
2466
  doneFold.agentType = doneLabel;
2440
2467
  doneFold.label = doneLabel;
2441
2468
  }
2442
- if (eventRunId) session.activeSubAgentRuns?.delete(eventRunId);
2443
- else session.activeSubAgentRuns?.clear();
2444
- const siblingsActive = (session.activeSubAgentRuns?.size || 0) > 0;
2469
+ if (eventRunId) activeRuns.delete(eventRunId);
2470
+ else activeRuns.clear();
2471
+ const siblingsActive = activeRuns.size > 0;
2445
2472
  // In verbose mode each sub-agent tool already rendered a full
2446
2473
  // transcript card live — flushing the fold would list every tool a
2447
2474
  // second time. The fold batch is the durable record ONLY when tools
@@ -2732,17 +2759,18 @@ function renderEvent(event) {
2732
2759
  // late events land correctly instead of corrupting fresh state —
2733
2760
  // but only for ONE turn boundary: if they're still around at the
2734
2761
  // next complete with no closure, force-clean to avoid stale lanes.
2735
- const lanesLive = (session.activeSubAgentRuns?.size || 0) > 0;
2762
+ const activeRuns = activeSubAgentRunsMap();
2763
+ const lanesLive = activeRuns.size > 0;
2736
2764
  if (lanesLive && !session._lanesPreservedAtTurnEnd) {
2737
2765
  session._lanesPreservedAtTurnEnd = true;
2738
- const n = session.activeSubAgentRuns.size;
2766
+ const n = activeRuns.size;
2739
2767
  process.stderr.write(` ${c.dim(`${n} agent run${n === 1 ? '' : 's'} still active in background — progress continues below`)}\n`);
2740
2768
  } else {
2741
2769
  session._lanesPreservedAtTurnEnd = false;
2742
2770
  if (showSubAgentTools(getVerbosity())) resetFoldedSubAgentTools();
2743
2771
  else flushFoldedSubAgentTools();
2744
2772
  resetSubAgents();
2745
- session.activeSubAgentRuns?.clear();
2773
+ activeRuns.clear();
2746
2774
  setSubAgentWindowActive(false);
2747
2775
  }
2748
2776
  session.inSubAgent = false;
@@ -4297,11 +4325,13 @@ export async function startTerminalRepl() {
4297
4325
  options: Array.isArray(req?.options) ? req.options : [],
4298
4326
  context: req?.context || '',
4299
4327
  });
4328
+ if (isInputDockMounted()) redrawDockInput();
4300
4329
  if (res?.answer) {
4301
4330
  process.stderr.write(` ${c.green('✓')} ${c.dim('answered:')} ${c.brand(res.answer)}\n`);
4302
4331
  } else {
4303
4332
  process.stderr.write(` ${c.dim('Question declined — agent proceeds with its own judgment.')}\n`);
4304
4333
  }
4334
+ if (isInputDockMounted()) redrawDockInput();
4305
4335
  return res;
4306
4336
  };
4307
4337
 
@@ -4857,6 +4887,9 @@ export async function startTerminalRepl() {
4857
4887
  let _bracketedPasteStartLine = '';
4858
4888
  let _bracketedPasteStartCursor = 0;
4859
4889
  let _promptHasInsertedPaste = false;
4890
+ let _suppressRawPasteLines = false;
4891
+ let _pastedInputValue = '';
4892
+ let _pastedInputLabel = '';
4860
4893
  const _pasteEndListeners = new Set();
4861
4894
  function onBracketedPasteEnd(cb) { _pasteEndListeners.add(cb); return () => _pasteEndListeners.delete(cb); }
4862
4895
  function isInBracketedPaste() { return _inBracketedPaste; }
@@ -4876,7 +4909,25 @@ export async function startTerminalRepl() {
4876
4909
  while (i < s.length) {
4877
4910
  if (!_inBracketedPaste) {
4878
4911
  const start = s.indexOf(PASTE_BEGIN, i);
4879
- if (start === -1) return;
4912
+ if (start === -1) {
4913
+ if (isRawMultilinePasteChunk(s)) {
4914
+ _suppressRawPasteLines = true;
4915
+ const baseLine = String(rl?.line || '');
4916
+ const baseCursor = typeof rl?.cursor === 'number' ? rl.cursor : baseLine.length;
4917
+ setImmediate(() => {
4918
+ try {
4919
+ insertPromptText(normalizePastedText(s), {
4920
+ baseLine,
4921
+ baseCursor,
4922
+ fromPaste: true,
4923
+ });
4924
+ } finally {
4925
+ _suppressRawPasteLines = false;
4926
+ }
4927
+ });
4928
+ }
4929
+ return;
4930
+ }
4880
4931
  _inBracketedPaste = true;
4881
4932
  _bracketedPasteBuffer = '';
4882
4933
  _suppressBracketedPasteLines = true;
@@ -5138,7 +5189,11 @@ export async function startTerminalRepl() {
5138
5189
  const line = String(baseLine || '');
5139
5190
  const cursor = typeof baseCursor === 'number' ? Math.max(0, Math.min(line.length, baseCursor)) : line.length;
5140
5191
  const next = `${line.slice(0, cursor)}${payload}${line.slice(cursor)}`;
5141
- if (fromPaste) _promptHasInsertedPaste = true;
5192
+ if (fromPaste) {
5193
+ _promptHasInsertedPaste = true;
5194
+ _pastedInputValue = next;
5195
+ _pastedInputLabel = pastedTextLabel(payload);
5196
+ }
5142
5197
  replaceReadlineLine(next, cursor + payload.length);
5143
5198
  renderIdleDockInput();
5144
5199
  }
@@ -5178,15 +5233,28 @@ export async function startTerminalRepl() {
5178
5233
 
5179
5234
  function renderIdleDockInput() {
5180
5235
  if (!isInputDockMounted()) return false;
5236
+ const line = rl.line || '';
5237
+ let displayLine = line;
5238
+ let displayCursor = typeof rl.cursor === 'number' ? rl.cursor : null;
5239
+ let fixedRows = null;
5240
+ if (_pastedInputValue && line === _pastedInputValue) {
5241
+ displayLine = _pastedInputLabel;
5242
+ displayCursor = _pastedInputLabel.length;
5243
+ fixedRows = 1;
5244
+ } else if (_pastedInputValue) {
5245
+ _pastedInputValue = '';
5246
+ _pastedInputLabel = '';
5247
+ }
5181
5248
  // rl.cursor is readline's byte offset within rl.line. Threading it
5182
5249
  // through to focusDockInput makes arrow-key navigation visually move
5183
5250
  // the terminal cursor within the buffer instead of always landing at
5184
5251
  // the end of the string.
5185
- return renderDockInput(userPrompt(), rl.line || '', {
5252
+ return renderDockInput(userPrompt(), displayLine, {
5186
5253
  context: buildContextStrip(),
5187
5254
  meta: buildDockMeta(),
5188
5255
  tips: idleInputTips(),
5189
- cursor: typeof rl.cursor === 'number' ? rl.cursor : null,
5256
+ cursor: displayCursor,
5257
+ fixedRows,
5190
5258
  });
5191
5259
  }
5192
5260
 
@@ -5232,7 +5300,7 @@ export async function startTerminalRepl() {
5232
5300
  readline.emitKeypressEvents(process.stdin, rl);
5233
5301
  process.stdin.on('keypress', (_str, key = {}) => {
5234
5302
  if (!inputActive) return;
5235
- if (_inBracketedPaste || _suppressBracketedPasteLines) return;
5303
+ if (_inBracketedPaste || _suppressBracketedPasteLines || _suppressRawPasteLines) return;
5236
5304
  if (key.name === 'return' || key.name === 'enter') return;
5237
5305
  if (key.name === 'f2') {
5238
5306
  clearSlashHint();
@@ -5243,7 +5311,7 @@ export async function startTerminalRepl() {
5243
5311
  }
5244
5312
  setImmediate(() => {
5245
5313
  if (!inputActive) return;
5246
- if (_inBracketedPaste || _suppressBracketedPasteLines) return;
5314
+ if (_inBracketedPaste || _suppressBracketedPasteLines || _suppressRawPasteLines) return;
5247
5315
  if (slashHintVisible && key.name === 'tab' && acceptSlashHint()) return;
5248
5316
  if (slashHintVisible && key.name === 'down' && moveSlashHintSelection(1)) return;
5249
5317
  if (slashHintVisible && key.name === 'up' && moveSlashHintSelection(-1)) return;
@@ -5305,12 +5373,16 @@ export async function startTerminalRepl() {
5305
5373
  if (pastedLines.length > 1 || trailing) {
5306
5374
  const text = [...pastedLines, trailing].join('\n');
5307
5375
  _promptHasInsertedPaste = true;
5376
+ _pastedInputValue = text;
5377
+ _pastedInputLabel = pastedTextLabel(text);
5308
5378
  replaceReadlineLine(text);
5309
5379
  renderIdleDockInput();
5310
5380
  return;
5311
5381
  }
5312
5382
  const line = pastedLines.join('\n');
5313
5383
  _promptHasInsertedPaste = false;
5384
+ _pastedInputValue = '';
5385
+ _pastedInputLabel = '';
5314
5386
  queueOrRunLine(line);
5315
5387
  }
5316
5388
 
@@ -5320,7 +5392,7 @@ export async function startTerminalRepl() {
5320
5392
  // or the user pressed Enter normally), the debounce falls back to old
5321
5393
  // behavior — a single Enter flushes almost instantly.
5322
5394
  rl.on('line', async (line) => {
5323
- if (_suppressBracketedPasteLines) {
5395
+ if (_suppressBracketedPasteLines || _suppressRawPasteLines) {
5324
5396
  _pasteLines = [];
5325
5397
  if (_pasteFlushTimer) {
5326
5398
  clearTimeout(_pasteFlushTimer);
@@ -5369,6 +5441,8 @@ export async function startTerminalRepl() {
5369
5441
  let input = line.trim();
5370
5442
  const selectedSlashCommand = selectedSlashCommandFor(input);
5371
5443
  inputActive = false;
5444
+ _pastedInputValue = '';
5445
+ _pastedInputLabel = '';
5372
5446
  clearSlashHint();
5373
5447
  if (selectedSlashCommand) input = selectedSlashCommand;
5374
5448
  if (!input) {
@@ -122,6 +122,25 @@ export function createToolRegistry({
122
122
  const name = String(toolDef.name || '').trim();
123
123
  if (!name || tools.has(name)) continue;
124
124
  const pluginName = toolDef._plugin_name || toolDef.plugin_name || null;
125
+ if (toolDef._composed?.kind === 'pi') {
126
+ tools.set(name, {
127
+ name,
128
+ description: toolDef.description || '',
129
+ inputSchema: toolDef.input_schema || toolDef.parameters || { type: 'object', properties: {} },
130
+ validateInput() { return []; },
131
+ async call() {
132
+ return {
133
+ success: false,
134
+ output: `Composed pi tool '${name}' is registered but pi runtime execution is not wired yet`,
135
+ _tool: name,
136
+ _plugin: pluginName,
137
+ _composed: toolDef._composed,
138
+ _blocked: true,
139
+ };
140
+ },
141
+ });
142
+ continue;
143
+ }
125
144
  tools.set(name, {
126
145
  name,
127
146
  description: toolDef.description || '',
@@ -518,6 +518,21 @@ function drawInputLines(lines) {
518
518
  // No save/restore — caller parks cursor via focusDockInput.
519
519
  }
520
520
 
521
+ function terminalCellWidthFromColumn(text, startColumn = 1) {
522
+ let col = Math.max(1, Math.floor(Number(startColumn) || 1));
523
+ const start = col;
524
+ for (const ch of queue.stripSequences(String(text ?? ''))) {
525
+ const cp = ch.codePointAt(0);
526
+ if (cp === 0x09) {
527
+ col = (Math.floor((col - 1) / 8) + 1) * 8 + 1;
528
+ continue;
529
+ }
530
+ if (cp === 0x0a || cp === 0x0d) continue;
531
+ col += queue.cellWidth(ch);
532
+ }
533
+ return Math.max(0, col - start);
534
+ }
535
+
521
536
  export function isInputDockMounted() {
522
537
  return mounted;
523
538
  }
@@ -650,6 +665,20 @@ export function redrawDockFrame() {
650
665
  return true;
651
666
  }
652
667
 
668
+ export function redrawDockInput() {
669
+ if (!mounted) return false;
670
+ contentTrackingActive = false;
671
+ renderFrame(lastFrame);
672
+ if (Array.isArray(lastFrame.overlayLines)) {
673
+ drawInputLines(lastFrame.overlayLines);
674
+ } else {
675
+ const layout = layoutInput(lastFrame.prefix, lastFrame.value);
676
+ drawInputLines(layout.lines);
677
+ }
678
+ parkCursorAtInput();
679
+ return true;
680
+ }
681
+
653
682
  export function prepareInputPrompt({ context = '', tips = '', meta = '' } = {}) {
654
683
  if (!mounted) return false;
655
684
  contentTrackingActive = false;
@@ -672,10 +701,13 @@ export function clearInputPrompt() {
672
701
  return true;
673
702
  }
674
703
 
675
- export function renderDockInput(prefix, value, { context = '', tips = '', meta = '', cursor = null } = {}) {
704
+ export function renderDockInput(prefix, value, { context = '', tips = '', meta = '', cursor = null, fixedRows = null } = {}) {
676
705
  if (!mounted) return false;
677
706
  contentTrackingActive = false;
678
- setInputRowsTo(computeInputRowsForBuffer(prefix, value));
707
+ const requestedRows = fixedRows == null
708
+ ? computeInputRowsForBuffer(prefix, value)
709
+ : Math.max(MIN_INPUT_ROWS, Math.min(inputRowsMax, Math.floor(Number(fixedRows) || MIN_INPUT_ROWS)));
710
+ setInputRowsTo(requestedRows);
679
711
  renderFrame({ context, tips, meta, prefix, value, cursor, overlayLines: null });
680
712
  const layout = layoutInput(prefix, value);
681
713
  drawInputLines(layout.lines);
@@ -722,28 +754,35 @@ export function renderDockOverlay({
722
754
  export function focusDockInput(prefix, value = '', cursorInValue = null) {
723
755
  if (!mounted) return false;
724
756
  contentTrackingActive = false;
757
+ const target = cursorTargetForInput(prefix, value, cursorInValue);
758
+ if (queue.isActive()) {
759
+ // Record the park position — every queue op re-parks here so readline
760
+ // echoes always land in the input row, even mid-stream.
761
+ queue.park(target.row, target.col);
762
+ return true;
763
+ }
764
+ moveTo(target.row, target.col);
765
+ return true;
766
+ }
767
+
768
+ function cursorTargetForInput(prefix, value = '', cursorInValue = null) {
725
769
  const layout = layoutInput(prefix, value);
726
770
  const valueStr = String(value || '');
727
771
  const rawCursor = cursorInValue == null
728
772
  ? valueStr.length
729
773
  : Math.max(0, Math.min(valueStr.length, Math.floor(cursorInValue)));
730
774
  const cursorSlice = valueStr.slice(0, rawCursor);
731
- const offset = visibleWidth(`${prefix || ''}${cursorSlice}`);
732
- const pos = cursorPositionInLines(layout.wrapped, offset);
775
+ const inputColumn = INPUT_INDENT + 1;
776
+ const measureInputLine = (line) => terminalCellWidthFromColumn(line, inputColumn);
777
+ const offset = terminalCellWidthFromColumn(`${prefix || ''}${cursorSlice}`, inputColumn);
778
+ const pos = cursorPositionInLines(layout.wrapped, offset, measureInputLine);
733
779
  const visibleRowIdx = Math.max(
734
780
  0,
735
781
  Math.min(inputRows - 1, pos.row - Math.max(0, layout.wrapped.length - inputRows)),
736
782
  );
737
783
  const row = inputRowStart() + visibleRowIdx;
738
784
  const col = Math.min(cols(), INPUT_INDENT + 1 + Math.max(0, pos.col));
739
- if (queue.isActive()) {
740
- // Record the park position — every queue op re-parks here so readline
741
- // echoes always land in the input row, even mid-stream.
742
- queue.park(row, col);
743
- return true;
744
- }
745
- moveTo(row, col);
746
- return true;
785
+ return { row, col };
747
786
  }
748
787
 
749
788
  export function inputRowColumn() {
@@ -763,6 +802,8 @@ export function _internals() {
763
802
  drawableColumns,
764
803
  resetContentCursor,
765
804
  contentCursor: () => ({ row: contentCursorRow, col: contentCursorCol, active: contentTrackingActive }),
805
+ cursorTargetForInput,
806
+ terminalCellWidthFromColumn,
766
807
  overlayRowsForWrapped,
767
808
  FIXED_ROWS,
768
809
  MAX_INPUT_ROWS_CAP,
@@ -76,19 +76,20 @@ export function tailWithEllipsis(lines, maxRows, ellipsis = '… ') {
76
76
  *
77
77
  * Used to place the terminal cursor after rendering the input buffer.
78
78
  */
79
- export function cursorPositionInLines(visibleLines, offset) {
79
+ export function cursorPositionInLines(visibleLines, offset, measure = visibleWidth) {
80
80
  const arr = Array.isArray(visibleLines) ? visibleLines : [];
81
+ const widthOf = typeof measure === 'function' ? measure : visibleWidth;
81
82
  let remaining = Math.max(0, Math.floor(offset));
82
83
  for (let row = 0; row < arr.length; row++) {
83
84
  const line = arr[row] || '';
84
- const w = visibleWidth(line);
85
+ const w = widthOf(line);
85
86
  if (remaining <= w) return { row, col: remaining };
86
87
  remaining -= w;
87
88
  // Newline between wrapped lines doesn't count as a visible column,
88
89
  // but consumes zero of the remaining offset either.
89
90
  }
90
91
  const lastRow = Math.max(0, arr.length - 1);
91
- return { row: lastRow, col: visibleWidth(arr[lastRow] || '') };
92
+ return { row: lastRow, col: widthOf(arr[lastRow] || '') };
92
93
  }
93
94
 
94
95
  // ── internals ────────────────────────────────────────────────────────────