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 +0 -0
- package/bridge/claude-agent-sdk-bridge.mjs +494 -89
- package/package.json +1 -1
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
|
-
|
|
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)
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
1718
|
-
|
|
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
|
-
|
|
1742
|
-
session.subagents.clear();
|
|
1743
|
-
|
|
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
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
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
|
|
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
|
|
2675
|
-
session.
|
|
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
|
|
2692
|
-
session.
|
|
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
|
-
|
|
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())
|
|
3304
|
-
|
|
3704
|
+
lines.on("close", () => {
|
|
3705
|
+
for (const session of sessions.values()) {
|
|
3706
|
+
clearSubagents(session);
|
|
3707
|
+
session.query.close();
|
|
3708
|
+
}
|
|
3709
|
+
});
|