@bahulam/code 2.6.13 → 2.6.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.
@@ -35,6 +35,7 @@ import { renderMissionReport, saveReport, toMarkdown as missionMarkdown } from '
35
35
  import {
36
36
  getVerbosity,
37
37
  setVerbosity,
38
+ showSubAgentTools,
38
39
  label as verbosityLabel,
39
40
  MODES as V_MODES,
40
41
  } from '../state/verbosity.mjs';
@@ -44,7 +45,7 @@ import { ApprovalManager } from '../core/approval.mjs';
44
45
  import { resolveBackendUrl } from '../core/backend-url.mjs';
45
46
  import { formatMessageWindow, lowWindowStatus, messagesRemaining } from '../core/rate-limit-display.mjs';
46
47
  import { formatAgentErrorGuidance } from '../core/error-guidance.mjs';
47
- import { BUILTIN_AGENTS, runAgent } from './agents.mjs';
48
+ import { BUILTIN_AGENTS, findBuiltinAgent, localAgentMatches, runAgent, runAgentDefinition } from './agents.mjs';
48
49
  import { createAgentFile, isVsCodeTerminal, listLocalAgents, openAgentFile, syncAgentsToBackend } from '../agents/scaffold.mjs';
49
50
  import { SessionManager } from '../core/session-manager.mjs';
50
51
  import { parseArgs } from '../config/cli-args.mjs';
@@ -134,6 +135,7 @@ import {
134
135
  } from '../ui/slash-commands.mjs';
135
136
  import { createOrbit } from '../state/orbit.mjs';
136
137
  import {
138
+ clearPinnedStatus,
137
139
  clearInputPrompt,
138
140
  focusDockInput,
139
141
  isInputDockMounted,
@@ -258,7 +260,7 @@ function renderHelp(topic = '') {
258
260
  function renderKeyboardHelp() {
259
261
  process.stderr.write(`\n ${c.bold('Keyboard')}\n`);
260
262
  process.stderr.write(` ${c.gray('Ctrl+C')} exit ${c.gray('↑↓')} history ${c.gray('Tab')} autocomplete\n`);
261
- process.stderr.write(` ${c.gray('Ctrl+D')} expand last tool ${c.gray('Space')} pause/resume ${c.gray('Esc')} interrupt\n\n`);
263
+ process.stderr.write(` ${c.gray('F2')} expand last tool ${c.gray('Space')} pause/resume ${c.gray('Esc')} interrupt\n\n`);
262
264
  }
263
265
 
264
266
  const MODEL_ROLE_ALIASES = new Map([
@@ -849,6 +851,164 @@ function updateStatusBar() {
849
851
 
850
852
  // ── Event Renderer ──
851
853
 
854
+ function isDeniedStatusMessage(message = '') {
855
+ return /^(?:Denied\s+\S+|Blocked by safety policy)\b/i.test(String(message || '').trim());
856
+ }
857
+
858
+ function toolCallId(data = {}, tool = 'tool') {
859
+ return data.call_id || data._callId || data.request_id || data.id ||
860
+ `${tool}:${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
861
+ }
862
+
863
+ function isSubAgentToolEvent(data = {}) {
864
+ return Boolean(data?.internal || data?.sub_agent);
865
+ }
866
+
867
+ function shouldFoldSubAgentTool(data = {}) {
868
+ return isSubAgentToolEvent(data) && !showSubAgentTools(getVerbosity());
869
+ }
870
+
871
+ function foldedSubAgentName(data = {}) {
872
+ return data?.sub_agent || data?.agent || data?.type || 'sub-agent';
873
+ }
874
+
875
+ function ensureFoldedSubAgentTools(agentType) {
876
+ const current = runtime.foldedSubAgentTools;
877
+ if (current && current.agentType === agentType) return current;
878
+ if (current?.entries?.length) flushFoldedSubAgentTools();
879
+ runtime.foldedSubAgentTools = {
880
+ agentType,
881
+ entries: [],
882
+ startedAt: Date.now(),
883
+ };
884
+ return runtime.foldedSubAgentTools;
885
+ }
886
+
887
+ function findFoldedToolEntry(fold, callId, tool) {
888
+ if (!fold) return null;
889
+ if (callId) {
890
+ const exact = fold.entries.find(entry => entry.callId === callId);
891
+ if (exact) return exact;
892
+ }
893
+ for (let i = fold.entries.length - 1; i >= 0; i--) {
894
+ const entry = fold.entries[i];
895
+ if (entry.tool === tool && !entry.result) return entry;
896
+ }
897
+ return null;
898
+ }
899
+
900
+ function foldSubAgentToolCall(data = {}) {
901
+ const tool = data?.tool || 'unknown';
902
+ const args = data?.args || {};
903
+ const callId = toolCallId(data, tool);
904
+ const agentType = foldedSubAgentName(data);
905
+ const fold = ensureFoldedSubAgentTools(agentType);
906
+ const existing = findFoldedToolEntry(fold, callId, tool);
907
+ const entry = existing || {
908
+ callId,
909
+ tool,
910
+ args,
911
+ summary: toolDisplaySummary(tool, args, { cwd: safeCwd() }),
912
+ startedAt: Date.now(),
913
+ result: null,
914
+ durationMs: null,
915
+ outcome: '',
916
+ tone: 'dim',
917
+ };
918
+ if (!existing) fold.entries.push(entry);
919
+ recordCard({ id: callId, tool, args, startedAt: entry.startedAt });
920
+ session.toolCounts[tool] = (session.toolCounts[tool] || 0) + 1;
921
+ startSpinner(`${agentType} → ${tool}`);
922
+ }
923
+
924
+ function foldSubAgentToolResult(data = {}) {
925
+ const tool = data?.tool || data?._tool || 'unknown';
926
+ const args = data?.args || {};
927
+ const callId = toolCallId(data, tool);
928
+ const agentType = foldedSubAgentName(data);
929
+ const fold = ensureFoldedSubAgentTools(agentType);
930
+ let entry = findFoldedToolEntry(fold, callId, tool);
931
+ if (!entry) {
932
+ entry = {
933
+ callId,
934
+ tool,
935
+ args,
936
+ summary: toolDisplaySummary(tool, args, { cwd: safeCwd() }),
937
+ startedAt: Date.now(),
938
+ result: null,
939
+ durationMs: null,
940
+ outcome: '',
941
+ tone: 'dim',
942
+ };
943
+ fold.entries.push(entry);
944
+ }
945
+ const durationMs = data?.duration_ms ?? (data?.duration_s != null ? data.duration_s * 1000 : null);
946
+ const summary = summarizeResult(tool, data);
947
+ entry.args = entry.args && Object.keys(entry.args).length ? entry.args : args;
948
+ entry.result = data;
949
+ entry.durationMs = durationMs;
950
+ entry.outcome = summary.text || '';
951
+ entry.tone = summary.tone || 'dim';
952
+ if (data._blocked) session.blockedOps++;
953
+ recordCard({ id: callId, tool, args: entry.args, result: data, durationMs, startedAt: entry.startedAt });
954
+ }
955
+
956
+ function foldedOutcome(entry) {
957
+ if (!entry?.outcome) return '';
958
+ const painter = entry.tone === 'success' ? paint.state.success
959
+ : entry.tone === 'warn' ? paint.state.warn
960
+ : entry.tone === 'danger' ? paint.state.danger
961
+ : paint.text.muted;
962
+ return `${paint.text.dim('—')} ${painter(entry.outcome)}`;
963
+ }
964
+
965
+ function foldedToolLine(entry, indent, columns) {
966
+ const head = formatCardHead(entry.tool, entry.args || {}, {
967
+ cwd: safeCwd(),
968
+ columns: Math.max(40, columns - indent.length - 2),
969
+ indent: '',
970
+ }).split('\n')[0];
971
+ const line = `${indent}${paint.text.dim('•')} ${head}${entry.outcome ? ` ${foldedOutcome(entry)}` : ''}`;
972
+ return fitAnsiLine(line, Math.max(32, columns));
973
+ }
974
+
975
+ function flushFoldedSubAgentTools() {
976
+ const fold = runtime.foldedSubAgentTools;
977
+ if (!fold) return;
978
+ const entries = Array.isArray(fold.entries) ? fold.entries : [];
979
+ runtime.foldedSubAgentTools = null;
980
+ if (!entries.length) return;
981
+
982
+ renderBlockBoundary('tool', { compactSame: true });
983
+ const indent = subAgentIndent();
984
+ const cols = process.stderr.columns || 120;
985
+ const shown = entries.slice(0, 4);
986
+ const extra = Math.max(0, entries.length - shown.length);
987
+ const header = `${indent}${paint.text.dim('⎿')} ${paint.text.dim(`${fold.agentType} tools · ${entries.length} tool use${entries.length === 1 ? '' : 's'}`)}`;
988
+ process.stderr.write(`${fitAnsiLine(header, cols)}\n`);
989
+ for (const entry of shown) {
990
+ process.stderr.write(`${foldedToolLine(entry, `${indent} `, cols)}\n`);
991
+ }
992
+ if (extra > 0) {
993
+ process.stderr.write(`${indent} ${paint.text.dim(`… +${extra} tool use${extra === 1 ? '' : 's'} · /last expands this batch`)}\n`);
994
+ } else {
995
+ process.stderr.write(`${indent} ${paint.text.dim('/last expands this batch')}\n`);
996
+ }
997
+ recordCard({
998
+ id: `sub-agent-tools:${fold.agentType}:${Date.now()}`,
999
+ tool: 'sub_agent_tools',
1000
+ args: { agent: fold.agentType, total: entries.length },
1001
+ result: {
1002
+ success: true,
1003
+ output: `${entries.length} folded sub-agent tool use${entries.length === 1 ? '' : 's'}`,
1004
+ tools: entries,
1005
+ },
1006
+ durationMs: Date.now() - (fold.startedAt || Date.now()),
1007
+ startedAt: fold.startedAt || Date.now(),
1008
+ });
1009
+ runtime.lastRenderedBlock = 'tool';
1010
+ }
1011
+
852
1012
  function renderEvent(event) {
853
1013
  const { type, data } = event;
854
1014
 
@@ -890,6 +1050,10 @@ function renderEvent(event) {
890
1050
  renderStagnation(data);
891
1051
  break;
892
1052
  }
1053
+ if (isDeniedStatusMessage(msg)) {
1054
+ stopSpinner();
1055
+ break;
1056
+ }
893
1057
  startSpinner(msg);
894
1058
  break;
895
1059
  }
@@ -927,6 +1091,7 @@ function renderEvent(event) {
927
1091
  if (text) {
928
1092
  flushContent();
929
1093
  stopSpinner();
1094
+ flushFoldedSubAgentTools();
930
1095
  if (runtime.streamedPartialText && text.startsWith(runtime.streamedPartialText)) {
931
1096
  text = text.slice(runtime.streamedPartialText.length);
932
1097
  } else if (runtime.streamedPartialText.includes(text)) {
@@ -1009,6 +1174,10 @@ function renderEvent(event) {
1009
1174
  session.totalToolCalls++;
1010
1175
  stopSpinner();
1011
1176
  flushContent();
1177
+ if (shouldFoldSubAgentTool(data)) {
1178
+ foldSubAgentToolCall(data);
1179
+ break;
1180
+ }
1012
1181
  renderToolCall(data);
1013
1182
  break;
1014
1183
  }
@@ -1039,6 +1208,7 @@ function renderEvent(event) {
1039
1208
  }
1040
1209
 
1041
1210
  case 'approval_denied': {
1211
+ stopSpinner();
1042
1212
  const reason = data?.reason || 'User denied';
1043
1213
  const toolName = data?.tool || '';
1044
1214
  const indent = subAgentIndent();
@@ -1051,6 +1221,10 @@ function renderEvent(event) {
1051
1221
  case 'tool_result':
1052
1222
  case 'tool_done': {
1053
1223
  stopSpinner();
1224
+ if (shouldFoldSubAgentTool(data)) {
1225
+ foldSubAgentToolResult(data);
1226
+ break;
1227
+ }
1054
1228
  renderToolResult(data, type);
1055
1229
  break;
1056
1230
  }
@@ -1153,6 +1327,7 @@ function renderEvent(event) {
1153
1327
  case 'sub_agent_start': {
1154
1328
  stopSpinner();
1155
1329
  clearPendingHead();
1330
+ flushFoldedSubAgentTools();
1156
1331
  const agentType = data?.type || 'sub-agent';
1157
1332
  const query = data?.query || '';
1158
1333
  renderBlockBoundary('subagent');
@@ -1182,6 +1357,7 @@ function renderEvent(event) {
1182
1357
  case 'sub_agent_complete': {
1183
1358
  stopSpinner();
1184
1359
  clearPendingHead();
1360
+ flushFoldedSubAgentTools();
1185
1361
  const agentType = data?.type || 'sub-agent';
1186
1362
  const usage = data?.usage || {};
1187
1363
  const tokens = (usage.input_tokens || 0) + (usage.output_tokens || 0);
@@ -1250,6 +1426,7 @@ function renderEvent(event) {
1250
1426
  case 'error':
1251
1427
  stopSpinner();
1252
1428
  flushContent();
1429
+ flushFoldedSubAgentTools();
1253
1430
  {
1254
1431
  const guidance = formatAgentErrorGuidance(data || {});
1255
1432
  renderBlockBoundary('status', { compactSame: true });
@@ -1291,6 +1468,7 @@ function renderEvent(event) {
1291
1468
  case 'complete': {
1292
1469
  stopSpinner();
1293
1470
  flushContent();
1471
+ flushFoldedSubAgentTools();
1294
1472
  resetSubAgents();
1295
1473
  session.inSubAgent = false;
1296
1474
 
@@ -1649,6 +1827,111 @@ function handleTasksCommand(rest, ctx) {
1649
1827
  }
1650
1828
  }
1651
1829
 
1830
+ async function prepareDirectAgentRunContext(ctx, instruction = '') {
1831
+ const cwd = safeCwd();
1832
+ const registered = await ctx.toolExecutor.registerProjectRoots([cwd]);
1833
+ const failed = registered.find(result => result?.success === false);
1834
+ if (failed) {
1835
+ throw new Error(`Could not register project root for sub-agent: ${failed.error || failed.root || cwd}`);
1836
+ }
1837
+
1838
+ const effectivePolicy = loadEffectivePolicy({ cwd });
1839
+ if (ctx.approval) {
1840
+ ctx.approval.policy = effectivePolicy.policy;
1841
+ if (ctx.approval.trustStore) ctx.approval.trustStore.policy = effectivePolicy.policy;
1842
+ }
1843
+ const projectContext = loadProjectContext({ cwd, previous: ctx.latestProjectContext || null });
1844
+ const projectResources = ctx.toolExecutor.getProjectResources();
1845
+ const envelope = buildContextEnvelope({
1846
+ cwd,
1847
+ effectivePolicy,
1848
+ projectContext,
1849
+ activeHints: [],
1850
+ projectResources,
1851
+ agentContext: ctx.toolExecutor.getAgentContext(),
1852
+ });
1853
+
1854
+ ctx.effectivePolicy = effectivePolicy;
1855
+ ctx.latestProjectContext = projectContext;
1856
+ ctx.latestEnvelope = envelope;
1857
+
1858
+ const execContext = {
1859
+ ...envelope,
1860
+ cwd,
1861
+ project_root: cwd,
1862
+ project_resources: projectResources,
1863
+ work_scope: buildWorkScope({
1864
+ instruction: instruction || 'Run sub-agent task',
1865
+ cwd,
1866
+ projectResources,
1867
+ }),
1868
+ };
1869
+ const modelOverrides = Object.fromEntries(sessionModelOverrideEntries());
1870
+ if (Object.keys(modelOverrides).length > 0) {
1871
+ execContext.model_overrides = modelOverrides;
1872
+ if (modelOverrides.reasoning) execContext.model_override = modelOverrides.reasoning;
1873
+ }
1874
+ return execContext;
1875
+ }
1876
+
1877
+ async function handleRunCommand(rest = '', ctx) {
1878
+ const parts = String(rest || '').trim().split(/\s+/).filter(Boolean);
1879
+ const target = parts.shift();
1880
+ const instruction = parts.join(' ');
1881
+
1882
+ if (!target) {
1883
+ process.stderr.write(` ${c.gray('Usage: /run <agent-or-workflow> [instruction]')}\n`);
1884
+ return;
1885
+ }
1886
+
1887
+ const localAgent = listLocalAgents(safeCwd()).find(agent => localAgentMatches(agent, target));
1888
+ const builtinAgent = findBuiltinAgent(target);
1889
+ const runnableAgent = localAgent || builtinAgent;
1890
+ if (runnableAgent) {
1891
+ try {
1892
+ const execContext = await prepareDirectAgentRunContext(ctx, instruction || target);
1893
+ return await runAgentDefinition(runnableAgent, instruction, ctx, session, renderEvent, {
1894
+ cwd: execContext.cwd,
1895
+ execContext,
1896
+ });
1897
+ } catch (err) {
1898
+ process.stderr.write(` ${c.red('✗')} ${err.message || String(err)}\n`);
1899
+ return;
1900
+ }
1901
+ }
1902
+
1903
+ try {
1904
+ process.stderr.write(` ${c.dim(`Running workflow '${target}'...`)}\n`);
1905
+ const result = await ctx.toolExecutor.execute('workflow_run_multi', {
1906
+ name: target,
1907
+ instruction,
1908
+ });
1909
+
1910
+ if (result?.success === false) {
1911
+ process.stderr.write(` ${c.red('✗')} ${result.output || `Workflow '${target}' failed.`}\n`);
1912
+ return;
1913
+ }
1914
+
1915
+ process.stderr.write(` ${c.green('✓')} ${c.dim(`Workflow '${target}' complete`)}\n`);
1916
+ const details = [];
1917
+ if (result?.run_id) details.push(`run ${result.run_id}`);
1918
+ if (result?.duration_s) details.push(`${result.duration_s}s`);
1919
+ if (result?.total_tokens) details.push(`${formatTokens(result.total_tokens)} tok`);
1920
+ if (result?.total_cost) details.push(formatCostValue(result.total_cost));
1921
+ if (details.length) process.stderr.write(` ${c.dim(details.join(' · '))}\n`);
1922
+
1923
+ const output = result?.result || result?.output || '';
1924
+ if (output) {
1925
+ process.stderr.write('\n');
1926
+ process.stderr.write(renderMarkdown(String(output), { width: process.stderr.columns || 96 }));
1927
+ process.stderr.write('\n');
1928
+ }
1929
+ } catch (err) {
1930
+ process.stderr.write(` ${c.red('✗')} ${err.message || String(err)}\n`);
1931
+ process.stderr.write(` ${c.gray('Usage: /run <agent-or-workflow> [instruction]')}\n`);
1932
+ }
1933
+ }
1934
+
1652
1935
  async function handleCommand(input, ctx) {
1653
1936
  const { cmd, rest, aliasTarget } = normalizeCommandInput(input);
1654
1937
  if (aliasTarget) {
@@ -1686,6 +1969,10 @@ async function handleCommand(input, ctx) {
1686
1969
  handleTasksCommand(rest, ctx);
1687
1970
  return;
1688
1971
 
1972
+ case '/run':
1973
+ await handleRunCommand(rest, ctx);
1974
+ return;
1975
+
1689
1976
  case '/attach':
1690
1977
  handleAttachCommand(rest, ctx);
1691
1978
  return;
@@ -2182,6 +2469,7 @@ async function handleCommand(input, ctx) {
2182
2469
  session.agentHistory.length = 0;
2183
2470
  session.toolCalls = 0;
2184
2471
  session.subAgentToolCalls = 0;
2472
+ runtime.foldedSubAgentTools = null;
2185
2473
  clearCards();
2186
2474
  process.stderr.write(` ${c.gray('Conversation cleared.')}\n`);
2187
2475
  return;
@@ -2501,6 +2789,7 @@ export async function startTerminalRepl() {
2501
2789
  flushContent();
2502
2790
  flushPendingHead();
2503
2791
  flushCompactReadRun();
2792
+ runtime.foldedSubAgentTools = null;
2504
2793
  clearCards();
2505
2794
 
2506
2795
  const preserved = {
@@ -2877,11 +3166,17 @@ export async function startTerminalRepl() {
2877
3166
  // terminals that ignore the request.
2878
3167
  const PASTE_BEGIN = '\x1b[200~';
2879
3168
  const PASTE_END = '\x1b[201~';
3169
+ const F2_SEQUENCES = new Set(['\x1bOQ', '\x1b[12~']);
2880
3170
  let _inBracketedPaste = false;
2881
3171
  let _bracketedPasteBuffer = '';
3172
+ let _suppressBracketedPasteLines = false;
3173
+ let _bracketedPasteStartLine = '';
3174
+ let _bracketedPasteStartCursor = 0;
3175
+ let _promptHasInsertedPaste = false;
2882
3176
  const _pasteEndListeners = new Set();
2883
3177
  function onBracketedPasteEnd(cb) { _pasteEndListeners.add(cb); return () => _pasteEndListeners.delete(cb); }
2884
3178
  function isInBracketedPaste() { return _inBracketedPaste; }
3179
+ function isF2Sequence(text) { return F2_SEQUENCES.has(String(text || '')); }
2885
3180
 
2886
3181
  if (process.stdin.isTTY) {
2887
3182
  try { process.stderr.write('\x1b[?2004h'); } catch {}
@@ -2900,6 +3195,11 @@ export async function startTerminalRepl() {
2900
3195
  if (start === -1) return;
2901
3196
  _inBracketedPaste = true;
2902
3197
  _bracketedPasteBuffer = '';
3198
+ _suppressBracketedPasteLines = true;
3199
+ _bracketedPasteStartLine = String(rl?.line || '');
3200
+ _bracketedPasteStartCursor = typeof rl?.cursor === 'number'
3201
+ ? rl.cursor
3202
+ : _bracketedPasteStartLine.length;
2903
3203
  i = start + PASTE_BEGIN.length;
2904
3204
  } else {
2905
3205
  const end = s.indexOf(PASTE_END, i);
@@ -2914,7 +3214,10 @@ export async function startTerminalRepl() {
2914
3214
  // Notify subscribers on next tick so readline finishes emitting its
2915
3215
  // synchronous `line` events for the buffered content first.
2916
3216
  const cbs = [..._pasteEndListeners];
2917
- setImmediate(() => { for (const cb of cbs) { try { cb(payload); } catch {} } });
3217
+ setImmediate(() => {
3218
+ for (const cb of cbs) { try { cb(payload); } catch {} }
3219
+ _suppressBracketedPasteLines = false;
3220
+ });
2918
3221
  i = end + PASTE_END.length;
2919
3222
  }
2920
3223
  }
@@ -2937,8 +3240,10 @@ export async function startTerminalRepl() {
2937
3240
 
2938
3241
  function printInputBottomRule() {
2939
3242
  if (isInputDockMounted()) {
3243
+ clearPinnedStatus();
2940
3244
  clearInputPrompt();
2941
3245
  moveToContent();
3246
+ if (process.stderr.isTTY && !term().plain) process.stderr.write('\r\x1b[2K');
2942
3247
  return;
2943
3248
  }
2944
3249
  if (term().plain) return;
@@ -2946,11 +3251,11 @@ export async function startTerminalRepl() {
2946
3251
  }
2947
3252
 
2948
3253
  function idleInputTips() {
2949
- return '[Enter] send [/] commands [Tab] complete [Ctrl+D] details';
3254
+ return '[Enter] send [/] commands [Tab] complete [F2] details';
2950
3255
  }
2951
3256
 
2952
3257
  function executionInputTips() {
2953
- return 'type any extra context (paths, corrections, follow-ups) · [Enter] send · [Esc] cancel · [Ctrl+P] pause';
3258
+ return 'type extra context · [Enter] send · [Esc] cancel · [Ctrl+P] pause · [F2] details';
2954
3259
  }
2955
3260
 
2956
3261
  // Proxy stream: swallows writes when the dock owns the input row so
@@ -3088,10 +3393,10 @@ export async function startTerminalRepl() {
3088
3393
  slashHintLine = '';
3089
3394
  }
3090
3395
 
3091
- function replaceReadlineLine(value) {
3396
+ function replaceReadlineLine(value, cursor = null) {
3092
3397
  const next = String(value || '');
3093
3398
  rl.line = next;
3094
- rl.cursor = next.length;
3399
+ rl.cursor = cursor == null ? next.length : Math.max(0, Math.min(next.length, Number(cursor) || 0));
3095
3400
  if (typeof rl._refreshLine === 'function') {
3096
3401
  rl._refreshLine();
3097
3402
  } else {
@@ -3101,6 +3406,17 @@ export async function startTerminalRepl() {
3101
3406
  }
3102
3407
  }
3103
3408
 
3409
+ function insertPromptText(text, { baseLine = rl.line || '', baseCursor = rl.cursor, fromPaste = false } = {}) {
3410
+ const payload = String(text || '');
3411
+ if (!payload) return;
3412
+ const line = String(baseLine || '');
3413
+ const cursor = typeof baseCursor === 'number' ? Math.max(0, Math.min(line.length, baseCursor)) : line.length;
3414
+ const next = `${line.slice(0, cursor)}${payload}${line.slice(cursor)}`;
3415
+ if (fromPaste) _promptHasInsertedPaste = true;
3416
+ replaceReadlineLine(next, cursor + payload.length);
3417
+ renderIdleDockInput();
3418
+ }
3419
+
3104
3420
  function acceptSlashHint() {
3105
3421
  const item = slashHintItems[slashHintSelected];
3106
3422
  if (!item) return false;
@@ -3190,8 +3506,18 @@ export async function startTerminalRepl() {
3190
3506
  readline.emitKeypressEvents(process.stdin, rl);
3191
3507
  process.stdin.on('keypress', (_str, key = {}) => {
3192
3508
  if (!inputActive) return;
3509
+ if (_inBracketedPaste || _suppressBracketedPasteLines) return;
3510
+ if (key.name === 'return' || key.name === 'enter') return;
3511
+ if (key.name === 'f2') {
3512
+ clearSlashHint();
3513
+ if (isInputDockMounted()) moveToContent();
3514
+ expandLast();
3515
+ renderIdleDockInput();
3516
+ return;
3517
+ }
3193
3518
  setImmediate(() => {
3194
3519
  if (!inputActive) return;
3520
+ if (_inBracketedPaste || _suppressBracketedPasteLines) return;
3195
3521
  if (slashHintVisible && key.name === 'tab' && acceptSlashHint()) return;
3196
3522
  if (slashHintVisible && key.name === 'down' && moveSlashHintSelection(1)) return;
3197
3523
  if (slashHintVisible && key.name === 'up' && moveSlashHintSelection(-1)) return;
@@ -3247,8 +3573,18 @@ export async function startTerminalRepl() {
3247
3573
  _pasteFlushTimer = null;
3248
3574
  }
3249
3575
  if (!_pasteLines.length) return;
3250
- const line = _pasteLines.join('\n');
3576
+ const trailing = String(rl.line || '');
3577
+ const pastedLines = _pasteLines.slice();
3251
3578
  _pasteLines = [];
3579
+ if (pastedLines.length > 1 || trailing) {
3580
+ const text = [...pastedLines, trailing].join('\n');
3581
+ _promptHasInsertedPaste = true;
3582
+ replaceReadlineLine(text);
3583
+ renderIdleDockInput();
3584
+ return;
3585
+ }
3586
+ const line = pastedLines.join('\n');
3587
+ _promptHasInsertedPaste = false;
3252
3588
  queueOrRunLine(line);
3253
3589
  }
3254
3590
 
@@ -3258,8 +3594,29 @@ export async function startTerminalRepl() {
3258
3594
  // or the user pressed Enter normally), the debounce falls back to old
3259
3595
  // behavior — a single Enter flushes almost instantly.
3260
3596
  rl.on('line', async (line) => {
3597
+ if (_suppressBracketedPasteLines) {
3598
+ _pasteLines = [];
3599
+ if (_pasteFlushTimer) {
3600
+ clearTimeout(_pasteFlushTimer);
3601
+ _pasteFlushTimer = null;
3602
+ }
3603
+ return;
3604
+ }
3605
+ if (!inputActive) {
3606
+ _pasteLines = [];
3607
+ if (_pasteFlushTimer) {
3608
+ clearTimeout(_pasteFlushTimer);
3609
+ _pasteFlushTimer = null;
3610
+ }
3611
+ return;
3612
+ }
3613
+ const submitInsertedPaste = _promptHasInsertedPaste || String(line || '').includes('\n');
3261
3614
  _pasteLines.push(line);
3262
3615
  if (_pasteFlushTimer) clearTimeout(_pasteFlushTimer);
3616
+ if (submitInsertedPaste) {
3617
+ flushPastedLines();
3618
+ return;
3619
+ }
3263
3620
  if (isInBracketedPaste()) {
3264
3621
  _pasteFlushTimer = null;
3265
3622
  } else {
@@ -3267,10 +3624,19 @@ export async function startTerminalRepl() {
3267
3624
  }
3268
3625
  });
3269
3626
 
3270
- onBracketedPasteEnd(() => {
3271
- // Readline has finished emitting synchronous line events for the pasted
3272
- // content by the time this fires (setImmediate in the pre-listener).
3273
- flushPastedLines();
3627
+ onBracketedPasteEnd((payload) => {
3628
+ _pasteLines = [];
3629
+ if (_pasteFlushTimer) {
3630
+ clearTimeout(_pasteFlushTimer);
3631
+ _pasteFlushTimer = null;
3632
+ }
3633
+ // Readline has finished emitting synchronous `line` events by now.
3634
+ // Treat paste as editing the prompt buffer; Enter remains the submit.
3635
+ insertPromptText(payload || '', {
3636
+ baseLine: _bracketedPasteStartLine,
3637
+ baseCursor: _bracketedPasteStartCursor,
3638
+ fromPaste: true,
3639
+ });
3274
3640
  });
3275
3641
 
3276
3642
  async function _handleLine(line) {
@@ -3637,6 +4003,14 @@ export async function startTerminalRepl() {
3637
4003
  const bytes2 = [...data];
3638
4004
  const text2 = data.toString('utf8');
3639
4005
 
4006
+ if (isF2Sequence(text2)) {
4007
+ stopSpinner();
4008
+ if (isInputDockMounted()) moveToContent();
4009
+ expandLast();
4010
+ if (isInputDockMounted()) redrawExecutionInput();
4011
+ return;
4012
+ }
4013
+
3640
4014
  // Esc key (single byte 0x1b, not part of arrow sequence)
3641
4015
  if (bytes2.length === 1 && bytes2[0] === 0x1b) {
3642
4016
  if (executionInputVisible || executionInputBuffer) {
@@ -3729,15 +4103,6 @@ export async function startTerminalRepl() {
3729
4103
  return;
3730
4104
  }
3731
4105
 
3732
- // Ctrl+D — expand last tool card (Mission Control §6.2). Only when
3733
- // there's no in-progress follow-up input.
3734
- if (!executionInputBuffer && bytes2.length === 1 && bytes2[0] === 0x04) {
3735
- stopSpinner();
3736
- if (isInputDockMounted()) moveToContent();
3737
- expandLast();
3738
- return;
3739
- }
3740
-
3741
4106
  // Any safe text (unicode, tabs, spaces, symbols) becomes a live
3742
4107
  // follow-up instruction. Enter sends it via resume(instruction).
3743
4108
  if (isSafeFollowUpText(text2)) {
@@ -3752,6 +4117,15 @@ export async function startTerminalRepl() {
3752
4117
  approval.setExecutionHooks({
3753
4118
  onPause: () => { execListenerActive = false; },
3754
4119
  onResume: () => { execListenerActive = true; },
4120
+ onApprovalPromptEnd: () => {
4121
+ if (!isInputDockMounted()) return;
4122
+ renderDockInput(executionInputPrefix(), executionInputBuffer, {
4123
+ context: buildContextStrip(),
4124
+ meta: buildDockMeta(),
4125
+ tips: executionInputTips(),
4126
+ });
4127
+ moveToContent();
4128
+ },
3755
4129
  });
3756
4130
 
3757
4131
  keypressCleanup = () => {