devez-vibe 1.9.52 → 1.9.54
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 +165 -93
- package/package.json +1 -1
package/bin/dvz.exe
CHANGED
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { execFile, spawn } from "node:child_process";
|
|
5
5
|
import { createReadStream, existsSync, readFileSync, readdirSync } from "node:fs";
|
|
6
|
-
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import { homedir, tmpdir } from "node:os";
|
|
9
9
|
import { dirname, join, resolve as resolvePath, sep } from "node:path";
|
|
@@ -1522,13 +1522,18 @@ function runClaudeCommand(params, args, deadlineMs = CLAUDE_COMMAND_TIMEOUT) {
|
|
|
1522
1522
|
const output = Buffer.concat(stdout).toString("utf8").trim();
|
|
1523
1523
|
const detail = Buffer.concat(stderr).toString("utf8").trim();
|
|
1524
1524
|
if (code === 0) resolve(output);
|
|
1525
|
-
else reject(new Error(detail || output || `Claude 명령이 종료 코드 ${code}로 실패했습니다.`));
|
|
1525
|
+
else reject(Object.assign(new Error(detail || output || `Claude 명령이 종료 코드 ${code}로 실패했습니다.`), { exitCode: code }));
|
|
1526
1526
|
});
|
|
1527
1527
|
});
|
|
1528
1528
|
}
|
|
1529
1529
|
|
|
1530
|
+
// Windows의 Claude Code는 가끔 출력을 다 낸 뒤에도 0xC0000005 같은 충돌 코드로
|
|
1531
|
+
// 끝난다. 읽기 명령이라 종료 코드로 실패하면 한 번 더 실행한다. 멈춤은 다시 기다리지 않는다.
|
|
1530
1532
|
async function runClaudeJson(params, args) {
|
|
1531
|
-
const output = await runClaudeCommand(params, args)
|
|
1533
|
+
const output = await runClaudeCommand(params, args).catch((error) => {
|
|
1534
|
+
if (error.exitCode === undefined) throw error;
|
|
1535
|
+
return runClaudeCommand(params, args);
|
|
1536
|
+
});
|
|
1532
1537
|
try { return output ? JSON.parse(output) : null; }
|
|
1533
1538
|
catch (error) {
|
|
1534
1539
|
throw new Error(`Claude 플러그인 응답을 해석하지 못했습니다: ${error.message}`);
|
|
@@ -1890,6 +1895,13 @@ function questionFallbackMessage(questions) {
|
|
|
1890
1895
|
].join("\n");
|
|
1891
1896
|
}
|
|
1892
1897
|
|
|
1898
|
+
// Windows는 충돌한 프로세스를 0xC0000000 이상의 종료 코드로 알린다(0xC0000005 접근
|
|
1899
|
+
// 위반, 0xC0000374 힙 손상). 설정·인증 오류의 일반 종료 코드와 달리 다시 띄우면 대개 뜬다.
|
|
1900
|
+
function isClaudeCliCrash(error) {
|
|
1901
|
+
const code = /process exited with code (\d+)/.exec(String(error?.message ?? error))?.[1];
|
|
1902
|
+
return Number(code) >= 0xC0000000;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1893
1905
|
async function createSession(params, resumeId) {
|
|
1894
1906
|
const id = resumeId || randomUUID();
|
|
1895
1907
|
const queue = new AsyncQueue();
|
|
@@ -1949,6 +1961,15 @@ async function createSession(params, resumeId) {
|
|
|
1949
1961
|
clearUsageLimitWait(session);
|
|
1950
1962
|
if (session.turn) finishTurn(session, new Error("Claude 연결이 종료되었습니다."));
|
|
1951
1963
|
clearSubagents(session);
|
|
1964
|
+
// A crashed CLI (Windows reports an access violation as exit code
|
|
1965
|
+
// 3221225477) leaves a query that rejects every later request. Dropping
|
|
1966
|
+
// the session makes the next prompt fail as missing, which the host answers
|
|
1967
|
+
// by resuming it from the transcript instead of the user reopening it.
|
|
1968
|
+
if (sessions.get(session.id) === session) {
|
|
1969
|
+
sessions.delete(session.id);
|
|
1970
|
+
session.queue.close();
|
|
1971
|
+
session.query.close();
|
|
1972
|
+
}
|
|
1952
1973
|
});
|
|
1953
1974
|
session.consumer = consumer;
|
|
1954
1975
|
let initialization;
|
|
@@ -2560,12 +2581,16 @@ const SUBAGENT_PULSE_MS = 5000;
|
|
|
2560
2581
|
// 자체 시험에서 "오래 조용한 행"을 만들 때 쓰는 나이.
|
|
2561
2582
|
const IDLE_ROW_AGE_MS = 120_000;
|
|
2562
2583
|
|
|
2584
|
+
// 순정 Claude Code처럼 행에 Agent의 name과 description을 함께 보인다. description의
|
|
2585
|
+
// 언어는 매 턴 지침이 화면 언어에 맞춘다.
|
|
2563
2586
|
function startSubagent(session, block) {
|
|
2564
2587
|
const input = block.input || {};
|
|
2588
|
+
const description = firstLine(input.description || "", SUBAGENT_DESCRIPTION_LIMIT);
|
|
2565
2589
|
const existing = findSubagent(session, block.id);
|
|
2566
2590
|
if (existing) {
|
|
2567
2591
|
existing.toolUseId = block.id;
|
|
2568
2592
|
existing.name = firstLine(input.name || input.subagent_type || input.agentType || existing.name || "agent", 40);
|
|
2593
|
+
existing.description = description || existing.description;
|
|
2569
2594
|
existing.backgroundTask = false;
|
|
2570
2595
|
existing.lastSeenAt = Date.now();
|
|
2571
2596
|
emitSubagents(session);
|
|
@@ -2577,8 +2602,7 @@ function startSubagent(session, block) {
|
|
|
2577
2602
|
taskId: "",
|
|
2578
2603
|
background: false,
|
|
2579
2604
|
name: firstLine(input.name || input.subagent_type || input.agentType || "agent", 40),
|
|
2580
|
-
|
|
2581
|
-
description: "",
|
|
2605
|
+
description,
|
|
2582
2606
|
tool: "",
|
|
2583
2607
|
startedAt: Date.now(),
|
|
2584
2608
|
lastSeenAt: Date.now(),
|
|
@@ -2665,7 +2689,7 @@ function resumeBackgroundSubagent(session, toolUseId, pending, result) {
|
|
|
2665
2689
|
taskId,
|
|
2666
2690
|
background: true,
|
|
2667
2691
|
name: known?.name || "agent",
|
|
2668
|
-
description: "",
|
|
2692
|
+
description: known?.description || "",
|
|
2669
2693
|
tool: "",
|
|
2670
2694
|
startedAt: Date.now(),
|
|
2671
2695
|
lastSeenAt: Date.now(),
|
|
@@ -2766,7 +2790,7 @@ function upsertStructuredSubagent(session, message) {
|
|
|
2766
2790
|
return running;
|
|
2767
2791
|
}
|
|
2768
2792
|
|
|
2769
|
-
function finishStructuredSubagent(session, message
|
|
2793
|
+
function finishStructuredSubagent(session, message) {
|
|
2770
2794
|
const taskId = firstLine(message.task_id || "", 80);
|
|
2771
2795
|
const toolUseId = firstLine(message.tool_use_id || "", 80);
|
|
2772
2796
|
const wasTranscriptHidden = taskId
|
|
@@ -2779,7 +2803,6 @@ function finishStructuredSubagent(session, message, kind, text) {
|
|
|
2779
2803
|
const running = (toolUseId && findSubagent(session, toolUseId))
|
|
2780
2804
|
|| (taskId && findSubagent(session, taskId));
|
|
2781
2805
|
if (!running) return wasHidden;
|
|
2782
|
-
emitSubagentLine(session, running.id, { kind, text });
|
|
2783
2806
|
session.subagents.delete(running.id);
|
|
2784
2807
|
emitSubagents(session);
|
|
2785
2808
|
return true;
|
|
@@ -2879,25 +2902,14 @@ function processSubagentSystemMessage(session, message) {
|
|
|
2879
2902
|
if (!running) return false;
|
|
2880
2903
|
const tool = firstLine(message.last_tool_name || "", 80);
|
|
2881
2904
|
if (tool) running.tool = tool;
|
|
2882
|
-
const summary = firstLine(message.summary || "", 200);
|
|
2883
|
-
if (summary && summary !== running.lastSummary) {
|
|
2884
|
-
running.lastSummary = summary;
|
|
2885
|
-
emitSubagentLine(session, running.id, { kind: "result", text: summary });
|
|
2886
|
-
}
|
|
2887
2905
|
emitSubagents(session);
|
|
2888
2906
|
return true;
|
|
2889
2907
|
}
|
|
2890
2908
|
if (message.subtype === "task_updated") {
|
|
2891
2909
|
const taskId = firstLine(message.task_id || "", 80);
|
|
2892
2910
|
const patch = message.patch || {};
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
return finishStructuredSubagent(
|
|
2896
|
-
session,
|
|
2897
|
-
message,
|
|
2898
|
-
status === "completed" ? "result" : "error",
|
|
2899
|
-
firstLine(patch.error || status, 200),
|
|
2900
|
-
);
|
|
2911
|
+
if (["completed", "failed", "killed"].includes(patch.status)) {
|
|
2912
|
+
return finishStructuredSubagent(session, message);
|
|
2901
2913
|
}
|
|
2902
2914
|
const running = taskId && findSubagent(session, taskId);
|
|
2903
2915
|
if (!running) return false;
|
|
@@ -2914,13 +2926,7 @@ function processSubagentSystemMessage(session, message) {
|
|
|
2914
2926
|
session.ambientSubagentTasks ||= new Set();
|
|
2915
2927
|
session.ambientSubagentTasks.add(firstLine(message.task_id, 80));
|
|
2916
2928
|
}
|
|
2917
|
-
|
|
2918
|
-
return finishStructuredSubagent(
|
|
2919
|
-
session,
|
|
2920
|
-
message,
|
|
2921
|
-
status === "completed" ? "result" : "error",
|
|
2922
|
-
firstLine(message.summary || status, 200),
|
|
2923
|
-
);
|
|
2929
|
+
return finishStructuredSubagent(session, message);
|
|
2924
2930
|
}
|
|
2925
2931
|
if (message.subtype === "background_tasks_changed") {
|
|
2926
2932
|
syncBackgroundSubagents(session, message.tasks);
|
|
@@ -2929,61 +2935,24 @@ function processSubagentSystemMessage(session, message) {
|
|
|
2929
2935
|
return false;
|
|
2930
2936
|
}
|
|
2931
2937
|
|
|
2932
|
-
// 서브에이전트가
|
|
2933
|
-
//
|
|
2938
|
+
// 서브에이전트가 지금 무슨 도구를 돌리는지는 자식 메시지에만 남는다. 목록 행에 쓸
|
|
2939
|
+
// 현재 도구만 갱신한다.
|
|
2934
2940
|
// 메시지의 agent_id는 그 하위 에이전트 작업의 task_id라 재개 뒤에도 같은 행을 찾는다.
|
|
2935
|
-
function messageSubagent(session, message) {
|
|
2936
|
-
return findSubagent(session, message.parent_tool_use_id)
|
|
2937
|
-
|| (message.agent_id ? findSubagent(session, message.agent_id) : undefined);
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
2941
|
function recordSubagentMessage(session, message) {
|
|
2941
|
-
const running =
|
|
2942
|
+
const running = findSubagent(session, message.parent_tool_use_id)
|
|
2943
|
+
|| (message.agent_id ? findSubagent(session, message.agent_id) : undefined);
|
|
2942
2944
|
if (!running) return;
|
|
2943
2945
|
running.lastSeenAt = Date.now();
|
|
2944
2946
|
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
2945
2947
|
let toolChanged = false;
|
|
2946
2948
|
for (const block of content) {
|
|
2947
|
-
if (block.type
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
} else if (block.type === "tool_use") {
|
|
2951
|
-
running.tool = subagentToolLabel(block);
|
|
2952
|
-
toolChanged = true;
|
|
2953
|
-
emitSubagentLine(session, running.id, {
|
|
2954
|
-
kind: "tool",
|
|
2955
|
-
text: running.tool,
|
|
2956
|
-
toolUseId: block.id,
|
|
2957
|
-
});
|
|
2958
|
-
}
|
|
2949
|
+
if (block.type !== "tool_use") continue;
|
|
2950
|
+
running.tool = subagentToolLabel(block);
|
|
2951
|
+
toolChanged = true;
|
|
2959
2952
|
}
|
|
2960
2953
|
if (toolChanged) emitSubagents(session);
|
|
2961
2954
|
}
|
|
2962
2955
|
|
|
2963
|
-
function recordSubagentResult(session, message) {
|
|
2964
|
-
const running = messageSubagent(session, message);
|
|
2965
|
-
if (!running) return;
|
|
2966
|
-
running.lastSeenAt = Date.now();
|
|
2967
|
-
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
2968
|
-
for (const block of content) {
|
|
2969
|
-
if (block.type !== "tool_result") continue;
|
|
2970
|
-
emitSubagentLine(session, running.id, {
|
|
2971
|
-
kind: block.is_error ? "error" : "result",
|
|
2972
|
-
text: firstLine(toolOutput(block.content, message.tool_use_result), 200),
|
|
2973
|
-
toolUseId: block.tool_use_id,
|
|
2974
|
-
});
|
|
2975
|
-
}
|
|
2976
|
-
}
|
|
2977
|
-
|
|
2978
|
-
function emitSubagentLine(session, parentToolUseId, line) {
|
|
2979
|
-
notify("turn/subagent/line", {
|
|
2980
|
-
threadId: session.id,
|
|
2981
|
-
turnId: session.turn?.id,
|
|
2982
|
-
parentToolUseId,
|
|
2983
|
-
line,
|
|
2984
|
-
});
|
|
2985
|
-
}
|
|
2986
|
-
|
|
2987
2956
|
function subagentToolLabel(block) {
|
|
2988
2957
|
const name = block.name || "Tool";
|
|
2989
2958
|
const input = block.input || {};
|
|
@@ -3167,10 +3136,6 @@ function finishNotifiedSubagents(session, notifications) {
|
|
|
3167
3136
|
}
|
|
3168
3137
|
}
|
|
3169
3138
|
if (!running) continue;
|
|
3170
|
-
emitSubagentLine(session, running.id, {
|
|
3171
|
-
kind: notification.status === "completed" ? "result" : "error",
|
|
3172
|
-
text: notification.summary || notification.status || "완료됨",
|
|
3173
|
-
});
|
|
3174
3139
|
session.subagents.delete(running.id);
|
|
3175
3140
|
emitSubagents(session);
|
|
3176
3141
|
}
|
|
@@ -3181,11 +3146,8 @@ function processUser(session, message) {
|
|
|
3181
3146
|
// prompts. Re-processing their notification XML would invent a new turn.
|
|
3182
3147
|
if (message.isReplay === true) return;
|
|
3183
3148
|
// 자식 tool_result의 tool_use_id는 부모 세션의 것과 다른 공간이므로, 부모 흐름에
|
|
3184
|
-
//
|
|
3185
|
-
if (message.parent_tool_use_id)
|
|
3186
|
-
recordSubagentResult(session, message);
|
|
3187
|
-
return;
|
|
3188
|
-
}
|
|
3149
|
+
// 섞이지 않게 버린다.
|
|
3150
|
+
if (message.parent_tool_use_id) return;
|
|
3189
3151
|
const notifications = taskNotifications(message);
|
|
3190
3152
|
if (notifications.length) {
|
|
3191
3153
|
if (!session.turn) {
|
|
@@ -4113,7 +4075,7 @@ async function transcriptPath(id) {
|
|
|
4113
4075
|
* conversation. A resume can re-append earlier messages under their original
|
|
4114
4076
|
* uuids, so each uuid's first appearance wins.
|
|
4115
4077
|
*/
|
|
4116
|
-
function transcriptEntries(raw) {
|
|
4078
|
+
function transcriptEntries(raw, sidechain = false) {
|
|
4117
4079
|
const messages = [];
|
|
4118
4080
|
const seen = new Set();
|
|
4119
4081
|
for (const line of raw.split("\n")) {
|
|
@@ -4123,7 +4085,7 @@ function transcriptEntries(raw) {
|
|
|
4123
4085
|
} catch {
|
|
4124
4086
|
continue;
|
|
4125
4087
|
}
|
|
4126
|
-
if (!entry || typeof entry !== "object" || entry.isSidechain) continue;
|
|
4088
|
+
if (!entry || typeof entry !== "object" || (!sidechain && entry.isSidechain)) continue;
|
|
4127
4089
|
if (entry.type !== "user" && entry.type !== "assistant" && entry.type !== "system") continue;
|
|
4128
4090
|
if (typeof entry.uuid === "string" && entry.uuid) {
|
|
4129
4091
|
if (seen.has(entry.uuid)) continue;
|
|
@@ -4168,14 +4130,60 @@ function restoreArtifacts(id, messages) {
|
|
|
4168
4130
|
});
|
|
4169
4131
|
}
|
|
4170
4132
|
|
|
4171
|
-
async function transcriptMessagesAt(path) {
|
|
4133
|
+
async function transcriptMessagesAt(path, sidechain = false) {
|
|
4172
4134
|
let raw;
|
|
4173
4135
|
try {
|
|
4174
4136
|
raw = await readFile(path, "utf8");
|
|
4175
4137
|
} catch {
|
|
4176
4138
|
return [];
|
|
4177
4139
|
}
|
|
4178
|
-
return transcriptEntries(raw);
|
|
4140
|
+
return transcriptEntries(raw, sidechain);
|
|
4141
|
+
}
|
|
4142
|
+
|
|
4143
|
+
// 하위 에이전트 기록은 본 기록 옆 `<세션 id>/subagents/agent-<작업 id>.jsonl`에 쌓인다.
|
|
4144
|
+
// 행 id는 도구 호출 id라, 작업 id를 아직 모르거나 이미 끝난 행은 meta 파일의 toolUseId로 찾는다.
|
|
4145
|
+
async function subagentTranscriptPath(session, id) {
|
|
4146
|
+
const main = await transcriptPath(liveSessionId(session.id));
|
|
4147
|
+
if (!main) return null;
|
|
4148
|
+
const dir = join(main.slice(0, -".jsonl".length), "subagents");
|
|
4149
|
+
const taskId = findSubagent(session, id)?.taskId || (id.startsWith("task:") ? id.slice(5) : "");
|
|
4150
|
+
if (taskId) return join(dir, `agent-${taskId}.jsonl`);
|
|
4151
|
+
let names;
|
|
4152
|
+
try {
|
|
4153
|
+
names = await readdir(dir);
|
|
4154
|
+
} catch {
|
|
4155
|
+
return null;
|
|
4156
|
+
}
|
|
4157
|
+
for (const name of names) {
|
|
4158
|
+
if (!name.endsWith(".meta.json")) continue;
|
|
4159
|
+
try {
|
|
4160
|
+
const meta = JSON.parse(await readFile(join(dir, name), "utf8"));
|
|
4161
|
+
if (meta.toolUseId === id) return join(dir, name.replace(/\.meta\.json$/, ".jsonl"));
|
|
4162
|
+
} catch {}
|
|
4163
|
+
}
|
|
4164
|
+
return null;
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
// 화면이 띄운 하위 에이전트 하나만 지켜본다. 기록 파일이 커질 때만 다시 읽어 turns를 보낸다.
|
|
4168
|
+
function watchSubagent(session, id) {
|
|
4169
|
+
clearInterval(session.subagentWatch?.timer);
|
|
4170
|
+
session.subagentWatch = null;
|
|
4171
|
+
if (!id) return;
|
|
4172
|
+
const watch = { id, size: -1 };
|
|
4173
|
+
session.subagentWatch = watch;
|
|
4174
|
+
watch.timer = setInterval(() => sendSubagentHistory(session, watch).catch(() => {}), 1000);
|
|
4175
|
+
watch.timer.unref?.();
|
|
4176
|
+
setImmediate(() => sendSubagentHistory(session, watch).catch(() => {}));
|
|
4177
|
+
}
|
|
4178
|
+
|
|
4179
|
+
async function sendSubagentHistory(session, watch) {
|
|
4180
|
+
if (lookupSession(session.id) !== session) return watchSubagent(session, null);
|
|
4181
|
+
const path = await subagentTranscriptPath(session, watch.id);
|
|
4182
|
+
const size = (path && (await stat(path).catch(() => null))?.size) ?? -1;
|
|
4183
|
+
if (session.subagentWatch !== watch || size === watch.size) return;
|
|
4184
|
+
watch.size = size;
|
|
4185
|
+
const turns = path ? historyTurns(await transcriptMessagesAt(path, true)) : [];
|
|
4186
|
+
notify("turn/subagent/history", { threadId: session.id, id: watch.id, turns });
|
|
4179
4187
|
}
|
|
4180
4188
|
|
|
4181
4189
|
/** Every message of `id`'s conversation: the transcript file when it exists,
|
|
@@ -4352,7 +4360,9 @@ async function dispatch(method, params = {}) {
|
|
|
4352
4360
|
return {};
|
|
4353
4361
|
}
|
|
4354
4362
|
if (method === "marketplace/upgrade") {
|
|
4355
|
-
const
|
|
4363
|
+
const name = String(params.marketplaceName || "");
|
|
4364
|
+
const args = ["plugin", "marketplace", "update", ...(name ? [name] : [])];
|
|
4365
|
+
const output = await runClaudeCommand(params, args, CLAUDE_INSTALL_TIMEOUT);
|
|
4356
4366
|
return { message: output, consideredRoots: ["Claude marketplaces"], errors: [] };
|
|
4357
4367
|
}
|
|
4358
4368
|
if (method === "skills/list") return claudeSkills(params);
|
|
@@ -4360,9 +4370,7 @@ async function dispatch(method, params = {}) {
|
|
|
4360
4370
|
if (method === "account/read") {
|
|
4361
4371
|
return await runClaudeJson(params, ["auth", "status", "--json"]) || {};
|
|
4362
4372
|
}
|
|
4363
|
-
if (method === "plugin/reload") {
|
|
4364
|
-
return { message: "Claude 플러그인 설정을 다시 읽었습니다. 새 대화부터 적용됩니다." };
|
|
4365
|
-
}
|
|
4373
|
+
if (method === "plugin/reload") return {};
|
|
4366
4374
|
if (method === "permissions/status") return claudePermissionStatus(params);
|
|
4367
4375
|
if (method === "permissions/update") return updateClaudePermission(params);
|
|
4368
4376
|
if (method === "permissions/auto-mode") return setClaudeAutoModeDisabled(params);
|
|
@@ -4381,7 +4389,12 @@ async function dispatch(method, params = {}) {
|
|
|
4381
4389
|
return { known: true, running: Boolean(session.turn), turnId: session.turn?.id ?? null };
|
|
4382
4390
|
}
|
|
4383
4391
|
if (method === "session/start") {
|
|
4384
|
-
|
|
4392
|
+
// Claude Code가 뜨는 도중 가끔 충돌한다. 새 세션은 아직 쓴 것이 없고 다시 만들 때
|
|
4393
|
+
// 새 id를 받으므로 한 번 더 띄운다.
|
|
4394
|
+
const { session, account, usage } = await createSession(params).catch((error) => {
|
|
4395
|
+
if (!isClaudeCliCrash(error)) throw error;
|
|
4396
|
+
return createSession(params);
|
|
4397
|
+
});
|
|
4385
4398
|
return {
|
|
4386
4399
|
id: session.id,
|
|
4387
4400
|
thread: { id: session.id, turns: [] },
|
|
@@ -4451,6 +4464,11 @@ async function dispatch(method, params = {}) {
|
|
|
4451
4464
|
nextCursor: null,
|
|
4452
4465
|
};
|
|
4453
4466
|
}
|
|
4467
|
+
if (method === "session/watchSubagent") {
|
|
4468
|
+
const session = lookupSession(params.sessionId);
|
|
4469
|
+
if (session) watchSubagent(session, params.id ? String(params.id) : null);
|
|
4470
|
+
return {};
|
|
4471
|
+
}
|
|
4454
4472
|
if (method === "session/history") {
|
|
4455
4473
|
const id = liveSessionId(params.sessionId);
|
|
4456
4474
|
const messages = await sessionMessages(id, params.cwd);
|
|
@@ -4802,6 +4820,17 @@ async function runCommandTimeoutSelfTest() {
|
|
|
4802
4820
|
30_000,
|
|
4803
4821
|
);
|
|
4804
4822
|
if (quick !== "ok") throw new Error(`Claude command self-test returned ${quick}`);
|
|
4823
|
+
// 출력 뒤 실패로 끝난 읽기 명령은 한 번 더 실행해 그 결과를 쓴다.
|
|
4824
|
+
const marker = join(tmpdir(), `dvz-json-retry-${process.pid}`);
|
|
4825
|
+
const flaky = `const fs = require("fs"); const first = !fs.existsSync(${JSON.stringify(marker)});`
|
|
4826
|
+
+ ` fs.writeFileSync(${JSON.stringify(marker)}, ""); process.stdout.write(first ? "[0]" : "[1]");`
|
|
4827
|
+
+ " process.exitCode = first ? 1 : 0;";
|
|
4828
|
+
try {
|
|
4829
|
+
const retried = await runClaudeJson({ claudePath: process.execPath }, ["-e", flaky]);
|
|
4830
|
+
if (JSON.stringify(retried) !== "[1]") throw new Error(`Claude read retry self-test returned ${JSON.stringify(retried)}`);
|
|
4831
|
+
} finally {
|
|
4832
|
+
await rm(marker, { force: true });
|
|
4833
|
+
}
|
|
4805
4834
|
}
|
|
4806
4835
|
|
|
4807
4836
|
// 사유가 늘어나면 안내 없이 영어 원문만 남으므로, 설치된 SDK의 목록과 대조한다.
|
|
@@ -4885,6 +4914,33 @@ async function runSelfTest() {
|
|
|
4885
4914
|
|| JSON.stringify(restoredPrompts) !== JSON.stringify(["compact 이전 질문", "compact 이후 질문"])) {
|
|
4886
4915
|
throw new Error(`Claude compacted transcript self-test failed: ${JSON.stringify({ restored: restored.length, restoredPrompts })}`);
|
|
4887
4916
|
}
|
|
4917
|
+
// 하위 에이전트 기록 파일은 모든 줄이 sidechain이라, 그 파일을 읽을 때만 남긴다.
|
|
4918
|
+
const subagentPrompt = historyTurns(transcriptEntries(compactedTranscript, true))
|
|
4919
|
+
.flatMap((turn) => turn.items)
|
|
4920
|
+
.some((item) => item.type === "userMessage" && item.content?.[0]?.text === "subagent");
|
|
4921
|
+
if (!subagentPrompt) throw new Error("Claude subagent transcript self-test failed");
|
|
4922
|
+
// 끝나서 작업 id를 모르는 행도 meta 파일의 toolUseId로 그 기록을 찾는다.
|
|
4923
|
+
const previousConfigDir = process.env.CLAUDE_CONFIG_DIR;
|
|
4924
|
+
const configDir = join(tmpdir(), `devez-subagent-self-test-${randomUUID()}`);
|
|
4925
|
+
const agentDir = join(configDir, "projects", "D--work", "subagent-session", "subagents");
|
|
4926
|
+
await mkdir(agentDir, { recursive: true });
|
|
4927
|
+
await writeFile(join(configDir, "projects", "D--work", "subagent-session.jsonl"), "");
|
|
4928
|
+
await writeFile(join(agentDir, "agent-a1.meta.json"), JSON.stringify({ toolUseId: "toolu_child" }));
|
|
4929
|
+
await writeFile(join(agentDir, "agent-a1.jsonl"), JSON.stringify({
|
|
4930
|
+
type: "user", uuid: "child-1", isSidechain: true, message: { role: "user", content: "child prompt" },
|
|
4931
|
+
}));
|
|
4932
|
+
process.env.CLAUDE_CONFIG_DIR = configDir;
|
|
4933
|
+
try {
|
|
4934
|
+
const childPath = await subagentTranscriptPath({ id: "subagent-session", subagents: new Map() }, "toolu_child");
|
|
4935
|
+
const childTurns = childPath ? historyTurns(await transcriptMessagesAt(childPath, true)) : [];
|
|
4936
|
+
if (!childTurns.some((turn) => turn.items.some((item) => item.content?.[0]?.text === "child prompt"))) {
|
|
4937
|
+
throw new Error(`Claude subagent transcript lookup self-test failed: ${childPath}`);
|
|
4938
|
+
}
|
|
4939
|
+
} finally {
|
|
4940
|
+
if (previousConfigDir === undefined) delete process.env.CLAUDE_CONFIG_DIR;
|
|
4941
|
+
else process.env.CLAUDE_CONFIG_DIR = previousConfigDir;
|
|
4942
|
+
await rm(configDir, { recursive: true, force: true });
|
|
4943
|
+
}
|
|
4888
4944
|
// A resumed transcript lists its Artifact pages once each at their latest
|
|
4889
4945
|
// publish, read through the file's toolUseResult spelling.
|
|
4890
4946
|
const artifactTranscript = [
|
|
@@ -4929,6 +4985,12 @@ async function runSelfTest() {
|
|
|
4929
4985
|
|| !isNewerVersion("2.2.0", "2.1.999")) {
|
|
4930
4986
|
throw new Error("Claude installed CLI preference self-test failed");
|
|
4931
4987
|
}
|
|
4988
|
+
// 충돌 종료만 다시 띄운다. 일반 종료 코드와 다른 실패는 그대로 알린다.
|
|
4989
|
+
if (!isClaudeCliCrash(new Error("Claude Code process exited with code 3221226356"))
|
|
4990
|
+
|| isClaudeCliCrash(new Error("Claude Code process exited with code 1"))
|
|
4991
|
+
|| isClaudeCliCrash(new Error("Claude 세션을 찾을 수 없습니다: abc"))) {
|
|
4992
|
+
throw new Error("Claude CLI crash retry self-test failed");
|
|
4993
|
+
}
|
|
4932
4994
|
const latestOptions = makeOptions({ cwd: process.cwd(), permissionMode: "plan" }, "00000000-0000-4000-8000-000000000000");
|
|
4933
4995
|
if (latestOptions.permissionMode !== BOOTSTRAP_PERMISSION_MODE
|
|
4934
4996
|
|| latestOptions.perTaskStopAffordance !== true
|
|
@@ -6271,6 +6333,18 @@ async function runSelfTest() {
|
|
|
6271
6333
|
if (commandSession.subagents.size !== 0) {
|
|
6272
6334
|
throw new Error(`Claude foreground row outlived its tool result: ${JSON.stringify([...commandSession.subagents])}`);
|
|
6273
6335
|
}
|
|
6336
|
+
// 행은 순정처럼 name(없으면 종류)과 description을 함께 싣는다.
|
|
6337
|
+
for (const [input, name] of [
|
|
6338
|
+
[{ name: "auth_review", description: "인증 흐름 조사", subagent_type: "Explore" }, "auth_review"],
|
|
6339
|
+
[{ description: "인증 흐름 조사", subagent_type: "Explore" }, "Explore"],
|
|
6340
|
+
]) {
|
|
6341
|
+
startSubagent(commandSession, { id: "toolu_named", input });
|
|
6342
|
+
const row = findSubagent(commandSession, "toolu_named");
|
|
6343
|
+
if (row?.name !== name || row.description !== "인증 흐름 조사") {
|
|
6344
|
+
throw new Error(`Claude subagent row name self-test failed: ${JSON.stringify(row)}`);
|
|
6345
|
+
}
|
|
6346
|
+
commandSession.subagents.delete("toolu_named");
|
|
6347
|
+
}
|
|
6274
6348
|
// 클라우드로 띄운 원격 Agent도 접수 결과에서 줄이 남고, 종료 통지가 거둔다.
|
|
6275
6349
|
commandSession.tools.set("toolu_remote", { name: "Agent", input: { name: "cloud_review" }, item: { id: "toolu_remote" } });
|
|
6276
6350
|
startSubagent(commandSession, { id: "toolu_remote", input: { name: "cloud_review" } });
|
|
@@ -6498,9 +6572,7 @@ async function runSelfTest() {
|
|
|
6498
6572
|
&& event.params.item.text === "automatic summary one")
|
|
6499
6573
|
|| !lifecycleEvents.some((event) => event.method === "item/completed"
|
|
6500
6574
|
&& event.params?.item?.type === "agentMessage"
|
|
6501
|
-
&& event.params.item.text === "automatic summary two")
|
|
6502
|
-
|| !lifecycleEvents.some((event) => event.method === "turn/subagent/line"
|
|
6503
|
-
&& event.params?.line?.kind === "error")) {
|
|
6575
|
+
&& event.params.item.text === "automatic summary two")) {
|
|
6504
6576
|
throw new Error(`Claude subagent lifecycle events self-test failed: ${lifecycleMethods}`);
|
|
6505
6577
|
}
|
|
6506
6578
|
const openingSession = {
|