devez-vibe 0.1.48 → 1.2.1
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 +257 -16
- package/package.json +1 -1
package/bin/dvz.exe
CHANGED
|
Binary file
|
|
@@ -14,6 +14,10 @@ import {
|
|
|
14
14
|
|
|
15
15
|
const VERSION = process.env.DEVEZ_VIBE_VERSION || "dev";
|
|
16
16
|
const sessions = new Map();
|
|
17
|
+
// The id this bridge proposes is not always the id the CLI persists the
|
|
18
|
+
// transcript under, so a session can be renamed mid-flight. Old id → live id,
|
|
19
|
+
// which keeps ids the host already handed out (or wrote to disk) resolvable.
|
|
20
|
+
const sessionAliases = new Map();
|
|
17
21
|
const pendingHostRequests = new Map();
|
|
18
22
|
const modelCatalogs = new Map();
|
|
19
23
|
let nextHostRequest = 1;
|
|
@@ -100,6 +104,21 @@ function applyClaudeExecutable(options, params) {
|
|
|
100
104
|
options.pathToClaudeCodeExecutable = executable;
|
|
101
105
|
}
|
|
102
106
|
|
|
107
|
+
// The SDK model list carries no window size, so the status line would stay
|
|
108
|
+
// blank until a turn reports one — and a fresh session has no turn yet. Claude
|
|
109
|
+
// ships a 200k window, and the `[1m]` variants a 1M one.
|
|
110
|
+
function claudeContextWindow(...names) {
|
|
111
|
+
const oneMillion = names.some((name) => String(name || "").includes("[1m]"));
|
|
112
|
+
return oneMillion ? 1_000_000 : 200_000;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function capabilityContextWindow(capabilities, ...names) {
|
|
116
|
+
const reported = Number(capabilities?.contextWindow || capabilities?.contextWindowSize || 0);
|
|
117
|
+
return reported > 0
|
|
118
|
+
? reported
|
|
119
|
+
: claudeContextWindow(...names, capabilities?.value, capabilities?.resolvedModel);
|
|
120
|
+
}
|
|
121
|
+
|
|
103
122
|
function modelCapabilities(models, model) {
|
|
104
123
|
const value = stripClaudeModel(model);
|
|
105
124
|
return models.find((candidate) => candidate.value === value || candidate.resolvedModel === value)
|
|
@@ -141,7 +160,7 @@ function catalogEntry(model, defaultResolvedModel) {
|
|
|
141
160
|
const efforts = model.supportsEffort && Array.isArray(model.supportedEffortLevels)
|
|
142
161
|
? model.supportedEffortLevels
|
|
143
162
|
: [];
|
|
144
|
-
const contextWindow =
|
|
163
|
+
const contextWindow = capabilityContextWindow(model, value, resolved);
|
|
145
164
|
return {
|
|
146
165
|
id: visibleModel(resolved),
|
|
147
166
|
model: visibleModel(value),
|
|
@@ -215,11 +234,78 @@ function rawSession(id) {
|
|
|
215
234
|
return id.startsWith("claude:") ? id.slice("claude:".length) : id;
|
|
216
235
|
}
|
|
217
236
|
|
|
237
|
+
/** Follows the rename chain from an id the host still remembers to the live one. */
|
|
238
|
+
function liveSessionId(id) {
|
|
239
|
+
let current = rawSession(String(id ?? ""));
|
|
240
|
+
const seen = new Set();
|
|
241
|
+
while (sessionAliases.has(current) && !seen.has(current)) {
|
|
242
|
+
seen.add(current);
|
|
243
|
+
current = sessionAliases.get(current);
|
|
244
|
+
}
|
|
245
|
+
return current;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function lookupSession(id) {
|
|
249
|
+
return sessions.get(liveSessionId(id));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Binds the session to the id the CLI actually persists under. `options.sessionId`
|
|
254
|
+
* is a request, not a guarantee: a session that gets rotated (a pre-warmed process
|
|
255
|
+
* the first turn does not reuse, a resume the CLI declines) writes its transcript
|
|
256
|
+
* under a different uuid, and everything downstream — `session/resume`,
|
|
257
|
+
* `session/history`, DevezCode's `-r` on the next launch — keys off the persisted
|
|
258
|
+
* id. Adopting it here and telling the host is what keeps a Claude-backed session
|
|
259
|
+
* resumable at all.
|
|
260
|
+
*/
|
|
261
|
+
function adoptSessionId(session, incoming) {
|
|
262
|
+
const real = rawSession(String(incoming ?? ""));
|
|
263
|
+
if (!real || real === session.id) return;
|
|
264
|
+
const previous = session.id;
|
|
265
|
+
sessions.delete(previous);
|
|
266
|
+
sessionAliases.set(previous, real);
|
|
267
|
+
session.id = real;
|
|
268
|
+
sessions.set(real, session);
|
|
269
|
+
notify("claude/session/rebound", {
|
|
270
|
+
threadId: visibleSession(previous),
|
|
271
|
+
newThreadId: visibleSession(real),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const PERMISSION_MODES = ["default", "acceptEdits", "plan", "auto", "bypassPermissions"];
|
|
276
|
+
|
|
277
|
+
function permissionMode(requested, fallback = "default") {
|
|
278
|
+
const mode = String(requested || "");
|
|
279
|
+
return PERMISSION_MODES.includes(mode) ? mode : fallback;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Moves a live session onto a mode the badge picked. A rejected mode — policy
|
|
283
|
+
// disables bypass, say — leaves the session on the one it already had.
|
|
284
|
+
async function applyPermissionMode(session, requested) {
|
|
285
|
+
const mode = permissionMode(requested, session.permissionMode || "default");
|
|
286
|
+
if (mode === session.permissionMode) return;
|
|
287
|
+
try {
|
|
288
|
+
await session.query.setPermissionMode(mode);
|
|
289
|
+
session.permissionMode = mode;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
notify("claude/permissionMode/rejected", {
|
|
292
|
+
threadId: visibleSession(session.id),
|
|
293
|
+
permissionMode: mode,
|
|
294
|
+
message: error?.message || String(error),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
218
299
|
function makeOptions(params, sessionId, resume) {
|
|
219
300
|
const options = {
|
|
220
301
|
cwd: params.cwd || process.cwd(),
|
|
221
302
|
includePartialMessages: true,
|
|
222
|
-
permissionMode:
|
|
303
|
+
permissionMode: permissionMode(params.permissionMode),
|
|
304
|
+
// Not a mode, a capability: the SDK refuses `bypassPermissions` outright
|
|
305
|
+
// unless the session was started with this. Devez Vibe already auto-allows
|
|
306
|
+
// tools through `canUseTool`, so allowing the mode to be *reachable* grants
|
|
307
|
+
// nothing the session did not already have.
|
|
308
|
+
allowDangerouslySkipPermissions: true,
|
|
223
309
|
enableFileCheckpointing: true,
|
|
224
310
|
persistSession: true,
|
|
225
311
|
settingSources: ["user", "project", "local"],
|
|
@@ -284,9 +370,13 @@ async function requestToolPermission(toolName, input, permission) {
|
|
|
284
370
|
return { behavior: "allow", updatedInput: { ...input, answers } };
|
|
285
371
|
}
|
|
286
372
|
|
|
373
|
+
// Plan mode only means anything if leaving it is the user's call — the CLI
|
|
374
|
+
// shows the plan and waits. The blanket allow below would answer for them.
|
|
375
|
+
const planApproval = toolName === "ExitPlanMode";
|
|
376
|
+
|
|
287
377
|
// Devez Vibe runs in its existing full-access profile. Claude's callback is
|
|
288
378
|
// still kept so AskUserQuestion and explicit user-authored ask rules reach UI.
|
|
289
|
-
if (!permission.matchedAskRule) {
|
|
379
|
+
if (!permission.matchedAskRule && !planApproval) {
|
|
290
380
|
return { behavior: "allow", updatedInput: input };
|
|
291
381
|
}
|
|
292
382
|
|
|
@@ -295,7 +385,12 @@ async function requestToolPermission(toolName, input, permission) {
|
|
|
295
385
|
reason: permission.decisionReason || permission.description || permission.title,
|
|
296
386
|
permissions: { tool: toolName, blockedPath: permission.blockedPath },
|
|
297
387
|
};
|
|
298
|
-
if (
|
|
388
|
+
if (planApproval) {
|
|
389
|
+
params = {
|
|
390
|
+
reason: input.plan || permission.description || "계획대로 진행할까요?",
|
|
391
|
+
permissions: { tool: toolName },
|
|
392
|
+
};
|
|
393
|
+
} else if (toolName === "Bash") {
|
|
299
394
|
method = "item/commandExecution/requestApproval";
|
|
300
395
|
params = {
|
|
301
396
|
command: input.command || "command",
|
|
@@ -330,6 +425,7 @@ async function createSession(params, resumeId) {
|
|
|
330
425
|
cwd: params.cwd || process.cwd(),
|
|
331
426
|
model: visibleModel(params.model),
|
|
332
427
|
effort: params.effort || "",
|
|
428
|
+
permissionMode: permissionMode(params.permissionMode),
|
|
333
429
|
models: [],
|
|
334
430
|
queue,
|
|
335
431
|
query: null,
|
|
@@ -341,6 +437,7 @@ async function createSession(params, resumeId) {
|
|
|
341
437
|
streamBlocks: new Map(),
|
|
342
438
|
tools: new Map(),
|
|
343
439
|
tasks: new Map(),
|
|
440
|
+
subagents: new Map(),
|
|
344
441
|
lastContextUsage: null,
|
|
345
442
|
lastContextWindow: 0,
|
|
346
443
|
};
|
|
@@ -433,7 +530,7 @@ function historyTokenUsage(messages, models, model) {
|
|
|
433
530
|
}
|
|
434
531
|
if (!counted) return null;
|
|
435
532
|
const capabilities = modelCapabilities(models, model);
|
|
436
|
-
const contextWindow =
|
|
533
|
+
const contextWindow = capabilityContextWindow(capabilities, model);
|
|
437
534
|
return {
|
|
438
535
|
total,
|
|
439
536
|
...(last ? { last } : {}),
|
|
@@ -482,14 +579,20 @@ function processStreamEvent(session, message) {
|
|
|
482
579
|
}
|
|
483
580
|
|
|
484
581
|
function processAssistant(session, message) {
|
|
485
|
-
if (!session.turn
|
|
582
|
+
if (!session.turn) return;
|
|
583
|
+
if (message.parent_tool_use_id) {
|
|
584
|
+
recordSubagentMessage(session, message);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
486
587
|
session.lastContextUsage = tokenBreakdown(message.message?.usage);
|
|
487
588
|
const capabilities = modelCapabilities(
|
|
488
589
|
session.models,
|
|
489
590
|
message.message?.model || session.model,
|
|
490
591
|
);
|
|
491
|
-
session.lastContextWindow =
|
|
492
|
-
capabilities
|
|
592
|
+
session.lastContextWindow = capabilityContextWindow(
|
|
593
|
+
capabilities,
|
|
594
|
+
message.message?.model,
|
|
595
|
+
session.model,
|
|
493
596
|
);
|
|
494
597
|
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
495
598
|
for (const block of content) {
|
|
@@ -523,6 +626,7 @@ function processToolUse(session, block) {
|
|
|
523
626
|
const item = toolItem(session, block.id, name, input);
|
|
524
627
|
session.tools.set(block.id, { name, input, item });
|
|
525
628
|
emitItem(session, "started", item);
|
|
629
|
+
if (SUBAGENT_TOOLS.includes(name)) startSubagent(session, block);
|
|
526
630
|
}
|
|
527
631
|
|
|
528
632
|
function toolItem(session, id, name, input) {
|
|
@@ -643,10 +747,123 @@ function numberedTaskSubject(subject, index) {
|
|
|
643
747
|
return /^\d+\.\s/.test(text) ? text : `${index + 1}. ${text}`;
|
|
644
748
|
}
|
|
645
749
|
|
|
750
|
+
// 서브에이전트는 자기 메시지를 부모 Task 툴콜의 `parent_tool_use_id`와 함께 흘려보낸다.
|
|
751
|
+
// 그 ID로 묶어 두면 지금 어떤 에이전트가 무슨 도구를 돌리는지 그대로 복원할 수 있다.
|
|
752
|
+
const SUBAGENT_TOOLS = ["Agent", "Task"];
|
|
753
|
+
|
|
754
|
+
function startSubagent(session, block) {
|
|
755
|
+
const input = block.input || {};
|
|
756
|
+
session.subagents.set(block.id, {
|
|
757
|
+
id: block.id,
|
|
758
|
+
name: firstLine(input.subagent_type || input.agentType || "agent", 40),
|
|
759
|
+
description: firstLine(input.description || input.prompt || "", 120),
|
|
760
|
+
tool: "",
|
|
761
|
+
startedAt: Date.now(),
|
|
762
|
+
});
|
|
763
|
+
emitSubagents(session);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// 서브에이전트가 실제로 무엇을 했는지는 자식 메시지에만 남는다. 열람용 기록은 여기서
|
|
767
|
+
// 한 줄씩 흘려보내고, 목록 행에 쓸 현재 도구만 따로 갱신한다.
|
|
768
|
+
function recordSubagentMessage(session, message) {
|
|
769
|
+
const running = session.subagents.get(message.parent_tool_use_id);
|
|
770
|
+
if (!running) return;
|
|
771
|
+
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
772
|
+
let toolChanged = false;
|
|
773
|
+
for (const block of content) {
|
|
774
|
+
if (block.type === "text") {
|
|
775
|
+
const text = String(block.text || "").trim();
|
|
776
|
+
if (text) emitSubagentLine(session, running.id, { kind: "text", text });
|
|
777
|
+
} else if (block.type === "tool_use") {
|
|
778
|
+
running.tool = subagentToolLabel(block);
|
|
779
|
+
toolChanged = true;
|
|
780
|
+
emitSubagentLine(session, running.id, {
|
|
781
|
+
kind: "tool",
|
|
782
|
+
text: running.tool,
|
|
783
|
+
toolUseId: block.id,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (toolChanged) emitSubagents(session);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function recordSubagentResult(session, message) {
|
|
791
|
+
const running = session.subagents.get(message.parent_tool_use_id);
|
|
792
|
+
if (!running) return;
|
|
793
|
+
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
794
|
+
for (const block of content) {
|
|
795
|
+
if (block.type !== "tool_result") continue;
|
|
796
|
+
emitSubagentLine(session, running.id, {
|
|
797
|
+
kind: block.is_error ? "error" : "result",
|
|
798
|
+
text: firstLine(toolOutput(block.content, message.tool_use_result), 200),
|
|
799
|
+
toolUseId: block.tool_use_id,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function emitSubagentLine(session, parentToolUseId, line) {
|
|
805
|
+
notify("turn/subagent/line", {
|
|
806
|
+
threadId: session.id,
|
|
807
|
+
turnId: session.turn?.id,
|
|
808
|
+
parentToolUseId,
|
|
809
|
+
line,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function subagentToolLabel(block) {
|
|
814
|
+
const name = block.name || "Tool";
|
|
815
|
+
const input = block.input || {};
|
|
816
|
+
const detail = input.command
|
|
817
|
+
?? input.pattern
|
|
818
|
+
?? input.file_path
|
|
819
|
+
?? input.description
|
|
820
|
+
?? input.query
|
|
821
|
+
?? input.url
|
|
822
|
+
?? "";
|
|
823
|
+
const text = firstLine(detail, 60);
|
|
824
|
+
return text ? `${name}(${text})` : name;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function finishSubagent(session, toolUseId) {
|
|
828
|
+
if (!session.subagents.delete(toolUseId)) return;
|
|
829
|
+
emitSubagents(session);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function clearSubagents(session) {
|
|
833
|
+
if (!session.subagents.size) return;
|
|
834
|
+
session.subagents.clear();
|
|
835
|
+
emitSubagents(session);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function firstLine(value, limit) {
|
|
839
|
+
return String(value ?? "").split("\n")[0].trim().slice(0, limit);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function emitSubagents(session) {
|
|
843
|
+
notify("turn/subagents/updated", {
|
|
844
|
+
threadId: session.id,
|
|
845
|
+
turnId: session.turn?.id,
|
|
846
|
+
subagents: [...session.subagents.values()].map((agent) => ({
|
|
847
|
+
id: agent.id,
|
|
848
|
+
name: agent.name,
|
|
849
|
+
description: agent.description,
|
|
850
|
+
tool: agent.tool,
|
|
851
|
+
elapsedMs: Date.now() - agent.startedAt,
|
|
852
|
+
})),
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
646
856
|
function processUser(session, message) {
|
|
857
|
+
// 자식 tool_result의 tool_use_id는 부모 세션의 것과 다른 공간이므로, 부모 흐름에
|
|
858
|
+
// 섞이기 전에 서브에이전트 기록으로 보낸다.
|
|
859
|
+
if (message.parent_tool_use_id) {
|
|
860
|
+
recordSubagentResult(session, message);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
647
863
|
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
648
864
|
for (const block of content) {
|
|
649
865
|
if (block.type !== "tool_result") continue;
|
|
866
|
+
finishSubagent(session, block.tool_use_id);
|
|
650
867
|
const pending = session.tools.get(block.tool_use_id);
|
|
651
868
|
if (!pending) continue;
|
|
652
869
|
pending.toolUseId = block.tool_use_id;
|
|
@@ -700,7 +917,9 @@ async function processResult(session, message) {
|
|
|
700
917
|
tokenUsage: {
|
|
701
918
|
total: totals,
|
|
702
919
|
...(session.lastContextUsage ? { last: session.lastContextUsage } : {}),
|
|
703
|
-
|
|
920
|
+
// `modelUsage` reports the window the turn actually ran under, so it wins
|
|
921
|
+
// over the size guessed from the model name.
|
|
922
|
+
modelContextWindow: totals.contextWindow || session.lastContextWindow || undefined,
|
|
704
923
|
},
|
|
705
924
|
});
|
|
706
925
|
const error = message.is_error && !interrupted
|
|
@@ -735,6 +954,7 @@ async function runPendingPrompt(session) {
|
|
|
735
954
|
|
|
736
955
|
function finishTurn(session, error, durationMs) {
|
|
737
956
|
if (!session.turn) return;
|
|
957
|
+
clearSubagents(session);
|
|
738
958
|
const turn = { id: session.turn.id, status: error ? "failed" : "completed" };
|
|
739
959
|
if (error) turn.error = { message: error instanceof Error ? error.message : error.message || String(error) };
|
|
740
960
|
if (durationMs != null) turn.durationMs = durationMs;
|
|
@@ -745,6 +965,7 @@ function finishTurn(session, error, durationMs) {
|
|
|
745
965
|
|
|
746
966
|
async function consume(session) {
|
|
747
967
|
for await (const message of session.query) {
|
|
968
|
+
adoptSessionId(session, message.session_id);
|
|
748
969
|
if (message.type === "stream_event") {
|
|
749
970
|
if (message.event?.type === "content_block_delta" && (message.event?.delta?.text || message.event?.delta?.thinking)) {
|
|
750
971
|
if (session.turn) session.turn.sawStreamText = true;
|
|
@@ -807,7 +1028,7 @@ async function inputContent(input, handoffContext) {
|
|
|
807
1028
|
}
|
|
808
1029
|
|
|
809
1030
|
async function startPrompt(params) {
|
|
810
|
-
const id =
|
|
1031
|
+
const id = liveSessionId(params.sessionId);
|
|
811
1032
|
const session = sessions.get(id);
|
|
812
1033
|
if (!session) throw new Error(`Claude 세션을 찾을 수 없습니다: ${id}`);
|
|
813
1034
|
// Claude runs one turn at a time, so extra input waits its turn instead of
|
|
@@ -831,6 +1052,7 @@ async function runPrompt(session, params) {
|
|
|
831
1052
|
await session.query.applyFlagSettings({ effortLevel: effort });
|
|
832
1053
|
}
|
|
833
1054
|
session.effort = effort;
|
|
1055
|
+
await applyPermissionMode(session, params.permissionMode);
|
|
834
1056
|
const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
|
|
835
1057
|
session.turn = { id: turnId, sawStreamText: false };
|
|
836
1058
|
session.lastContextUsage = null;
|
|
@@ -953,6 +1175,13 @@ function historyTurns(messages) {
|
|
|
953
1175
|
|
|
954
1176
|
async function dispatch(method, params = {}) {
|
|
955
1177
|
if (method === "model/list") return loadModelCatalog(params);
|
|
1178
|
+
if (method === "session/permissionMode") {
|
|
1179
|
+
const session = lookupSession(params.sessionId);
|
|
1180
|
+
// A session that has not started yet picks the mode up from its first turn.
|
|
1181
|
+
if (!session) return { permissionMode: permissionMode(params.permissionMode) };
|
|
1182
|
+
await applyPermissionMode(session, params.permissionMode);
|
|
1183
|
+
return { permissionMode: session.permissionMode };
|
|
1184
|
+
}
|
|
956
1185
|
if (method === "session/start") {
|
|
957
1186
|
const { session, account, usage } = await createSession(params);
|
|
958
1187
|
return {
|
|
@@ -966,7 +1195,7 @@ async function dispatch(method, params = {}) {
|
|
|
966
1195
|
};
|
|
967
1196
|
}
|
|
968
1197
|
if (method === "session/resume") {
|
|
969
|
-
const id =
|
|
1198
|
+
const id = liveSessionId(params.sessionId);
|
|
970
1199
|
const existing = sessions.get(id);
|
|
971
1200
|
if (existing) {
|
|
972
1201
|
const messages = await getSessionMessages(id, { dir: existing.cwd, includeSystemMessages: true });
|
|
@@ -1022,13 +1251,13 @@ async function dispatch(method, params = {}) {
|
|
|
1022
1251
|
};
|
|
1023
1252
|
}
|
|
1024
1253
|
if (method === "session/history") {
|
|
1025
|
-
const id =
|
|
1254
|
+
const id = liveSessionId(params.sessionId);
|
|
1026
1255
|
const messages = await getSessionMessages(id, { dir: params.cwd, includeSystemMessages: true });
|
|
1027
1256
|
return { data: historyTurns(messages), nextCursor: null };
|
|
1028
1257
|
}
|
|
1029
1258
|
if (method === "session/prompt") return startPrompt(params);
|
|
1030
1259
|
if (method === "session/interrupt") {
|
|
1031
|
-
const session =
|
|
1260
|
+
const session = lookupSession(params.sessionId);
|
|
1032
1261
|
// Stopping the run drops what was waiting behind it too, so nothing the user
|
|
1033
1262
|
// just cancelled starts on its own afterwards.
|
|
1034
1263
|
if (session) session.pendingPrompts.length = 0;
|
|
@@ -1048,7 +1277,7 @@ async function dispatch(method, params = {}) {
|
|
|
1048
1277
|
return startPrompt({ ...params, input: [{ type: "text", text: "/compact" }] });
|
|
1049
1278
|
}
|
|
1050
1279
|
if (method === "session/fork") {
|
|
1051
|
-
const source =
|
|
1280
|
+
const source = liveSessionId(params.sessionId);
|
|
1052
1281
|
const forked = await forkSession(source, { dir: params.cwd });
|
|
1053
1282
|
const id = forked.sessionId || forked;
|
|
1054
1283
|
const { session, account, usage } = await createSession(params, id);
|
|
@@ -1063,17 +1292,20 @@ async function dispatch(method, params = {}) {
|
|
|
1063
1292
|
};
|
|
1064
1293
|
}
|
|
1065
1294
|
if (method === "session/close") {
|
|
1066
|
-
const session =
|
|
1295
|
+
const session = lookupSession(params.sessionId);
|
|
1067
1296
|
if (session) {
|
|
1068
1297
|
session.queue.close();
|
|
1069
1298
|
session.query.close();
|
|
1070
1299
|
sessions.delete(session.id);
|
|
1300
|
+
for (const [from, to] of sessionAliases) {
|
|
1301
|
+
if (to === session.id) sessionAliases.delete(from);
|
|
1302
|
+
}
|
|
1071
1303
|
if (params.delete) await deleteSession(session.id, { dir: session.cwd });
|
|
1072
1304
|
}
|
|
1073
1305
|
return {};
|
|
1074
1306
|
}
|
|
1075
1307
|
if (method === "account/usage") {
|
|
1076
|
-
const session =
|
|
1308
|
+
const session = lookupSession(params.sessionId) || [...sessions.values()][0];
|
|
1077
1309
|
if (!session) return { account: null, usage: null };
|
|
1078
1310
|
return { account: await safeAccount(session.query), usage: await safeUsage(session.query) };
|
|
1079
1311
|
}
|
|
@@ -1083,6 +1315,7 @@ async function dispatch(method, params = {}) {
|
|
|
1083
1315
|
session.query.close();
|
|
1084
1316
|
}
|
|
1085
1317
|
sessions.clear();
|
|
1318
|
+
sessionAliases.clear();
|
|
1086
1319
|
return {};
|
|
1087
1320
|
}
|
|
1088
1321
|
throw new Error(`지원하지 않는 Claude 브리지 메서드: ${method}`);
|
|
@@ -1133,6 +1366,14 @@ function runSelfTest() {
|
|
|
1133
1366
|
if (usage.totalTokens !== 68_802 || usage.inputTokens !== 68_502) {
|
|
1134
1367
|
throw new Error(`Claude usage self-test failed: ${JSON.stringify(usage)}`);
|
|
1135
1368
|
}
|
|
1369
|
+
const windows = [
|
|
1370
|
+
catalogEntry({ value: "opus[1m]", resolvedModel: "claude-opus-5[1m]" }, "").contextWindow,
|
|
1371
|
+
catalogEntry({ value: "sonnet", resolvedModel: "claude-sonnet-5" }, "").contextWindow,
|
|
1372
|
+
catalogEntry({ value: "haiku", resolvedModel: "x", contextWindow: 300_000 }, "").contextWindow,
|
|
1373
|
+
];
|
|
1374
|
+
if (windows.join(",") !== "1000000,200000,300000") {
|
|
1375
|
+
throw new Error(`Claude context window self-test failed: ${windows.join(",")}`);
|
|
1376
|
+
}
|
|
1136
1377
|
process.stdout.write("Claude bridge self-test passed\n");
|
|
1137
1378
|
}
|
|
1138
1379
|
|