devez-vibe 1.2.7 → 1.2.8

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/LICENSE CHANGED
@@ -1,33 +1,33 @@
1
- DevezCode source code license
2
-
3
- The MIT License below applies only to the DevezCode source code and to files
4
- that are not listed in the exception below.
5
-
6
- Exceptions:
7
-
8
- * `Resources/Images/FileTypes/` and `Resources/Licenses/` are Microsoft Visual
9
- Studio Image Library materials, subject to the included Microsoft terms.
10
- * `Resources/Images/ShellPresets/` contains third-party brand assets, subject
11
- to their respective owners' terms.
12
-
13
- MIT License
14
-
15
- Copyright (c) 2026 DevezCode contributors
16
-
17
- Permission is hereby granted, free of charge, to any person obtaining a copy
18
- of this software and associated documentation files (the "Software"), to deal
19
- in the Software without restriction, including without limitation the rights
20
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21
- copies of the Software, and to permit persons to whom the Software is
22
- furnished to do so, subject to the following conditions:
23
-
24
- The above copyright notice and this permission notice shall be included in all
25
- copies or substantial portions of the Software.
26
-
27
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
- SOFTWARE.
1
+ DevezCode source code license
2
+
3
+ The MIT License below applies only to the DevezCode source code and to files
4
+ that are not listed in the exception below.
5
+
6
+ Exceptions:
7
+
8
+ * `Resources/Images/FileTypes/` and `Resources/Licenses/` are Microsoft Visual
9
+ Studio Image Library materials, subject to the included Microsoft terms.
10
+ * `Resources/Images/ShellPresets/` contains third-party brand assets, subject
11
+ to their respective owners' terms.
12
+
13
+ MIT License
14
+
15
+ Copyright (c) 2026 DevezCode contributors
16
+
17
+ Permission is hereby granted, free of charge, to any person obtaining a copy
18
+ of this software and associated documentation files (the "Software"), to deal
19
+ in the Software without restriction, including without limitation the rights
20
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21
+ copies of the Software, and to permit persons to whom the Software is
22
+ furnished to do so, subject to the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be included in all
25
+ copies or substantial portions of the Software.
26
+
27
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
+ SOFTWARE.
package/bin/dvz.exe CHANGED
Binary file
@@ -497,9 +497,48 @@ function emitItem(session, phase, item) {
497
497
  notify(`item/${phase}`, { threadId: session.id, turnId: session.turn?.id, item });
498
498
  }
499
499
 
500
- function emitDelta(session, method, itemId, delta) {
501
- notify(method, { threadId: session.id, turnId: session.turn?.id, itemId, delta, provider: "Claude" });
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
542
 
504
543
  // Windows rounds larger timer delays up to the next scheduler slice. Ten
505
544
  // milliseconds lands near one terminal frame instead of visibly stepping at ~30ms.
@@ -632,7 +671,7 @@ async function processStreamEvent(session, message) {
632
671
  flushSmoothStreams(session);
633
672
  session.streamBlocks.clear();
634
673
  }
635
- if (event.type === "content_block_start") {
674
+ if (event.type === "content_block_start") {
636
675
  const block = event.content_block || {};
637
676
  if (block.type !== "text" && block.type !== "thinking") return;
638
677
  const id = nextItemId(session, block.type);
@@ -642,24 +681,54 @@ async function processStreamEvent(session, message) {
642
681
  const smooth = block.type === "text"
643
682
  ? new SmoothTextStream((delta) => emitDelta(session, "item/agentMessage/delta", id, delta))
644
683
  : null;
645
- session.streamBlocks.set(event.index, { id, type: block.type, text: "", smooth });
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
+ });
646
692
  emitItem(session, "started", item);
647
693
  return;
648
694
  }
649
- if (event.type === "content_block_delta") {
695
+ if (event.type === "content_block_delta") {
650
696
  const current = session.streamBlocks.get(event.index);
651
697
  if (!current) return;
652
- const delta = event.delta?.text || event.delta?.thinking || "";
653
- if (!delta) return;
654
- current.text += delta;
655
- if (current.smooth) current.smooth.push(delta);
656
- else emitDelta(session, "item/reasoning/summaryTextDelta", current.id, delta);
657
- return;
658
- }
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
+ }
659
722
  if (event.type === "content_block_stop") {
660
- const current = session.streamBlocks.get(event.index);
661
- if (!current) return;
662
- await current.smooth?.finish();
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();
663
732
  const item = current.type === "text"
664
733
  ? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
665
734
  : { id: current.id, type: "reasoning", summary: [current.text] };
@@ -684,22 +753,29 @@ function processAssistant(session, message) {
684
753
  message.message?.model,
685
754
  session.model,
686
755
  );
687
- const content = Array.isArray(message.message?.content) ? message.message.content : [];
688
- for (const block of content) {
689
- if (block.type === "tool_use") processToolUse(session, block);
690
- }
691
- if (!session.streamBlocks.size && !session.turn.sawStreamText) {
692
- for (const block of content) {
693
- if (block.type !== "text" && block.type !== "thinking") continue;
694
- const id = nextItemId(session, block.type);
695
- const item = block.type === "text"
696
- ? { id, type: "agentMessage", text: block.text || "", provider: "Claude" }
697
- : { id, type: "reasoning", summary: [block.thinking || ""] };
698
- emitItem(session, "started", item);
699
- emitItem(session, "completed", item);
700
- }
701
- }
702
- }
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
+ }
703
779
 
704
780
  function processToolUse(session, block) {
705
781
  const name = block.name || "Tool";
@@ -762,51 +838,51 @@ function prepareTaskPlanForCreate(tasks, subject) {
762
838
  if (numberedTaskIndex(subject) === 1) tasks.clear();
763
839
  }
764
840
 
765
- function applyTaskUpdate(tasks, input, turnId, onIntermediate) {
766
- const task = tasks.get(String(input.taskId));
767
- if (!task) return false;
768
- if (input.subject) task.subject = input.subject;
769
- const status = input.status;
770
- if (status === "in_progress" || status === "completed") {
771
- const entries = [...tasks.values()];
772
- const targetIndex = entries.indexOf(task);
773
-
774
- // Claude occasionally closes a later pending task at the end of a turn
775
- // without ever starting it. Keep the visible plan truthful and sequential:
776
- // every skipped predecessor and the target itself pass through in_progress.
777
- for (let index = 0; index < targetIndex; index++) {
778
- const previous = entries[index];
779
- if (previous.status === "completed") continue;
780
- if (previous.status !== "in_progress") {
781
- previous.status = "in_progress";
782
- previous.turnId = turnId;
783
- onIntermediate?.();
784
- }
785
- previous.status = "completed";
786
- previous.turnId = turnId;
787
- onIntermediate?.();
788
- }
789
-
790
- for (let index = 0; index < entries.length; index++) {
791
- const other = entries[index];
792
- if (other === task || other.status !== "in_progress") continue;
793
- other.status = index < targetIndex ? "completed" : "pending";
794
- other.turnId = turnId;
795
- onIntermediate?.();
796
- }
797
-
798
- if (status === "completed" && task.status !== "in_progress" && task.status !== "completed") {
799
- task.status = "in_progress";
800
- task.turnId = turnId;
801
- onIntermediate?.();
802
- }
803
- task.status = status;
804
- } else if (status) {
805
- task.status = status;
806
- }
807
- task.turnId = turnId;
808
- return true;
809
- }
841
+ function applyTaskUpdate(tasks, input, turnId, onIntermediate) {
842
+ const task = tasks.get(String(input.taskId));
843
+ if (!task) return false;
844
+ if (input.subject) task.subject = input.subject;
845
+ const status = input.status;
846
+ if (status === "in_progress" || status === "completed") {
847
+ const entries = [...tasks.values()];
848
+ const targetIndex = entries.indexOf(task);
849
+
850
+ // Claude occasionally closes a later pending task at the end of a turn
851
+ // without ever starting it. Keep the visible plan truthful and sequential:
852
+ // every skipped predecessor and the target itself pass through in_progress.
853
+ for (let index = 0; index < targetIndex; index++) {
854
+ const previous = entries[index];
855
+ if (previous.status === "completed") continue;
856
+ if (previous.status !== "in_progress") {
857
+ previous.status = "in_progress";
858
+ previous.turnId = turnId;
859
+ onIntermediate?.();
860
+ }
861
+ previous.status = "completed";
862
+ previous.turnId = turnId;
863
+ onIntermediate?.();
864
+ }
865
+
866
+ for (let index = 0; index < entries.length; index++) {
867
+ const other = entries[index];
868
+ if (other === task || other.status !== "in_progress") continue;
869
+ other.status = index < targetIndex ? "completed" : "pending";
870
+ other.turnId = turnId;
871
+ onIntermediate?.();
872
+ }
873
+
874
+ if (status === "completed" && task.status !== "in_progress" && task.status !== "completed") {
875
+ task.status = "in_progress";
876
+ task.turnId = turnId;
877
+ onIntermediate?.();
878
+ }
879
+ task.status = status;
880
+ } else if (status) {
881
+ task.status = status;
882
+ }
883
+ task.turnId = turnId;
884
+ return true;
885
+ }
810
886
 
811
887
  // TaskList에는 예전 계획까지 함께 들어올 수 있다. 마지막으로 번호가 1부터
812
888
  // 시작한 묶음만 현재 계획으로 삼되, 번호가 없는 목록은 손실 없이 그대로 둔다.
@@ -829,9 +905,9 @@ function updatePlanFromToolUse(session, name, toolUseId, input) {
829
905
  status: "pending",
830
906
  turnId,
831
907
  });
832
- } else if (name === "TaskUpdate") {
833
- applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session));
834
- }
908
+ } else if (name === "TaskUpdate") {
909
+ applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session));
910
+ }
835
911
  emitPlan(session);
836
912
  }
837
913
 
@@ -1335,7 +1411,7 @@ async function startPrompt(params) {
1335
1411
  return runPrompt(session, params);
1336
1412
  }
1337
1413
 
1338
- async function runPrompt(session, params) {
1414
+ async function runPrompt(session, params) {
1339
1415
  const id = session.id;
1340
1416
  if (params.model) {
1341
1417
  const model = stripClaudeModel(params.model);
@@ -1346,12 +1422,13 @@ async function runPrompt(session, params) {
1346
1422
  if (effort) {
1347
1423
  await session.query.applyFlagSettings({ effortLevel: effort });
1348
1424
  }
1349
- session.effort = effort;
1350
- await applyPermissionMode(session, params.permissionMode);
1351
- const turnId = beginTurn(session);
1352
- session.queue.push({
1353
- type: "user",
1354
- message: { role: "user", content: await inputContent(params.input, params.handoffContext) },
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 },
1355
1432
  parent_tool_use_id: null,
1356
1433
  session_id: id,
1357
1434
  origin: { kind: "human" },
@@ -1361,9 +1438,16 @@ async function runPrompt(session, params) {
1361
1438
 
1362
1439
  // A background task notification is an internal user message that starts its
1363
1440
  // own Claude response even though the host did not submit a new prompt.
1364
- function beginTurn(session) {
1365
- const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
1366
- session.turn = { id: turnId, sawStreamText: false };
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
+ };
1367
1451
  session.lastContextUsage = null;
1368
1452
  notify("turn/started", { threadId: session.id, turn: { id: turnId } });
1369
1453
  return turnId;
@@ -1717,33 +1801,33 @@ async function runSelfTest() {
1717
1801
  }
1718
1802
  applyTaskUpdate(restoredTasks, { taskId: "26", status: "completed" }, "resumed-turn");
1719
1803
  applyTaskUpdate(restoredTasks, { taskId: "27", status: "in_progress" }, "resumed-turn");
1720
- if (restoredTasks.size !== 6
1721
- || restoredTasks.get("25")?.status !== "completed"
1722
- || restoredTasks.get("26")?.status !== "completed"
1723
- || restoredTasks.get("27")?.status !== "in_progress"
1724
- || [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
1725
- throw new Error(`Claude sequential task update self-test failed: ${JSON.stringify([...restoredTasks])}`);
1726
- }
1727
- const skippedTasks = new Map([
1728
- ["1", { id: "1", subject: "1. 조사", status: "pending" }],
1729
- ["2", { id: "2", subject: "2. 분석", status: "pending" }],
1730
- ["3", { id: "3", subject: "3. 검증", status: "pending" }],
1731
- ]);
1732
- const transitions = [];
1733
- const snapshot = () => transitions.push([...skippedTasks.values()].map((task) => task.status).join(","));
1734
- applyTaskUpdate(skippedTasks, { taskId: "3", status: "completed" }, "turn", snapshot);
1735
- snapshot();
1736
- if (transitions.join("|") !== [
1737
- "in_progress,pending,pending",
1738
- "completed,pending,pending",
1739
- "completed,in_progress,pending",
1740
- "completed,completed,pending",
1741
- "completed,completed,in_progress",
1742
- "completed,completed,completed",
1743
- ].join("|")) {
1744
- throw new Error(`Claude skipped task transition self-test failed: ${transitions.join("|")}`);
1745
- }
1746
- const mixedPlans = new Map([
1804
+ if (restoredTasks.size !== 6
1805
+ || restoredTasks.get("25")?.status !== "completed"
1806
+ || restoredTasks.get("26")?.status !== "completed"
1807
+ || restoredTasks.get("27")?.status !== "in_progress"
1808
+ || [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
1809
+ throw new Error(`Claude sequential task update self-test failed: ${JSON.stringify([...restoredTasks])}`);
1810
+ }
1811
+ const skippedTasks = new Map([
1812
+ ["1", { id: "1", subject: "1. 조사", status: "pending" }],
1813
+ ["2", { id: "2", subject: "2. 분석", status: "pending" }],
1814
+ ["3", { id: "3", subject: "3. 검증", status: "pending" }],
1815
+ ]);
1816
+ const transitions = [];
1817
+ const snapshot = () => transitions.push([...skippedTasks.values()].map((task) => task.status).join(","));
1818
+ applyTaskUpdate(skippedTasks, { taskId: "3", status: "completed" }, "turn", snapshot);
1819
+ snapshot();
1820
+ if (transitions.join("|") !== [
1821
+ "in_progress,pending,pending",
1822
+ "completed,pending,pending",
1823
+ "completed,in_progress,pending",
1824
+ "completed,completed,pending",
1825
+ "completed,completed,in_progress",
1826
+ "completed,completed,completed",
1827
+ ].join("|")) {
1828
+ throw new Error(`Claude skipped task transition self-test failed: ${transitions.join("|")}`);
1829
+ }
1830
+ const mixedPlans = new Map([
1747
1831
  ["old-1", { subject: "1. 이전 작업", status: "completed" }],
1748
1832
  ["old-2", { subject: "2. 이전 검증", status: "completed" }],
1749
1833
  ...[...restoredTasks],
@@ -1882,13 +1966,91 @@ async function runSelfTest() {
1882
1966
  .filter(Boolean)
1883
1967
  .map((line) => JSON.parse(line));
1884
1968
  const lifecycleMethods = lifecycleEvents.map((event) => event.method);
1885
- if (!lifecycleMethods.includes("turn/subagents/updated")
1886
- || lifecycleMethods.filter((method) => method === "turn/started").length < 3
1887
- || !lifecycleEvents.some((event) => event.method === "turn/subagent/line"
1888
- && event.params?.line?.kind === "error")) {
1889
- throw new Error(`Claude subagent lifecycle events self-test failed: ${lifecycleMethods}`);
1890
- }
1891
- const smoothText = "Claude가 👨‍👩‍👧‍👦 한 문장을 한꺼번에 보내도 부드럽게 표시합니다.";
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가 👨‍👩‍👧‍👦 한 문장을 한꺼번에 보내도 부드럽게 표시합니다.";
1892
2054
  const emitted = [];
1893
2055
  const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
1894
2056
  smooth.push(smoothText);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",