devez-vibe 1.6.3 → 1.6.4

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/bin/dvz.exe CHANGED
Binary file
@@ -1058,25 +1058,30 @@ async function createSession(params, resumeId) {
1058
1058
  tools: new Map(),
1059
1059
  permissionDenials: [],
1060
1060
  tasks: new Map(),
1061
- planCreatePending: false,
1062
- subagents: new Map(),
1063
- knownSubagents: new Map(),
1064
- lastContextUsage: null,
1061
+ planCreatePending: false,
1062
+ subagents: new Map(),
1063
+ knownSubagents: new Map(),
1064
+ hiddenSubagentTasks: new Set(),
1065
+ subagentPulse: null,
1066
+ lastContextUsage: null,
1065
1067
  lastContextWindow: 0,
1066
1068
  };
1067
1069
  const agentQuery = await startAgentQuery(queue, makeOptions(params, id, resumeId));
1068
1070
  session.query = agentQuery;
1069
1071
  sessions.set(id, session);
1070
- const consumer = consume(session).catch((error) => {
1071
- notify("error", {
1072
- threadId: id,
1073
- provider: "Claude",
1074
- error: { message: error instanceof Error ? error.message : String(error) },
1075
- willRetry: false,
1076
- });
1077
- clearSubagents(session);
1078
- if (session.turn) finishTurn(session, error);
1079
- });
1072
+ const consumer = consume(session)
1073
+ .catch((error) => {
1074
+ notify("error", {
1075
+ threadId: id,
1076
+ provider: "Claude",
1077
+ error: { message: error instanceof Error ? error.message : String(error) },
1078
+ willRetry: false,
1079
+ });
1080
+ if (session.turn) finishTurn(session, error);
1081
+ })
1082
+ .finally(() => {
1083
+ clearSubagents(session);
1084
+ });
1080
1085
  session.consumer = consumer;
1081
1086
  let initialization;
1082
1087
  try {
@@ -1582,21 +1587,37 @@ function numberedTaskSubject(subject, index) {
1582
1587
 
1583
1588
  // 서브에이전트는 자기 메시지를 부모 Task 툴콜의 `parent_tool_use_id`와 함께 흘려보낸다.
1584
1589
  // 그 ID로 묶어 두면 지금 어떤 에이전트가 무슨 도구를 돌리는지 그대로 복원할 수 있다.
1585
- const SUBAGENT_TOOLS = ["Agent", "Task"];
1586
-
1587
- function startSubagent(session, block) {
1588
- const input = block.input || {};
1589
- session.subagents.set(block.id, {
1590
- id: block.id,
1591
- taskId: "",
1590
+ const SUBAGENT_TOOLS = ["Agent", "Task"];
1591
+ const SUBAGENT_PULSE_MS = 5000;
1592
+ const BACKGROUND_SUBAGENT_LEASE_MS = 60_000;
1593
+
1594
+ function startSubagent(session, block) {
1595
+ const input = block.input || {};
1596
+ const existing = findSubagent(session, block.id);
1597
+ if (existing) {
1598
+ existing.toolUseId = block.id;
1599
+ existing.name = firstLine(input.subagent_type || input.agentType || existing.name || "agent", 40);
1600
+ existing.description = firstLine(
1601
+ input.description || input.prompt || existing.description || "",
1602
+ 120,
1603
+ );
1604
+ existing.lastSeenAt = Date.now();
1605
+ emitSubagents(session);
1606
+ return;
1607
+ }
1608
+ session.subagents.set(block.id, {
1609
+ id: block.id,
1610
+ toolUseId: block.id,
1611
+ taskId: "",
1592
1612
  background: false,
1593
1613
  name: firstLine(input.subagent_type || input.agentType || "agent", 40),
1594
- description: firstLine(input.description || input.prompt || "", 120),
1595
- tool: "",
1596
- startedAt: Date.now(),
1597
- });
1598
- emitSubagents(session);
1599
- }
1614
+ description: firstLine(input.description || input.prompt || "", 120),
1615
+ tool: "",
1616
+ startedAt: Date.now(),
1617
+ lastSeenAt: Date.now(),
1618
+ });
1619
+ emitSubagents(session);
1620
+ }
1600
1621
 
1601
1622
  function isBackgroundSubagentResult(result) {
1602
1623
  return result?.isAsync === true || result?.status === "async_launched";
@@ -1604,12 +1625,13 @@ function isBackgroundSubagentResult(result) {
1604
1625
 
1605
1626
  // Claude Code treats an async Agent result as a launch receipt. The agent remains
1606
1627
  // live until its later task-notification names the same task or tool-use id.
1607
- function keepBackgroundSubagent(session, toolUseId, result) {
1608
- if (!isBackgroundSubagentResult(result)) return false;
1609
- const running = session.subagents.get(toolUseId);
1610
- if (!running) return false;
1611
- running.background = true;
1612
- running.taskId = firstLine(result?.agentId || result?.taskId || "", 80);
1628
+ function keepBackgroundSubagent(session, toolUseId, result) {
1629
+ if (!isBackgroundSubagentResult(result)) return false;
1630
+ const running = findSubagent(session, toolUseId);
1631
+ if (!running) return false;
1632
+ running.background = true;
1633
+ running.taskId = firstLine(result?.agentId || result?.taskId || "", 80);
1634
+ running.lastSeenAt = Date.now();
1613
1635
  if (running.taskId) {
1614
1636
  session.knownSubagents.set(running.taskId, {
1615
1637
  name: running.name,
@@ -1629,15 +1651,17 @@ function resumeBackgroundSubagent(session, toolUseId, pending, result) {
1629
1651
  if (existing) return true;
1630
1652
  const known = session.knownSubagents.get(taskId);
1631
1653
  const input = pending?.input || {};
1632
- const running = {
1633
- id: toolUseId,
1634
- taskId,
1654
+ const running = {
1655
+ id: toolUseId,
1656
+ toolUseId,
1657
+ taskId,
1635
1658
  background: true,
1636
1659
  name: known?.name || "agent",
1637
- description: known?.description || firstLine(input.summary || input.message || "", 120),
1638
- tool: "",
1639
- startedAt: Date.now(),
1640
- };
1660
+ description: known?.description || firstLine(input.summary || input.message || "", 120),
1661
+ tool: "",
1662
+ startedAt: Date.now(),
1663
+ lastSeenAt: Date.now(),
1664
+ };
1641
1665
  session.subagents.set(toolUseId, running);
1642
1666
  session.knownSubagents.set(taskId, {
1643
1667
  name: running.name,
@@ -1647,16 +1671,209 @@ function resumeBackgroundSubagent(session, toolUseId, pending, result) {
1647
1671
  return true;
1648
1672
  }
1649
1673
 
1650
- function findSubagent(session, id) {
1651
- return session.subagents.get(id)
1652
- || [...session.subagents.values()].find((agent) => agent.taskId === id);
1653
- }
1654
-
1655
- // 서브에이전트가 실제로 무엇을 했는지는 자식 메시지에만 남는다. 열람용 기록은 여기서
1674
+ function findSubagent(session, id) {
1675
+ return session.subagents.get(id)
1676
+ || [...session.subagents.values()].find(
1677
+ (agent) => agent.taskId === id || agent.toolUseId === id,
1678
+ );
1679
+ }
1680
+
1681
+ function isSubagentTaskType(taskType) {
1682
+ return String(taskType || "").toLowerCase().includes("agent");
1683
+ }
1684
+
1685
+ // Claude's SDK exposes task_started/task_progress/task_updated/task_notification
1686
+ // as structured lifecycle edges. Prefer those fields over parsing the injected
1687
+ // XML fallback so a formatting change cannot leave a stale running row.
1688
+ function upsertStructuredSubagent(session, message) {
1689
+ const taskId = firstLine(message.task_id || "", 80);
1690
+ const toolUseId = firstLine(message.tool_use_id || "", 80);
1691
+ const subagentType = firstLine(message.subagent_type || "", 40);
1692
+ if (taskId && session.hiddenSubagentTasks?.has(taskId)) return null;
1693
+ const byTool = toolUseId && findSubagent(session, toolUseId);
1694
+ const byTask = taskId && findSubagent(session, taskId);
1695
+ let running = byTool || byTask;
1696
+ // The SDK does not order the background level snapshot against task_started.
1697
+ // If both paths created a row, keep the tool-use row and fold the placeholder
1698
+ // into it before publishing the next snapshot.
1699
+ if (byTool && byTask && byTool !== byTask) {
1700
+ byTool.background ||= byTask.background;
1701
+ byTool.taskId ||= byTask.taskId;
1702
+ byTool.toolUseId ||= byTask.toolUseId;
1703
+ if ((!byTool.name || byTool.name === "agent") && byTask.name) byTool.name = byTask.name;
1704
+ if (!byTool.description) byTool.description = byTask.description;
1705
+ if (!byTool.tool) byTool.tool = byTask.tool;
1706
+ byTool.startedAt = Math.min(byTool.startedAt, byTask.startedAt);
1707
+ byTool.lastSeenAt = Math.max(byTool.lastSeenAt || 0, byTask.lastSeenAt || 0);
1708
+ session.subagents.delete(byTask.id);
1709
+ running = byTool;
1710
+ }
1711
+ if (!running && !subagentType && !isSubagentTaskType(message.task_type)) return null;
1712
+ if (!running) {
1713
+ const known = taskId ? session.knownSubagents.get(taskId) : null;
1714
+ const id = toolUseId || `task:${taskId}`;
1715
+ running = {
1716
+ id,
1717
+ toolUseId,
1718
+ taskId,
1719
+ background: false,
1720
+ name: subagentType || known?.name || "agent",
1721
+ description: firstLine(message.description || known?.description || "", 120),
1722
+ tool: "",
1723
+ startedAt: Date.now(),
1724
+ lastSeenAt: Date.now(),
1725
+ };
1726
+ session.subagents.set(id, running);
1727
+ }
1728
+ if (toolUseId) running.toolUseId = toolUseId;
1729
+ if (taskId) running.taskId = taskId;
1730
+ if (subagentType) running.name = subagentType;
1731
+ const description = firstLine(message.description || "", 120);
1732
+ if (description) running.description = description;
1733
+ running.lastSeenAt = Date.now();
1734
+ if (running.taskId) {
1735
+ session.knownSubagents.set(running.taskId, {
1736
+ name: running.name,
1737
+ description: running.description,
1738
+ });
1739
+ }
1740
+ return running;
1741
+ }
1742
+
1743
+ function finishStructuredSubagent(session, message, kind, text) {
1744
+ const taskId = firstLine(message.task_id || "", 80);
1745
+ const toolUseId = firstLine(message.tool_use_id || "", 80);
1746
+ const wasHidden = taskId ? session.hiddenSubagentTasks?.delete(taskId) === true : false;
1747
+ const running = (toolUseId && findSubagent(session, toolUseId))
1748
+ || (taskId && findSubagent(session, taskId));
1749
+ if (!running) return wasHidden;
1750
+ emitSubagentLine(session, running.id, { kind, text });
1751
+ session.subagents.delete(running.id);
1752
+ emitSubagents(session);
1753
+ return true;
1754
+ }
1755
+
1756
+ function syncBackgroundSubagents(session, tasks) {
1757
+ const live = (Array.isArray(tasks) ? tasks : []).filter((task) => {
1758
+ const taskId = firstLine(task?.task_id || "", 80);
1759
+ return !taskId || !session.hiddenSubagentTasks?.has(taskId);
1760
+ });
1761
+ const liveIds = new Set(live.map((task) => firstLine(task?.task_id || "", 80)).filter(Boolean));
1762
+ let changed = false;
1763
+ for (const [id, running] of session.subagents) {
1764
+ if (!running.background || !running.taskId || liveIds.has(running.taskId)) continue;
1765
+ session.subagents.delete(id);
1766
+ changed = true;
1767
+ }
1768
+ for (const task of live) {
1769
+ const taskId = firstLine(task?.task_id || "", 80);
1770
+ if (!taskId) continue;
1771
+ let running = findSubagent(session, taskId);
1772
+ const known = session.knownSubagents.get(taskId);
1773
+ if (!running && !known && !isSubagentTaskType(task?.task_type)) continue;
1774
+ if (!running) {
1775
+ running = {
1776
+ id: `task:${taskId}`,
1777
+ toolUseId: "",
1778
+ taskId,
1779
+ background: true,
1780
+ name: known?.name || "agent",
1781
+ description: firstLine(task?.description || known?.description || "", 120),
1782
+ tool: "",
1783
+ startedAt: Date.now(),
1784
+ lastSeenAt: Date.now(),
1785
+ };
1786
+ session.subagents.set(running.id, running);
1787
+ changed = true;
1788
+ }
1789
+ running.background = true;
1790
+ running.lastSeenAt = Date.now();
1791
+ const description = firstLine(task?.description || "", 120);
1792
+ if (description && description !== running.description) {
1793
+ running.description = description;
1794
+ changed = true;
1795
+ }
1796
+ }
1797
+ if (changed || liveIds.size) emitSubagents(session);
1798
+ }
1799
+
1800
+ function processSubagentSystemMessage(session, message) {
1801
+ if (message.subtype === "task_started") {
1802
+ if (message.skip_transcript === true) {
1803
+ const taskId = firstLine(message.task_id || "", 80);
1804
+ const toolUseId = firstLine(message.tool_use_id || "", 80);
1805
+ if (taskId) {
1806
+ session.hiddenSubagentTasks ||= new Set();
1807
+ session.hiddenSubagentTasks.add(taskId);
1808
+ }
1809
+ const running = (toolUseId && findSubagent(session, toolUseId))
1810
+ || (taskId && findSubagent(session, taskId));
1811
+ if (running) {
1812
+ session.subagents.delete(running.id);
1813
+ emitSubagents(session);
1814
+ }
1815
+ return true;
1816
+ }
1817
+ const running = upsertStructuredSubagent(session, message);
1818
+ if (!running) return false;
1819
+ emitSubagents(session);
1820
+ return true;
1821
+ }
1822
+ if (message.subtype === "task_progress") {
1823
+ const running = upsertStructuredSubagent(session, message);
1824
+ if (!running) return false;
1825
+ const tool = firstLine(message.last_tool_name || "", 80);
1826
+ if (tool) running.tool = tool;
1827
+ const summary = firstLine(message.summary || "", 200);
1828
+ if (summary && summary !== running.lastSummary) {
1829
+ running.lastSummary = summary;
1830
+ emitSubagentLine(session, running.id, { kind: "result", text: summary });
1831
+ }
1832
+ emitSubagents(session);
1833
+ return true;
1834
+ }
1835
+ if (message.subtype === "task_updated") {
1836
+ const taskId = firstLine(message.task_id || "", 80);
1837
+ const patch = message.patch || {};
1838
+ const status = patch.status;
1839
+ if (["completed", "failed", "killed"].includes(status)) {
1840
+ return finishStructuredSubagent(
1841
+ session,
1842
+ message,
1843
+ status === "completed" ? "result" : "error",
1844
+ firstLine(patch.error || status, 200),
1845
+ );
1846
+ }
1847
+ const running = taskId && findSubagent(session, taskId);
1848
+ if (!running) return false;
1849
+ if (patch.description) running.description = firstLine(patch.description, 120);
1850
+ if (patch.is_backgrounded === true) running.background = true;
1851
+ running.lastSeenAt = Date.now();
1852
+ emitSubagents(session);
1853
+ return true;
1854
+ }
1855
+ if (message.subtype === "task_notification") {
1856
+ const status = firstLine(message.status || "completed", 40);
1857
+ return finishStructuredSubagent(
1858
+ session,
1859
+ message,
1860
+ status === "completed" ? "result" : "error",
1861
+ firstLine(message.summary || status, 200),
1862
+ );
1863
+ }
1864
+ if (message.subtype === "background_tasks_changed") {
1865
+ syncBackgroundSubagents(session, message.tasks);
1866
+ return true;
1867
+ }
1868
+ return false;
1869
+ }
1870
+
1871
+ // 서브에이전트가 실제로 무엇을 했는지는 자식 메시지에만 남는다. 열람용 기록은 여기서
1656
1872
  // 한 줄씩 흘려보내고, 목록 행에 쓸 현재 도구만 따로 갱신한다.
1657
- function recordSubagentMessage(session, message) {
1658
- const running = findSubagent(session, message.parent_tool_use_id);
1659
- if (!running) return;
1873
+ function recordSubagentMessage(session, message) {
1874
+ const running = findSubagent(session, message.parent_tool_use_id);
1875
+ if (!running) return;
1876
+ running.lastSeenAt = Date.now();
1660
1877
  const content = Array.isArray(message.message?.content) ? message.message.content : [];
1661
1878
  let toolChanged = false;
1662
1879
  for (const block of content) {
@@ -1676,9 +1893,10 @@ function recordSubagentMessage(session, message) {
1676
1893
  if (toolChanged) emitSubagents(session);
1677
1894
  }
1678
1895
 
1679
- function recordSubagentResult(session, message) {
1680
- const running = findSubagent(session, message.parent_tool_use_id);
1681
- if (!running) return;
1896
+ function recordSubagentResult(session, message) {
1897
+ const running = findSubagent(session, message.parent_tool_use_id);
1898
+ if (!running) return;
1899
+ running.lastSeenAt = Date.now();
1682
1900
  const content = Array.isArray(message.message?.content) ? message.message.content : [];
1683
1901
  for (const block of content) {
1684
1902
  if (block.type !== "tool_result") continue;
@@ -1713,10 +1931,12 @@ function subagentToolLabel(block) {
1713
1931
  return text ? `${name}(${text})` : name;
1714
1932
  }
1715
1933
 
1716
- function finishSubagent(session, toolUseId) {
1717
- if (!session.subagents.delete(toolUseId)) return;
1718
- emitSubagents(session);
1719
- }
1934
+ function finishSubagent(session, toolUseId) {
1935
+ const running = findSubagent(session, toolUseId);
1936
+ if (!running) return;
1937
+ session.subagents.delete(running.id);
1938
+ emitSubagents(session);
1939
+ }
1720
1940
 
1721
1941
  function finishSubagentTask(session, taskId) {
1722
1942
  if (!taskId) return false;
@@ -1737,23 +1957,38 @@ function clearForegroundSubagents(session) {
1737
1957
  if (changed) emitSubagents(session);
1738
1958
  }
1739
1959
 
1740
- function clearSubagents(session) {
1741
- if (!session.subagents.size) return;
1742
- session.subagents.clear();
1743
- emitSubagents(session);
1744
- }
1960
+ function clearSubagents(session) {
1961
+ const changed = session.subagents.size > 0;
1962
+ session.subagents.clear();
1963
+ session.hiddenSubagentTasks?.clear();
1964
+ if (session.subagentPulse) {
1965
+ clearInterval(session.subagentPulse);
1966
+ session.subagentPulse = null;
1967
+ }
1968
+ if (changed) emitSubagents(session);
1969
+ }
1745
1970
 
1746
1971
  function firstLine(value, limit) {
1747
1972
  return String(value ?? "").split("\n")[0].trim().slice(0, limit);
1748
1973
  }
1749
1974
 
1750
- function emitSubagents(session) {
1751
- // 하위 에이전트가 도는 동안 주기적으로 목록을 다시 알린다. 화면이
1752
- // 생존 신호로 살아 있는 행과 종료 신호를 놓친 행을 구분한다.
1753
- if (session.subagents.size && !session.subagentPulse) {
1754
- session.subagentPulse = setInterval(() => emitSubagents(session), 5000);
1755
- session.subagentPulse.unref?.();
1756
- } else if (!session.subagents.size && session.subagentPulse) {
1975
+ function emitSubagents(session, pulse = false) {
1976
+ // 구조화된 진행 신호가 끊긴 백그라운드 행은 pulse로 영구 연장하지 않는다.
1977
+ // 정상 작업은 task_progress/background_tasks_changed가 lease를 갱신하고,
1978
+ // 종료 edge와 level 신호를 모두 놓친 행만 유한 시간 뒤 제거된다.
1979
+ if (pulse) {
1980
+ const now = Date.now();
1981
+ for (const [id, agent] of session.subagents) {
1982
+ const lastSeenAt = agent.lastSeenAt || agent.startedAt || now;
1983
+ if (agent.background && now - lastSeenAt >= BACKGROUND_SUBAGENT_LEASE_MS) {
1984
+ session.subagents.delete(id);
1985
+ }
1986
+ }
1987
+ }
1988
+ if (session.subagents.size && !session.subagentPulse) {
1989
+ session.subagentPulse = setInterval(() => emitSubagents(session, true), SUBAGENT_PULSE_MS);
1990
+ session.subagentPulse.unref?.();
1991
+ } else if (!session.subagents.size && session.subagentPulse) {
1757
1992
  clearInterval(session.subagentPulse);
1758
1993
  session.subagentPulse = null;
1759
1994
  }
@@ -2047,10 +2282,13 @@ async function consume(session) {
2047
2282
  if (session.turn) session.turn.sawStreamText = true;
2048
2283
  }
2049
2284
  await processStreamEvent(session, message);
2050
- } else if (message.type === "assistant") processAssistant(session, message);
2051
- else if (message.type === "user") processUser(session, message);
2052
- else if (message.type === "result") await processResult(session, message);
2053
- else if (message.type === "system" && message.subtype === "compact_boundary") {
2285
+ } else if (message.type === "assistant") processAssistant(session, message);
2286
+ else if (message.type === "user") processUser(session, message);
2287
+ else if (message.type === "result") await processResult(session, message);
2288
+ else if (message.type === "system" && processSubagentSystemMessage(session, message)) {
2289
+ // Structured SDK task lifecycle handled above.
2290
+ }
2291
+ else if (message.type === "system" && message.subtype === "compact_boundary") {
2054
2292
  noteCompactBoundary(session, message.compact_metadata);
2055
2293
  notify("thread/compacted", { threadId: session.id });
2056
2294
  } else if (message.type === "system" && message.subtype === "permission_denied") {
@@ -2668,11 +2906,12 @@ async function dispatch(method, params = {}) {
2668
2906
  usage,
2669
2907
  };
2670
2908
  }
2671
- if (method === "session/close") {
2672
- const session = lookupSession(params.sessionId);
2673
- if (session) {
2674
- session.queue.close();
2675
- session.query.close();
2909
+ if (method === "session/close") {
2910
+ const session = lookupSession(params.sessionId);
2911
+ if (session) {
2912
+ clearSubagents(session);
2913
+ session.queue.close();
2914
+ session.query.close();
2676
2915
  sessions.delete(session.id);
2677
2916
  for (const [from, to] of sessionAliases) {
2678
2917
  if (to === session.id) sessionAliases.delete(from);
@@ -2686,10 +2925,11 @@ async function dispatch(method, params = {}) {
2686
2925
  if (!session) return { account: null, usage: null };
2687
2926
  return { account: await safeAccount(session.query), usage: await safeUsage(session.query) };
2688
2927
  }
2689
- if (method === "shutdown") {
2690
- for (const session of sessions.values()) {
2691
- session.queue.close();
2692
- session.query.close();
2928
+ if (method === "shutdown") {
2929
+ for (const session of sessions.values()) {
2930
+ clearSubagents(session);
2931
+ session.queue.close();
2932
+ session.query.close();
2693
2933
  }
2694
2934
  sessions.clear();
2695
2935
  sessionAliases.clear();
@@ -3132,11 +3372,173 @@ async function runSelfTest() {
3132
3372
  <status>failed</status><summary>Agent failed</summary>
3133
3373
  </task-notification>` },
3134
3374
  });
3135
- if (lifecycleSession.subagents.size !== 0 || lifecycleSession.turn === null) {
3136
- throw new Error("Claude failed task notification did not finish the resumed agent");
3137
- }
3138
- finishTurn(lifecycleSession, null, 1);
3139
- } finally {
3375
+ if (lifecycleSession.subagents.size !== 0 || lifecycleSession.turn === null) {
3376
+ throw new Error("Claude failed task notification did not finish the resumed agent");
3377
+ }
3378
+ finishTurn(lifecycleSession, null, 1);
3379
+
3380
+ const structuredSession = {
3381
+ id: "structured-self-test",
3382
+ turn: null,
3383
+ subagents: new Map(),
3384
+ knownSubagents: new Map(),
3385
+ hiddenSubagentTasks: new Set(),
3386
+ subagentPulse: null,
3387
+ };
3388
+ processSubagentSystemMessage(structuredSession, {
3389
+ type: "system",
3390
+ subtype: "task_started",
3391
+ task_id: "agent-structured",
3392
+ tool_use_id: "toolu_structured",
3393
+ task_type: "agent",
3394
+ subagent_type: "Explore",
3395
+ description: "Inspect structured events",
3396
+ });
3397
+ processSubagentSystemMessage(structuredSession, {
3398
+ type: "system",
3399
+ subtype: "background_tasks_changed",
3400
+ tasks: [{
3401
+ task_id: "agent-structured",
3402
+ task_type: "agent",
3403
+ description: "Inspect structured events",
3404
+ }],
3405
+ });
3406
+ processSubagentSystemMessage(structuredSession, {
3407
+ type: "system",
3408
+ subtype: "task_progress",
3409
+ task_id: "agent-structured",
3410
+ tool_use_id: "toolu_structured",
3411
+ subagent_type: "Explore",
3412
+ description: "Inspect structured events",
3413
+ last_tool_name: "Read",
3414
+ summary: "Reading lifecycle source",
3415
+ });
3416
+ const structured = structuredSession.subagents.get("toolu_structured");
3417
+ if (!structured?.background || structured.tool !== "Read") {
3418
+ throw new Error(`Claude structured task progress self-test failed: ${JSON.stringify(structured)}`);
3419
+ }
3420
+ processSubagentSystemMessage(structuredSession, {
3421
+ type: "system",
3422
+ subtype: "background_tasks_changed",
3423
+ tasks: [],
3424
+ });
3425
+ if (structuredSession.subagents.size !== 0) {
3426
+ throw new Error("Claude background task snapshot did not remove a finished subagent");
3427
+ }
3428
+
3429
+ processSubagentSystemMessage(structuredSession, {
3430
+ type: "system",
3431
+ subtype: "background_tasks_changed",
3432
+ tasks: [{ task_id: "agent-reordered", task_type: "agent", description: "Race" }],
3433
+ });
3434
+ startSubagent(structuredSession, {
3435
+ id: "toolu_reordered",
3436
+ input: { subagent_type: "Explore", description: "Race" },
3437
+ });
3438
+ processSubagentSystemMessage(structuredSession, {
3439
+ type: "system",
3440
+ subtype: "task_started",
3441
+ task_id: "agent-reordered",
3442
+ tool_use_id: "toolu_reordered",
3443
+ task_type: "agent",
3444
+ subagent_type: "Explore",
3445
+ });
3446
+ const reordered = findSubagent(structuredSession, "toolu_reordered");
3447
+ if (structuredSession.subagents.size !== 1
3448
+ || !reordered?.background
3449
+ || reordered.taskId !== "agent-reordered") {
3450
+ throw new Error(`Claude reordered lifecycle self-test failed: ${JSON.stringify([...structuredSession.subagents])}`);
3451
+ }
3452
+ processSubagentSystemMessage(structuredSession, {
3453
+ type: "system",
3454
+ subtype: "task_notification",
3455
+ task_id: "agent-reordered",
3456
+ tool_use_id: "toolu_reordered",
3457
+ status: "completed",
3458
+ summary: "Agent finished",
3459
+ });
3460
+
3461
+ processSubagentSystemMessage(structuredSession, {
3462
+ type: "system",
3463
+ subtype: "task_started",
3464
+ task_id: "agent-edge-first",
3465
+ tool_use_id: "toolu_edge_first",
3466
+ task_type: "agent",
3467
+ subagent_type: "Explore",
3468
+ description: "Edge first",
3469
+ });
3470
+ startSubagent(structuredSession, {
3471
+ id: "toolu_edge_first",
3472
+ input: { subagent_type: "Explore", description: "Edge first" },
3473
+ });
3474
+ const edgeFirst = findSubagent(structuredSession, "toolu_edge_first");
3475
+ if (structuredSession.subagents.size !== 1 || edgeFirst?.taskId !== "agent-edge-first") {
3476
+ throw new Error(`Claude edge-first lifecycle self-test failed: ${JSON.stringify([...structuredSession.subagents])}`);
3477
+ }
3478
+ finishSubagent(structuredSession, "toolu_edge_first");
3479
+ if (structuredSession.subagents.size !== 0) {
3480
+ throw new Error("Claude aliased tool completion did not remove the subagent row");
3481
+ }
3482
+
3483
+ processSubagentSystemMessage(structuredSession, {
3484
+ type: "system",
3485
+ subtype: "task_started",
3486
+ task_id: "agent-hidden",
3487
+ task_type: "agent",
3488
+ subagent_type: "Explore",
3489
+ skip_transcript: true,
3490
+ });
3491
+ processSubagentSystemMessage(structuredSession, {
3492
+ type: "system",
3493
+ subtype: "background_tasks_changed",
3494
+ tasks: [{ task_id: "agent-hidden", task_type: "agent", description: "Housekeeping" }],
3495
+ });
3496
+ if (structuredSession.subagents.size !== 0) {
3497
+ throw new Error("Claude skip_transcript task was shown as a subagent row");
3498
+ }
3499
+ processSubagentSystemMessage(structuredSession, {
3500
+ type: "system",
3501
+ subtype: "task_notification",
3502
+ task_id: "agent-hidden",
3503
+ status: "completed",
3504
+ summary: "Hidden task finished",
3505
+ });
3506
+
3507
+ processSubagentSystemMessage(structuredSession, {
3508
+ type: "system",
3509
+ subtype: "task_started",
3510
+ task_id: "agent-notified",
3511
+ tool_use_id: "toolu_notified",
3512
+ task_type: "agent",
3513
+ subagent_type: "Explore",
3514
+ });
3515
+ processSubagentSystemMessage(structuredSession, {
3516
+ type: "system",
3517
+ subtype: "task_notification",
3518
+ task_id: "agent-notified",
3519
+ tool_use_id: "toolu_notified",
3520
+ status: "failed",
3521
+ summary: "Agent failed",
3522
+ });
3523
+ if (structuredSession.subagents.size !== 0) {
3524
+ throw new Error("Claude structured task notification did not remove a failed subagent");
3525
+ }
3526
+
3527
+ structuredSession.subagents.set("expired", {
3528
+ id: "expired",
3529
+ taskId: "agent-expired",
3530
+ background: true,
3531
+ name: "Explore",
3532
+ description: "",
3533
+ tool: "",
3534
+ startedAt: Date.now() - BACKGROUND_SUBAGENT_LEASE_MS - 1,
3535
+ lastSeenAt: Date.now() - BACKGROUND_SUBAGENT_LEASE_MS - 1,
3536
+ });
3537
+ emitSubagents(structuredSession, true);
3538
+ if (structuredSession.subagents.size !== 0) {
3539
+ throw new Error("Claude expired background subagent lease self-test failed");
3540
+ }
3541
+ } finally {
3140
3542
  process.stdout.write = stdoutWrite;
3141
3543
  }
3142
3544
  const lifecycleEvents = captured
@@ -3299,6 +3701,9 @@ lines.on("line", async (line) => {
3299
3701
  catch (error) { write({ id: message.id, error: rpcError(error) }); }
3300
3702
  });
3301
3703
 
3302
- lines.on("close", () => {
3303
- for (const session of sessions.values()) session.query.close();
3304
- });
3704
+ lines.on("close", () => {
3705
+ for (const session of sessions.values()) {
3706
+ clearSubagents(session);
3707
+ session.query.close();
3708
+ }
3709
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.6.3",
3
+ "version": "1.6.4",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",