@bahulam/code 2.6.12 → 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.
- 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 +22 -4
- package/src/core/approval.mjs +172 -24
- package/src/core/headless.mjs +14 -3
- package/src/core/local-agent.mjs +3 -2
- package/src/core/tool-executor.mjs +13 -3
- package/src/index.mjs +1 -1
- package/src/terminal/agents.mjs +194 -18
- package/src/terminal/repl-render.mjs +22 -4
- package/src/terminal/repl-state.mjs +1 -0
- package/src/terminal/repl.mjs +395 -21
- package/src/terminal/tool-display.mjs +135 -2
- package/src/ui/approval.mjs +200 -14
- package/src/ui/icons.mjs +11 -5
- package/src/ui/input-dock.mjs +90 -19
- package/src/ui/slash-commands.mjs +10 -0
- package/src/ui/tool-card.mjs +109 -21
- package/src/ui/tool-details.mjs +110 -11
- 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';
|
|
@@ -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('
|
|
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(() => {
|
|
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 [
|
|
3254
|
+
return '[Enter] send [/] commands [Tab] complete [F2] details';
|
|
2950
3255
|
}
|
|
2951
3256
|
|
|
2952
3257
|
function executionInputTips() {
|
|
2953
|
-
return 'type
|
|
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
|
|
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
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
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 = () => {
|