devez-vibe 1.9.53 → 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 +142 -42
- 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();
|
|
@@ -2569,12 +2581,16 @@ const SUBAGENT_PULSE_MS = 5000;
|
|
|
2569
2581
|
// 자체 시험에서 "오래 조용한 행"을 만들 때 쓰는 나이.
|
|
2570
2582
|
const IDLE_ROW_AGE_MS = 120_000;
|
|
2571
2583
|
|
|
2584
|
+
// 순정 Claude Code처럼 행에 Agent의 name과 description을 함께 보인다. description의
|
|
2585
|
+
// 언어는 매 턴 지침이 화면 언어에 맞춘다.
|
|
2572
2586
|
function startSubagent(session, block) {
|
|
2573
2587
|
const input = block.input || {};
|
|
2588
|
+
const description = firstLine(input.description || "", SUBAGENT_DESCRIPTION_LIMIT);
|
|
2574
2589
|
const existing = findSubagent(session, block.id);
|
|
2575
2590
|
if (existing) {
|
|
2576
2591
|
existing.toolUseId = block.id;
|
|
2577
2592
|
existing.name = firstLine(input.name || input.subagent_type || input.agentType || existing.name || "agent", 40);
|
|
2593
|
+
existing.description = description || existing.description;
|
|
2578
2594
|
existing.backgroundTask = false;
|
|
2579
2595
|
existing.lastSeenAt = Date.now();
|
|
2580
2596
|
emitSubagents(session);
|
|
@@ -2586,8 +2602,7 @@ function startSubagent(session, block) {
|
|
|
2586
2602
|
taskId: "",
|
|
2587
2603
|
background: false,
|
|
2588
2604
|
name: firstLine(input.name || input.subagent_type || input.agentType || "agent", 40),
|
|
2589
|
-
|
|
2590
|
-
description: "",
|
|
2605
|
+
description,
|
|
2591
2606
|
tool: "",
|
|
2592
2607
|
startedAt: Date.now(),
|
|
2593
2608
|
lastSeenAt: Date.now(),
|
|
@@ -2674,7 +2689,7 @@ function resumeBackgroundSubagent(session, toolUseId, pending, result) {
|
|
|
2674
2689
|
taskId,
|
|
2675
2690
|
background: true,
|
|
2676
2691
|
name: known?.name || "agent",
|
|
2677
|
-
description: "",
|
|
2692
|
+
description: known?.description || "",
|
|
2678
2693
|
tool: "",
|
|
2679
2694
|
startedAt: Date.now(),
|
|
2680
2695
|
lastSeenAt: Date.now(),
|
|
@@ -4060,7 +4075,7 @@ async function transcriptPath(id) {
|
|
|
4060
4075
|
* conversation. A resume can re-append earlier messages under their original
|
|
4061
4076
|
* uuids, so each uuid's first appearance wins.
|
|
4062
4077
|
*/
|
|
4063
|
-
function transcriptEntries(raw) {
|
|
4078
|
+
function transcriptEntries(raw, sidechain = false) {
|
|
4064
4079
|
const messages = [];
|
|
4065
4080
|
const seen = new Set();
|
|
4066
4081
|
for (const line of raw.split("\n")) {
|
|
@@ -4070,7 +4085,7 @@ function transcriptEntries(raw) {
|
|
|
4070
4085
|
} catch {
|
|
4071
4086
|
continue;
|
|
4072
4087
|
}
|
|
4073
|
-
if (!entry || typeof entry !== "object" || entry.isSidechain) continue;
|
|
4088
|
+
if (!entry || typeof entry !== "object" || (!sidechain && entry.isSidechain)) continue;
|
|
4074
4089
|
if (entry.type !== "user" && entry.type !== "assistant" && entry.type !== "system") continue;
|
|
4075
4090
|
if (typeof entry.uuid === "string" && entry.uuid) {
|
|
4076
4091
|
if (seen.has(entry.uuid)) continue;
|
|
@@ -4115,14 +4130,60 @@ function restoreArtifacts(id, messages) {
|
|
|
4115
4130
|
});
|
|
4116
4131
|
}
|
|
4117
4132
|
|
|
4118
|
-
async function transcriptMessagesAt(path) {
|
|
4133
|
+
async function transcriptMessagesAt(path, sidechain = false) {
|
|
4119
4134
|
let raw;
|
|
4120
4135
|
try {
|
|
4121
4136
|
raw = await readFile(path, "utf8");
|
|
4122
4137
|
} catch {
|
|
4123
4138
|
return [];
|
|
4124
4139
|
}
|
|
4125
|
-
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 });
|
|
4126
4187
|
}
|
|
4127
4188
|
|
|
4128
4189
|
/** Every message of `id`'s conversation: the transcript file when it exists,
|
|
@@ -4299,7 +4360,9 @@ async function dispatch(method, params = {}) {
|
|
|
4299
4360
|
return {};
|
|
4300
4361
|
}
|
|
4301
4362
|
if (method === "marketplace/upgrade") {
|
|
4302
|
-
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);
|
|
4303
4366
|
return { message: output, consideredRoots: ["Claude marketplaces"], errors: [] };
|
|
4304
4367
|
}
|
|
4305
4368
|
if (method === "skills/list") return claudeSkills(params);
|
|
@@ -4307,9 +4370,7 @@ async function dispatch(method, params = {}) {
|
|
|
4307
4370
|
if (method === "account/read") {
|
|
4308
4371
|
return await runClaudeJson(params, ["auth", "status", "--json"]) || {};
|
|
4309
4372
|
}
|
|
4310
|
-
if (method === "plugin/reload") {
|
|
4311
|
-
return { message: "Claude 플러그인 설정을 다시 읽었습니다. 새 대화부터 적용됩니다." };
|
|
4312
|
-
}
|
|
4373
|
+
if (method === "plugin/reload") return {};
|
|
4313
4374
|
if (method === "permissions/status") return claudePermissionStatus(params);
|
|
4314
4375
|
if (method === "permissions/update") return updateClaudePermission(params);
|
|
4315
4376
|
if (method === "permissions/auto-mode") return setClaudeAutoModeDisabled(params);
|
|
@@ -4328,7 +4389,12 @@ async function dispatch(method, params = {}) {
|
|
|
4328
4389
|
return { known: true, running: Boolean(session.turn), turnId: session.turn?.id ?? null };
|
|
4329
4390
|
}
|
|
4330
4391
|
if (method === "session/start") {
|
|
4331
|
-
|
|
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
|
+
});
|
|
4332
4398
|
return {
|
|
4333
4399
|
id: session.id,
|
|
4334
4400
|
thread: { id: session.id, turns: [] },
|
|
@@ -4398,6 +4464,11 @@ async function dispatch(method, params = {}) {
|
|
|
4398
4464
|
nextCursor: null,
|
|
4399
4465
|
};
|
|
4400
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
|
+
}
|
|
4401
4472
|
if (method === "session/history") {
|
|
4402
4473
|
const id = liveSessionId(params.sessionId);
|
|
4403
4474
|
const messages = await sessionMessages(id, params.cwd);
|
|
@@ -4429,15 +4500,6 @@ async function dispatch(method, params = {}) {
|
|
|
4429
4500
|
}
|
|
4430
4501
|
return {};
|
|
4431
4502
|
}
|
|
4432
|
-
// 하위 에이전트 행의 x. 행은 도구 호출 id로 알려도 stop_task는 작업 id를 받으므로,
|
|
4433
|
-
// task_started가 아직 오지 않은 행은 멈출 수 없다. 행은 종료 통지가 거둔다.
|
|
4434
|
-
if (method === "session/stopTask") {
|
|
4435
|
-
const session = lookupSession(params.sessionId);
|
|
4436
|
-
const running = session && findSubagent(session, String(params.id || ""));
|
|
4437
|
-
if (!running?.taskId) throw new Error("아직 멈출 수 있는 작업 정보가 없습니다. 잠시 후 다시 시도하세요.");
|
|
4438
|
-
await session.query.stopTask(running.taskId);
|
|
4439
|
-
return {};
|
|
4440
|
-
}
|
|
4441
4503
|
if (method === "session/compact") {
|
|
4442
4504
|
return startPrompt({ ...params, input: [{ type: "text", text: "/compact" }] });
|
|
4443
4505
|
}
|
|
@@ -4758,6 +4820,17 @@ async function runCommandTimeoutSelfTest() {
|
|
|
4758
4820
|
30_000,
|
|
4759
4821
|
);
|
|
4760
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
|
+
}
|
|
4761
4834
|
}
|
|
4762
4835
|
|
|
4763
4836
|
// 사유가 늘어나면 안내 없이 영어 원문만 남으므로, 설치된 SDK의 목록과 대조한다.
|
|
@@ -4841,6 +4914,33 @@ async function runSelfTest() {
|
|
|
4841
4914
|
|| JSON.stringify(restoredPrompts) !== JSON.stringify(["compact 이전 질문", "compact 이후 질문"])) {
|
|
4842
4915
|
throw new Error(`Claude compacted transcript self-test failed: ${JSON.stringify({ restored: restored.length, restoredPrompts })}`);
|
|
4843
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
|
+
}
|
|
4844
4944
|
// A resumed transcript lists its Artifact pages once each at their latest
|
|
4845
4945
|
// publish, read through the file's toolUseResult spelling.
|
|
4846
4946
|
const artifactTranscript = [
|
|
@@ -4885,6 +4985,12 @@ async function runSelfTest() {
|
|
|
4885
4985
|
|| !isNewerVersion("2.2.0", "2.1.999")) {
|
|
4886
4986
|
throw new Error("Claude installed CLI preference self-test failed");
|
|
4887
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
|
+
}
|
|
4888
4994
|
const latestOptions = makeOptions({ cwd: process.cwd(), permissionMode: "plan" }, "00000000-0000-4000-8000-000000000000");
|
|
4889
4995
|
if (latestOptions.permissionMode !== BOOTSTRAP_PERMISSION_MODE
|
|
4890
4996
|
|| latestOptions.perTaskStopAffordance !== true
|
|
@@ -5527,24 +5633,6 @@ async function runSelfTest() {
|
|
|
5527
5633
|
await dispatch("session/interrupt", { sessionId: limitSession.id });
|
|
5528
5634
|
resumeAfterUsageLimit(limitSession, cancelledWait, cancelledWait.resetsAt);
|
|
5529
5635
|
if (limitSession.turn || retryInputs.length !== 1) throw new Error("Cancelled usage limit resumed");
|
|
5530
|
-
// 행의 x는 행 id가 아니라 SDK 작업 id로 멈춘다. 작업 id를 아직 모르는 행은 거절한다.
|
|
5531
|
-
const stoppedTasks = [];
|
|
5532
|
-
const stopSession = {
|
|
5533
|
-
id: "stop-task-self-test",
|
|
5534
|
-
query: { async stopTask(taskId) { stoppedTasks.push(taskId); } },
|
|
5535
|
-
subagents: new Map([
|
|
5536
|
-
["toolu_stop", { id: "toolu_stop", toolUseId: "toolu_stop", taskId: "agent-stop" }],
|
|
5537
|
-
["toolu_pending", { id: "toolu_pending", toolUseId: "toolu_pending", taskId: "" }],
|
|
5538
|
-
]),
|
|
5539
|
-
};
|
|
5540
|
-
sessions.set(stopSession.id, stopSession);
|
|
5541
|
-
await dispatch("session/stopTask", { sessionId: stopSession.id, id: "toolu_stop" });
|
|
5542
|
-
const pendingStop = await dispatch("session/stopTask", { sessionId: stopSession.id, id: "toolu_pending" })
|
|
5543
|
-
.then(() => null, (error) => error);
|
|
5544
|
-
sessions.delete(stopSession.id);
|
|
5545
|
-
if (stoppedTasks.join() !== "agent-stop" || !pendingStop) {
|
|
5546
|
-
throw new Error(`Claude per-task stop self-test failed: ${stoppedTasks}`);
|
|
5547
|
-
}
|
|
5548
5636
|
// Ultracode rides with each prompt's effort; a steer that becomes a turn keeps it.
|
|
5549
5637
|
const flagCalls = [];
|
|
5550
5638
|
const ultracodeSession = {
|
|
@@ -6245,6 +6333,18 @@ async function runSelfTest() {
|
|
|
6245
6333
|
if (commandSession.subagents.size !== 0) {
|
|
6246
6334
|
throw new Error(`Claude foreground row outlived its tool result: ${JSON.stringify([...commandSession.subagents])}`);
|
|
6247
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
|
+
}
|
|
6248
6348
|
// 클라우드로 띄운 원격 Agent도 접수 결과에서 줄이 남고, 종료 통지가 거둔다.
|
|
6249
6349
|
commandSession.tools.set("toolu_remote", { name: "Agent", input: { name: "cloud_review" }, item: { id: "toolu_remote" } });
|
|
6250
6350
|
startSubagent(commandSession, { id: "toolu_remote", input: { name: "cloud_review" } });
|