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 CHANGED
Binary file
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { randomUUID } from "node:crypto";
4
- import { readFile } from "node:fs/promises";
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 info = await getSessionInfo(id, { dir: params.cwd });
1612
- if (!info) throw new Error(`Claude 세션을 찾을 없습니다: ${id}`);
1613
- const messages = await getSessionMessages(id, { dir: params.cwd, includeSystemMessages: true });
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.cwd || params.cwd,
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.2.8",
3
+ "version": "1.2.9",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",