@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.
@@ -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
- rebuildSubAgentWindow,
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('Sync explicitly with:')} /agents sync ${created.slug}\n`);
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('Sync after editing:')} /agents sync ${agent.slug}\n`);
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 Supabase.`)}\n`);
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 ensureFoldedSubAgentTools(agentType) {
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.agentType === agentType) return current;
1552
- if (current?.entries?.length) flushFoldedSubAgentTools();
1553
- runtime.foldedSubAgentTools = {
1554
- agentType,
1555
- entries: [],
1556
- startedAt: Date.now(),
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
- return runtime.foldedSubAgentTools;
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 findFoldedToolEntry(fold, callId, tool) {
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 = toolCallId(data, tool);
1712
+ const callId = explicitToolCallId(data);
1578
1713
  const agentType = foldedSubAgentName(data);
1579
- const fold = ensureFoldedSubAgentTools(agentType);
1580
- const existing = findFoldedToolEntry(fold, callId, tool);
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: toolDisplaySummary(tool, args, { cwd: safeCwd() }),
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
- updateSpinner(`${agentType} ${tool}`);
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 = toolCallId(data, tool);
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(fold) {
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 lines = fold.entries.slice(-7).map(entry =>
1654
- foldedToolLine(entry, '', Math.max(40, cols - 4)).trimStart()
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
- rebuildSubAgentWindow(lines);
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 flushFoldedSubAgentTools() {
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: data?.tool, detail: data?.args?.file_path || data?.args?.path || data?.args?.pattern || data?.args?.query || '' });
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(data?.internal || data?.sub_agent);
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 (shouldFoldSubAgentTool(data)) {
1923
- foldSubAgentToolCall(data);
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(data);
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 = data?.success !== false;
1972
- watchState.addEntry('done', { label: data?.tool, detail: success ? '✓' : '✗' });
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(data)) {
1975
- foldSubAgentToolResult(data);
2199
+ if (shouldFoldSubAgentTool(eventData)) {
2200
+ foldSubAgentToolResult(eventData);
1976
2201
  break;
1977
2202
  }
1978
2203
  stopSpinner();
1979
- renderToolResult(data, type);
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: agentType, query }).replace(/^\n/, '') + '\n');
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
- // Phase per sub-agent run: the status line counts elapsed time and
2107
- // tool calls live ("plan agent · 4 calls · 32s") for the whole run.
2108
- startSpinner(`${agentType} agent`, { phase: `sub:${agentType}:${Date.now()}` });
2109
- pushSubAgentWindowLine(subAgentStartingLine(agentType));
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 = data?.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 = data?.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
- setSubAgentWindowActive(false);
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
- type: agentType,
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
- flushFoldedSubAgentTools();
2428
- resetSubAgents();
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
- process.stderr.write(` ${c.gray('Usage: /run <agent-or-workflow> [instruction]')}\n`);
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 runnableAgent = localAgent || builtinAgent;
2855
- if (runnableAgent) {
2856
- try {
2857
- const execContext = await prepareDirectAgentRunContext(ctx, instruction || target);
2858
- return await runAgentDefinition(runnableAgent, instruction, ctx, session, renderEvent, {
2859
- cwd: execContext.cwd,
2860
- execContext,
2861
- });
2862
- } catch (err) {
2863
- process.stderr.write(` ${c.red('✗')} ${err.message || String(err)}\n`);
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
- try {
2869
- process.stderr.write(` ${c.dim(`Running workflow '${target}'...`)}\n`);
2870
- const result = await ctx.toolExecutor.execute('workflow_run_multi', {
2871
- name: target,
2872
- instruction,
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
- if (result?.success === false) {
2876
- process.stderr.write(` ${c.red('✗')} ${result.output || `Workflow '${target}' failed.`}\n`);
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
- process.stderr.write(` ${c.green('✓')} ${c.dim(`Workflow '${target}' complete`)}\n`);
2881
- const details = [];
2882
- if (result?.run_id) details.push(`run ${result.run_id}`);
2883
- if (result?.duration_s) details.push(`${result.duration_s}s`);
2884
- if (result?.total_tokens) details.push(`${formatTokens(result.total_tokens)} tok`);
2885
- if (result?.total_cost) details.push(formatCostValue(result.total_cost));
2886
- if (details.length) process.stderr.write(` ${c.dim(details.join(' · '))}\n`);
2887
-
2888
- const output = result?.result || result?.output || '';
2889
- if (output) {
2890
- process.stderr.write('\n');
2891
- process.stderr.write(renderMarkdown(String(output), { width: process.stderr.columns || 96 }));
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
- runtime.foldedSubAgentTools = null;
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
- runtime.foldedSubAgentTools = null;
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: '',