@bahulam/code 2.6.13 → 2.6.15
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.
- package/package.json +4 -4
- package/src/agents/scaffold.mjs +1 -0
- package/src/commands/agent.mjs +3 -2
- package/src/core/approval-log.mjs +45 -4
- package/src/core/approval.mjs +265 -41
- package/src/core/file-diff.mjs +1 -1
- package/src/core/headless.mjs +14 -3
- package/src/core/local-agent.mjs +3 -2
- package/src/core/risk-tier.mjs +53 -2
- package/src/core/safety.mjs +61 -4
- package/src/core/tool-executor.mjs +38 -16
- package/src/core/trust.mjs +5 -3
- package/src/index.mjs +1 -1
- package/src/terminal/agents.mjs +194 -18
- package/src/terminal/repl-render.mjs +126 -11
- package/src/terminal/repl-state.mjs +2 -0
- package/src/terminal/repl.mjs +553 -88
- package/src/terminal/tool-display.mjs +154 -2
- package/src/ui/approval.mjs +211 -14
- package/src/ui/icons.mjs +11 -5
- package/src/ui/input-dock.mjs +214 -29
- package/src/ui/slash-commands.mjs +10 -0
- package/src/ui/tool-card.mjs +261 -30
- package/src/ui/tool-details.mjs +206 -14
- package/src/ui/transcript-block.mjs +2 -3
package/src/terminal/repl.mjs
CHANGED
|
@@ -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';
|
|
@@ -83,12 +84,14 @@ import {
|
|
|
83
84
|
isInlineOutcomeTool,
|
|
84
85
|
renderBlockBoundary,
|
|
85
86
|
renderExploreRun,
|
|
87
|
+
renderFileDiffEvent,
|
|
86
88
|
renderStagnation,
|
|
87
89
|
renderToolCall,
|
|
88
90
|
renderToolResult,
|
|
89
91
|
startContentStream,
|
|
90
92
|
startSpinner,
|
|
91
93
|
stopSpinner,
|
|
94
|
+
transcriptRenderableLines,
|
|
92
95
|
thinkingPrefix,
|
|
93
96
|
updateSpinner,
|
|
94
97
|
} from './repl-render.mjs';
|
|
@@ -134,6 +137,7 @@ import {
|
|
|
134
137
|
} from '../ui/slash-commands.mjs';
|
|
135
138
|
import { createOrbit } from '../state/orbit.mjs';
|
|
136
139
|
import {
|
|
140
|
+
clearPinnedStatus,
|
|
137
141
|
clearInputPrompt,
|
|
138
142
|
focusDockInput,
|
|
139
143
|
isInputDockMounted,
|
|
@@ -258,7 +262,7 @@ function renderHelp(topic = '') {
|
|
|
258
262
|
function renderKeyboardHelp() {
|
|
259
263
|
process.stderr.write(`\n ${c.bold('Keyboard')}\n`);
|
|
260
264
|
process.stderr.write(` ${c.gray('Ctrl+C')} exit ${c.gray('↑↓')} history ${c.gray('Tab')} autocomplete\n`);
|
|
261
|
-
process.stderr.write(` ${c.gray('
|
|
265
|
+
process.stderr.write(` ${c.gray('F2')} expand last tool ${c.gray('Space')} pause/resume ${c.gray('Esc')} interrupt\n\n`);
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
const MODEL_ROLE_ALIASES = new Map([
|
|
@@ -849,6 +853,164 @@ function updateStatusBar() {
|
|
|
849
853
|
|
|
850
854
|
// ── Event Renderer ──
|
|
851
855
|
|
|
856
|
+
function isDeniedStatusMessage(message = '') {
|
|
857
|
+
return /^(?:Denied\s+\S+|Blocked by safety policy)\b/i.test(String(message || '').trim());
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function toolCallId(data = {}, tool = 'tool') {
|
|
861
|
+
return data.call_id || data._callId || data.request_id || data.id ||
|
|
862
|
+
`${tool}:${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function isSubAgentToolEvent(data = {}) {
|
|
866
|
+
return Boolean(data?.internal || data?.sub_agent);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function shouldFoldSubAgentTool(data = {}) {
|
|
870
|
+
return isSubAgentToolEvent(data) && !showSubAgentTools(getVerbosity());
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function foldedSubAgentName(data = {}) {
|
|
874
|
+
return data?.sub_agent || data?.agent || data?.type || 'sub-agent';
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function ensureFoldedSubAgentTools(agentType) {
|
|
878
|
+
const current = runtime.foldedSubAgentTools;
|
|
879
|
+
if (current && current.agentType === agentType) return current;
|
|
880
|
+
if (current?.entries?.length) flushFoldedSubAgentTools();
|
|
881
|
+
runtime.foldedSubAgentTools = {
|
|
882
|
+
agentType,
|
|
883
|
+
entries: [],
|
|
884
|
+
startedAt: Date.now(),
|
|
885
|
+
};
|
|
886
|
+
return runtime.foldedSubAgentTools;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function findFoldedToolEntry(fold, callId, tool) {
|
|
890
|
+
if (!fold) return null;
|
|
891
|
+
if (callId) {
|
|
892
|
+
const exact = fold.entries.find(entry => entry.callId === callId);
|
|
893
|
+
if (exact) return exact;
|
|
894
|
+
}
|
|
895
|
+
for (let i = fold.entries.length - 1; i >= 0; i--) {
|
|
896
|
+
const entry = fold.entries[i];
|
|
897
|
+
if (entry.tool === tool && !entry.result) return entry;
|
|
898
|
+
}
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function foldSubAgentToolCall(data = {}) {
|
|
903
|
+
const tool = data?.tool || 'unknown';
|
|
904
|
+
const args = data?.args || {};
|
|
905
|
+
const callId = toolCallId(data, tool);
|
|
906
|
+
const agentType = foldedSubAgentName(data);
|
|
907
|
+
const fold = ensureFoldedSubAgentTools(agentType);
|
|
908
|
+
const existing = findFoldedToolEntry(fold, callId, tool);
|
|
909
|
+
const entry = existing || {
|
|
910
|
+
callId,
|
|
911
|
+
tool,
|
|
912
|
+
args,
|
|
913
|
+
summary: toolDisplaySummary(tool, args, { cwd: safeCwd() }),
|
|
914
|
+
startedAt: Date.now(),
|
|
915
|
+
result: null,
|
|
916
|
+
durationMs: null,
|
|
917
|
+
outcome: '',
|
|
918
|
+
tone: 'dim',
|
|
919
|
+
};
|
|
920
|
+
if (!existing) fold.entries.push(entry);
|
|
921
|
+
recordCard({ id: callId, tool, args, startedAt: entry.startedAt });
|
|
922
|
+
session.toolCounts[tool] = (session.toolCounts[tool] || 0) + 1;
|
|
923
|
+
startSpinner(`${agentType} → ${tool}`);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function foldSubAgentToolResult(data = {}) {
|
|
927
|
+
const tool = data?.tool || data?._tool || 'unknown';
|
|
928
|
+
const args = data?.args || {};
|
|
929
|
+
const callId = toolCallId(data, tool);
|
|
930
|
+
const agentType = foldedSubAgentName(data);
|
|
931
|
+
const fold = ensureFoldedSubAgentTools(agentType);
|
|
932
|
+
let entry = findFoldedToolEntry(fold, callId, tool);
|
|
933
|
+
if (!entry) {
|
|
934
|
+
entry = {
|
|
935
|
+
callId,
|
|
936
|
+
tool,
|
|
937
|
+
args,
|
|
938
|
+
summary: toolDisplaySummary(tool, args, { cwd: safeCwd() }),
|
|
939
|
+
startedAt: Date.now(),
|
|
940
|
+
result: null,
|
|
941
|
+
durationMs: null,
|
|
942
|
+
outcome: '',
|
|
943
|
+
tone: 'dim',
|
|
944
|
+
};
|
|
945
|
+
fold.entries.push(entry);
|
|
946
|
+
}
|
|
947
|
+
const durationMs = data?.duration_ms ?? (data?.duration_s != null ? data.duration_s * 1000 : null);
|
|
948
|
+
const summary = summarizeResult(tool, data);
|
|
949
|
+
entry.args = entry.args && Object.keys(entry.args).length ? entry.args : args;
|
|
950
|
+
entry.result = data;
|
|
951
|
+
entry.durationMs = durationMs;
|
|
952
|
+
entry.outcome = summary.text || '';
|
|
953
|
+
entry.tone = summary.tone || 'dim';
|
|
954
|
+
if (data._blocked) session.blockedOps++;
|
|
955
|
+
recordCard({ id: callId, tool, args: entry.args, result: data, durationMs, startedAt: entry.startedAt });
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function foldedOutcome(entry) {
|
|
959
|
+
if (!entry?.outcome) return '';
|
|
960
|
+
const painter = entry.tone === 'success' ? paint.state.success
|
|
961
|
+
: entry.tone === 'warn' ? paint.state.warn
|
|
962
|
+
: entry.tone === 'danger' ? paint.state.danger
|
|
963
|
+
: paint.text.muted;
|
|
964
|
+
return `${paint.text.dim('—')} ${painter(entry.outcome)}`;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function foldedToolLine(entry, indent, columns) {
|
|
968
|
+
const head = formatCardHead(entry.tool, entry.args || {}, {
|
|
969
|
+
cwd: safeCwd(),
|
|
970
|
+
columns: Math.max(40, columns - indent.length - 2),
|
|
971
|
+
indent: '',
|
|
972
|
+
}).split('\n')[0];
|
|
973
|
+
const line = `${indent}${paint.text.dim('•')} ${head}${entry.outcome ? ` ${foldedOutcome(entry)}` : ''}`;
|
|
974
|
+
return fitAnsiLine(line, Math.max(32, columns));
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function flushFoldedSubAgentTools() {
|
|
978
|
+
const fold = runtime.foldedSubAgentTools;
|
|
979
|
+
if (!fold) return;
|
|
980
|
+
const entries = Array.isArray(fold.entries) ? fold.entries : [];
|
|
981
|
+
runtime.foldedSubAgentTools = null;
|
|
982
|
+
if (!entries.length) return;
|
|
983
|
+
|
|
984
|
+
renderBlockBoundary('tool', { compactSame: true });
|
|
985
|
+
const indent = subAgentIndent();
|
|
986
|
+
const cols = process.stderr.columns || 120;
|
|
987
|
+
const shown = entries.slice(0, 4);
|
|
988
|
+
const extra = Math.max(0, entries.length - shown.length);
|
|
989
|
+
const header = `${indent}${paint.text.dim('⎿')} ${paint.text.dim(`${fold.agentType} tools · ${entries.length} tool use${entries.length === 1 ? '' : 's'}`)}`;
|
|
990
|
+
process.stderr.write(`${fitAnsiLine(header, cols)}\n`);
|
|
991
|
+
for (const entry of shown) {
|
|
992
|
+
process.stderr.write(`${foldedToolLine(entry, `${indent} `, cols)}\n`);
|
|
993
|
+
}
|
|
994
|
+
if (extra > 0) {
|
|
995
|
+
process.stderr.write(`${indent} ${paint.text.dim(`… +${extra} tool use${extra === 1 ? '' : 's'} · /last expands this batch`)}\n`);
|
|
996
|
+
} else {
|
|
997
|
+
process.stderr.write(`${indent} ${paint.text.dim('/last expands this batch')}\n`);
|
|
998
|
+
}
|
|
999
|
+
recordCard({
|
|
1000
|
+
id: `sub-agent-tools:${fold.agentType}:${Date.now()}`,
|
|
1001
|
+
tool: 'sub_agent_tools',
|
|
1002
|
+
args: { agent: fold.agentType, total: entries.length },
|
|
1003
|
+
result: {
|
|
1004
|
+
success: true,
|
|
1005
|
+
output: `${entries.length} folded sub-agent tool use${entries.length === 1 ? '' : 's'}`,
|
|
1006
|
+
tools: entries,
|
|
1007
|
+
},
|
|
1008
|
+
durationMs: Date.now() - (fold.startedAt || Date.now()),
|
|
1009
|
+
startedAt: fold.startedAt || Date.now(),
|
|
1010
|
+
});
|
|
1011
|
+
runtime.lastRenderedBlock = 'tool';
|
|
1012
|
+
}
|
|
1013
|
+
|
|
852
1014
|
function renderEvent(event) {
|
|
853
1015
|
const { type, data } = event;
|
|
854
1016
|
|
|
@@ -890,6 +1052,10 @@ function renderEvent(event) {
|
|
|
890
1052
|
renderStagnation(data);
|
|
891
1053
|
break;
|
|
892
1054
|
}
|
|
1055
|
+
if (isDeniedStatusMessage(msg)) {
|
|
1056
|
+
stopSpinner();
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
893
1059
|
startSpinner(msg);
|
|
894
1060
|
break;
|
|
895
1061
|
}
|
|
@@ -927,6 +1093,7 @@ function renderEvent(event) {
|
|
|
927
1093
|
if (text) {
|
|
928
1094
|
flushContent();
|
|
929
1095
|
stopSpinner();
|
|
1096
|
+
flushFoldedSubAgentTools();
|
|
930
1097
|
if (runtime.streamedPartialText && text.startsWith(runtime.streamedPartialText)) {
|
|
931
1098
|
text = text.slice(runtime.streamedPartialText.length);
|
|
932
1099
|
} else if (runtime.streamedPartialText.includes(text)) {
|
|
@@ -934,17 +1101,20 @@ function renderEvent(event) {
|
|
|
934
1101
|
}
|
|
935
1102
|
}
|
|
936
1103
|
if (text) {
|
|
937
|
-
renderBlockBoundary('content');
|
|
938
|
-
if (!runtime.contentHeaderPrinted) {
|
|
939
|
-
process.stdout.write(`${transcriptHeader('bahulam', { tone: 'assistant' })}\n`);
|
|
940
|
-
runtime.contentHeaderPrinted = true;
|
|
941
|
-
}
|
|
942
1104
|
const rendered = renderMarkdown(text);
|
|
943
|
-
|
|
944
|
-
|
|
1105
|
+
const lines = transcriptRenderableLines(rendered);
|
|
1106
|
+
if (lines.length) {
|
|
1107
|
+
renderBlockBoundary('content', { compactSame: true });
|
|
1108
|
+
if (!runtime.contentHeaderPrinted) {
|
|
1109
|
+
process.stdout.write(`${transcriptHeader('bahulam', { tone: 'assistant' })}\n`);
|
|
1110
|
+
runtime.contentHeaderPrinted = true;
|
|
1111
|
+
}
|
|
1112
|
+
for (const line of lines) {
|
|
1113
|
+
process.stdout.write(`${transcriptLine(line, { tone: 'assistant' })}\n`);
|
|
1114
|
+
}
|
|
1115
|
+
runtime.renderedContentThisTurn = true;
|
|
1116
|
+
runtime.lastRenderedBlock = 'content';
|
|
945
1117
|
}
|
|
946
|
-
runtime.renderedContentThisTurn = true;
|
|
947
|
-
runtime.lastRenderedBlock = 'content';
|
|
948
1118
|
}
|
|
949
1119
|
break;
|
|
950
1120
|
}
|
|
@@ -1009,6 +1179,10 @@ function renderEvent(event) {
|
|
|
1009
1179
|
session.totalToolCalls++;
|
|
1010
1180
|
stopSpinner();
|
|
1011
1181
|
flushContent();
|
|
1182
|
+
if (shouldFoldSubAgentTool(data)) {
|
|
1183
|
+
foldSubAgentToolCall(data);
|
|
1184
|
+
break;
|
|
1185
|
+
}
|
|
1012
1186
|
renderToolCall(data);
|
|
1013
1187
|
break;
|
|
1014
1188
|
}
|
|
@@ -1039,6 +1213,7 @@ function renderEvent(event) {
|
|
|
1039
1213
|
}
|
|
1040
1214
|
|
|
1041
1215
|
case 'approval_denied': {
|
|
1216
|
+
stopSpinner();
|
|
1042
1217
|
const reason = data?.reason || 'User denied';
|
|
1043
1218
|
const toolName = data?.tool || '';
|
|
1044
1219
|
const indent = subAgentIndent();
|
|
@@ -1051,10 +1226,22 @@ function renderEvent(event) {
|
|
|
1051
1226
|
case 'tool_result':
|
|
1052
1227
|
case 'tool_done': {
|
|
1053
1228
|
stopSpinner();
|
|
1229
|
+
if (shouldFoldSubAgentTool(data)) {
|
|
1230
|
+
foldSubAgentToolResult(data);
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1054
1233
|
renderToolResult(data, type);
|
|
1055
1234
|
break;
|
|
1056
1235
|
}
|
|
1057
1236
|
|
|
1237
|
+
case 'file_diff': {
|
|
1238
|
+
stopSpinner();
|
|
1239
|
+
flushContent();
|
|
1240
|
+
flushPendingHead();
|
|
1241
|
+
renderFileDiffEvent(data);
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1058
1245
|
case 'plan': {
|
|
1059
1246
|
stopSpinner();
|
|
1060
1247
|
flushContent();
|
|
@@ -1153,6 +1340,7 @@ function renderEvent(event) {
|
|
|
1153
1340
|
case 'sub_agent_start': {
|
|
1154
1341
|
stopSpinner();
|
|
1155
1342
|
clearPendingHead();
|
|
1343
|
+
flushFoldedSubAgentTools();
|
|
1156
1344
|
const agentType = data?.type || 'sub-agent';
|
|
1157
1345
|
const query = data?.query || '';
|
|
1158
1346
|
renderBlockBoundary('subagent');
|
|
@@ -1182,6 +1370,7 @@ function renderEvent(event) {
|
|
|
1182
1370
|
case 'sub_agent_complete': {
|
|
1183
1371
|
stopSpinner();
|
|
1184
1372
|
clearPendingHead();
|
|
1373
|
+
flushFoldedSubAgentTools();
|
|
1185
1374
|
const agentType = data?.type || 'sub-agent';
|
|
1186
1375
|
const usage = data?.usage || {};
|
|
1187
1376
|
const tokens = (usage.input_tokens || 0) + (usage.output_tokens || 0);
|
|
@@ -1250,6 +1439,7 @@ function renderEvent(event) {
|
|
|
1250
1439
|
case 'error':
|
|
1251
1440
|
stopSpinner();
|
|
1252
1441
|
flushContent();
|
|
1442
|
+
flushFoldedSubAgentTools();
|
|
1253
1443
|
{
|
|
1254
1444
|
const guidance = formatAgentErrorGuidance(data || {});
|
|
1255
1445
|
renderBlockBoundary('status', { compactSame: true });
|
|
@@ -1291,22 +1481,26 @@ function renderEvent(event) {
|
|
|
1291
1481
|
case 'complete': {
|
|
1292
1482
|
stopSpinner();
|
|
1293
1483
|
flushContent();
|
|
1484
|
+
flushFoldedSubAgentTools();
|
|
1294
1485
|
resetSubAgents();
|
|
1295
1486
|
session.inSubAgent = false;
|
|
1296
1487
|
|
|
1297
1488
|
const summary = data?.summary || '';
|
|
1298
1489
|
if (summary && !runtime.renderedContentThisTurn) {
|
|
1299
|
-
renderBlockBoundary('content');
|
|
1300
|
-
if (!runtime.contentHeaderPrinted) {
|
|
1301
|
-
process.stdout.write(`${transcriptHeader('bahulam', { tone: 'assistant' })}\n`);
|
|
1302
|
-
runtime.contentHeaderPrinted = true;
|
|
1303
|
-
}
|
|
1304
1490
|
const rendered = renderMarkdown(summary);
|
|
1305
|
-
|
|
1306
|
-
|
|
1491
|
+
const lines = transcriptRenderableLines(rendered);
|
|
1492
|
+
if (lines.length) {
|
|
1493
|
+
renderBlockBoundary('content', { compactSame: true });
|
|
1494
|
+
if (!runtime.contentHeaderPrinted) {
|
|
1495
|
+
process.stdout.write(`${transcriptHeader('bahulam', { tone: 'assistant' })}\n`);
|
|
1496
|
+
runtime.contentHeaderPrinted = true;
|
|
1497
|
+
}
|
|
1498
|
+
for (const line of lines) {
|
|
1499
|
+
process.stdout.write(`${transcriptLine(line, { tone: 'assistant' })}\n`);
|
|
1500
|
+
}
|
|
1501
|
+
runtime.renderedContentThisTurn = true;
|
|
1502
|
+
runtime.lastRenderedBlock = 'content';
|
|
1307
1503
|
}
|
|
1308
|
-
runtime.renderedContentThisTurn = true;
|
|
1309
|
-
runtime.lastRenderedBlock = 'content';
|
|
1310
1504
|
}
|
|
1311
1505
|
|
|
1312
1506
|
// Update session token counts
|
|
@@ -1649,6 +1843,111 @@ function handleTasksCommand(rest, ctx) {
|
|
|
1649
1843
|
}
|
|
1650
1844
|
}
|
|
1651
1845
|
|
|
1846
|
+
async function prepareDirectAgentRunContext(ctx, instruction = '') {
|
|
1847
|
+
const cwd = safeCwd();
|
|
1848
|
+
const registered = await ctx.toolExecutor.registerProjectRoots([cwd]);
|
|
1849
|
+
const failed = registered.find(result => result?.success === false);
|
|
1850
|
+
if (failed) {
|
|
1851
|
+
throw new Error(`Could not register project root for sub-agent: ${failed.error || failed.root || cwd}`);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
const effectivePolicy = loadEffectivePolicy({ cwd });
|
|
1855
|
+
if (ctx.approval) {
|
|
1856
|
+
ctx.approval.policy = effectivePolicy.policy;
|
|
1857
|
+
if (ctx.approval.trustStore) ctx.approval.trustStore.policy = effectivePolicy.policy;
|
|
1858
|
+
}
|
|
1859
|
+
const projectContext = loadProjectContext({ cwd, previous: ctx.latestProjectContext || null });
|
|
1860
|
+
const projectResources = ctx.toolExecutor.getProjectResources();
|
|
1861
|
+
const envelope = buildContextEnvelope({
|
|
1862
|
+
cwd,
|
|
1863
|
+
effectivePolicy,
|
|
1864
|
+
projectContext,
|
|
1865
|
+
activeHints: [],
|
|
1866
|
+
projectResources,
|
|
1867
|
+
agentContext: ctx.toolExecutor.getAgentContext(),
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1870
|
+
ctx.effectivePolicy = effectivePolicy;
|
|
1871
|
+
ctx.latestProjectContext = projectContext;
|
|
1872
|
+
ctx.latestEnvelope = envelope;
|
|
1873
|
+
|
|
1874
|
+
const execContext = {
|
|
1875
|
+
...envelope,
|
|
1876
|
+
cwd,
|
|
1877
|
+
project_root: cwd,
|
|
1878
|
+
project_resources: projectResources,
|
|
1879
|
+
work_scope: buildWorkScope({
|
|
1880
|
+
instruction: instruction || 'Run sub-agent task',
|
|
1881
|
+
cwd,
|
|
1882
|
+
projectResources,
|
|
1883
|
+
}),
|
|
1884
|
+
};
|
|
1885
|
+
const modelOverrides = Object.fromEntries(sessionModelOverrideEntries());
|
|
1886
|
+
if (Object.keys(modelOverrides).length > 0) {
|
|
1887
|
+
execContext.model_overrides = modelOverrides;
|
|
1888
|
+
if (modelOverrides.reasoning) execContext.model_override = modelOverrides.reasoning;
|
|
1889
|
+
}
|
|
1890
|
+
return execContext;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
async function handleRunCommand(rest = '', ctx) {
|
|
1894
|
+
const parts = String(rest || '').trim().split(/\s+/).filter(Boolean);
|
|
1895
|
+
const target = parts.shift();
|
|
1896
|
+
const instruction = parts.join(' ');
|
|
1897
|
+
|
|
1898
|
+
if (!target) {
|
|
1899
|
+
process.stderr.write(` ${c.gray('Usage: /run <agent-or-workflow> [instruction]')}\n`);
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
const localAgent = listLocalAgents(safeCwd()).find(agent => localAgentMatches(agent, target));
|
|
1904
|
+
const builtinAgent = findBuiltinAgent(target);
|
|
1905
|
+
const runnableAgent = localAgent || builtinAgent;
|
|
1906
|
+
if (runnableAgent) {
|
|
1907
|
+
try {
|
|
1908
|
+
const execContext = await prepareDirectAgentRunContext(ctx, instruction || target);
|
|
1909
|
+
return await runAgentDefinition(runnableAgent, instruction, ctx, session, renderEvent, {
|
|
1910
|
+
cwd: execContext.cwd,
|
|
1911
|
+
execContext,
|
|
1912
|
+
});
|
|
1913
|
+
} catch (err) {
|
|
1914
|
+
process.stderr.write(` ${c.red('✗')} ${err.message || String(err)}\n`);
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
try {
|
|
1920
|
+
process.stderr.write(` ${c.dim(`Running workflow '${target}'...`)}\n`);
|
|
1921
|
+
const result = await ctx.toolExecutor.execute('workflow_run_multi', {
|
|
1922
|
+
name: target,
|
|
1923
|
+
instruction,
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
if (result?.success === false) {
|
|
1927
|
+
process.stderr.write(` ${c.red('✗')} ${result.output || `Workflow '${target}' failed.`}\n`);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
process.stderr.write(` ${c.green('✓')} ${c.dim(`Workflow '${target}' complete`)}\n`);
|
|
1932
|
+
const details = [];
|
|
1933
|
+
if (result?.run_id) details.push(`run ${result.run_id}`);
|
|
1934
|
+
if (result?.duration_s) details.push(`${result.duration_s}s`);
|
|
1935
|
+
if (result?.total_tokens) details.push(`${formatTokens(result.total_tokens)} tok`);
|
|
1936
|
+
if (result?.total_cost) details.push(formatCostValue(result.total_cost));
|
|
1937
|
+
if (details.length) process.stderr.write(` ${c.dim(details.join(' · '))}\n`);
|
|
1938
|
+
|
|
1939
|
+
const output = result?.result || result?.output || '';
|
|
1940
|
+
if (output) {
|
|
1941
|
+
process.stderr.write('\n');
|
|
1942
|
+
process.stderr.write(renderMarkdown(String(output), { width: process.stderr.columns || 96 }));
|
|
1943
|
+
process.stderr.write('\n');
|
|
1944
|
+
}
|
|
1945
|
+
} catch (err) {
|
|
1946
|
+
process.stderr.write(` ${c.red('✗')} ${err.message || String(err)}\n`);
|
|
1947
|
+
process.stderr.write(` ${c.gray('Usage: /run <agent-or-workflow> [instruction]')}\n`);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1652
1951
|
async function handleCommand(input, ctx) {
|
|
1653
1952
|
const { cmd, rest, aliasTarget } = normalizeCommandInput(input);
|
|
1654
1953
|
if (aliasTarget) {
|
|
@@ -1686,6 +1985,10 @@ async function handleCommand(input, ctx) {
|
|
|
1686
1985
|
handleTasksCommand(rest, ctx);
|
|
1687
1986
|
return;
|
|
1688
1987
|
|
|
1988
|
+
case '/run':
|
|
1989
|
+
await handleRunCommand(rest, ctx);
|
|
1990
|
+
return;
|
|
1991
|
+
|
|
1689
1992
|
case '/attach':
|
|
1690
1993
|
handleAttachCommand(rest, ctx);
|
|
1691
1994
|
return;
|
|
@@ -2182,6 +2485,7 @@ async function handleCommand(input, ctx) {
|
|
|
2182
2485
|
session.agentHistory.length = 0;
|
|
2183
2486
|
session.toolCalls = 0;
|
|
2184
2487
|
session.subAgentToolCalls = 0;
|
|
2488
|
+
runtime.foldedSubAgentTools = null;
|
|
2185
2489
|
clearCards();
|
|
2186
2490
|
process.stderr.write(` ${c.gray('Conversation cleared.')}\n`);
|
|
2187
2491
|
return;
|
|
@@ -2496,11 +2800,60 @@ export async function startTerminalRepl() {
|
|
|
2496
2800
|
|
|
2497
2801
|
const ctx = { auth, toolExecutor, approval, jsonlWriter, sessionMgr, checkpoints, effectivePolicy, latestProjectContext, latestEnvelope, pendingVisionPaths: [] };
|
|
2498
2802
|
|
|
2803
|
+
let startupOutputRow = 1;
|
|
2804
|
+
let startupOutputCol = 1;
|
|
2805
|
+
|
|
2806
|
+
function trackStartupOutput(chunk) {
|
|
2807
|
+
if (!process.stderr.isTTY || term().plain) return;
|
|
2808
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk ?? '');
|
|
2809
|
+
if (!text) return;
|
|
2810
|
+
const clean = text
|
|
2811
|
+
.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, '')
|
|
2812
|
+
.replace(/\x1b[()][A-Za-z0-9]/g, '');
|
|
2813
|
+
const width = Math.max(1, process.stderr.columns || process.stdout.columns || 80);
|
|
2814
|
+
for (const ch of clean) {
|
|
2815
|
+
if (ch === '\r') {
|
|
2816
|
+
startupOutputCol = 1;
|
|
2817
|
+
continue;
|
|
2818
|
+
}
|
|
2819
|
+
if (ch === '\n') {
|
|
2820
|
+
startupOutputRow++;
|
|
2821
|
+
startupOutputCol = 1;
|
|
2822
|
+
continue;
|
|
2823
|
+
}
|
|
2824
|
+
startupOutputCol++;
|
|
2825
|
+
if (startupOutputCol > width) {
|
|
2826
|
+
startupOutputRow++;
|
|
2827
|
+
startupOutputCol = 1;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
function startStartupOutputTracking() {
|
|
2833
|
+
if (!process.stderr.isTTY || term().plain) return () => {};
|
|
2834
|
+
const originalWrite = process.stderr.write;
|
|
2835
|
+
function trackedStartupWrite(chunk, ...args) {
|
|
2836
|
+
trackStartupOutput(chunk);
|
|
2837
|
+
return originalWrite.call(this, chunk, ...args);
|
|
2838
|
+
}
|
|
2839
|
+
process.stderr.write = trackedStartupWrite;
|
|
2840
|
+
return () => {
|
|
2841
|
+
if (process.stderr.write === trackedStartupWrite) {
|
|
2842
|
+
process.stderr.write = originalWrite;
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
function startupCursorSeed() {
|
|
2848
|
+
return { row: startupOutputRow, col: startupOutputCol };
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2499
2851
|
async function startNewSession({ announce = true } = {}) {
|
|
2500
2852
|
stopSpinner();
|
|
2501
2853
|
flushContent();
|
|
2502
2854
|
flushPendingHead();
|
|
2503
2855
|
flushCompactReadRun();
|
|
2856
|
+
runtime.foldedSubAgentTools = null;
|
|
2504
2857
|
clearCards();
|
|
2505
2858
|
|
|
2506
2859
|
const preserved = {
|
|
@@ -2814,57 +3167,67 @@ export async function startTerminalRepl() {
|
|
|
2814
3167
|
// ── Print banner + preflight + init BEFORE mounting the status bar ──
|
|
2815
3168
|
// The status bar shrinks the scroll region; if it mounts first, the
|
|
2816
3169
|
// banner scrolls off-screen before the user ever sees it.
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
process.stderr.write(
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
3170
|
+
const stopStartupOutputTracking = startStartupOutputTracking();
|
|
3171
|
+
let dockCursor = startupCursorSeed();
|
|
3172
|
+
try {
|
|
3173
|
+
printBanner(auth);
|
|
3174
|
+
|
|
3175
|
+
// Preflight diagnostic (PRD-055 §9). Non-blocking; opt-out via
|
|
3176
|
+
// KEPLER_NO_PREFLIGHT=1 (used by tests / scripted runs).
|
|
3177
|
+
if (process.env.KEPLER_NO_PREFLIGHT !== '1' && !cliArgs.freeswim) {
|
|
3178
|
+
try { await runPreflight({ auth, cwd: safeCwd(), version: VERSION }); }
|
|
3179
|
+
catch { /* preflight is best-effort */ }
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
// ── Initialization ──
|
|
3183
|
+
process.stderr.write(` ${c.brand('⠋')} ${c.dim('Initializing...')}\r`);
|
|
3184
|
+
await fetchUser(ctx);
|
|
3185
|
+
|
|
3186
|
+
// Clear the spinner line
|
|
3187
|
+
process.stderr.write(`\r${' '.repeat(60)}\r`);
|
|
3188
|
+
process.stderr.write(` ${c.green('✓')} ${c.dim('Ready; projects will be indexed on demand')}\n`);
|
|
3189
|
+
if (session.user) {
|
|
3190
|
+
process.stderr.write(` ${c.green('✓')} ${c.dim(`Logged in as ${session.user.github_username || session.user.email || 'user'}`)}\n`);
|
|
3191
|
+
}
|
|
3192
|
+
// ── Resume previous session ──
|
|
3193
|
+
if (cliArgs.resume) {
|
|
3194
|
+
const lastSession = cliArgs.resumeSessionId
|
|
3195
|
+
? { sessionId: cliArgs.resumeSessionId }
|
|
3196
|
+
: sessionMgr.getLastSession();
|
|
3197
|
+
|
|
3198
|
+
if (lastSession) {
|
|
3199
|
+
const resumed = await activateResumedSession(lastSession.sessionId, 'startup');
|
|
3200
|
+
if (resumed.ok) {
|
|
3201
|
+
process.stderr.write(` ${c.green('↺')} ${c.dim(`Resumed session: ${messageCountLabel(resumed.messages)}`)}`);
|
|
3202
|
+
process.stderr.write(` ${c.dim('· project')} ${c.brand(path.basename(safeCwd()))}`);
|
|
3203
|
+
process.stderr.write(` ${c.dim(`· agent ${resumed.historyMode}`)}`);
|
|
3204
|
+
if (resumed.switchedProject) process.stderr.write(` ${c.dim('(cwd restored)')}`);
|
|
3205
|
+
if (resumed.projectMissing) process.stderr.write(` ${c.yellow('(saved project path unavailable; using current cwd)')}`);
|
|
3206
|
+
if (resumed.instruction) process.stderr.write(` ${c.dim('—')} ${c.dim(resumed.instruction.slice(0, 50))}`);
|
|
3207
|
+
process.stderr.write('\n');
|
|
3208
|
+
renderResumePreview(resumed, { renderEvent });
|
|
3209
|
+
} else {
|
|
3210
|
+
process.stderr.write(` ${c.yellow('!')} ${c.dim(resumed.reason || 'No conversation found for session ' + lastSession.sessionId)}\n`);
|
|
3211
|
+
}
|
|
2853
3212
|
} else {
|
|
2854
|
-
process.stderr.write(` ${c.yellow('!')} ${c.dim(
|
|
3213
|
+
process.stderr.write(` ${c.yellow('!')} ${c.dim('No previous session to resume')}\n`);
|
|
2855
3214
|
}
|
|
2856
|
-
} else {
|
|
2857
|
-
process.stderr.write(` ${c.yellow('!')} ${c.dim('No previous session to resume')}\n`);
|
|
2858
3215
|
}
|
|
2859
|
-
}
|
|
2860
3216
|
|
|
2861
|
-
|
|
3217
|
+
process.stderr.write(`\n ${c.dim('Press')} ${c.brand('Enter')} ${c.dim('to start, or type a prompt below.')}\n`);
|
|
3218
|
+
} finally {
|
|
3219
|
+
dockCursor = startupCursorSeed();
|
|
3220
|
+
stopStartupOutputTracking();
|
|
3221
|
+
}
|
|
2862
3222
|
|
|
2863
3223
|
// Keep one bottom-reserved UI surface: the fixed input dock. The older
|
|
2864
3224
|
// status bar used the same terminal scroll-region primitive, so mounting
|
|
2865
3225
|
// both would make prompt placement unpredictable.
|
|
2866
3226
|
orbitRef.current = createOrbit();
|
|
2867
|
-
const inputDockActive = mountInputDock(
|
|
3227
|
+
const inputDockActive = mountInputDock({
|
|
3228
|
+
initialContentRow: dockCursor.row,
|
|
3229
|
+
initialContentCol: dockCursor.col,
|
|
3230
|
+
});
|
|
2868
3231
|
if (inputDockActive) {
|
|
2869
3232
|
process.on('beforeExit', unmountInputDock);
|
|
2870
3233
|
process.on('exit', unmountInputDock);
|
|
@@ -2877,11 +3240,17 @@ export async function startTerminalRepl() {
|
|
|
2877
3240
|
// terminals that ignore the request.
|
|
2878
3241
|
const PASTE_BEGIN = '\x1b[200~';
|
|
2879
3242
|
const PASTE_END = '\x1b[201~';
|
|
3243
|
+
const F2_SEQUENCES = new Set(['\x1bOQ', '\x1b[12~']);
|
|
2880
3244
|
let _inBracketedPaste = false;
|
|
2881
3245
|
let _bracketedPasteBuffer = '';
|
|
3246
|
+
let _suppressBracketedPasteLines = false;
|
|
3247
|
+
let _bracketedPasteStartLine = '';
|
|
3248
|
+
let _bracketedPasteStartCursor = 0;
|
|
3249
|
+
let _promptHasInsertedPaste = false;
|
|
2882
3250
|
const _pasteEndListeners = new Set();
|
|
2883
3251
|
function onBracketedPasteEnd(cb) { _pasteEndListeners.add(cb); return () => _pasteEndListeners.delete(cb); }
|
|
2884
3252
|
function isInBracketedPaste() { return _inBracketedPaste; }
|
|
3253
|
+
function isF2Sequence(text) { return F2_SEQUENCES.has(String(text || '')); }
|
|
2885
3254
|
|
|
2886
3255
|
if (process.stdin.isTTY) {
|
|
2887
3256
|
try { process.stderr.write('\x1b[?2004h'); } catch {}
|
|
@@ -2900,6 +3269,11 @@ export async function startTerminalRepl() {
|
|
|
2900
3269
|
if (start === -1) return;
|
|
2901
3270
|
_inBracketedPaste = true;
|
|
2902
3271
|
_bracketedPasteBuffer = '';
|
|
3272
|
+
_suppressBracketedPasteLines = true;
|
|
3273
|
+
_bracketedPasteStartLine = String(rl?.line || '');
|
|
3274
|
+
_bracketedPasteStartCursor = typeof rl?.cursor === 'number'
|
|
3275
|
+
? rl.cursor
|
|
3276
|
+
: _bracketedPasteStartLine.length;
|
|
2903
3277
|
i = start + PASTE_BEGIN.length;
|
|
2904
3278
|
} else {
|
|
2905
3279
|
const end = s.indexOf(PASTE_END, i);
|
|
@@ -2914,7 +3288,10 @@ export async function startTerminalRepl() {
|
|
|
2914
3288
|
// Notify subscribers on next tick so readline finishes emitting its
|
|
2915
3289
|
// synchronous `line` events for the buffered content first.
|
|
2916
3290
|
const cbs = [..._pasteEndListeners];
|
|
2917
|
-
setImmediate(() => {
|
|
3291
|
+
setImmediate(() => {
|
|
3292
|
+
for (const cb of cbs) { try { cb(payload); } catch {} }
|
|
3293
|
+
_suppressBracketedPasteLines = false;
|
|
3294
|
+
});
|
|
2918
3295
|
i = end + PASTE_END.length;
|
|
2919
3296
|
}
|
|
2920
3297
|
}
|
|
@@ -2937,8 +3314,10 @@ export async function startTerminalRepl() {
|
|
|
2937
3314
|
|
|
2938
3315
|
function printInputBottomRule() {
|
|
2939
3316
|
if (isInputDockMounted()) {
|
|
3317
|
+
clearPinnedStatus();
|
|
2940
3318
|
clearInputPrompt();
|
|
2941
3319
|
moveToContent();
|
|
3320
|
+
if (process.stderr.isTTY && !term().plain) process.stderr.write('\r\x1b[2K');
|
|
2942
3321
|
return;
|
|
2943
3322
|
}
|
|
2944
3323
|
if (term().plain) return;
|
|
@@ -2946,11 +3325,11 @@ export async function startTerminalRepl() {
|
|
|
2946
3325
|
}
|
|
2947
3326
|
|
|
2948
3327
|
function idleInputTips() {
|
|
2949
|
-
return '[Enter] send [/] commands [Tab] complete [
|
|
3328
|
+
return '[Enter] send [/] commands [Tab] complete [F2] details';
|
|
2950
3329
|
}
|
|
2951
3330
|
|
|
2952
3331
|
function executionInputTips() {
|
|
2953
|
-
return 'type
|
|
3332
|
+
return 'type extra context · [Enter] send · [Esc] cancel · [Ctrl+P] pause · [F2] details';
|
|
2954
3333
|
}
|
|
2955
3334
|
|
|
2956
3335
|
// Proxy stream: swallows writes when the dock owns the input row so
|
|
@@ -3088,10 +3467,10 @@ export async function startTerminalRepl() {
|
|
|
3088
3467
|
slashHintLine = '';
|
|
3089
3468
|
}
|
|
3090
3469
|
|
|
3091
|
-
function replaceReadlineLine(value) {
|
|
3470
|
+
function replaceReadlineLine(value, cursor = null) {
|
|
3092
3471
|
const next = String(value || '');
|
|
3093
3472
|
rl.line = next;
|
|
3094
|
-
rl.cursor = next.length;
|
|
3473
|
+
rl.cursor = cursor == null ? next.length : Math.max(0, Math.min(next.length, Number(cursor) || 0));
|
|
3095
3474
|
if (typeof rl._refreshLine === 'function') {
|
|
3096
3475
|
rl._refreshLine();
|
|
3097
3476
|
} else {
|
|
@@ -3101,6 +3480,17 @@ export async function startTerminalRepl() {
|
|
|
3101
3480
|
}
|
|
3102
3481
|
}
|
|
3103
3482
|
|
|
3483
|
+
function insertPromptText(text, { baseLine = rl.line || '', baseCursor = rl.cursor, fromPaste = false } = {}) {
|
|
3484
|
+
const payload = String(text || '');
|
|
3485
|
+
if (!payload) return;
|
|
3486
|
+
const line = String(baseLine || '');
|
|
3487
|
+
const cursor = typeof baseCursor === 'number' ? Math.max(0, Math.min(line.length, baseCursor)) : line.length;
|
|
3488
|
+
const next = `${line.slice(0, cursor)}${payload}${line.slice(cursor)}`;
|
|
3489
|
+
if (fromPaste) _promptHasInsertedPaste = true;
|
|
3490
|
+
replaceReadlineLine(next, cursor + payload.length);
|
|
3491
|
+
renderIdleDockInput();
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3104
3494
|
function acceptSlashHint() {
|
|
3105
3495
|
const item = slashHintItems[slashHintSelected];
|
|
3106
3496
|
if (!item) return false;
|
|
@@ -3190,8 +3580,18 @@ export async function startTerminalRepl() {
|
|
|
3190
3580
|
readline.emitKeypressEvents(process.stdin, rl);
|
|
3191
3581
|
process.stdin.on('keypress', (_str, key = {}) => {
|
|
3192
3582
|
if (!inputActive) return;
|
|
3583
|
+
if (_inBracketedPaste || _suppressBracketedPasteLines) return;
|
|
3584
|
+
if (key.name === 'return' || key.name === 'enter') return;
|
|
3585
|
+
if (key.name === 'f2') {
|
|
3586
|
+
clearSlashHint();
|
|
3587
|
+
if (isInputDockMounted()) moveToContent();
|
|
3588
|
+
expandLast();
|
|
3589
|
+
renderIdleDockInput();
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3193
3592
|
setImmediate(() => {
|
|
3194
3593
|
if (!inputActive) return;
|
|
3594
|
+
if (_inBracketedPaste || _suppressBracketedPasteLines) return;
|
|
3195
3595
|
if (slashHintVisible && key.name === 'tab' && acceptSlashHint()) return;
|
|
3196
3596
|
if (slashHintVisible && key.name === 'down' && moveSlashHintSelection(1)) return;
|
|
3197
3597
|
if (slashHintVisible && key.name === 'up' && moveSlashHintSelection(-1)) return;
|
|
@@ -3247,8 +3647,18 @@ export async function startTerminalRepl() {
|
|
|
3247
3647
|
_pasteFlushTimer = null;
|
|
3248
3648
|
}
|
|
3249
3649
|
if (!_pasteLines.length) return;
|
|
3250
|
-
const
|
|
3650
|
+
const trailing = String(rl.line || '');
|
|
3651
|
+
const pastedLines = _pasteLines.slice();
|
|
3251
3652
|
_pasteLines = [];
|
|
3653
|
+
if (pastedLines.length > 1 || trailing) {
|
|
3654
|
+
const text = [...pastedLines, trailing].join('\n');
|
|
3655
|
+
_promptHasInsertedPaste = true;
|
|
3656
|
+
replaceReadlineLine(text);
|
|
3657
|
+
renderIdleDockInput();
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
const line = pastedLines.join('\n');
|
|
3661
|
+
_promptHasInsertedPaste = false;
|
|
3252
3662
|
queueOrRunLine(line);
|
|
3253
3663
|
}
|
|
3254
3664
|
|
|
@@ -3258,8 +3668,29 @@ export async function startTerminalRepl() {
|
|
|
3258
3668
|
// or the user pressed Enter normally), the debounce falls back to old
|
|
3259
3669
|
// behavior — a single Enter flushes almost instantly.
|
|
3260
3670
|
rl.on('line', async (line) => {
|
|
3671
|
+
if (_suppressBracketedPasteLines) {
|
|
3672
|
+
_pasteLines = [];
|
|
3673
|
+
if (_pasteFlushTimer) {
|
|
3674
|
+
clearTimeout(_pasteFlushTimer);
|
|
3675
|
+
_pasteFlushTimer = null;
|
|
3676
|
+
}
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
if (!inputActive) {
|
|
3680
|
+
_pasteLines = [];
|
|
3681
|
+
if (_pasteFlushTimer) {
|
|
3682
|
+
clearTimeout(_pasteFlushTimer);
|
|
3683
|
+
_pasteFlushTimer = null;
|
|
3684
|
+
}
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3687
|
+
const submitInsertedPaste = _promptHasInsertedPaste || String(line || '').includes('\n');
|
|
3261
3688
|
_pasteLines.push(line);
|
|
3262
3689
|
if (_pasteFlushTimer) clearTimeout(_pasteFlushTimer);
|
|
3690
|
+
if (submitInsertedPaste) {
|
|
3691
|
+
flushPastedLines();
|
|
3692
|
+
return;
|
|
3693
|
+
}
|
|
3263
3694
|
if (isInBracketedPaste()) {
|
|
3264
3695
|
_pasteFlushTimer = null;
|
|
3265
3696
|
} else {
|
|
@@ -3267,10 +3698,19 @@ export async function startTerminalRepl() {
|
|
|
3267
3698
|
}
|
|
3268
3699
|
});
|
|
3269
3700
|
|
|
3270
|
-
onBracketedPasteEnd(() => {
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3701
|
+
onBracketedPasteEnd((payload) => {
|
|
3702
|
+
_pasteLines = [];
|
|
3703
|
+
if (_pasteFlushTimer) {
|
|
3704
|
+
clearTimeout(_pasteFlushTimer);
|
|
3705
|
+
_pasteFlushTimer = null;
|
|
3706
|
+
}
|
|
3707
|
+
// Readline has finished emitting synchronous `line` events by now.
|
|
3708
|
+
// Treat paste as editing the prompt buffer; Enter remains the submit.
|
|
3709
|
+
insertPromptText(payload || '', {
|
|
3710
|
+
baseLine: _bracketedPasteStartLine,
|
|
3711
|
+
baseCursor: _bracketedPasteStartCursor,
|
|
3712
|
+
fromPaste: true,
|
|
3713
|
+
});
|
|
3274
3714
|
});
|
|
3275
3715
|
|
|
3276
3716
|
async function _handleLine(line) {
|
|
@@ -3485,6 +3925,21 @@ export async function startTerminalRepl() {
|
|
|
3485
3925
|
}
|
|
3486
3926
|
runtime.afterContentFlush = focusExecutionInput;
|
|
3487
3927
|
|
|
3928
|
+
function printExecutionInstruction(instruction) {
|
|
3929
|
+
if (isInputDockMounted()) {
|
|
3930
|
+
clearInputPrompt();
|
|
3931
|
+
moveToContent();
|
|
3932
|
+
} else if (executionInputVisible) {
|
|
3933
|
+
process.stderr.write('\n');
|
|
3934
|
+
}
|
|
3935
|
+
renderBlockBoundary('user', { compactSame: true });
|
|
3936
|
+
process.stderr.write(`${transcriptHeader('you', { tone: 'user' })} ${paint.text.dim('follow-up')}\n`);
|
|
3937
|
+
for (const line of String(instruction || '').split('\n')) {
|
|
3938
|
+
process.stderr.write(`${transcriptLine(line, { tone: 'user' })}\n`);
|
|
3939
|
+
}
|
|
3940
|
+
runtime.lastRenderedBlock = 'user';
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3488
3943
|
async function submitExecutionInstruction() {
|
|
3489
3944
|
const instruction = executionInputBuffer.trim();
|
|
3490
3945
|
executionInputBuffer = '';
|
|
@@ -3503,18 +3958,14 @@ export async function startTerminalRepl() {
|
|
|
3503
3958
|
executionInputVisible = false;
|
|
3504
3959
|
return;
|
|
3505
3960
|
}
|
|
3961
|
+
printExecutionInstruction(instruction);
|
|
3506
3962
|
if (isInputDockMounted()) {
|
|
3507
|
-
clearInputPrompt();
|
|
3508
|
-
moveToContent();
|
|
3509
|
-
process.stderr.write(`${executionInputPrefix()}${instruction}\n`);
|
|
3510
3963
|
renderDockInput(executionInputPrefix(), '', {
|
|
3511
3964
|
context: buildContextStrip(),
|
|
3512
3965
|
meta: buildDockMeta(),
|
|
3513
3966
|
tips: executionInputTips(),
|
|
3514
3967
|
});
|
|
3515
3968
|
moveToContent();
|
|
3516
|
-
} else if (executionInputVisible) {
|
|
3517
|
-
process.stderr.write('\n');
|
|
3518
3969
|
}
|
|
3519
3970
|
executionInputVisible = false;
|
|
3520
3971
|
// Live steering (PRD-081 §5.2): submit through the dedicated
|
|
@@ -3540,18 +3991,26 @@ export async function startTerminalRepl() {
|
|
|
3540
3991
|
});
|
|
3541
3992
|
|
|
3542
3993
|
if (status === 'accepted') {
|
|
3994
|
+
renderBlockBoundary('status', { compactSame: true });
|
|
3543
3995
|
process.stderr.write(` ${c.green('↳')} ${c.dim('sent to running agent')}\n`);
|
|
3996
|
+
runtime.lastRenderedBlock = 'status';
|
|
3544
3997
|
} else if (status === 'duplicate') {
|
|
3998
|
+
renderBlockBoundary('status', { compactSame: true });
|
|
3545
3999
|
process.stderr.write(` ${c.dim('↳ already sent (idempotent)')}\n`);
|
|
4000
|
+
runtime.lastRenderedBlock = 'status';
|
|
3546
4001
|
} else if (status === 'queued_next_turn') {
|
|
3547
4002
|
_queuedLines.push(instruction);
|
|
4003
|
+
renderBlockBoundary('status', { compactSame: true });
|
|
3548
4004
|
process.stderr.write(` ${c.yellow('↳')} ${c.dim('task ended — queued for next turn')}\n`);
|
|
4005
|
+
runtime.lastRenderedBlock = 'status';
|
|
3549
4006
|
} else {
|
|
3550
4007
|
// no_task, error, or unknown — fall back to next-turn queue so the
|
|
3551
4008
|
// user's text is never silently lost.
|
|
3552
4009
|
_queuedLines.push(instruction);
|
|
3553
4010
|
const errBits = result && result.error ? ` ${c.dim(`(${String(result.error).slice(0, 80)})`)}` : '';
|
|
4011
|
+
renderBlockBoundary('status', { compactSame: true });
|
|
3554
4012
|
process.stderr.write(` ${c.yellow('↳')} ${c.dim('queued for next turn')}${errBits}\n`);
|
|
4013
|
+
runtime.lastRenderedBlock = 'status';
|
|
3555
4014
|
}
|
|
3556
4015
|
}
|
|
3557
4016
|
|
|
@@ -3637,6 +4096,14 @@ export async function startTerminalRepl() {
|
|
|
3637
4096
|
const bytes2 = [...data];
|
|
3638
4097
|
const text2 = data.toString('utf8');
|
|
3639
4098
|
|
|
4099
|
+
if (isF2Sequence(text2)) {
|
|
4100
|
+
stopSpinner();
|
|
4101
|
+
if (isInputDockMounted()) moveToContent();
|
|
4102
|
+
expandLast();
|
|
4103
|
+
if (isInputDockMounted()) redrawExecutionInput();
|
|
4104
|
+
return;
|
|
4105
|
+
}
|
|
4106
|
+
|
|
3640
4107
|
// Esc key (single byte 0x1b, not part of arrow sequence)
|
|
3641
4108
|
if (bytes2.length === 1 && bytes2[0] === 0x1b) {
|
|
3642
4109
|
if (executionInputVisible || executionInputBuffer) {
|
|
@@ -3729,15 +4196,6 @@ export async function startTerminalRepl() {
|
|
|
3729
4196
|
return;
|
|
3730
4197
|
}
|
|
3731
4198
|
|
|
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
4199
|
// Any safe text (unicode, tabs, spaces, symbols) becomes a live
|
|
3742
4200
|
// follow-up instruction. Enter sends it via resume(instruction).
|
|
3743
4201
|
if (isSafeFollowUpText(text2)) {
|
|
@@ -3752,6 +4210,15 @@ export async function startTerminalRepl() {
|
|
|
3752
4210
|
approval.setExecutionHooks({
|
|
3753
4211
|
onPause: () => { execListenerActive = false; },
|
|
3754
4212
|
onResume: () => { execListenerActive = true; },
|
|
4213
|
+
onApprovalPromptEnd: () => {
|
|
4214
|
+
if (!isInputDockMounted()) return;
|
|
4215
|
+
renderDockInput(executionInputPrefix(), executionInputBuffer, {
|
|
4216
|
+
context: buildContextStrip(),
|
|
4217
|
+
meta: buildDockMeta(),
|
|
4218
|
+
tips: executionInputTips(),
|
|
4219
|
+
});
|
|
4220
|
+
moveToContent();
|
|
4221
|
+
},
|
|
3755
4222
|
});
|
|
3756
4223
|
|
|
3757
4224
|
keypressCleanup = () => {
|
|
@@ -3773,8 +4240,6 @@ export async function startTerminalRepl() {
|
|
|
3773
4240
|
moveToContent();
|
|
3774
4241
|
}
|
|
3775
4242
|
startContentStream();
|
|
3776
|
-
process.stderr.write(`\n${transcriptHeader('bahulam', { tone: 'assistant' })}\n`);
|
|
3777
|
-
runtime.contentHeaderPrinted = true;
|
|
3778
4243
|
|
|
3779
4244
|
// Immediate feedback so the screen isn't blank between submit and the
|
|
3780
4245
|
// first backend event. The first `status`, `thinking`, or `content_*`
|