@bahulam/code 0.1.11 → 0.1.13
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 +1 -1
- package/src/auth/tarang-auth.mjs +313 -0
- package/src/commands/agent.mjs +7 -7
- package/src/commands/install.mjs +295 -0
- package/src/commands/plugin-manage.mjs +280 -88
- package/src/config/cli-args.mjs +16 -0
- package/src/config/settings-loader.mjs +15 -0
- package/src/core/background-tasks.mjs +186 -0
- package/src/core/headless.mjs +54 -3
- package/src/core/local-agent.mjs +10 -1
- package/src/core/risk-tier.mjs +1 -0
- package/src/core/stream-client.mjs +95 -15
- package/src/core/tool-executor.mjs +266 -15
- package/src/local-service/agent-relay.mjs +1 -1
- package/src/local-service/server.mjs +116 -14
- package/src/orchestration/approval.mjs +30 -0
- package/src/orchestration/completion-triggers.mjs +40 -0
- package/src/orchestration/dispatch.mjs +118 -0
- package/src/orchestration/events.mjs +19 -0
- package/src/orchestration/graph.mjs +126 -0
- package/src/orchestration/node-runner.mjs +193 -0
- package/src/orchestration/runner.mjs +200 -0
- package/src/plugins/executor.mjs +2 -2
- package/src/plugins/manifest.mjs +30 -27
- package/src/plugins/pi-compat/loader-hook.mjs +45 -0
- package/src/plugins/pi-compat/probe.mjs +294 -0
- package/src/plugins/pi-compat/scaffold.mjs +487 -0
- package/src/plugins/pi-compat/shim.mjs +134 -0
- package/src/plugins/pi-compose.mjs +147 -0
- package/src/plugins/preflight.mjs +35 -10
- package/src/plugins/registry.mjs +6 -0
- package/src/terminal/agents.mjs +8 -3
- package/src/terminal/main.mjs +39 -7
- package/src/terminal/paste-input.mjs +23 -0
- package/src/terminal/repl-render.mjs +65 -10
- package/src/terminal/repl-state.mjs +4 -2
- package/src/terminal/repl.mjs +624 -103
- package/src/tools/agent.mjs +6 -2
- package/src/tools/registry.mjs +107 -4
- package/src/ui/input-dock.mjs +5 -2
- package/src/ui/slash-commands.mjs +1 -1
- package/src/ui/sub-agent.mjs +14 -8
package/src/terminal/repl.mjs
CHANGED
|
@@ -64,10 +64,14 @@ import { SkillInstaller } from '../skills/installer.mjs';
|
|
|
64
64
|
import { SkillsLoader } from '../skills/loader.mjs';
|
|
65
65
|
import { openSkillsPicker, formatSkillsList } from './skills-picker.mjs';
|
|
66
66
|
import { createAgentFile, isVsCodeTerminal, listLocalAgents, openAgentFile, syncAgentsToBackend } from '../agents/scaffold.mjs';
|
|
67
|
+
import { listLocalWorkflows } from '../agents/workflow_scaffold.mjs';
|
|
68
|
+
import { dispatch } from '../orchestration/dispatch.mjs';
|
|
69
|
+
import { registerJobCompletionDispatch } from '../orchestration/completion-triggers.mjs';
|
|
67
70
|
import { PluginRegistry } from '../plugins/registry.mjs';
|
|
68
71
|
import { SessionManager } from '../core/session-manager.mjs';
|
|
69
72
|
import { parseArgs } from '../config/cli-args.mjs';
|
|
70
73
|
import { pickModelOverridesForm } from './repl-model-form.mjs';
|
|
74
|
+
import { isRawMultilinePasteChunk, normalizePastedText, pastedTextLabel } from './paste-input.mjs';
|
|
71
75
|
import {
|
|
72
76
|
MODEL_CATEGORY_ORDER,
|
|
73
77
|
formatCategoryBadge,
|
|
@@ -115,7 +119,7 @@ import {
|
|
|
115
119
|
flushPendingHead,
|
|
116
120
|
isInlineOutcomeTool,
|
|
117
121
|
pushSubAgentWindowLine,
|
|
118
|
-
|
|
122
|
+
rebuildSubAgentWindowGroups,
|
|
119
123
|
renderBlockBoundary,
|
|
120
124
|
renderExploreRun,
|
|
121
125
|
renderFileDiffEvent,
|
|
@@ -966,6 +970,7 @@ async function handleAgentsCommand(rest = '', ctx) {
|
|
|
966
970
|
force: Boolean(flags.force),
|
|
967
971
|
});
|
|
968
972
|
process.stderr.write(` ${c.green('✓')} ${c.dim('Created local agent:')} ${created.filePath}\n`);
|
|
973
|
+
process.stderr.write(` ${c.dim('Available now in this workspace:')} /run ${created.slug} "<task>"\n`);
|
|
969
974
|
const shouldOpen = !flags['no-open'] && (Boolean(flags.open) || isVsCodeTerminal());
|
|
970
975
|
if (shouldOpen) {
|
|
971
976
|
const opened = openAgentFile(created.filePath, {
|
|
@@ -977,7 +982,7 @@ async function handleAgentsCommand(rest = '', ctx) {
|
|
|
977
982
|
process.stderr.write(` ${c.dim(opened.reason)}\n`);
|
|
978
983
|
}
|
|
979
984
|
}
|
|
980
|
-
process.stderr.write(` ${c.dim('
|
|
985
|
+
process.stderr.write(` ${c.dim('Optional cloud/account sync:')} /agents sync ${created.slug}\n`);
|
|
981
986
|
} catch (err) {
|
|
982
987
|
process.stderr.write(` ${c.red(err.message || String(err))}\n`);
|
|
983
988
|
}
|
|
@@ -1003,7 +1008,7 @@ async function handleAgentsCommand(rest = '', ctx) {
|
|
|
1003
1008
|
process.stderr.write(` ${c.yellow('!')} ${c.dim(opened.reason)}\n`);
|
|
1004
1009
|
process.stderr.write(` ${c.dim('Agent file:')} ${agent.source}\n`);
|
|
1005
1010
|
}
|
|
1006
|
-
process.stderr.write(` ${c.dim('
|
|
1011
|
+
process.stderr.write(` ${c.dim('Local changes are available immediately in this workspace. Optional cloud/account sync:')} /agents sync ${agent.slug}\n`);
|
|
1007
1012
|
return;
|
|
1008
1013
|
}
|
|
1009
1014
|
|
|
@@ -1025,7 +1030,7 @@ async function handleAgentsCommand(rest = '', ctx) {
|
|
|
1025
1030
|
agents: selected,
|
|
1026
1031
|
});
|
|
1027
1032
|
const synced = result.synced ?? selected.length;
|
|
1028
|
-
process.stderr.write(` ${c.green('✓')} ${c.dim(`Synced ${synced} agent${synced === 1 ? '' : 's'} to
|
|
1033
|
+
process.stderr.write(` ${c.green('✓')} ${c.dim(`Synced ${synced} agent${synced === 1 ? '' : 's'} to the backend for account/cloud reuse.`)}\n`);
|
|
1029
1034
|
} catch (err) {
|
|
1030
1035
|
process.stderr.write(` ${c.red(err.message || String(err))}\n`);
|
|
1031
1036
|
}
|
|
@@ -1520,10 +1525,14 @@ function isDeniedStatusMessage(message = '') {
|
|
|
1520
1525
|
}
|
|
1521
1526
|
|
|
1522
1527
|
function toolCallId(data = {}, tool = 'tool') {
|
|
1523
|
-
return data.call_id || data._callId || data.request_id || data.id ||
|
|
1528
|
+
return data.call_id || data._callId || data.tool_call_id || data.request_id || data.id ||
|
|
1524
1529
|
`${tool}:${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1525
1530
|
}
|
|
1526
1531
|
|
|
1532
|
+
function explicitToolCallId(data = {}) {
|
|
1533
|
+
return data.call_id || data._callId || data.tool_call_id || data.request_id || data.id || null;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1527
1536
|
function isSubAgentToolEvent(data = {}) {
|
|
1528
1537
|
return Boolean(data?.internal || data?.sub_agent);
|
|
1529
1538
|
}
|
|
@@ -1533,7 +1542,7 @@ function shouldFoldSubAgentTool(data = {}) {
|
|
|
1533
1542
|
}
|
|
1534
1543
|
|
|
1535
1544
|
function foldedSubAgentName(data = {}) {
|
|
1536
|
-
return data?.sub_agent || data?.agent || data?.type || 'sub-agent';
|
|
1545
|
+
return data?.sub_agent_label || data?.sub_agent || data?.agent || data?.type || 'sub-agent';
|
|
1537
1546
|
}
|
|
1538
1547
|
|
|
1539
1548
|
function subAgentStartingLine(agentType = 'sub-agent') {
|
|
@@ -1546,19 +1555,131 @@ function subAgentStartingLine(agentType = 'sub-agent') {
|
|
|
1546
1555
|
return `→ starting ${normalized}`;
|
|
1547
1556
|
}
|
|
1548
1557
|
|
|
1549
|
-
function
|
|
1558
|
+
function subAgentRunId(data = {}) {
|
|
1559
|
+
return data?.run_id || data?.sub_agent_run_id || null;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function normalizeSubAgentRunData(data = {}) {
|
|
1563
|
+
if (!data || typeof data !== 'object') return data;
|
|
1564
|
+
const runId = subAgentRunId(data);
|
|
1565
|
+
if (!runId) return data;
|
|
1566
|
+
const laneRun = session.activeSubAgentRuns?.get(runId);
|
|
1567
|
+
const patch = {};
|
|
1568
|
+
if (!data.run_id) patch.run_id = runId;
|
|
1569
|
+
if (laneRun?.type && !data.sub_agent) patch.sub_agent = laneRun.type;
|
|
1570
|
+
if (laneRun && !data.sub_agent_label) patch.sub_agent_label = subAgentLaneLabel(laneRun);
|
|
1571
|
+
return Object.keys(patch).length ? { ...data, ...patch } : data;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function foldedSubAgentKey(data = {}) {
|
|
1575
|
+
return subAgentRunId(data) || foldedSubAgentName(data);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
function hasParallelSubAgentRuns() {
|
|
1579
|
+
return activeSubAgentLanes().length > 1;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
function hasReliableSubAgentAttribution(data = {}) {
|
|
1583
|
+
return !(hasParallelSubAgentRuns() && isSubAgentToolEvent(data) && !subAgentRunId(data));
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
function ensureFoldedSubAgentTools(agentType, key = agentType, data = {}) {
|
|
1587
|
+
runtime.foldedSubAgentToolMap = runtime.foldedSubAgentToolMap || new Map();
|
|
1550
1588
|
const current = runtime.foldedSubAgentTools;
|
|
1551
|
-
if (current && current.
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1589
|
+
if (current && current.key === key) return current;
|
|
1590
|
+
|
|
1591
|
+
let fold = runtime.foldedSubAgentToolMap.get(key);
|
|
1592
|
+
if (!fold) {
|
|
1593
|
+
const runId = subAgentRunId(data);
|
|
1594
|
+
const laneRun = runId ? session.activeSubAgentRuns?.get(runId) : null;
|
|
1595
|
+
fold = {
|
|
1596
|
+
key,
|
|
1597
|
+
runId,
|
|
1598
|
+
agentType,
|
|
1599
|
+
label: laneRun?.label || agentType,
|
|
1600
|
+
query: laneRun?.query || data?.query || '',
|
|
1601
|
+
entries: [],
|
|
1602
|
+
startedAt: Date.now(),
|
|
1603
|
+
};
|
|
1604
|
+
runtime.foldedSubAgentToolMap.set(key, fold);
|
|
1605
|
+
} else {
|
|
1606
|
+
const runId = subAgentRunId(data);
|
|
1607
|
+
const laneRun = runId ? session.activeSubAgentRuns?.get(runId) : null;
|
|
1608
|
+
if (runId && !fold.runId) fold.runId = runId;
|
|
1609
|
+
if (laneRun) fold.label = subAgentLaneLabel(laneRun);
|
|
1610
|
+
if (laneRun?.query && !fold.query) fold.query = laneRun.query;
|
|
1611
|
+
}
|
|
1612
|
+
runtime.foldedSubAgentTools = fold;
|
|
1613
|
+
return fold;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
function createSubAgentLane(agentType, query, runId, data = {}) {
|
|
1617
|
+
session.activeSubAgentRuns = session.activeSubAgentRuns || new Map();
|
|
1618
|
+
const sameTypeOrdinals = [...session.activeSubAgentRuns.values()]
|
|
1619
|
+
.filter(run => run.type === agentType)
|
|
1620
|
+
.map(run => Number(run.ordinal || 1));
|
|
1621
|
+
const ordinal = sameTypeOrdinals.length ? Math.max(...sameTypeOrdinals) + 1 : 1;
|
|
1622
|
+
const lane = {
|
|
1623
|
+
type: agentType,
|
|
1624
|
+
ordinal,
|
|
1625
|
+
label: agentType,
|
|
1626
|
+
runId,
|
|
1627
|
+
query,
|
|
1628
|
+
tools: 0,
|
|
1629
|
+
// Backend signals the run starts as one of N siblings — label with
|
|
1630
|
+
// the ordinal from the first event (explore#1) so the open block and
|
|
1631
|
+
// the close line agree even for the first run of a batch.
|
|
1632
|
+
forceOrdinal: Number(data?.parallel_batch) > 1,
|
|
1557
1633
|
};
|
|
1558
|
-
|
|
1634
|
+
session.activeSubAgentRuns.set(runId, lane);
|
|
1635
|
+
ensureFoldedSubAgentTools(agentType, runId, { type: agentType, query, run_id: runId });
|
|
1636
|
+
_syncSubAgentWindow();
|
|
1637
|
+
return lane;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function activeSubAgentLanes() {
|
|
1641
|
+
return session.activeSubAgentRuns instanceof Map
|
|
1642
|
+
? [...session.activeSubAgentRuns.values()]
|
|
1643
|
+
: [];
|
|
1559
1644
|
}
|
|
1560
1645
|
|
|
1561
|
-
function
|
|
1646
|
+
function subAgentLaneLabel(lane, lanes = activeSubAgentLanes()) {
|
|
1647
|
+
const sameType = lanes.filter(item => item.type === lane.type).length;
|
|
1648
|
+
if (lane.forceOrdinal || sameType > 1 || Number(lane.ordinal || 1) > 1) {
|
|
1649
|
+
return `${lane.type}#${lane.ordinal || 1}`;
|
|
1650
|
+
}
|
|
1651
|
+
return lane.label || lane.type || 'sub-agent';
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
function removeFoldedSubAgentTools(key) {
|
|
1655
|
+
const map = runtime.foldedSubAgentToolMap;
|
|
1656
|
+
if (key && map?.has(key)) {
|
|
1657
|
+
const fold = map.get(key);
|
|
1658
|
+
map.delete(key);
|
|
1659
|
+
if (runtime.foldedSubAgentTools?.key === key) {
|
|
1660
|
+
runtime.foldedSubAgentTools = map.values().next().value || null;
|
|
1661
|
+
}
|
|
1662
|
+
return fold;
|
|
1663
|
+
}
|
|
1664
|
+
if (!key && map?.size) {
|
|
1665
|
+
const folds = [...map.values()];
|
|
1666
|
+
map.clear();
|
|
1667
|
+
runtime.foldedSubAgentTools = null;
|
|
1668
|
+
return folds;
|
|
1669
|
+
}
|
|
1670
|
+
const fold = runtime.foldedSubAgentTools;
|
|
1671
|
+
runtime.foldedSubAgentTools = null;
|
|
1672
|
+
if (map?.clear) map.clear();
|
|
1673
|
+
return fold;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
function resetFoldedSubAgentTools() {
|
|
1677
|
+
runtime.foldedSubAgentTools = null;
|
|
1678
|
+
if (runtime.foldedSubAgentToolMap?.clear) runtime.foldedSubAgentToolMap.clear();
|
|
1679
|
+
else runtime.foldedSubAgentToolMap = new Map();
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function findFoldedToolEntry(fold, callId, tool, summary = null) {
|
|
1562
1683
|
if (!fold) return null;
|
|
1563
1684
|
if (callId) {
|
|
1564
1685
|
const exact = fold.entries.find(entry => entry.callId === callId);
|
|
@@ -1568,21 +1689,37 @@ function findFoldedToolEntry(fold, callId, tool) {
|
|
|
1568
1689
|
const entry = fold.entries[i];
|
|
1569
1690
|
if (entry.tool === tool && !entry.result) return entry;
|
|
1570
1691
|
}
|
|
1692
|
+
// The same underlying call arrives on two event streams with different
|
|
1693
|
+
// id namespaces (bridge tool_call call_id vs framework sub_agent_tool
|
|
1694
|
+
// tool_id). When an entry for this exact tool+summary exists — even one
|
|
1695
|
+
// already resolved — treat the second stream's event as the same call
|
|
1696
|
+
// instead of double-counting it (54 "tool uses" for 27 real calls).
|
|
1697
|
+
if (summary) {
|
|
1698
|
+
for (let i = fold.entries.length - 1; i >= 0; i--) {
|
|
1699
|
+
const entry = fold.entries[i];
|
|
1700
|
+
if (entry.tool === tool && entry.summary === summary) return entry;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1571
1703
|
return null;
|
|
1572
1704
|
}
|
|
1573
1705
|
|
|
1574
1706
|
function foldSubAgentToolCall(data = {}) {
|
|
1707
|
+
// Under parallel runs, an event without a run_id cannot be attributed
|
|
1708
|
+
// to a lane — drop it from the fold rather than guess (it would render
|
|
1709
|
+
// under whichever agent's group happens to match by name).
|
|
1710
|
+
if (!hasReliableSubAgentAttribution(data)) return;
|
|
1575
1711
|
const tool = data?.tool || 'unknown';
|
|
1576
1712
|
const args = data?.args || {};
|
|
1577
|
-
const callId =
|
|
1713
|
+
const callId = explicitToolCallId(data);
|
|
1578
1714
|
const agentType = foldedSubAgentName(data);
|
|
1579
|
-
const fold = ensureFoldedSubAgentTools(agentType);
|
|
1580
|
-
const
|
|
1715
|
+
const fold = ensureFoldedSubAgentTools(agentType, foldedSubAgentKey(data), data);
|
|
1716
|
+
const displaySummary = toolDisplaySummary(tool, args, { cwd: safeCwd() });
|
|
1717
|
+
const existing = findFoldedToolEntry(fold, callId, tool, displaySummary);
|
|
1581
1718
|
const entry = existing || {
|
|
1582
1719
|
callId,
|
|
1583
1720
|
tool,
|
|
1584
1721
|
args,
|
|
1585
|
-
summary:
|
|
1722
|
+
summary: displaySummary,
|
|
1586
1723
|
startedAt: Date.now(),
|
|
1587
1724
|
result: null,
|
|
1588
1725
|
durationMs: null,
|
|
@@ -1592,7 +1729,9 @@ function foldSubAgentToolCall(data = {}) {
|
|
|
1592
1729
|
if (!existing) fold.entries.push(entry);
|
|
1593
1730
|
recordCard({ id: callId, tool, args, startedAt: entry.startedAt });
|
|
1594
1731
|
session.toolCounts[tool] = (session.toolCounts[tool] || 0) + 1;
|
|
1595
|
-
|
|
1732
|
+
// Parallel runs own the aggregate spinner text ("2 agents · explore#1 8
|
|
1733
|
+
// · explore#2 10") — don't clobber it with a single run's tool.
|
|
1734
|
+
if (!hasParallelSubAgentRuns()) updateSpinner(`${agentType} → ${tool}`);
|
|
1596
1735
|
// Live sub-agent window: rebuild from the accumulating fold entries so
|
|
1597
1736
|
// the same rich '• tool head — outcome' bullets that appear in the
|
|
1598
1737
|
// final summary render live during the run. Users previously saw only
|
|
@@ -1602,13 +1741,37 @@ function foldSubAgentToolCall(data = {}) {
|
|
|
1602
1741
|
_syncSubAgentWindow(fold);
|
|
1603
1742
|
}
|
|
1604
1743
|
|
|
1744
|
+
function foldSubAgentToolProgress(data = {}) {
|
|
1745
|
+
const tool = data?.tool || 'unknown';
|
|
1746
|
+
const args = data?.args || {};
|
|
1747
|
+
const callId = explicitToolCallId(data);
|
|
1748
|
+
const agentType = foldedSubAgentName(data);
|
|
1749
|
+
const fold = ensureFoldedSubAgentTools(agentType, foldedSubAgentKey(data), data);
|
|
1750
|
+
const displaySummary = toolDisplaySummary(tool, args, { cwd: safeCwd() });
|
|
1751
|
+
const existing = findFoldedToolEntry(fold, callId, tool, displaySummary);
|
|
1752
|
+
if (!existing) {
|
|
1753
|
+
fold.entries.push({
|
|
1754
|
+
callId,
|
|
1755
|
+
tool,
|
|
1756
|
+
args,
|
|
1757
|
+
summary: displaySummary,
|
|
1758
|
+
startedAt: Date.now(),
|
|
1759
|
+
result: null,
|
|
1760
|
+
durationMs: null,
|
|
1761
|
+
outcome: '',
|
|
1762
|
+
tone: 'dim',
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
_syncSubAgentWindow(fold);
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1605
1768
|
function foldSubAgentToolResult(data = {}) {
|
|
1606
1769
|
const tool = data?.tool || data?._tool || 'unknown';
|
|
1607
1770
|
const args = data?.args || {};
|
|
1608
|
-
const callId =
|
|
1771
|
+
const callId = explicitToolCallId(data);
|
|
1609
1772
|
const agentType = foldedSubAgentName(data);
|
|
1610
|
-
const fold = ensureFoldedSubAgentTools(agentType);
|
|
1611
|
-
let entry = findFoldedToolEntry(fold, callId, tool);
|
|
1773
|
+
const fold = ensureFoldedSubAgentTools(agentType, foldedSubAgentKey(data), data);
|
|
1774
|
+
let entry = findFoldedToolEntry(fold, callId, tool, toolDisplaySummary(tool, args, { cwd: safeCwd() }));
|
|
1612
1775
|
if (!entry) {
|
|
1613
1776
|
entry = {
|
|
1614
1777
|
callId,
|
|
@@ -1632,7 +1795,7 @@ function foldSubAgentToolResult(data = {}) {
|
|
|
1632
1795
|
entry.tone = summary.tone || 'dim';
|
|
1633
1796
|
if (data._blocked) session.blockedOps++;
|
|
1634
1797
|
recordCard({ id: callId, tool, args: entry.args, result: data, durationMs, startedAt: entry.startedAt });
|
|
1635
|
-
updateSpinner(`${agentType} → ${tool}`);
|
|
1798
|
+
if (!hasParallelSubAgentRuns()) updateSpinner(`${agentType} → ${tool}`);
|
|
1636
1799
|
// Same live-window sync on result — updates the '• tool' line into
|
|
1637
1800
|
// '• tool — outcome' as each result lands, without waiting for the
|
|
1638
1801
|
// whole sub-agent to complete.
|
|
@@ -1645,15 +1808,63 @@ function foldSubAgentToolResult(data = {}) {
|
|
|
1645
1808
|
* Uses the same foldedToolLine formatter as the completion summary for
|
|
1646
1809
|
* consistency.
|
|
1647
1810
|
*/
|
|
1648
|
-
function _syncSubAgentWindow(
|
|
1649
|
-
if (!fold || !Array.isArray(fold.entries)) return;
|
|
1811
|
+
function _syncSubAgentWindow() {
|
|
1650
1812
|
const cols = process.stderr.columns || 120;
|
|
1651
1813
|
// Reserve column budget for the ' ' indent the window applies in
|
|
1652
1814
|
// presentStatus so we don't wrap.
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1815
|
+
const maxToolRows = 6;
|
|
1816
|
+
const lanes = activeSubAgentLanes();
|
|
1817
|
+
const visibleLanes = lanes.length ? lanes : (
|
|
1818
|
+
runtime.foldedSubAgentToolMap?.size
|
|
1819
|
+
? [...runtime.foldedSubAgentToolMap.values()].map(fold => ({
|
|
1820
|
+
type: fold.agentType,
|
|
1821
|
+
label: fold.label,
|
|
1822
|
+
runId: fold.runId || fold.key,
|
|
1823
|
+
ordinal: 1,
|
|
1824
|
+
}))
|
|
1825
|
+
: []
|
|
1655
1826
|
);
|
|
1656
|
-
|
|
1827
|
+
if (!visibleLanes.length) return;
|
|
1828
|
+
const multi = visibleLanes.length > 1;
|
|
1829
|
+
const groups = [];
|
|
1830
|
+
for (const lane of visibleLanes) {
|
|
1831
|
+
// With multiple lanes, a lane shows ONLY its own run's fold — never
|
|
1832
|
+
// the label/name-keyed or "current" fallbacks, which would leak
|
|
1833
|
+
// unattributed or sibling entries into the wrong agent's group.
|
|
1834
|
+
const fold = runtime.foldedSubAgentToolMap?.get(lane.runId)
|
|
1835
|
+
|| (multi ? null : (runtime.foldedSubAgentToolMap?.get(lane.label) || runtime.foldedSubAgentTools));
|
|
1836
|
+
const entries = Array.isArray(fold?.entries) ? fold.entries : [];
|
|
1837
|
+
const label = subAgentLaneLabel(lane, visibleLanes);
|
|
1838
|
+
if (multi) {
|
|
1839
|
+
// One rotating row per agent under the aggregate spinner: the
|
|
1840
|
+
// latest tool, refreshed as calls arrive. Counts live in the
|
|
1841
|
+
// spinner summary ("2 agents · explore#1 31 · explore#2 14").
|
|
1842
|
+
const last = entries[entries.length - 1];
|
|
1843
|
+
const body = last
|
|
1844
|
+
? foldedToolLine(last, '', Math.max(40, cols - 16)).trimStart()
|
|
1845
|
+
: subAgentStartingLine(lane.type);
|
|
1846
|
+
groups.push({
|
|
1847
|
+
key: lane.runId || label,
|
|
1848
|
+
runId: lane.runId,
|
|
1849
|
+
label,
|
|
1850
|
+
header: '',
|
|
1851
|
+
lines: [fitAnsiLine(`${paint.brand.data(label)} ${body}`, cols)],
|
|
1852
|
+
});
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
const lines = entries.slice(-maxToolRows).map(entry =>
|
|
1856
|
+
foldedToolLine(entry, '', Math.max(40, cols - 4)).trimStart()
|
|
1857
|
+
);
|
|
1858
|
+
if (!lines.length) lines.push(subAgentStartingLine(lane.type));
|
|
1859
|
+
groups.push({
|
|
1860
|
+
key: lane.runId || lane.label || lane.type,
|
|
1861
|
+
runId: lane.runId,
|
|
1862
|
+
label,
|
|
1863
|
+
header: '',
|
|
1864
|
+
lines,
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
rebuildSubAgentWindowGroups(groups);
|
|
1657
1868
|
}
|
|
1658
1869
|
|
|
1659
1870
|
function foldedOutcome(entry) {
|
|
@@ -1675,11 +1886,8 @@ function foldedToolLine(entry, indent, columns) {
|
|
|
1675
1886
|
return fitAnsiLine(line, Math.max(32, columns));
|
|
1676
1887
|
}
|
|
1677
1888
|
|
|
1678
|
-
function
|
|
1679
|
-
const fold = runtime.foldedSubAgentTools;
|
|
1680
|
-
if (!fold) return;
|
|
1889
|
+
function flushOneFoldedSubAgentTools(fold) {
|
|
1681
1890
|
const entries = Array.isArray(fold.entries) ? fold.entries : [];
|
|
1682
|
-
runtime.foldedSubAgentTools = null;
|
|
1683
1891
|
if (!entries.length) return;
|
|
1684
1892
|
|
|
1685
1893
|
renderBlockBoundary('tool', { compactSame: true });
|
|
@@ -1712,6 +1920,16 @@ function flushFoldedSubAgentTools() {
|
|
|
1712
1920
|
runtime.lastRenderedBlock = 'tool';
|
|
1713
1921
|
}
|
|
1714
1922
|
|
|
1923
|
+
function flushFoldedSubAgentTools(key = null) {
|
|
1924
|
+
const fold = removeFoldedSubAgentTools(key);
|
|
1925
|
+
if (!fold) return;
|
|
1926
|
+
if (Array.isArray(fold)) {
|
|
1927
|
+
for (const item of fold) flushOneFoldedSubAgentTools(item);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
flushOneFoldedSubAgentTools(fold);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1715
1933
|
function renderEvent(event) {
|
|
1716
1934
|
const { type, data } = event;
|
|
1717
1935
|
|
|
@@ -1907,10 +2125,11 @@ function renderEvent(event) {
|
|
|
1907
2125
|
|
|
1908
2126
|
case 'tool_call':
|
|
1909
2127
|
case 'tool_request': {
|
|
2128
|
+
const eventData = normalizeSubAgentRunData(data);
|
|
1910
2129
|
if (watchState.active) {
|
|
1911
|
-
watchState.addEntry('tool', { label:
|
|
2130
|
+
watchState.addEntry('tool', { label: eventData?.tool, detail: eventData?.args?.file_path || eventData?.args?.path || eventData?.args?.pattern || eventData?.args?.query || '' });
|
|
1912
2131
|
}
|
|
1913
|
-
const isInternal = Boolean(
|
|
2132
|
+
const isInternal = Boolean(eventData?.internal || eventData?.sub_agent);
|
|
1914
2133
|
if (isInternal) {
|
|
1915
2134
|
session.subAgentToolCalls++;
|
|
1916
2135
|
session.totalSubAgentToolCalls++;
|
|
@@ -1919,13 +2138,16 @@ function renderEvent(event) {
|
|
|
1919
2138
|
session.totalPrimaryToolCalls++;
|
|
1920
2139
|
}
|
|
1921
2140
|
session.totalToolCalls++;
|
|
1922
|
-
if (
|
|
1923
|
-
|
|
2141
|
+
if (!hasReliableSubAgentAttribution(eventData)) {
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
if (shouldFoldSubAgentTool(eventData)) {
|
|
2145
|
+
foldSubAgentToolCall(eventData);
|
|
1924
2146
|
break;
|
|
1925
2147
|
}
|
|
1926
2148
|
stopSpinner();
|
|
1927
2149
|
flushContent();
|
|
1928
|
-
renderToolCall(
|
|
2150
|
+
renderToolCall(eventData);
|
|
1929
2151
|
break;
|
|
1930
2152
|
}
|
|
1931
2153
|
|
|
@@ -1967,16 +2189,20 @@ function renderEvent(event) {
|
|
|
1967
2189
|
|
|
1968
2190
|
case 'tool_result':
|
|
1969
2191
|
case 'tool_done': {
|
|
2192
|
+
const eventData = normalizeSubAgentRunData(data);
|
|
1970
2193
|
if (watchState.active) {
|
|
1971
|
-
const success =
|
|
1972
|
-
watchState.addEntry('done', { label:
|
|
2194
|
+
const success = eventData?.success !== false;
|
|
2195
|
+
watchState.addEntry('done', { label: eventData?.tool, detail: success ? '✓' : '✗' });
|
|
1973
2196
|
}
|
|
1974
|
-
if (
|
|
1975
|
-
|
|
2197
|
+
if (!hasReliableSubAgentAttribution(eventData)) {
|
|
2198
|
+
break;
|
|
2199
|
+
}
|
|
2200
|
+
if (shouldFoldSubAgentTool(eventData)) {
|
|
2201
|
+
foldSubAgentToolResult(eventData);
|
|
1976
2202
|
break;
|
|
1977
2203
|
}
|
|
1978
2204
|
stopSpinner();
|
|
1979
|
-
renderToolResult(
|
|
2205
|
+
renderToolResult(eventData, type);
|
|
1980
2206
|
break;
|
|
1981
2207
|
}
|
|
1982
2208
|
|
|
@@ -2090,32 +2316,69 @@ function renderEvent(event) {
|
|
|
2090
2316
|
if (watchState.active) {
|
|
2091
2317
|
watchState.addEntry('spawn', { type: data?.type, label: (data?.query || '').slice(0, 60) });
|
|
2092
2318
|
}
|
|
2093
|
-
stopSpinner();
|
|
2094
|
-
clearPendingHead();
|
|
2095
|
-
flushFoldedSubAgentTools();
|
|
2096
2319
|
const agentType = data?.type || 'sub-agent';
|
|
2097
2320
|
const query = data?.query || '';
|
|
2321
|
+
// Parallel sub-agents: track each run in its own display lane. Two
|
|
2322
|
+
// concurrent runs of the same type are only distinguishable by the
|
|
2323
|
+
// backend-issued run_id; label lanes explore#1 / explore#2 when
|
|
2324
|
+
// more than one run is active. Solo runs render exactly as before.
|
|
2325
|
+
session.activeSubAgentRuns = session.activeSubAgentRuns || new Map();
|
|
2326
|
+
const hadActiveRuns = session.activeSubAgentRuns.size > 0;
|
|
2327
|
+
if (!hadActiveRuns) {
|
|
2328
|
+
stopSpinner();
|
|
2329
|
+
clearPendingHead();
|
|
2330
|
+
flushFoldedSubAgentTools();
|
|
2331
|
+
}
|
|
2332
|
+
const runId = data?.run_id || `${agentType}:${Date.now().toString(36)}`;
|
|
2333
|
+
const lane = createSubAgentLane(agentType, query, runId, data);
|
|
2334
|
+
const parallel = session.activeSubAgentRuns.size > 1 || lane.forceOrdinal;
|
|
2335
|
+
const label = subAgentLaneLabel(lane);
|
|
2098
2336
|
renderBlockBoundary('subagent');
|
|
2099
|
-
process.stderr.write(renderSubAgentOpen({ type:
|
|
2337
|
+
process.stderr.write(renderSubAgentOpen({ id: runId, type: label, query, parentDepth: parallel ? 0 : undefined }).replace(/^\n/, '') + '\n');
|
|
2100
2338
|
runtime.lastRenderedBlock = 'subagent';
|
|
2101
2339
|
session.inSubAgent = inSubAgentBlock(); // kept for legacy readers
|
|
2102
2340
|
session.subAgentCounts[agentType] = (session.subAgentCounts[agentType] || 0) + 1;
|
|
2103
2341
|
// Fixed-height live tool window under the spinner (queue mode):
|
|
2104
2342
|
// inner tool calls stream here instead of flooding the transcript.
|
|
2105
2343
|
setSubAgentWindowActive(true);
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2344
|
+
if (hadActiveRuns) {
|
|
2345
|
+
updateSpinner(`${session.activeSubAgentRuns.size} agents running`);
|
|
2346
|
+
} else {
|
|
2347
|
+
// Phase per sub-agent run: the status line counts elapsed time and
|
|
2348
|
+
// tool calls live ("plan agent · 4 calls · 32s") for the whole run.
|
|
2349
|
+
// First run of a signaled batch starts the spinner with its
|
|
2350
|
+
// ordinal label so the display matches the open block.
|
|
2351
|
+
startSpinner(`${label} agent`, { phase: `sub:${agentType}:${Date.now()}` });
|
|
2352
|
+
}
|
|
2353
|
+
_syncSubAgentWindow();
|
|
2110
2354
|
break;
|
|
2111
2355
|
}
|
|
2112
2356
|
|
|
2113
2357
|
case 'sub_agent_tool': {
|
|
2114
2358
|
// The regular tool_call event renders the card, indented by the
|
|
2115
2359
|
// sub-agent stack depth. Just update the spinner text here.
|
|
2360
|
+
const eventData = normalizeSubAgentRunData(data);
|
|
2116
2361
|
const agentType = data?.type || 'sub-agent';
|
|
2117
|
-
const tool =
|
|
2362
|
+
const tool = eventData?.tool || '';
|
|
2118
2363
|
if (!tool) break;
|
|
2364
|
+
// Lane attribution for parallel runs: prefix window/spinner lines
|
|
2365
|
+
// with the run's label so interleaved streams stay readable.
|
|
2366
|
+
const eventRunId = subAgentRunId(eventData);
|
|
2367
|
+
if (!eventRunId && hasParallelSubAgentRuns()) {
|
|
2368
|
+
break;
|
|
2369
|
+
}
|
|
2370
|
+
const laneRun = session.activeSubAgentRuns?.get(eventRunId);
|
|
2371
|
+
if (laneRun) laneRun.tools++;
|
|
2372
|
+
foldSubAgentToolProgress(eventData);
|
|
2373
|
+
const laneParallel = (session.activeSubAgentRuns?.size || 0) > 1;
|
|
2374
|
+
if (laneParallel) {
|
|
2375
|
+
bumpSpinnerProgress();
|
|
2376
|
+
const activeLanes = activeSubAgentLanes();
|
|
2377
|
+
const lanes = activeLanes
|
|
2378
|
+
.map(r => `${subAgentLaneLabel(r, activeLanes)} ${r.tools}`).join(' · ');
|
|
2379
|
+
updateSpinner(`${session.activeSubAgentRuns.size} agents · ${lanes}`);
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2119
2382
|
// Feed the live window from THIS event — it always fires (55/55 in
|
|
2120
2383
|
// observed runs), unlike the inner tool_call render path which
|
|
2121
2384
|
// diverts for explore-category tools and folded verbosity modes.
|
|
@@ -2130,7 +2393,7 @@ function renderEvent(event) {
|
|
|
2130
2393
|
// + window; the fallback line is only for the "otherwise blind"
|
|
2131
2394
|
// case that surfaced in earlier local terminal reports.
|
|
2132
2395
|
if (!rqueue.isActive()) {
|
|
2133
|
-
const label =
|
|
2396
|
+
const label = eventData?.label || '';
|
|
2134
2397
|
const hint = label ? ` · ${label}` : '';
|
|
2135
2398
|
const key = `${agentType}:${tool}:${label}`;
|
|
2136
2399
|
if (session._lastSubAgentInlineKey !== key) {
|
|
@@ -2149,6 +2412,15 @@ function renderEvent(event) {
|
|
|
2149
2412
|
break;
|
|
2150
2413
|
}
|
|
2151
2414
|
|
|
2415
|
+
case 'sub_agent_tool_result': {
|
|
2416
|
+
const eventData = normalizeSubAgentRunData(data);
|
|
2417
|
+
if (!hasReliableSubAgentAttribution(eventData)) {
|
|
2418
|
+
break;
|
|
2419
|
+
}
|
|
2420
|
+
foldSubAgentToolResult(eventData);
|
|
2421
|
+
break;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2152
2424
|
case 'sub_agent_complete': {
|
|
2153
2425
|
if (watchState.active) {
|
|
2154
2426
|
const agentType = data?.type || 'sub-agent';
|
|
@@ -2156,11 +2428,42 @@ function renderEvent(event) {
|
|
|
2156
2428
|
const durationS = data?.duration_s || 0;
|
|
2157
2429
|
watchState.addEntry('done', { type: agentType, detail: `${toolCalls} tools · ${durationS.toFixed(1)}s`, status: 'done' });
|
|
2158
2430
|
}
|
|
2159
|
-
|
|
2160
|
-
stopSpinner();
|
|
2161
|
-
clearPendingHead();
|
|
2162
|
-
flushFoldedSubAgentTools();
|
|
2431
|
+
const eventData = normalizeSubAgentRunData(data);
|
|
2163
2432
|
const agentType = data?.type || 'sub-agent';
|
|
2433
|
+
// Retire this run's display lane; while sibling runs are still
|
|
2434
|
+
// active keep the shared window/spinner alive for them.
|
|
2435
|
+
const eventRunId = subAgentRunId(eventData);
|
|
2436
|
+
const doneRun = session.activeSubAgentRuns?.get(eventRunId);
|
|
2437
|
+
const doneLabel = doneRun ? subAgentLaneLabel(doneRun) : agentType;
|
|
2438
|
+
const doneFold = eventRunId ? removeFoldedSubAgentTools(eventRunId) : null;
|
|
2439
|
+
if (doneFold) {
|
|
2440
|
+
doneFold.agentType = doneLabel;
|
|
2441
|
+
doneFold.label = doneLabel;
|
|
2442
|
+
}
|
|
2443
|
+
if (eventRunId) session.activeSubAgentRuns?.delete(eventRunId);
|
|
2444
|
+
else session.activeSubAgentRuns?.clear();
|
|
2445
|
+
const siblingsActive = (session.activeSubAgentRuns?.size || 0) > 0;
|
|
2446
|
+
// In verbose mode each sub-agent tool already rendered a full
|
|
2447
|
+
// transcript card live — flushing the fold would list every tool a
|
|
2448
|
+
// second time. The fold batch is the durable record ONLY when tools
|
|
2449
|
+
// were folded (default verbosity).
|
|
2450
|
+
const toolsRenderedLive = showSubAgentTools(getVerbosity());
|
|
2451
|
+
if (siblingsActive) {
|
|
2452
|
+
if (doneFold && !toolsRenderedLive) flushOneFoldedSubAgentTools(doneFold);
|
|
2453
|
+
_syncSubAgentWindow();
|
|
2454
|
+
} else {
|
|
2455
|
+
setSubAgentWindowActive(false);
|
|
2456
|
+
stopSpinner();
|
|
2457
|
+
clearPendingHead();
|
|
2458
|
+
session._lanesPreservedAtTurnEnd = false;
|
|
2459
|
+
if (toolsRenderedLive) {
|
|
2460
|
+
if (!doneFold) removeFoldedSubAgentTools(null);
|
|
2461
|
+
} else if (doneFold) {
|
|
2462
|
+
flushOneFoldedSubAgentTools(doneFold);
|
|
2463
|
+
} else {
|
|
2464
|
+
flushFoldedSubAgentTools();
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2164
2467
|
const usage = data?.usage || {};
|
|
2165
2468
|
// Output tokens = generation size. Summing input+output across a
|
|
2166
2469
|
// multi-iteration sub-agent double-counts the context re-shipped each
|
|
@@ -2173,7 +2476,8 @@ function renderEvent(event) {
|
|
|
2173
2476
|
const summary = data?.result_summary
|
|
2174
2477
|
|| (data?.result_length > 0 ? `${agentType} returned ${data.result_length} chars` : '');
|
|
2175
2478
|
process.stderr.write(renderSubAgentClose({
|
|
2176
|
-
|
|
2479
|
+
id: eventRunId,
|
|
2480
|
+
type: doneLabel,
|
|
2177
2481
|
success: data?.success !== false,
|
|
2178
2482
|
summary,
|
|
2179
2483
|
costUsd,
|
|
@@ -2424,8 +2728,24 @@ function renderEvent(event) {
|
|
|
2424
2728
|
if (session.turns === 1 && session.user) telemetry.track('first_answer', {});
|
|
2425
2729
|
stopSpinner();
|
|
2426
2730
|
flushContent();
|
|
2427
|
-
|
|
2428
|
-
|
|
2731
|
+
// Background/parallel runs can outlive the turn. When lanes are
|
|
2732
|
+
// still active at turn end, preserve them (folds, stack, window) so
|
|
2733
|
+
// late events land correctly instead of corrupting fresh state —
|
|
2734
|
+
// but only for ONE turn boundary: if they're still around at the
|
|
2735
|
+
// next complete with no closure, force-clean to avoid stale lanes.
|
|
2736
|
+
const lanesLive = (session.activeSubAgentRuns?.size || 0) > 0;
|
|
2737
|
+
if (lanesLive && !session._lanesPreservedAtTurnEnd) {
|
|
2738
|
+
session._lanesPreservedAtTurnEnd = true;
|
|
2739
|
+
const n = session.activeSubAgentRuns.size;
|
|
2740
|
+
process.stderr.write(` ${c.dim(`${n} agent run${n === 1 ? '' : 's'} still active in background — progress continues below`)}\n`);
|
|
2741
|
+
} else {
|
|
2742
|
+
session._lanesPreservedAtTurnEnd = false;
|
|
2743
|
+
if (showSubAgentTools(getVerbosity())) resetFoldedSubAgentTools();
|
|
2744
|
+
else flushFoldedSubAgentTools();
|
|
2745
|
+
resetSubAgents();
|
|
2746
|
+
session.activeSubAgentRuns?.clear();
|
|
2747
|
+
setSubAgentWindowActive(false);
|
|
2748
|
+
}
|
|
2429
2749
|
session.inSubAgent = false;
|
|
2430
2750
|
|
|
2431
2751
|
const summary = data?.summary || '';
|
|
@@ -2839,57 +3159,160 @@ async function prepareDirectAgentRunContext(ctx, instruction = '') {
|
|
|
2839
3159
|
return execContext;
|
|
2840
3160
|
}
|
|
2841
3161
|
|
|
3162
|
+
function stripWrappingQuotes(value = '') {
|
|
3163
|
+
const text = String(value || '').trim();
|
|
3164
|
+
if (text.length >= 2) {
|
|
3165
|
+
const first = text[0];
|
|
3166
|
+
const last = text[text.length - 1];
|
|
3167
|
+
if ((first === '"' || first === "'") && first === last) {
|
|
3168
|
+
return text.slice(1, -1).trim();
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
return text;
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
function addRunTarget(map, agent, kind = 'agent') {
|
|
3175
|
+
const slug = String(agent?.slug || agent?.command || agent?.name || '').trim();
|
|
3176
|
+
if (!slug || map.has(`${kind}:${slug}`)) return;
|
|
3177
|
+
map.set(`${kind}:${slug}`, {
|
|
3178
|
+
slug,
|
|
3179
|
+
name: agent?.name || slug,
|
|
3180
|
+
description: agent?.description || '',
|
|
3181
|
+
scope: agent?.source_scope || agent?.source || kind,
|
|
3182
|
+
kind,
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
function printRunUsage(ctx) {
|
|
3187
|
+
process.stderr.write(` ${c.gray('Usage: /run <agent-or-workflow> [instruction]')}\n`);
|
|
3188
|
+
process.stderr.write(` ${c.gray('Example: /run docker-analyzer Analyze all running Docker containers')}\n`);
|
|
3189
|
+
|
|
3190
|
+
const targets = new Map();
|
|
3191
|
+
for (const agent of listLocalAgents(safeCwd())) addRunTarget(targets, agent);
|
|
3192
|
+
for (const agent of BUILTIN_AGENTS) addRunTarget(targets, agent);
|
|
3193
|
+
for (const agent of ctx.toolExecutor?.listRunnables?.() || []) addRunTarget(targets, agent);
|
|
3194
|
+
for (const agent of pluginRegistry?.listAgents?.() || []) addRunTarget(targets, agent);
|
|
3195
|
+
|
|
3196
|
+
const agents = [...targets.values()].filter(item => item.kind === 'agent').slice(0, 12);
|
|
3197
|
+
if (agents.length) {
|
|
3198
|
+
process.stderr.write(`\n ${c.dim('Agents')}\n`);
|
|
3199
|
+
for (const agent of agents) {
|
|
3200
|
+
const desc = agent.description ? ` ${c.dim('- ' + agent.description)}` : '';
|
|
3201
|
+
const scope = agent.scope ? ` ${c.dim('[' + agent.scope + ']')}` : '';
|
|
3202
|
+
process.stderr.write(` ${c.brand(agent.slug)}${scope}${desc}\n`);
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
const workflows = listLocalWorkflows(safeCwd()).slice(0, 8);
|
|
3207
|
+
if (workflows.length) {
|
|
3208
|
+
process.stderr.write(`\n ${c.dim('Workflows')}\n`);
|
|
3209
|
+
for (const workflow of workflows) {
|
|
3210
|
+
const slug = workflow.slug || workflow.name;
|
|
3211
|
+
const desc = workflow.description ? ` ${c.dim('- ' + workflow.description)}` : '';
|
|
3212
|
+
process.stderr.write(` ${c.brand(slug)}${desc}\n`);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
|
|
2842
3217
|
async function handleRunCommand(rest = '', ctx) {
|
|
2843
3218
|
const parts = String(rest || '').trim().split(/\s+/).filter(Boolean);
|
|
2844
3219
|
const target = parts.shift();
|
|
2845
|
-
const instruction = parts.join(' ');
|
|
3220
|
+
const instruction = stripWrappingQuotes(parts.join(' '));
|
|
2846
3221
|
|
|
2847
3222
|
if (!target) {
|
|
2848
|
-
|
|
3223
|
+
printRunUsage(ctx);
|
|
2849
3224
|
return;
|
|
2850
3225
|
}
|
|
2851
3226
|
|
|
2852
3227
|
const localAgent = listLocalAgents(safeCwd()).find(agent => localAgentMatches(agent, target));
|
|
2853
3228
|
const builtinAgent = findBuiltinAgent(target);
|
|
2854
|
-
const
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
3229
|
+
const registeredAgent = ctx.toolExecutor?.listRunnables?.()
|
|
3230
|
+
?.find(agent => localAgentMatches(agent, target));
|
|
3231
|
+
const pluginAgent = pluginRegistry?.listAgents?.()
|
|
3232
|
+
?.find(agent => localAgentMatches(agent, target));
|
|
3233
|
+
const runnableAgent = localAgent || builtinAgent || registeredAgent || pluginAgent;
|
|
3234
|
+
|
|
3235
|
+
const creds = ctx.auth?.loadCredentials?.() || {};
|
|
3236
|
+
const dispatchCtx = {
|
|
3237
|
+
toolExecutor: ctx.toolExecutor,
|
|
3238
|
+
listRunnables: () => ctx.toolExecutor?.listRunnables?.() || [],
|
|
3239
|
+
listLocalWorkflows: () => listLocalWorkflows(safeCwd()),
|
|
3240
|
+
renderEvent,
|
|
3241
|
+
sessionSubstrate: makeSessionSubstrate(ctx),
|
|
3242
|
+
auth: { token: creds.token || null },
|
|
3243
|
+
credentials: {
|
|
3244
|
+
apiKey: process.env.ANTHROPIC_API_KEY || creds.anthropicKey || null,
|
|
3245
|
+
openRouterKey: process.env.OPENROUTER_API_KEY || creds.openRouterKey || null,
|
|
3246
|
+
},
|
|
3247
|
+
cwd: safeCwd(),
|
|
3248
|
+
};
|
|
3249
|
+
|
|
3250
|
+
try {
|
|
3251
|
+
if (!runnableAgent) process.stderr.write(` ${c.dim(`Running workflow '${target}'...`)}\n`);
|
|
3252
|
+
const outcome = await dispatch({
|
|
3253
|
+
type: 'manual',
|
|
3254
|
+
source: 'repl:/run',
|
|
3255
|
+
target: runnableAgent ? { kind: 'agent', slug: target, agent: runnableAgent } : target,
|
|
3256
|
+
params: { instruction },
|
|
3257
|
+
channel: null,
|
|
3258
|
+
}, dispatchCtx);
|
|
3259
|
+
|
|
3260
|
+
if (!outcome.dispatched) {
|
|
3261
|
+
process.stderr.write(` ${c.red('✗')} ${outcome.reason}\n`);
|
|
3262
|
+
process.stderr.write(` ${c.gray('Usage: /run <agent-or-workflow> [instruction]')}\n`);
|
|
2864
3263
|
return;
|
|
2865
3264
|
}
|
|
2866
|
-
}
|
|
2867
3265
|
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
3266
|
+
const result = outcome.result || {};
|
|
3267
|
+
if (outcome.channel === 'server') {
|
|
3268
|
+
if (result?.success === false) {
|
|
3269
|
+
process.stderr.write(` ${c.red('✗')} ${result.output || `Workflow '${target}' failed.`}\n`);
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
process.stderr.write(` ${c.green('✓')} ${c.dim(`Workflow '${target}' complete`)}\n`);
|
|
3273
|
+
const details = [];
|
|
3274
|
+
if (result?.run_id) details.push(`run ${result.run_id}`);
|
|
3275
|
+
if (result?.duration_s) details.push(`${result.duration_s}s`);
|
|
3276
|
+
if (result?.total_tokens) details.push(`${formatTokens(result.total_tokens)} tok`);
|
|
3277
|
+
if (result?.total_cost) details.push(formatCostValue(result.total_cost));
|
|
3278
|
+
if (details.length) process.stderr.write(` ${c.dim(details.join(' · '))}\n`);
|
|
3279
|
+
|
|
3280
|
+
// Push server workflow output to conversation history so the main
|
|
3281
|
+
// agent sees the outcome next turn (mirrors local graph run below).
|
|
3282
|
+
const serverOutput = result?.result || result?.output || '';
|
|
3283
|
+
if (serverOutput && result?.success !== false) {
|
|
3284
|
+
session.history.push(
|
|
3285
|
+
{ role: 'user', content: `[${target}] ${instruction || 'run'}` },
|
|
3286
|
+
{ role: 'assistant', content: String(serverOutput) },
|
|
3287
|
+
);
|
|
3288
|
+
} else if (result?.success === false && result?.output) {
|
|
3289
|
+
session.history.push(
|
|
3290
|
+
{ role: 'user', content: `[${target}] ${instruction || 'run'}` },
|
|
3291
|
+
{ role: 'assistant', content: `Workflow failed: ${String(result.output)}` },
|
|
3292
|
+
);
|
|
3293
|
+
}
|
|
2874
3294
|
|
|
2875
|
-
|
|
2876
|
-
|
|
3295
|
+
const output = serverOutput;
|
|
3296
|
+
if (output) {
|
|
3297
|
+
process.stderr.write('\n');
|
|
3298
|
+
process.stderr.write(renderMarkdown(String(output), { width: process.stderr.columns || 96 }));
|
|
3299
|
+
process.stderr.write('\n');
|
|
3300
|
+
}
|
|
2877
3301
|
return;
|
|
2878
3302
|
}
|
|
2879
3303
|
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
if (result
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
process.stderr.write('\n');
|
|
3304
|
+
// Local graph run. Session-substrate agent runs already push their own
|
|
3305
|
+
// conversation history; feed workflow results (and logged-out direct
|
|
3306
|
+
// runs) back so the main agent sees the outcome next turn.
|
|
3307
|
+
if (result.status === 'failed') {
|
|
3308
|
+
process.stderr.write(` ${c.red('✗')} ${result.output || `'${target}' failed.`}\n`);
|
|
3309
|
+
}
|
|
3310
|
+
const historyCovered = Boolean(runnableAgent) && Boolean(creds.token);
|
|
3311
|
+
if (result.output && result.status !== 'failed' && !historyCovered) {
|
|
3312
|
+
session.history.push(
|
|
3313
|
+
{ role: 'user', content: `[${target}] ${instruction || 'run'}` },
|
|
3314
|
+
{ role: 'assistant', content: String(result.output) },
|
|
3315
|
+
);
|
|
2893
3316
|
}
|
|
2894
3317
|
} catch (err) {
|
|
2895
3318
|
process.stderr.write(` ${c.red('✗')} ${err.message || String(err)}\n`);
|
|
@@ -2897,6 +3320,38 @@ async function handleRunCommand(rest = '', ctx) {
|
|
|
2897
3320
|
}
|
|
2898
3321
|
}
|
|
2899
3322
|
|
|
3323
|
+
// Bridges the graph engine's session substrate onto runAgentDefinition:
|
|
3324
|
+
// the node's events stream through the same renderer, and the run keeps
|
|
3325
|
+
// its existing conversation-history feedback.
|
|
3326
|
+
function makeSessionSubstrate(ctx) {
|
|
3327
|
+
return (agent, node, instruction) => (async function* () {
|
|
3328
|
+
const queue = [];
|
|
3329
|
+
let notify = null;
|
|
3330
|
+
let finished = false;
|
|
3331
|
+
const push = (event) => {
|
|
3332
|
+
queue.push(event);
|
|
3333
|
+
const wake = notify; notify = null;
|
|
3334
|
+
wake?.();
|
|
3335
|
+
};
|
|
3336
|
+
const execContext = await prepareDirectAgentRunContext(ctx, instruction);
|
|
3337
|
+
const done = runAgentDefinition(agent, instruction, ctx, session, push, {
|
|
3338
|
+
cwd: execContext.cwd,
|
|
3339
|
+
execContext,
|
|
3340
|
+
pluginRegistry,
|
|
3341
|
+
}).catch(err => push({ type: 'error', data: { message: err?.message || String(err) } }))
|
|
3342
|
+
.finally(() => {
|
|
3343
|
+
finished = true;
|
|
3344
|
+
const wake = notify; notify = null;
|
|
3345
|
+
wake?.();
|
|
3346
|
+
});
|
|
3347
|
+
while (!finished || queue.length) {
|
|
3348
|
+
if (!queue.length) await new Promise(resolve => { notify = resolve; });
|
|
3349
|
+
while (queue.length) yield queue.shift();
|
|
3350
|
+
}
|
|
3351
|
+
await done;
|
|
3352
|
+
})();
|
|
3353
|
+
}
|
|
3354
|
+
|
|
2900
3355
|
async function handleCommand(input, ctx) {
|
|
2901
3356
|
const { cmd, rest, aliasTarget } = normalizeCommandInput(input);
|
|
2902
3357
|
if (aliasTarget) {
|
|
@@ -3449,7 +3904,8 @@ async function handleCommand(input, ctx) {
|
|
|
3449
3904
|
session.agentHistory.length = 0;
|
|
3450
3905
|
session.toolCalls = 0;
|
|
3451
3906
|
session.subAgentToolCalls = 0;
|
|
3452
|
-
|
|
3907
|
+
session.activeSubAgentRuns = new Map();
|
|
3908
|
+
resetFoldedSubAgentTools();
|
|
3453
3909
|
clearCards();
|
|
3454
3910
|
process.stderr.write(` ${c.gray('Conversation cleared.')}\n`);
|
|
3455
3911
|
return;
|
|
@@ -3889,6 +4345,26 @@ export async function startTerminalRepl() {
|
|
|
3889
4345
|
|
|
3890
4346
|
const ctx = { auth, toolExecutor: null, approval, jsonlWriter, sessionMgr, checkpoints, effectivePolicy, latestProjectContext, latestEnvelope, pendingVisionPaths: [] };
|
|
3891
4347
|
|
|
4348
|
+
// Wake-on-finish: background jobs with on_complete dispatch their target
|
|
4349
|
+
// agent through the trigger funnel when they exit. The ctx builder runs
|
|
4350
|
+
// lazily at fire time so it sees the live tool executor.
|
|
4351
|
+
registerJobCompletionDispatch(() => {
|
|
4352
|
+
const creds = ctx.auth?.loadCredentials?.() || {};
|
|
4353
|
+
return {
|
|
4354
|
+
toolExecutor: ctx.toolExecutor,
|
|
4355
|
+
listRunnables: () => ctx.toolExecutor?.listRunnables?.() || [],
|
|
4356
|
+
listLocalWorkflows: () => listLocalWorkflows(safeCwd()),
|
|
4357
|
+
renderEvent,
|
|
4358
|
+
sessionSubstrate: makeSessionSubstrate(ctx),
|
|
4359
|
+
auth: { token: creds.token || null },
|
|
4360
|
+
credentials: {
|
|
4361
|
+
apiKey: process.env.ANTHROPIC_API_KEY || creds.anthropicKey || null,
|
|
4362
|
+
openRouterKey: process.env.OPENROUTER_API_KEY || creds.openRouterKey || null,
|
|
4363
|
+
},
|
|
4364
|
+
cwd: safeCwd(),
|
|
4365
|
+
};
|
|
4366
|
+
});
|
|
4367
|
+
|
|
3892
4368
|
let startupOutputRow = 1;
|
|
3893
4369
|
let startupOutputCol = 1;
|
|
3894
4370
|
|
|
@@ -3942,7 +4418,7 @@ export async function startTerminalRepl() {
|
|
|
3942
4418
|
flushContent();
|
|
3943
4419
|
flushPendingHead();
|
|
3944
4420
|
flushExploreRun();
|
|
3945
|
-
|
|
4421
|
+
resetFoldedSubAgentTools();
|
|
3946
4422
|
clearCards();
|
|
3947
4423
|
|
|
3948
4424
|
const preserved = {
|
|
@@ -4009,6 +4485,7 @@ export async function startTerminalRepl() {
|
|
|
4009
4485
|
lastTurnDuration: 0,
|
|
4010
4486
|
toolCounts: {},
|
|
4011
4487
|
subAgentCounts: {},
|
|
4488
|
+
activeSubAgentRuns: new Map(),
|
|
4012
4489
|
savedUsd: 0,
|
|
4013
4490
|
lastTask: '',
|
|
4014
4491
|
lastReasoning: '',
|
|
@@ -4381,6 +4858,9 @@ export async function startTerminalRepl() {
|
|
|
4381
4858
|
let _bracketedPasteStartLine = '';
|
|
4382
4859
|
let _bracketedPasteStartCursor = 0;
|
|
4383
4860
|
let _promptHasInsertedPaste = false;
|
|
4861
|
+
let _suppressRawPasteLines = false;
|
|
4862
|
+
let _pastedInputValue = '';
|
|
4863
|
+
let _pastedInputLabel = '';
|
|
4384
4864
|
const _pasteEndListeners = new Set();
|
|
4385
4865
|
function onBracketedPasteEnd(cb) { _pasteEndListeners.add(cb); return () => _pasteEndListeners.delete(cb); }
|
|
4386
4866
|
function isInBracketedPaste() { return _inBracketedPaste; }
|
|
@@ -4400,7 +4880,25 @@ export async function startTerminalRepl() {
|
|
|
4400
4880
|
while (i < s.length) {
|
|
4401
4881
|
if (!_inBracketedPaste) {
|
|
4402
4882
|
const start = s.indexOf(PASTE_BEGIN, i);
|
|
4403
|
-
if (start === -1)
|
|
4883
|
+
if (start === -1) {
|
|
4884
|
+
if (isRawMultilinePasteChunk(s)) {
|
|
4885
|
+
_suppressRawPasteLines = true;
|
|
4886
|
+
const baseLine = String(rl?.line || '');
|
|
4887
|
+
const baseCursor = typeof rl?.cursor === 'number' ? rl.cursor : baseLine.length;
|
|
4888
|
+
setImmediate(() => {
|
|
4889
|
+
try {
|
|
4890
|
+
insertPromptText(normalizePastedText(s), {
|
|
4891
|
+
baseLine,
|
|
4892
|
+
baseCursor,
|
|
4893
|
+
fromPaste: true,
|
|
4894
|
+
});
|
|
4895
|
+
} finally {
|
|
4896
|
+
_suppressRawPasteLines = false;
|
|
4897
|
+
}
|
|
4898
|
+
});
|
|
4899
|
+
}
|
|
4900
|
+
return;
|
|
4901
|
+
}
|
|
4404
4902
|
_inBracketedPaste = true;
|
|
4405
4903
|
_bracketedPasteBuffer = '';
|
|
4406
4904
|
_suppressBracketedPasteLines = true;
|
|
@@ -4662,7 +5160,11 @@ export async function startTerminalRepl() {
|
|
|
4662
5160
|
const line = String(baseLine || '');
|
|
4663
5161
|
const cursor = typeof baseCursor === 'number' ? Math.max(0, Math.min(line.length, baseCursor)) : line.length;
|
|
4664
5162
|
const next = `${line.slice(0, cursor)}${payload}${line.slice(cursor)}`;
|
|
4665
|
-
if (fromPaste)
|
|
5163
|
+
if (fromPaste) {
|
|
5164
|
+
_promptHasInsertedPaste = true;
|
|
5165
|
+
_pastedInputValue = next;
|
|
5166
|
+
_pastedInputLabel = pastedTextLabel(payload);
|
|
5167
|
+
}
|
|
4666
5168
|
replaceReadlineLine(next, cursor + payload.length);
|
|
4667
5169
|
renderIdleDockInput();
|
|
4668
5170
|
}
|
|
@@ -4702,15 +5204,28 @@ export async function startTerminalRepl() {
|
|
|
4702
5204
|
|
|
4703
5205
|
function renderIdleDockInput() {
|
|
4704
5206
|
if (!isInputDockMounted()) return false;
|
|
5207
|
+
const line = rl.line || '';
|
|
5208
|
+
let displayLine = line;
|
|
5209
|
+
let displayCursor = typeof rl.cursor === 'number' ? rl.cursor : null;
|
|
5210
|
+
let fixedRows = null;
|
|
5211
|
+
if (_pastedInputValue && line === _pastedInputValue) {
|
|
5212
|
+
displayLine = _pastedInputLabel;
|
|
5213
|
+
displayCursor = _pastedInputLabel.length;
|
|
5214
|
+
fixedRows = 1;
|
|
5215
|
+
} else if (_pastedInputValue) {
|
|
5216
|
+
_pastedInputValue = '';
|
|
5217
|
+
_pastedInputLabel = '';
|
|
5218
|
+
}
|
|
4705
5219
|
// rl.cursor is readline's byte offset within rl.line. Threading it
|
|
4706
5220
|
// through to focusDockInput makes arrow-key navigation visually move
|
|
4707
5221
|
// the terminal cursor within the buffer instead of always landing at
|
|
4708
5222
|
// the end of the string.
|
|
4709
|
-
return renderDockInput(userPrompt(),
|
|
5223
|
+
return renderDockInput(userPrompt(), displayLine, {
|
|
4710
5224
|
context: buildContextStrip(),
|
|
4711
5225
|
meta: buildDockMeta(),
|
|
4712
5226
|
tips: idleInputTips(),
|
|
4713
|
-
cursor:
|
|
5227
|
+
cursor: displayCursor,
|
|
5228
|
+
fixedRows,
|
|
4714
5229
|
});
|
|
4715
5230
|
}
|
|
4716
5231
|
|
|
@@ -4756,7 +5271,7 @@ export async function startTerminalRepl() {
|
|
|
4756
5271
|
readline.emitKeypressEvents(process.stdin, rl);
|
|
4757
5272
|
process.stdin.on('keypress', (_str, key = {}) => {
|
|
4758
5273
|
if (!inputActive) return;
|
|
4759
|
-
if (_inBracketedPaste || _suppressBracketedPasteLines) return;
|
|
5274
|
+
if (_inBracketedPaste || _suppressBracketedPasteLines || _suppressRawPasteLines) return;
|
|
4760
5275
|
if (key.name === 'return' || key.name === 'enter') return;
|
|
4761
5276
|
if (key.name === 'f2') {
|
|
4762
5277
|
clearSlashHint();
|
|
@@ -4767,7 +5282,7 @@ export async function startTerminalRepl() {
|
|
|
4767
5282
|
}
|
|
4768
5283
|
setImmediate(() => {
|
|
4769
5284
|
if (!inputActive) return;
|
|
4770
|
-
if (_inBracketedPaste || _suppressBracketedPasteLines) return;
|
|
5285
|
+
if (_inBracketedPaste || _suppressBracketedPasteLines || _suppressRawPasteLines) return;
|
|
4771
5286
|
if (slashHintVisible && key.name === 'tab' && acceptSlashHint()) return;
|
|
4772
5287
|
if (slashHintVisible && key.name === 'down' && moveSlashHintSelection(1)) return;
|
|
4773
5288
|
if (slashHintVisible && key.name === 'up' && moveSlashHintSelection(-1)) return;
|
|
@@ -4829,12 +5344,16 @@ export async function startTerminalRepl() {
|
|
|
4829
5344
|
if (pastedLines.length > 1 || trailing) {
|
|
4830
5345
|
const text = [...pastedLines, trailing].join('\n');
|
|
4831
5346
|
_promptHasInsertedPaste = true;
|
|
5347
|
+
_pastedInputValue = text;
|
|
5348
|
+
_pastedInputLabel = pastedTextLabel(text);
|
|
4832
5349
|
replaceReadlineLine(text);
|
|
4833
5350
|
renderIdleDockInput();
|
|
4834
5351
|
return;
|
|
4835
5352
|
}
|
|
4836
5353
|
const line = pastedLines.join('\n');
|
|
4837
5354
|
_promptHasInsertedPaste = false;
|
|
5355
|
+
_pastedInputValue = '';
|
|
5356
|
+
_pastedInputLabel = '';
|
|
4838
5357
|
queueOrRunLine(line);
|
|
4839
5358
|
}
|
|
4840
5359
|
|
|
@@ -4844,7 +5363,7 @@ export async function startTerminalRepl() {
|
|
|
4844
5363
|
// or the user pressed Enter normally), the debounce falls back to old
|
|
4845
5364
|
// behavior — a single Enter flushes almost instantly.
|
|
4846
5365
|
rl.on('line', async (line) => {
|
|
4847
|
-
if (_suppressBracketedPasteLines) {
|
|
5366
|
+
if (_suppressBracketedPasteLines || _suppressRawPasteLines) {
|
|
4848
5367
|
_pasteLines = [];
|
|
4849
5368
|
if (_pasteFlushTimer) {
|
|
4850
5369
|
clearTimeout(_pasteFlushTimer);
|
|
@@ -4893,6 +5412,8 @@ export async function startTerminalRepl() {
|
|
|
4893
5412
|
let input = line.trim();
|
|
4894
5413
|
const selectedSlashCommand = selectedSlashCommandFor(input);
|
|
4895
5414
|
inputActive = false;
|
|
5415
|
+
_pastedInputValue = '';
|
|
5416
|
+
_pastedInputLabel = '';
|
|
4896
5417
|
clearSlashHint();
|
|
4897
5418
|
if (selectedSlashCommand) input = selectedSlashCommand;
|
|
4898
5419
|
if (!input) {
|