devez-vibe 1.2.8 → 1.2.9
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 +294 -218
- package/package.json +1 -1
package/bin/dvz.exe
CHANGED
|
Binary file
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import {
|
|
4
|
+
import { createReadStream } from "node:fs";
|
|
5
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
5
8
|
import { createInterface } from "node:readline";
|
|
6
9
|
import {
|
|
7
10
|
deleteSession,
|
|
@@ -497,48 +500,48 @@ function emitItem(session, phase, item) {
|
|
|
497
500
|
notify(`item/${phase}`, { threadId: session.id, turnId: session.turn?.id, item });
|
|
498
501
|
}
|
|
499
502
|
|
|
500
|
-
function emitDelta(session, method, itemId, delta) {
|
|
501
|
-
notify(method, { threadId: session.id, turnId: session.turn?.id, itemId, delta, provider: "Claude" });
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function isKoreanPrompt(input) {
|
|
505
|
-
const prompt = (Array.isArray(input) ? input : [])
|
|
506
|
-
.filter((item) => item?.type === "text")
|
|
507
|
-
.map((item) => String(item.text || ""))
|
|
508
|
-
.join("\n");
|
|
509
|
-
return /[\uac00-\ud7a3]/.test(prompt);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function openingNotice(input) {
|
|
513
|
-
return isKoreanPrompt(input)
|
|
514
|
-
? "요청 내용을 확인하고 필요한 작업을 진행하겠습니다."
|
|
515
|
-
: "I’ll review the request and proceed with the necessary work.";
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function normalizeProgressText(turn, text) {
|
|
519
|
-
const value = String(text || "");
|
|
520
|
-
const trimmed = value.trim();
|
|
521
|
-
if (turn?.koreanRequest
|
|
522
|
-
&& trimmed.length <= 160
|
|
523
|
-
&& !/[\uac00-\ud7a3]/.test(trimmed)
|
|
524
|
-
&& /^Now\b[^\r\n]*[.!?]?$/i.test(trimmed)) {
|
|
525
|
-
return "다음 부분을 이어서 확인하겠습니다.";
|
|
526
|
-
}
|
|
527
|
-
return value;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// Claude can answer with a tool_use as its first and only content block even
|
|
531
|
-
// when the prompt asks for an opening update. Keep the visible contract stable
|
|
532
|
-
// without duplicating a real model-written update.
|
|
533
|
-
function emitOpeningNotice(session) {
|
|
534
|
-
if (!session.turn || session.turn.openingNoticeEmitted) return;
|
|
535
|
-
const text = session.turn.openingNotice;
|
|
536
|
-
const id = nextItemId(session, "opening");
|
|
537
|
-
const item = { id, type: "agentMessage", text, provider: "Claude" };
|
|
538
|
-
emitItem(session, "started", item);
|
|
539
|
-
emitItem(session, "completed", item);
|
|
540
|
-
session.turn.openingNoticeEmitted = true;
|
|
541
|
-
}
|
|
503
|
+
function emitDelta(session, method, itemId, delta) {
|
|
504
|
+
notify(method, { threadId: session.id, turnId: session.turn?.id, itemId, delta, provider: "Claude" });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function isKoreanPrompt(input) {
|
|
508
|
+
const prompt = (Array.isArray(input) ? input : [])
|
|
509
|
+
.filter((item) => item?.type === "text")
|
|
510
|
+
.map((item) => String(item.text || ""))
|
|
511
|
+
.join("\n");
|
|
512
|
+
return /[\uac00-\ud7a3]/.test(prompt);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function openingNotice(input) {
|
|
516
|
+
return isKoreanPrompt(input)
|
|
517
|
+
? "요청 내용을 확인하고 필요한 작업을 진행하겠습니다."
|
|
518
|
+
: "I’ll review the request and proceed with the necessary work.";
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function normalizeProgressText(turn, text) {
|
|
522
|
+
const value = String(text || "");
|
|
523
|
+
const trimmed = value.trim();
|
|
524
|
+
if (turn?.koreanRequest
|
|
525
|
+
&& trimmed.length <= 160
|
|
526
|
+
&& !/[\uac00-\ud7a3]/.test(trimmed)
|
|
527
|
+
&& /^Now\b[^\r\n]*[.!?]?$/i.test(trimmed)) {
|
|
528
|
+
return "다음 부분을 이어서 확인하겠습니다.";
|
|
529
|
+
}
|
|
530
|
+
return value;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Claude can answer with a tool_use as its first and only content block even
|
|
534
|
+
// when the prompt asks for an opening update. Keep the visible contract stable
|
|
535
|
+
// without duplicating a real model-written update.
|
|
536
|
+
function emitOpeningNotice(session) {
|
|
537
|
+
if (!session.turn || session.turn.openingNoticeEmitted) return;
|
|
538
|
+
const text = session.turn.openingNotice;
|
|
539
|
+
const id = nextItemId(session, "opening");
|
|
540
|
+
const item = { id, type: "agentMessage", text, provider: "Claude" };
|
|
541
|
+
emitItem(session, "started", item);
|
|
542
|
+
emitItem(session, "completed", item);
|
|
543
|
+
session.turn.openingNoticeEmitted = true;
|
|
544
|
+
}
|
|
542
545
|
|
|
543
546
|
// Windows rounds larger timer delays up to the next scheduler slice. Ten
|
|
544
547
|
// milliseconds lands near one terminal frame instead of visibly stepping at ~30ms.
|
|
@@ -671,7 +674,7 @@ async function processStreamEvent(session, message) {
|
|
|
671
674
|
flushSmoothStreams(session);
|
|
672
675
|
session.streamBlocks.clear();
|
|
673
676
|
}
|
|
674
|
-
if (event.type === "content_block_start") {
|
|
677
|
+
if (event.type === "content_block_start") {
|
|
675
678
|
const block = event.content_block || {};
|
|
676
679
|
if (block.type !== "text" && block.type !== "thinking") return;
|
|
677
680
|
const id = nextItemId(session, block.type);
|
|
@@ -681,54 +684,54 @@ async function processStreamEvent(session, message) {
|
|
|
681
684
|
const smooth = block.type === "text"
|
|
682
685
|
? new SmoothTextStream((delta) => emitDelta(session, "item/agentMessage/delta", id, delta))
|
|
683
686
|
: null;
|
|
684
|
-
session.streamBlocks.set(event.index, {
|
|
685
|
-
id,
|
|
686
|
-
type: block.type,
|
|
687
|
-
text: "",
|
|
688
|
-
smooth,
|
|
689
|
-
languagePending: block.type === "text" && session.turn.koreanRequest ? "" : null,
|
|
690
|
-
holdEnglishProgress: false,
|
|
691
|
-
});
|
|
687
|
+
session.streamBlocks.set(event.index, {
|
|
688
|
+
id,
|
|
689
|
+
type: block.type,
|
|
690
|
+
text: "",
|
|
691
|
+
smooth,
|
|
692
|
+
languagePending: block.type === "text" && session.turn.koreanRequest ? "" : null,
|
|
693
|
+
holdEnglishProgress: false,
|
|
694
|
+
});
|
|
692
695
|
emitItem(session, "started", item);
|
|
693
696
|
return;
|
|
694
697
|
}
|
|
695
|
-
if (event.type === "content_block_delta") {
|
|
698
|
+
if (event.type === "content_block_delta") {
|
|
696
699
|
const current = session.streamBlocks.get(event.index);
|
|
697
700
|
if (!current) return;
|
|
698
|
-
const delta = event.delta?.text || event.delta?.thinking || "";
|
|
699
|
-
if (!delta) return;
|
|
700
|
-
current.text += delta;
|
|
701
|
-
if (current.type === "text") session.turn.sawVisibleText = true;
|
|
702
|
-
if (!current.smooth) {
|
|
703
|
-
emitDelta(session, "item/reasoning/summaryTextDelta", current.id, delta);
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
if (current.languagePending != null) {
|
|
707
|
-
current.languagePending += delta;
|
|
708
|
-
const probe = current.languagePending.trimStart();
|
|
709
|
-
const lower = probe.toLowerCase();
|
|
710
|
-
if (!current.holdEnglishProgress && "now".startsWith(lower)) return;
|
|
711
|
-
if (/^now(?:\s|$)/i.test(probe)) {
|
|
712
|
-
current.holdEnglishProgress = true;
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
current.smooth.push(current.languagePending);
|
|
716
|
-
current.languagePending = null;
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
current.smooth.push(delta);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
701
|
+
const delta = event.delta?.text || event.delta?.thinking || "";
|
|
702
|
+
if (!delta) return;
|
|
703
|
+
current.text += delta;
|
|
704
|
+
if (current.type === "text") session.turn.sawVisibleText = true;
|
|
705
|
+
if (!current.smooth) {
|
|
706
|
+
emitDelta(session, "item/reasoning/summaryTextDelta", current.id, delta);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (current.languagePending != null) {
|
|
710
|
+
current.languagePending += delta;
|
|
711
|
+
const probe = current.languagePending.trimStart();
|
|
712
|
+
const lower = probe.toLowerCase();
|
|
713
|
+
if (!current.holdEnglishProgress && "now".startsWith(lower)) return;
|
|
714
|
+
if (/^now(?:\s|$)/i.test(probe)) {
|
|
715
|
+
current.holdEnglishProgress = true;
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
current.smooth.push(current.languagePending);
|
|
719
|
+
current.languagePending = null;
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
current.smooth.push(delta);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
722
725
|
if (event.type === "content_block_stop") {
|
|
723
|
-
const current = session.streamBlocks.get(event.index);
|
|
724
|
-
if (!current) return;
|
|
725
|
-
if (current.languagePending != null) {
|
|
726
|
-
const visible = normalizeProgressText(session.turn, current.languagePending);
|
|
727
|
-
current.text = visible;
|
|
728
|
-
current.smooth?.push(visible);
|
|
729
|
-
current.languagePending = null;
|
|
730
|
-
}
|
|
731
|
-
await current.smooth?.finish();
|
|
726
|
+
const current = session.streamBlocks.get(event.index);
|
|
727
|
+
if (!current) return;
|
|
728
|
+
if (current.languagePending != null) {
|
|
729
|
+
const visible = normalizeProgressText(session.turn, current.languagePending);
|
|
730
|
+
current.text = visible;
|
|
731
|
+
current.smooth?.push(visible);
|
|
732
|
+
current.languagePending = null;
|
|
733
|
+
}
|
|
734
|
+
await current.smooth?.finish();
|
|
732
735
|
const item = current.type === "text"
|
|
733
736
|
? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
|
|
734
737
|
: { id: current.id, type: "reasoning", summary: [current.text] };
|
|
@@ -753,29 +756,29 @@ function processAssistant(session, message) {
|
|
|
753
756
|
message.message?.model,
|
|
754
757
|
session.model,
|
|
755
758
|
);
|
|
756
|
-
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
757
|
-
const hasToolUse = content.some((block) => block.type === "tool_use");
|
|
758
|
-
const hasVisibleText = session.turn.sawVisibleText || content.some(
|
|
759
|
-
(block) => block.type === "text" && String(block.text || "").trim(),
|
|
760
|
-
);
|
|
761
|
-
// Without partial SDK events, replay completed text before tool items so the
|
|
762
|
-
// visible order still matches the assistant content order.
|
|
763
|
-
if (!session.streamBlocks.size && !session.turn.sawStreamText) {
|
|
764
|
-
for (const block of content) {
|
|
765
|
-
if (block.type !== "text" && block.type !== "thinking") continue;
|
|
766
|
-
const id = nextItemId(session, block.type);
|
|
767
|
-
const item = block.type === "text"
|
|
768
|
-
? { id, type: "agentMessage", text: normalizeProgressText(session.turn, block.text), provider: "Claude" }
|
|
769
|
-
: { id, type: "reasoning", summary: [block.thinking || ""] };
|
|
770
|
-
emitItem(session, "started", item);
|
|
771
|
-
emitItem(session, "completed", item);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
if (hasToolUse && !hasVisibleText) emitOpeningNotice(session);
|
|
775
|
-
for (const block of content) {
|
|
776
|
-
if (block.type === "tool_use") processToolUse(session, block);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
759
|
+
const content = Array.isArray(message.message?.content) ? message.message.content : [];
|
|
760
|
+
const hasToolUse = content.some((block) => block.type === "tool_use");
|
|
761
|
+
const hasVisibleText = session.turn.sawVisibleText || content.some(
|
|
762
|
+
(block) => block.type === "text" && String(block.text || "").trim(),
|
|
763
|
+
);
|
|
764
|
+
// Without partial SDK events, replay completed text before tool items so the
|
|
765
|
+
// visible order still matches the assistant content order.
|
|
766
|
+
if (!session.streamBlocks.size && !session.turn.sawStreamText) {
|
|
767
|
+
for (const block of content) {
|
|
768
|
+
if (block.type !== "text" && block.type !== "thinking") continue;
|
|
769
|
+
const id = nextItemId(session, block.type);
|
|
770
|
+
const item = block.type === "text"
|
|
771
|
+
? { id, type: "agentMessage", text: normalizeProgressText(session.turn, block.text), provider: "Claude" }
|
|
772
|
+
: { id, type: "reasoning", summary: [block.thinking || ""] };
|
|
773
|
+
emitItem(session, "started", item);
|
|
774
|
+
emitItem(session, "completed", item);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (hasToolUse && !hasVisibleText) emitOpeningNotice(session);
|
|
778
|
+
for (const block of content) {
|
|
779
|
+
if (block.type === "tool_use") processToolUse(session, block);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
779
782
|
|
|
780
783
|
function processToolUse(session, block) {
|
|
781
784
|
const name = block.name || "Tool";
|
|
@@ -1411,7 +1414,7 @@ async function startPrompt(params) {
|
|
|
1411
1414
|
return runPrompt(session, params);
|
|
1412
1415
|
}
|
|
1413
1416
|
|
|
1414
|
-
async function runPrompt(session, params) {
|
|
1417
|
+
async function runPrompt(session, params) {
|
|
1415
1418
|
const id = session.id;
|
|
1416
1419
|
if (params.model) {
|
|
1417
1420
|
const model = stripClaudeModel(params.model);
|
|
@@ -1422,13 +1425,13 @@ async function runPrompt(session, params) {
|
|
|
1422
1425
|
if (effort) {
|
|
1423
1426
|
await session.query.applyFlagSettings({ effortLevel: effort });
|
|
1424
1427
|
}
|
|
1425
|
-
session.effort = effort;
|
|
1426
|
-
await applyPermissionMode(session, params.permissionMode);
|
|
1427
|
-
const content = await inputContent(params.input, params.handoffContext);
|
|
1428
|
-
const turnId = beginTurn(session, params.input);
|
|
1429
|
-
session.queue.push({
|
|
1430
|
-
type: "user",
|
|
1431
|
-
message: { role: "user", content },
|
|
1428
|
+
session.effort = effort;
|
|
1429
|
+
await applyPermissionMode(session, params.permissionMode);
|
|
1430
|
+
const content = await inputContent(params.input, params.handoffContext);
|
|
1431
|
+
const turnId = beginTurn(session, params.input);
|
|
1432
|
+
session.queue.push({
|
|
1433
|
+
type: "user",
|
|
1434
|
+
message: { role: "user", content },
|
|
1432
1435
|
parent_tool_use_id: null,
|
|
1433
1436
|
session_id: id,
|
|
1434
1437
|
origin: { kind: "human" },
|
|
@@ -1438,16 +1441,16 @@ async function runPrompt(session, params) {
|
|
|
1438
1441
|
|
|
1439
1442
|
// A background task notification is an internal user message that starts its
|
|
1440
1443
|
// own Claude response even though the host did not submit a new prompt.
|
|
1441
|
-
function beginTurn(session, input = []) {
|
|
1442
|
-
const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
|
|
1443
|
-
session.turn = {
|
|
1444
|
-
id: turnId,
|
|
1445
|
-
sawStreamText: false,
|
|
1446
|
-
sawVisibleText: false,
|
|
1447
|
-
koreanRequest: isKoreanPrompt(input),
|
|
1448
|
-
openingNotice: openingNotice(input),
|
|
1449
|
-
openingNoticeEmitted: false,
|
|
1450
|
-
};
|
|
1444
|
+
function beginTurn(session, input = []) {
|
|
1445
|
+
const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
|
|
1446
|
+
session.turn = {
|
|
1447
|
+
id: turnId,
|
|
1448
|
+
sawStreamText: false,
|
|
1449
|
+
sawVisibleText: false,
|
|
1450
|
+
koreanRequest: isKoreanPrompt(input),
|
|
1451
|
+
openingNotice: openingNotice(input),
|
|
1452
|
+
openingNoticeEmitted: false,
|
|
1453
|
+
};
|
|
1451
1454
|
session.lastContextUsage = null;
|
|
1452
1455
|
notify("turn/started", { threadId: session.id, turn: { id: turnId } });
|
|
1453
1456
|
return turnId;
|
|
@@ -1569,6 +1572,75 @@ function historyTurns(messages) {
|
|
|
1569
1572
|
return historyState(messages).turns;
|
|
1570
1573
|
}
|
|
1571
1574
|
|
|
1575
|
+
// A transcript lives in a folder encoded from the cwd string, so two spellings of
|
|
1576
|
+
// the same path (Windows differs only in case) resolve to different folders and a
|
|
1577
|
+
// session recorded under one spelling is invisible to the other. Remember the
|
|
1578
|
+
// spelling the transcript was written with, keyed by session id.
|
|
1579
|
+
const transcriptCwds = new Map();
|
|
1580
|
+
|
|
1581
|
+
function claudeProjectsDir() {
|
|
1582
|
+
return join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude"), "projects");
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/** Read the cwd a transcript records. Only the head is scanned: the opening
|
|
1586
|
+
* records carry no cwd, but a real turn shows up long before the limit. */
|
|
1587
|
+
async function readTranscriptCwd(path, limit = 200) {
|
|
1588
|
+
const stream = createReadStream(path, { encoding: "utf8" });
|
|
1589
|
+
try {
|
|
1590
|
+
const reader = createInterface({ input: stream, crlfDelay: Infinity });
|
|
1591
|
+
try {
|
|
1592
|
+
let seen = 0;
|
|
1593
|
+
for await (const line of reader) {
|
|
1594
|
+
if (++seen > limit) break;
|
|
1595
|
+
let cwd;
|
|
1596
|
+
try {
|
|
1597
|
+
cwd = JSON.parse(line)?.cwd;
|
|
1598
|
+
} catch {
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
if (typeof cwd === "string" && cwd) return cwd;
|
|
1602
|
+
}
|
|
1603
|
+
return null;
|
|
1604
|
+
} finally {
|
|
1605
|
+
reader.close();
|
|
1606
|
+
}
|
|
1607
|
+
} finally {
|
|
1608
|
+
stream.destroy();
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/** Locate the transcript of `id` under any project folder and report the cwd it
|
|
1613
|
+
* records, which is the spelling the SDK needs to find it again. */
|
|
1614
|
+
async function transcriptCwd(id) {
|
|
1615
|
+
if (transcriptCwds.has(id)) return transcriptCwds.get(id);
|
|
1616
|
+
let entries;
|
|
1617
|
+
try {
|
|
1618
|
+
entries = await readdir(claudeProjectsDir(), { withFileTypes: true });
|
|
1619
|
+
} catch {
|
|
1620
|
+
return null;
|
|
1621
|
+
}
|
|
1622
|
+
for (const entry of entries) {
|
|
1623
|
+
if (!entry.isDirectory()) continue;
|
|
1624
|
+
let cwd;
|
|
1625
|
+
try {
|
|
1626
|
+
cwd = await readTranscriptCwd(join(claudeProjectsDir(), entry.name, `${id}.jsonl`));
|
|
1627
|
+
} catch {
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
if (cwd) {
|
|
1631
|
+
transcriptCwds.set(id, cwd);
|
|
1632
|
+
return cwd;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
return null;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/** The cwd to read `id`'s transcript with: the one the transcript itself records,
|
|
1639
|
+
* falling back to the host's when no transcript is on disk. */
|
|
1640
|
+
async function readableCwd(id, cwd) {
|
|
1641
|
+
return await transcriptCwd(id) || cwd;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1572
1644
|
async function dispatch(method, params = {}) {
|
|
1573
1645
|
if (method === "model/list") return loadModelCatalog(params);
|
|
1574
1646
|
if (method === "session/permissionMode") {
|
|
@@ -1608,15 +1680,19 @@ async function dispatch(method, params = {}) {
|
|
|
1608
1680
|
tokenUsage: historyTokenUsage(messages, existing.models, existing.model),
|
|
1609
1681
|
};
|
|
1610
1682
|
}
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1613
|
-
|
|
1683
|
+
const dir = await readableCwd(id, params.cwd);
|
|
1684
|
+
// The transcript itself decides whether there is anything to resume:
|
|
1685
|
+
// getSessionInfo only sees sessions the CLI indexed, and a bridge-run session
|
|
1686
|
+
// whose transcript is intact can be missing from that index.
|
|
1687
|
+
const messages = await getSessionMessages(id, { dir, includeSystemMessages: true });
|
|
1688
|
+
if (!messages.length) throw new Error(`Claude 세션을 찾을 수 없습니다: ${id}`);
|
|
1689
|
+
const info = await getSessionInfo(id, { dir });
|
|
1614
1690
|
const lastModel = [...messages].reverse().find((message) => message.type === "assistant")?.message?.model;
|
|
1615
1691
|
// The transcript's own model outranks the host's fallback, which is only what
|
|
1616
1692
|
// a new session would have opened on.
|
|
1617
1693
|
const { session, account, usage } = await createSession({
|
|
1618
1694
|
...params,
|
|
1619
|
-
cwd: info
|
|
1695
|
+
cwd: info?.cwd || dir,
|
|
1620
1696
|
model: params.model || lastModel || params.fallbackModel,
|
|
1621
1697
|
effort: params.effort || params.fallbackEffort,
|
|
1622
1698
|
}, id);
|
|
@@ -1657,7 +1733,7 @@ async function dispatch(method, params = {}) {
|
|
|
1657
1733
|
}
|
|
1658
1734
|
if (method === "session/history") {
|
|
1659
1735
|
const id = liveSessionId(params.sessionId);
|
|
1660
|
-
const messages = await getSessionMessages(id, { dir: params.cwd, includeSystemMessages: true });
|
|
1736
|
+
const messages = await getSessionMessages(id, { dir: await readableCwd(id, params.cwd), includeSystemMessages: true });
|
|
1661
1737
|
return { data: historyTurns(messages), nextCursor: null };
|
|
1662
1738
|
}
|
|
1663
1739
|
if (method === "session/prompt") return startPrompt(params);
|
|
@@ -1683,7 +1759,7 @@ async function dispatch(method, params = {}) {
|
|
|
1683
1759
|
}
|
|
1684
1760
|
if (method === "session/fork") {
|
|
1685
1761
|
const source = liveSessionId(params.sessionId);
|
|
1686
|
-
const forked = await forkSession(source, { dir: params.cwd });
|
|
1762
|
+
const forked = await forkSession(source, { dir: await readableCwd(source, params.cwd) });
|
|
1687
1763
|
const id = forked.sessionId || forked;
|
|
1688
1764
|
const { session, account, usage } = await createSession(params, id);
|
|
1689
1765
|
return {
|
|
@@ -1966,91 +2042,91 @@ async function runSelfTest() {
|
|
|
1966
2042
|
.filter(Boolean)
|
|
1967
2043
|
.map((line) => JSON.parse(line));
|
|
1968
2044
|
const lifecycleMethods = lifecycleEvents.map((event) => event.method);
|
|
1969
|
-
if (!lifecycleMethods.includes("turn/subagents/updated")
|
|
1970
|
-
|| lifecycleMethods.filter((method) => method === "turn/started").length < 3
|
|
1971
|
-
|| !lifecycleEvents.some((event) => event.method === "turn/subagent/line"
|
|
1972
|
-
&& event.params?.line?.kind === "error")) {
|
|
1973
|
-
throw new Error(`Claude subagent lifecycle events self-test failed: ${lifecycleMethods}`);
|
|
1974
|
-
}
|
|
1975
|
-
const openingSession = {
|
|
1976
|
-
id: "opening-self-test",
|
|
1977
|
-
model: "claude:default",
|
|
1978
|
-
models: [],
|
|
1979
|
-
turn: null,
|
|
1980
|
-
turnSequence: 1,
|
|
1981
|
-
itemSequence: 1,
|
|
1982
|
-
streamBlocks: new Map(),
|
|
1983
|
-
tools: new Map(),
|
|
1984
|
-
tasks: new Map(),
|
|
1985
|
-
subagents: new Map(),
|
|
1986
|
-
knownSubagents: new Map(),
|
|
1987
|
-
lastContextUsage: null,
|
|
1988
|
-
lastContextWindow: 0,
|
|
1989
|
-
};
|
|
1990
|
-
const openingCaptured = [];
|
|
1991
|
-
process.stdout.write = (chunk) => {
|
|
1992
|
-
openingCaptured.push(String(chunk));
|
|
1993
|
-
return true;
|
|
1994
|
-
};
|
|
1995
|
-
try {
|
|
1996
|
-
beginTurn(openingSession, [{ type: "text", text: "provider 메뉴를 수정해" }]);
|
|
1997
|
-
processAssistant(openingSession, {
|
|
1998
|
-
message: {
|
|
1999
|
-
content: [{ type: "tool_use", id: "read-1", name: "Read", input: { file_path: "src/main.rs" } }],
|
|
2000
|
-
},
|
|
2001
|
-
});
|
|
2002
|
-
} finally {
|
|
2003
|
-
process.stdout.write = stdoutWrite;
|
|
2004
|
-
}
|
|
2005
|
-
const openingEvents = openingCaptured
|
|
2006
|
-
.join("")
|
|
2007
|
-
.trim()
|
|
2008
|
-
.split("\n")
|
|
2009
|
-
.filter(Boolean)
|
|
2010
|
-
.map((line) => JSON.parse(line));
|
|
2011
|
-
const openingMessageIndex = openingEvents.findIndex((event) =>
|
|
2012
|
-
event.method === "item/completed"
|
|
2013
|
-
&& event.params?.item?.type === "agentMessage"
|
|
2014
|
-
&& event.params.item.text === "요청 내용을 확인하고 필요한 작업을 진행하겠습니다.");
|
|
2015
|
-
const openingToolIndex = openingEvents.findIndex((event) =>
|
|
2016
|
-
event.method === "item/started" && event.params?.item?.type === "dynamicToolCall");
|
|
2017
|
-
if (openingMessageIndex < 0 || openingToolIndex < 0 || openingMessageIndex > openingToolIndex) {
|
|
2018
|
-
throw new Error(`Claude opening notice order self-test failed: ${JSON.stringify(openingEvents)}`);
|
|
2019
|
-
}
|
|
2020
|
-
const languageCaptured = [];
|
|
2021
|
-
process.stdout.write = (chunk) => {
|
|
2022
|
-
languageCaptured.push(String(chunk));
|
|
2023
|
-
return true;
|
|
2024
|
-
};
|
|
2025
|
-
try {
|
|
2026
|
-
openingSession.streamBlocks.clear();
|
|
2027
|
-
await processStreamEvent(openingSession, {
|
|
2028
|
-
event: { type: "content_block_start", index: 0, content_block: { type: "text" } },
|
|
2029
|
-
});
|
|
2030
|
-
await processStreamEvent(openingSession, {
|
|
2031
|
-
event: { type: "content_block_delta", index: 0, delta: { text: "Now the tile view logic." } },
|
|
2032
|
-
});
|
|
2033
|
-
await processStreamEvent(openingSession, {
|
|
2034
|
-
event: { type: "content_block_stop", index: 0 },
|
|
2035
|
-
});
|
|
2036
|
-
} finally {
|
|
2037
|
-
process.stdout.write = stdoutWrite;
|
|
2038
|
-
}
|
|
2039
|
-
const languageEvents = languageCaptured
|
|
2040
|
-
.join("")
|
|
2041
|
-
.trim()
|
|
2042
|
-
.split("\n")
|
|
2043
|
-
.filter(Boolean)
|
|
2044
|
-
.map((line) => JSON.parse(line));
|
|
2045
|
-
const visibleLanguage = languageEvents
|
|
2046
|
-
.filter((event) => event.method === "item/agentMessage/delta")
|
|
2047
|
-
.map((event) => event.params?.delta || "")
|
|
2048
|
-
.join("");
|
|
2049
|
-
if (visibleLanguage !== "다음 부분을 이어서 확인하겠습니다."
|
|
2050
|
-
|| languageEvents.some((event) => JSON.stringify(event).includes("Now the tile view logic."))) {
|
|
2051
|
-
throw new Error(`Claude Korean progress normalization self-test failed: ${JSON.stringify(languageEvents)}`);
|
|
2052
|
-
}
|
|
2053
|
-
const smoothText = "Claude가 👨👩👧👦 한 문장을 한꺼번에 보내도 부드럽게 표시합니다.";
|
|
2045
|
+
if (!lifecycleMethods.includes("turn/subagents/updated")
|
|
2046
|
+
|| lifecycleMethods.filter((method) => method === "turn/started").length < 3
|
|
2047
|
+
|| !lifecycleEvents.some((event) => event.method === "turn/subagent/line"
|
|
2048
|
+
&& event.params?.line?.kind === "error")) {
|
|
2049
|
+
throw new Error(`Claude subagent lifecycle events self-test failed: ${lifecycleMethods}`);
|
|
2050
|
+
}
|
|
2051
|
+
const openingSession = {
|
|
2052
|
+
id: "opening-self-test",
|
|
2053
|
+
model: "claude:default",
|
|
2054
|
+
models: [],
|
|
2055
|
+
turn: null,
|
|
2056
|
+
turnSequence: 1,
|
|
2057
|
+
itemSequence: 1,
|
|
2058
|
+
streamBlocks: new Map(),
|
|
2059
|
+
tools: new Map(),
|
|
2060
|
+
tasks: new Map(),
|
|
2061
|
+
subagents: new Map(),
|
|
2062
|
+
knownSubagents: new Map(),
|
|
2063
|
+
lastContextUsage: null,
|
|
2064
|
+
lastContextWindow: 0,
|
|
2065
|
+
};
|
|
2066
|
+
const openingCaptured = [];
|
|
2067
|
+
process.stdout.write = (chunk) => {
|
|
2068
|
+
openingCaptured.push(String(chunk));
|
|
2069
|
+
return true;
|
|
2070
|
+
};
|
|
2071
|
+
try {
|
|
2072
|
+
beginTurn(openingSession, [{ type: "text", text: "provider 메뉴를 수정해" }]);
|
|
2073
|
+
processAssistant(openingSession, {
|
|
2074
|
+
message: {
|
|
2075
|
+
content: [{ type: "tool_use", id: "read-1", name: "Read", input: { file_path: "src/main.rs" } }],
|
|
2076
|
+
},
|
|
2077
|
+
});
|
|
2078
|
+
} finally {
|
|
2079
|
+
process.stdout.write = stdoutWrite;
|
|
2080
|
+
}
|
|
2081
|
+
const openingEvents = openingCaptured
|
|
2082
|
+
.join("")
|
|
2083
|
+
.trim()
|
|
2084
|
+
.split("\n")
|
|
2085
|
+
.filter(Boolean)
|
|
2086
|
+
.map((line) => JSON.parse(line));
|
|
2087
|
+
const openingMessageIndex = openingEvents.findIndex((event) =>
|
|
2088
|
+
event.method === "item/completed"
|
|
2089
|
+
&& event.params?.item?.type === "agentMessage"
|
|
2090
|
+
&& event.params.item.text === "요청 내용을 확인하고 필요한 작업을 진행하겠습니다.");
|
|
2091
|
+
const openingToolIndex = openingEvents.findIndex((event) =>
|
|
2092
|
+
event.method === "item/started" && event.params?.item?.type === "dynamicToolCall");
|
|
2093
|
+
if (openingMessageIndex < 0 || openingToolIndex < 0 || openingMessageIndex > openingToolIndex) {
|
|
2094
|
+
throw new Error(`Claude opening notice order self-test failed: ${JSON.stringify(openingEvents)}`);
|
|
2095
|
+
}
|
|
2096
|
+
const languageCaptured = [];
|
|
2097
|
+
process.stdout.write = (chunk) => {
|
|
2098
|
+
languageCaptured.push(String(chunk));
|
|
2099
|
+
return true;
|
|
2100
|
+
};
|
|
2101
|
+
try {
|
|
2102
|
+
openingSession.streamBlocks.clear();
|
|
2103
|
+
await processStreamEvent(openingSession, {
|
|
2104
|
+
event: { type: "content_block_start", index: 0, content_block: { type: "text" } },
|
|
2105
|
+
});
|
|
2106
|
+
await processStreamEvent(openingSession, {
|
|
2107
|
+
event: { type: "content_block_delta", index: 0, delta: { text: "Now the tile view logic." } },
|
|
2108
|
+
});
|
|
2109
|
+
await processStreamEvent(openingSession, {
|
|
2110
|
+
event: { type: "content_block_stop", index: 0 },
|
|
2111
|
+
});
|
|
2112
|
+
} finally {
|
|
2113
|
+
process.stdout.write = stdoutWrite;
|
|
2114
|
+
}
|
|
2115
|
+
const languageEvents = languageCaptured
|
|
2116
|
+
.join("")
|
|
2117
|
+
.trim()
|
|
2118
|
+
.split("\n")
|
|
2119
|
+
.filter(Boolean)
|
|
2120
|
+
.map((line) => JSON.parse(line));
|
|
2121
|
+
const visibleLanguage = languageEvents
|
|
2122
|
+
.filter((event) => event.method === "item/agentMessage/delta")
|
|
2123
|
+
.map((event) => event.params?.delta || "")
|
|
2124
|
+
.join("");
|
|
2125
|
+
if (visibleLanguage !== "다음 부분을 이어서 확인하겠습니다."
|
|
2126
|
+
|| languageEvents.some((event) => JSON.stringify(event).includes("Now the tile view logic."))) {
|
|
2127
|
+
throw new Error(`Claude Korean progress normalization self-test failed: ${JSON.stringify(languageEvents)}`);
|
|
2128
|
+
}
|
|
2129
|
+
const smoothText = "Claude가 👨👩👧👦 한 문장을 한꺼번에 보내도 부드럽게 표시합니다.";
|
|
2054
2130
|
const emitted = [];
|
|
2055
2131
|
const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
|
|
2056
2132
|
smooth.push(smoothText);
|