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