devez-vibe 1.9.40 → 1.9.41
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 +151 -74
- package/package.json +1 -1
package/bin/dvz.exe
CHANGED
|
Binary file
|
|
@@ -1857,15 +1857,15 @@ function prepareTaskPlanForCreate(tasks, subject) {
|
|
|
1857
1857
|
// 한 단계에 걸린 시간은 진행 중으로 바뀐 순간과 완료된 순간의 차이다. 기록에는
|
|
1858
1858
|
// 항목마다 시각이 남으므로, 다시 읽을 때도 같은 방식으로 되살릴 수 있다.
|
|
1859
1859
|
function markTaskStatus(task, status, at) {
|
|
1860
|
-
if (task.status !== status) {
|
|
1861
|
-
if (status === "in_progress") {
|
|
1862
|
-
task.startedAt = at;
|
|
1863
|
-
delete task.elapsedMs;
|
|
1864
|
-
} else if (status === "completed" && task.startedAt != null && at != null) {
|
|
1865
|
-
task.elapsedMs = Math.max(0, at - task.startedAt);
|
|
1866
|
-
} else if (status === "pending") {
|
|
1867
|
-
delete task.startedAt;
|
|
1868
|
-
delete task.elapsedMs;
|
|
1860
|
+
if (task.status !== status) {
|
|
1861
|
+
if (status === "in_progress") {
|
|
1862
|
+
task.startedAt = at;
|
|
1863
|
+
delete task.elapsedMs;
|
|
1864
|
+
} else if (status === "completed" && task.startedAt != null && at != null) {
|
|
1865
|
+
task.elapsedMs = Math.max(0, at - task.startedAt);
|
|
1866
|
+
} else if (status === "pending") {
|
|
1867
|
+
delete task.startedAt;
|
|
1868
|
+
delete task.elapsedMs;
|
|
1869
1869
|
}
|
|
1870
1870
|
}
|
|
1871
1871
|
task.status = status;
|
|
@@ -1987,19 +1987,19 @@ function updatePlanFromToolResult(session, pending, message) {
|
|
|
1987
1987
|
const turnId = session.turn?.id;
|
|
1988
1988
|
const previous = session.tasks;
|
|
1989
1989
|
session.tasks = new Map();
|
|
1990
|
-
for (const task of value.tasks || value) {
|
|
1991
|
-
const id = String(task.id);
|
|
1992
|
-
const restored = {
|
|
1993
|
-
id,
|
|
1994
|
-
subject: task.subject || task.description || "작업",
|
|
1995
|
-
status: previous.get(id)?.status || "pending",
|
|
1990
|
+
for (const task of value.tasks || value) {
|
|
1991
|
+
const id = String(task.id);
|
|
1992
|
+
const restored = {
|
|
1993
|
+
id,
|
|
1994
|
+
subject: task.subject || task.description || "작업",
|
|
1995
|
+
status: previous.get(id)?.status || "pending",
|
|
1996
1996
|
// 이 턴에서 다룬 적 없는 작업은 이전 턴의 것이므로 원래 turnId를 지켜 준다.
|
|
1997
|
-
turnId: previous.get(id)?.turnId ?? turnId,
|
|
1998
|
-
startedAt: previous.get(id)?.startedAt,
|
|
1999
|
-
elapsedMs: previous.get(id)?.elapsedMs,
|
|
2000
|
-
};
|
|
2001
|
-
markTaskStatus(restored, task.status || "pending", Date.now());
|
|
2002
|
-
session.tasks.set(id, restored);
|
|
1997
|
+
turnId: previous.get(id)?.turnId ?? turnId,
|
|
1998
|
+
startedAt: previous.get(id)?.startedAt,
|
|
1999
|
+
elapsedMs: previous.get(id)?.elapsedMs,
|
|
2000
|
+
};
|
|
2001
|
+
markTaskStatus(restored, task.status || "pending", Date.now());
|
|
2002
|
+
session.tasks.set(id, restored);
|
|
2003
2003
|
}
|
|
2004
2004
|
session.tasks = latestTaskPlan(session.tasks);
|
|
2005
2005
|
emitPlan(session);
|
|
@@ -2027,11 +2027,11 @@ function emitPlan(session) {
|
|
|
2027
2027
|
notify("turn/plan/updated", {
|
|
2028
2028
|
threadId: session.id,
|
|
2029
2029
|
turnId: session.turn?.id,
|
|
2030
|
-
plan: [...session.tasks.values()].map((task, index) => ({
|
|
2031
|
-
id: task.id,
|
|
2032
|
-
step: numberedTaskSubject(task.subject, index),
|
|
2033
|
-
status: planStatus(task.status),
|
|
2034
|
-
elapsedMs: task.elapsedMs ?? null,
|
|
2030
|
+
plan: [...session.tasks.values()].map((task, index) => ({
|
|
2031
|
+
id: task.id,
|
|
2032
|
+
step: numberedTaskSubject(task.subject, index),
|
|
2033
|
+
status: planStatus(task.status),
|
|
2034
|
+
elapsedMs: task.elapsedMs ?? null,
|
|
2035
2035
|
})),
|
|
2036
2036
|
});
|
|
2037
2037
|
}
|
|
@@ -2871,13 +2871,28 @@ async function processResult(session, message) {
|
|
|
2871
2871
|
notify("warning", { threadId: session.id, provider: "Claude", message: `Claude response error: ${text}` });
|
|
2872
2872
|
}
|
|
2873
2873
|
if (error && waitForUsageLimit(session)) return;
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2874
|
+
// A steer the CLI could not fold into this run gets a run of its own, which
|
|
2875
|
+
// `queued_turn_count` announces. Closing the turn here dropped the host's
|
|
2876
|
+
// spinner and filed a completion until that answer's first frame arrived.
|
|
2877
|
+
// A count of 0 can still hide a steer read just after the result; the steer
|
|
2878
|
+
// debt opens a fresh turn for it then, as before.
|
|
2879
|
+
const steerFollows = !error && !interrupted && !assistantError
|
|
2880
|
+
&& session.steerPending > 0 && message.queued_turn_count > 0;
|
|
2881
|
+
if (steerFollows) {
|
|
2882
|
+
session.steerPending -= 1;
|
|
2883
|
+
session.turn.awaitingSteer = true;
|
|
2884
|
+
releaseHeldProgress(session);
|
|
2885
|
+
flushPendingPlan(session);
|
|
2886
|
+
clearForegroundSubagents(session);
|
|
2887
|
+
} else {
|
|
2888
|
+
finishTurn(session, error, message.duration_ms);
|
|
2889
|
+
}
|
|
2890
|
+
// Not awaited: the next run's frames queue behind this message, and the
|
|
2891
|
+
// account lookup used to hold them back.
|
|
2892
|
+
Promise.all([safeAccount(session.query), safeUsage(session.query)])
|
|
2893
|
+
.then(([account, usage]) => notify("claude/account/updated", { threadId: session.id, account, usage }))
|
|
2894
|
+
.catch(() => {});
|
|
2895
|
+
if (!steerFollows) await runPendingPrompt(session);
|
|
2881
2896
|
}
|
|
2882
2897
|
|
|
2883
2898
|
// Compaction ends the turn's only assistant message, so the context figure would
|
|
@@ -2975,12 +2990,13 @@ function beginUntrackedTurn(session, message) {
|
|
|
2975
2990
|
return true;
|
|
2976
2991
|
}
|
|
2977
2992
|
|
|
2978
|
-
async function consumeMessage(session, message) {
|
|
2979
|
-
adoptSessionId(session, message.session_id);
|
|
2993
|
+
async function consumeMessage(session, message) {
|
|
2994
|
+
adoptSessionId(session, message.session_id);
|
|
2980
2995
|
beginUntrackedTurn(session, message);
|
|
2981
2996
|
// A queued SDK response may beat the timer. It already resumes the work.
|
|
2982
2997
|
if (!message.parent_tool_use_id && (message.type === "assistant" || message.type === "stream_event")) {
|
|
2983
2998
|
clearUsageLimitWait(session);
|
|
2999
|
+
if (session.turn) session.turn.awaitingSteer = false;
|
|
2984
3000
|
}
|
|
2985
3001
|
if (message.type === "stream_event") {
|
|
2986
3002
|
if (message.event?.type === "content_block_delta" && (message.event?.delta?.text || message.event?.delta?.thinking)) {
|
|
@@ -3356,20 +3372,20 @@ function historyState(messages) {
|
|
|
3356
3372
|
} else if (pending.name === "TaskList" && Array.isArray(message.tool_use_result?.tasks)) {
|
|
3357
3373
|
const known = new Map(tasks);
|
|
3358
3374
|
tasks.clear();
|
|
3359
|
-
for (const task of message.tool_use_result.tasks) {
|
|
3360
|
-
const previous = known.get(String(task.id));
|
|
3361
|
-
const restored = {
|
|
3375
|
+
for (const task of message.tool_use_result.tasks) {
|
|
3376
|
+
const previous = known.get(String(task.id));
|
|
3377
|
+
const restored = {
|
|
3362
3378
|
id: String(task.id),
|
|
3363
3379
|
subject: task.subject || "작업",
|
|
3364
|
-
status: previous?.status || "pending",
|
|
3380
|
+
status: previous?.status || "pending",
|
|
3365
3381
|
turnId: previous?.turnId ?? turn.id,
|
|
3366
3382
|
// A listing restates the plan; it does not re-run it, so the
|
|
3367
3383
|
// timings already measured for these steps have to survive it.
|
|
3368
3384
|
startedAt: previous?.startedAt,
|
|
3369
3385
|
elapsedMs: previous?.elapsedMs,
|
|
3370
|
-
};
|
|
3371
|
-
markTaskStatus(restored, task.status || "pending", messageTime(message));
|
|
3372
|
-
tasks.set(String(task.id), restored);
|
|
3386
|
+
};
|
|
3387
|
+
markTaskStatus(restored, task.status || "pending", messageTime(message));
|
|
3388
|
+
tasks.set(String(task.id), restored);
|
|
3373
3389
|
}
|
|
3374
3390
|
const current = latestTaskPlan(tasks);
|
|
3375
3391
|
tasks.clear();
|
|
@@ -3392,9 +3408,9 @@ function historyState(messages) {
|
|
|
3392
3408
|
const text = [...tasks.values()].map((task, index) => `${task.status === "completed" ? "✓" : task.status === "in_progress" ? "▸" : "□"} ${numberedTaskSubject(task.subject, index)}`).join("\n");
|
|
3393
3409
|
// The text alone cannot say how long a step took, so the measured times ride
|
|
3394
3410
|
// alongside it and the restored plan shows its total instead of zero.
|
|
3395
|
-
const steps = [...tasks.values()].map((task, index) => ({
|
|
3396
|
-
id: task.id,
|
|
3397
|
-
step: numberedTaskSubject(task.subject, index),
|
|
3411
|
+
const steps = [...tasks.values()].map((task, index) => ({
|
|
3412
|
+
id: task.id,
|
|
3413
|
+
step: numberedTaskSubject(task.subject, index),
|
|
3398
3414
|
status: task.status || "pending",
|
|
3399
3415
|
elapsedMs: task.elapsedMs ?? null,
|
|
3400
3416
|
}));
|
|
@@ -3884,6 +3900,9 @@ async function dispatch(method, params = {}) {
|
|
|
3884
3900
|
if (session.turn === turn) delete turn.interruptRequested;
|
|
3885
3901
|
throw error;
|
|
3886
3902
|
}
|
|
3903
|
+
// Kept open for a steer whose run has not begun: nothing is running to
|
|
3904
|
+
// send the result that would close it.
|
|
3905
|
+
if (session.turn === turn && turn.awaitingSteer) finishTurn(session, null);
|
|
3887
3906
|
}
|
|
3888
3907
|
return {};
|
|
3889
3908
|
}
|
|
@@ -4460,35 +4479,35 @@ async function runSelfTest() {
|
|
|
4460
4479
|
at(taskUse("timed-done", "timed-done", "TaskUpdate", { taskId: "t1", status: "completed" }), "2026-08-07T01:00:09.000Z"),
|
|
4461
4480
|
];
|
|
4462
4481
|
const timedPlan = historyState(timedMessages).turns.at(-1)?.items.find((item) => item.type === "plan");
|
|
4463
|
-
if (timedPlan?.steps?.[0]?.elapsedMs !== 6000 || timedPlan.steps[0].status !== "completed") {
|
|
4464
|
-
throw new Error(`Claude plan step timing self-test failed: ${JSON.stringify(timedPlan)}`);
|
|
4465
|
-
}
|
|
4466
|
-
const timedLiveSession = {
|
|
4467
|
-
id: "timed-live-plan",
|
|
4468
|
-
turn: { id: "timed-live-turn" },
|
|
4469
|
-
tasks: historyState(timedMessages).tasks,
|
|
4470
|
-
};
|
|
4471
|
-
const timedLiveEvents = [];
|
|
4472
|
-
const timedLiveWrite = process.stdout.write;
|
|
4473
|
-
process.stdout.write = (chunk) => { timedLiveEvents.push(JSON.parse(String(chunk))); return true; };
|
|
4474
|
-
try {
|
|
4475
|
-
updatePlanFromToolResult(timedLiveSession, { name: "TaskList" }, {
|
|
4476
|
-
tool_use_result: { tasks: [{ id: "t1", subject: "1. 확인한 설정", status: "completed" }] },
|
|
4477
|
-
});
|
|
4478
|
-
} finally {
|
|
4479
|
-
process.stdout.write = timedLiveWrite;
|
|
4480
|
-
}
|
|
4481
|
-
const liveMeasured = timedLiveEvents.at(-1)?.params?.plan?.[0];
|
|
4482
|
-
if (liveMeasured?.elapsedMs !== 6000 || timedLiveSession.tasks.get("t1")?.elapsedMs !== 6000) {
|
|
4483
|
-
throw new Error(`Claude live plan timing self-test failed: ${JSON.stringify(liveMeasured)}`);
|
|
4484
|
-
}
|
|
4485
|
-
const restartedTask = { status: "pending" };
|
|
4486
|
-
markTaskStatus(restartedTask, "in_progress", 1000);
|
|
4487
|
-
markTaskStatus(restartedTask, "completed", 7000);
|
|
4488
|
-
markTaskStatus(restartedTask, "in_progress", 9000);
|
|
4489
|
-
markTaskStatus(restartedTask, "completed", 10000);
|
|
4490
|
-
if (restartedTask.elapsedMs !== 1000) throw new Error("Claude restarted task timing self-test failed");
|
|
4491
|
-
const skippedTasks = new Map([
|
|
4482
|
+
if (timedPlan?.steps?.[0]?.elapsedMs !== 6000 || timedPlan.steps[0].status !== "completed") {
|
|
4483
|
+
throw new Error(`Claude plan step timing self-test failed: ${JSON.stringify(timedPlan)}`);
|
|
4484
|
+
}
|
|
4485
|
+
const timedLiveSession = {
|
|
4486
|
+
id: "timed-live-plan",
|
|
4487
|
+
turn: { id: "timed-live-turn" },
|
|
4488
|
+
tasks: historyState(timedMessages).tasks,
|
|
4489
|
+
};
|
|
4490
|
+
const timedLiveEvents = [];
|
|
4491
|
+
const timedLiveWrite = process.stdout.write;
|
|
4492
|
+
process.stdout.write = (chunk) => { timedLiveEvents.push(JSON.parse(String(chunk))); return true; };
|
|
4493
|
+
try {
|
|
4494
|
+
updatePlanFromToolResult(timedLiveSession, { name: "TaskList" }, {
|
|
4495
|
+
tool_use_result: { tasks: [{ id: "t1", subject: "1. 확인한 설정", status: "completed" }] },
|
|
4496
|
+
});
|
|
4497
|
+
} finally {
|
|
4498
|
+
process.stdout.write = timedLiveWrite;
|
|
4499
|
+
}
|
|
4500
|
+
const liveMeasured = timedLiveEvents.at(-1)?.params?.plan?.[0];
|
|
4501
|
+
if (liveMeasured?.elapsedMs !== 6000 || timedLiveSession.tasks.get("t1")?.elapsedMs !== 6000) {
|
|
4502
|
+
throw new Error(`Claude live plan timing self-test failed: ${JSON.stringify(liveMeasured)}`);
|
|
4503
|
+
}
|
|
4504
|
+
const restartedTask = { status: "pending" };
|
|
4505
|
+
markTaskStatus(restartedTask, "in_progress", 1000);
|
|
4506
|
+
markTaskStatus(restartedTask, "completed", 7000);
|
|
4507
|
+
markTaskStatus(restartedTask, "in_progress", 9000);
|
|
4508
|
+
markTaskStatus(restartedTask, "completed", 10000);
|
|
4509
|
+
if (restartedTask.elapsedMs !== 1000) throw new Error("Claude restarted task timing self-test failed");
|
|
4510
|
+
const skippedTasks = new Map([
|
|
4492
4511
|
["1", { id: "1", subject: "1. 조사", status: "pending" }],
|
|
4493
4512
|
["2", { id: "2", subject: "2. 분석", status: "pending" }],
|
|
4494
4513
|
["3", { id: "3", subject: "3. 검증", status: "pending" }],
|
|
@@ -4869,6 +4888,64 @@ async function runSelfTest() {
|
|
|
4869
4888
|
if (authTurn?.error?.codexErrorInfo !== "unauthorized" || !authTurn.error.message.includes("401")) {
|
|
4870
4889
|
throw new Error(`Claude login failure lost its tag: ${JSON.stringify(authTurn)}`);
|
|
4871
4890
|
}
|
|
4891
|
+
// A steer answered in a run of its own stays in the turn it joined.
|
|
4892
|
+
const steerNotices = [];
|
|
4893
|
+
const steerWrite = process.stdout.write;
|
|
4894
|
+
process.stdout.write = (chunk) => { steerNotices.push(JSON.parse(String(chunk))); return true; };
|
|
4895
|
+
const steerSession = {
|
|
4896
|
+
...automaticTurnSession,
|
|
4897
|
+
id: "steer-continuation-self-test",
|
|
4898
|
+
turn: null,
|
|
4899
|
+
streamBlocks: new Map(),
|
|
4900
|
+
tools: new Map(),
|
|
4901
|
+
tasks: new Map(),
|
|
4902
|
+
subagents: new Map(),
|
|
4903
|
+
pendingPrompts: [],
|
|
4904
|
+
query: { ...automaticTurnSession.query, async interrupt() {} },
|
|
4905
|
+
};
|
|
4906
|
+
sessions.set(steerSession.id, steerSession);
|
|
4907
|
+
const steerResult = (queued) => ({ type: "result", is_error: false, stop_reason: "end_turn", modelUsage: {}, queued_turn_count: queued });
|
|
4908
|
+
const steerRun = { type: "stream_event", parent_tool_use_id: null, event: { type: "message_start", message: {} } };
|
|
4909
|
+
const steerLifecycle = () => steerNotices
|
|
4910
|
+
.filter((notice) => notice.method === "turn/started" || notice.method === "turn/completed")
|
|
4911
|
+
.map((notice) => notice.method)
|
|
4912
|
+
.join();
|
|
4913
|
+
try {
|
|
4914
|
+
const steerTurn = beginTurn(steerSession);
|
|
4915
|
+
steerSession.steerPending = 1;
|
|
4916
|
+
await consumeMessage(steerSession, steerResult(1));
|
|
4917
|
+
await consumeMessage(steerSession, steerRun);
|
|
4918
|
+
if (steerSession.turn?.id !== steerTurn || steerSession.steerPending !== 0) {
|
|
4919
|
+
throw new Error("Claude steer run left the turn it joined");
|
|
4920
|
+
}
|
|
4921
|
+
await consumeMessage(steerSession, steerResult(0));
|
|
4922
|
+
if (steerLifecycle() !== "turn/started,turn/completed") {
|
|
4923
|
+
throw new Error(`Claude steer run reopened its turn: ${steerLifecycle()}`);
|
|
4924
|
+
}
|
|
4925
|
+
// A steer read only after the result still gets a turn from the debt.
|
|
4926
|
+
steerNotices.length = 0;
|
|
4927
|
+
beginTurn(steerSession);
|
|
4928
|
+
steerSession.steerPending = 1;
|
|
4929
|
+
await consumeMessage(steerSession, steerResult(0));
|
|
4930
|
+
await consumeMessage(steerSession, steerRun);
|
|
4931
|
+
if (steerLifecycle() !== "turn/started,turn/completed,turn/started") {
|
|
4932
|
+
throw new Error(`Claude late steer lost its turn: ${steerLifecycle()}`);
|
|
4933
|
+
}
|
|
4934
|
+
// Esc before the steer's run begins closes the turn instead of stranding it.
|
|
4935
|
+
finishTurn(steerSession, null);
|
|
4936
|
+
beginTurn(steerSession);
|
|
4937
|
+
steerSession.steerPending = 1;
|
|
4938
|
+
await consumeMessage(steerSession, steerResult(1));
|
|
4939
|
+
steerNotices.length = 0;
|
|
4940
|
+
await dispatch("session/interrupt", { sessionId: steerSession.id });
|
|
4941
|
+
const stopped = steerNotices.find((notice) => notice.method === "turn/completed")?.params?.turn;
|
|
4942
|
+
if (steerSession.turn || stopped?.status !== "interrupted") {
|
|
4943
|
+
throw new Error(`Claude steer wait survived Esc: ${JSON.stringify(stopped)}`);
|
|
4944
|
+
}
|
|
4945
|
+
} finally {
|
|
4946
|
+
process.stdout.write = steerWrite;
|
|
4947
|
+
sessions.delete(steerSession.id);
|
|
4948
|
+
}
|
|
4872
4949
|
beginTurn(limitSession);
|
|
4873
4950
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
4874
4951
|
await rejectLimit();
|