devez-vibe 1.2.13 → 1.2.15

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
@@ -438,10 +438,10 @@ async function createSession(params, resumeId) {
438
438
  turnSequence: 1,
439
439
  itemSequence: 1,
440
440
  streamBlocks: new Map(),
441
- tools: new Map(),
442
- tasks: new Map(),
443
- planCreatePending: false,
444
- subagents: new Map(),
441
+ tools: new Map(),
442
+ tasks: new Map(),
443
+ planCreatePending: false,
444
+ subagents: new Map(),
445
445
  knownSubagents: new Map(),
446
446
  lastContextUsage: null,
447
447
  lastContextWindow: 0,
@@ -519,6 +519,10 @@ function openingNotice(input) {
519
519
  : "I’ll review the request and proceed with the necessary work.";
520
520
  }
521
521
 
522
+ // `Now the tile view logic.` carries nothing a Korean reader needs, and the
523
+ // stand-in that used to replace it carried even less — the same sentence before
524
+ // every tool call, however many calls the turn made. Drop the line instead; the
525
+ // tool item that follows already names what is being read.
522
526
  function normalizeProgressText(turn, text) {
523
527
  const value = String(text || "");
524
528
  const trimmed = value.trim();
@@ -526,7 +530,7 @@ function normalizeProgressText(turn, text) {
526
530
  && trimmed.length <= 160
527
531
  && !/[\uac00-\ud7a3]/.test(trimmed)
528
532
  && /^Now\b[^\r\n]*[.!?]?$/i.test(trimmed)) {
529
- return "다음 부분을 이어서 확인하겠습니다.";
533
+ return "";
530
534
  }
531
535
  return value;
532
536
  }
@@ -547,7 +551,7 @@ function emitOpeningNotice(session) {
547
551
  // Windows rounds larger timer delays up to the next scheduler slice. Ten
548
552
  // milliseconds lands near one terminal frame instead of visibly stepping at ~30ms.
549
553
  const SMOOTH_TEXT_INTERVAL_MS = 10;
550
- const SMOOTH_TEXT_TARGET_FRAMES = 10;
554
+ const SMOOTH_TEXT_TARGET_FRAMES = 10;
551
555
  const SMOOTH_TEXT_MAX_GRAPHEMES = 24;
552
556
  const graphemeSegmenter = typeof Intl.Segmenter === "function"
553
557
  ? new Intl.Segmenter(undefined, { granularity: "grapheme" })
@@ -582,21 +586,21 @@ class SmoothTextStream {
582
586
  this.waiters = [];
583
587
  }
584
588
 
585
- push(text) {
586
- this.pending += text;
587
- this.schedule();
588
- }
589
-
590
- schedule() {
591
- if (this.timer != null) return;
592
- // Wait one visual frame before the first drain. Claude often sends several
593
- // tiny deltas back-to-back; batching them removes the uneven one-character
594
- // jumps while keeping added latency below one frame.
595
- this.timer = setTimeout(() => {
596
- this.timer = null;
597
- this.drain();
598
- }, this.intervalMs);
599
- }
589
+ push(text) {
590
+ this.pending += text;
591
+ this.schedule();
592
+ }
593
+
594
+ schedule() {
595
+ if (this.timer != null) return;
596
+ // Wait one visual frame before the first drain. Claude often sends several
597
+ // tiny deltas back-to-back; batching them removes the uneven one-character
598
+ // jumps while keeping added latency below one frame.
599
+ this.timer = setTimeout(() => {
600
+ this.timer = null;
601
+ this.drain();
602
+ }, this.intervalMs);
603
+ }
600
604
 
601
605
  drain() {
602
606
  if (!this.pending) {
@@ -605,13 +609,13 @@ class SmoothTextStream {
605
609
  return;
606
610
  }
607
611
  const { chunk, rest } = takeSmoothTextChunk(this.pending);
608
- this.pending = rest;
609
- this.emit(chunk);
610
- if (this.pending) {
611
- this.schedule();
612
- } else {
613
- for (const resolve of this.waiters.splice(0)) resolve();
614
- }
612
+ this.pending = rest;
613
+ this.emit(chunk);
614
+ if (this.pending) {
615
+ this.schedule();
616
+ } else {
617
+ for (const resolve of this.waiters.splice(0)) resolve();
618
+ }
615
619
  }
616
620
 
617
621
  finish() {
@@ -678,6 +682,12 @@ function historyTokenUsage(messages, models, model) {
678
682
  };
679
683
  }
680
684
 
685
+ function emitHeldStart(session, current) {
686
+ if (!current?.pendingStart) return;
687
+ emitItem(session, "started", current.pendingStart);
688
+ current.pendingStart = null;
689
+ }
690
+
681
691
  async function processStreamEvent(session, message) {
682
692
  if (!session.turn || message.parent_tool_use_id) return;
683
693
  const event = message.event || {};
@@ -695,15 +705,20 @@ async function processStreamEvent(session, message) {
695
705
  const smooth = block.type === "text"
696
706
  ? new SmoothTextStream((delta) => emitDelta(session, "item/agentMessage/delta", id, delta))
697
707
  : null;
708
+ // A held English line can end up dropped entirely, and an item announced
709
+ // before that decision would stay on screen as an empty bubble. Hold the
710
+ // start too, and emit it with the first text that survives.
711
+ const held = block.type === "text" && session.turn.koreanRequest;
698
712
  session.streamBlocks.set(event.index, {
699
713
  id,
700
714
  type: block.type,
701
715
  text: "",
702
716
  smooth,
703
- languagePending: block.type === "text" && session.turn.koreanRequest ? "" : null,
717
+ languagePending: held ? "" : null,
704
718
  holdEnglishProgress: false,
719
+ pendingStart: held ? item : null,
705
720
  });
706
- emitItem(session, "started", item);
721
+ if (!held) emitItem(session, "started", item);
707
722
  return;
708
723
  }
709
724
  if (event.type === "content_block_delta") {
@@ -712,7 +727,9 @@ async function processStreamEvent(session, message) {
712
727
  const delta = event.delta?.text || event.delta?.thinking || "";
713
728
  if (!delta) return;
714
729
  current.text += delta;
715
- if (current.type === "text") session.turn.sawVisibleText = true;
730
+ // Held text may still be dropped, and counting it as visible would suppress
731
+ // the opening notice in its place — leaving the turn with nothing to show.
732
+ if (current.type === "text" && current.languagePending == null) session.turn.sawVisibleText = true;
716
733
  if (!current.smooth) {
717
734
  emitDelta(session, "item/reasoning/summaryTextDelta", current.id, delta);
718
735
  return;
@@ -726,6 +743,8 @@ async function processStreamEvent(session, message) {
726
743
  current.holdEnglishProgress = true;
727
744
  return;
728
745
  }
746
+ emitHeldStart(session, current);
747
+ session.turn.sawVisibleText = true;
729
748
  current.smooth.push(current.languagePending);
730
749
  current.languagePending = null;
731
750
  return;
@@ -739,9 +758,16 @@ async function processStreamEvent(session, message) {
739
758
  if (current.languagePending != null) {
740
759
  const visible = normalizeProgressText(session.turn, current.languagePending);
741
760
  current.text = visible;
742
- current.smooth?.push(visible);
743
761
  current.languagePending = null;
762
+ if (!visible.trim() && current.pendingStart) {
763
+ session.streamBlocks.delete(event.index);
764
+ return;
765
+ }
766
+ emitHeldStart(session, current);
767
+ if (visible.trim()) session.turn.sawVisibleText = true;
768
+ current.smooth?.push(visible);
744
769
  }
770
+ emitHeldStart(session, current);
745
771
  await current.smooth?.finish();
746
772
  const item = current.type === "text"
747
773
  ? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
@@ -777,9 +803,13 @@ function processAssistant(session, message) {
777
803
  if (!session.streamBlocks.size && !session.turn.sawStreamText) {
778
804
  for (const block of content) {
779
805
  if (block.type !== "text" && block.type !== "thinking") continue;
806
+ const visible = block.type === "text"
807
+ ? normalizeProgressText(session.turn, block.text)
808
+ : "";
809
+ if (block.type === "text" && !visible.trim() && String(block.text || "").trim()) continue;
780
810
  const id = nextItemId(session, block.type);
781
811
  const item = block.type === "text"
782
- ? { id, type: "agentMessage", text: normalizeProgressText(session.turn, block.text), provider: "Claude" }
812
+ ? { id, type: "agentMessage", text: visible, provider: "Claude" }
783
813
  : { id, type: "reasoning", summary: [block.thinking || ""] };
784
814
  emitItem(session, "started", item);
785
815
  emitItem(session, "completed", item);
@@ -791,7 +821,7 @@ function processAssistant(session, message) {
791
821
  }
792
822
  }
793
823
 
794
- function processToolUse(session, block) {
824
+ function processToolUse(session, block) {
795
825
  const name = block.name || "Tool";
796
826
  const input = block.input || {};
797
827
  if (name === "TaskCreate" || name === "TaskUpdate" || name === "TaskList") {
@@ -799,13 +829,13 @@ function processToolUse(session, block) {
799
829
  session.tools.set(block.id, { name, input, suppressed: true });
800
830
  return;
801
831
  }
802
- if (name === "AskUserQuestion") {
803
- flushPendingPlan(session);
804
- session.tools.set(block.id, { name, input, suppressed: true });
805
- return;
806
- }
807
- flushPendingPlan(session);
808
- const item = toolItem(session, block.id, name, input);
832
+ if (name === "AskUserQuestion") {
833
+ flushPendingPlan(session);
834
+ session.tools.set(block.id, { name, input, suppressed: true });
835
+ return;
836
+ }
837
+ flushPendingPlan(session);
838
+ const item = toolItem(session, block.id, name, input);
809
839
  session.tools.set(block.id, { name, input, item });
810
840
  emitItem(session, "started", item);
811
841
  if (SUBAGENT_TOOLS.includes(name)) startSubagent(session, block);
@@ -911,30 +941,30 @@ function latestTaskPlan(tasks) {
911
941
  return new Map(entries.slice(start));
912
942
  }
913
943
 
914
- function updatePlanFromToolUse(session, name, toolUseId, input) {
944
+ function updatePlanFromToolUse(session, name, toolUseId, input) {
915
945
  const turnId = session.turn?.id;
916
946
  if (name === "TaskCreate") {
917
947
  prepareTaskPlanForCreate(session.tasks, input.subject);
918
- session.tasks.set(`pending:${toolUseId}`, {
948
+ session.tasks.set(`pending:${toolUseId}`, {
919
949
  id: `pending:${toolUseId}`,
920
950
  subject: input.subject || input.description || "작업",
921
951
  status: "pending",
922
- turnId,
923
- });
924
- session.planCreatePending = true;
925
- return;
926
- } else if (name === "TaskUpdate") {
927
- session.planCreatePending = false;
928
- applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session));
929
- }
930
- if (name === "TaskUpdate") emitPlan(session);
931
- }
932
-
933
- function flushPendingPlan(session) {
934
- if (!session.planCreatePending) return;
935
- session.planCreatePending = false;
936
- emitPlan(session);
937
- }
952
+ turnId,
953
+ });
954
+ session.planCreatePending = true;
955
+ return;
956
+ } else if (name === "TaskUpdate") {
957
+ session.planCreatePending = false;
958
+ applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session));
959
+ }
960
+ if (name === "TaskUpdate") emitPlan(session);
961
+ }
962
+
963
+ function flushPendingPlan(session) {
964
+ if (!session.planCreatePending) return;
965
+ session.planCreatePending = false;
966
+ emitPlan(session);
967
+ }
938
968
 
939
969
  function updatePlanFromToolResult(session, pending, message) {
940
970
  const value = message.tool_use_result;
@@ -1347,9 +1377,9 @@ async function runPendingPrompt(session) {
1347
1377
  }
1348
1378
  }
1349
1379
 
1350
- function finishTurn(session, error, durationMs) {
1351
- if (!session.turn) return;
1352
- flushPendingPlan(session);
1380
+ function finishTurn(session, error, durationMs) {
1381
+ if (!session.turn) return;
1382
+ flushPendingPlan(session);
1353
1383
  flushSmoothStreams(session);
1354
1384
  clearForegroundSubagents(session);
1355
1385
  const turn = { id: session.turn.id, status: error ? "failed" : "completed" };
@@ -1936,53 +1966,53 @@ async function runSelfTest() {
1936
1966
  }
1937
1967
  prepareTaskPlanForCreate(restoredTasks, "7. 추가 작업");
1938
1968
  if (restoredTasks.size !== 6) throw new Error("Claude appended task unexpectedly reset the plan");
1939
- prepareTaskPlanForCreate(restoredTasks, "1. 새 작업");
1940
- if (restoredTasks.size !== 0) throw new Error("Claude new task plan did not reset the previous plan");
1941
- const batchedPlanSession = {
1942
- id: "batched-plan-self-test",
1943
- turn: { id: "batched-plan-turn" },
1944
- tasks: new Map(),
1945
- planCreatePending: false,
1946
- };
1947
- const batchedPlanCaptured = [];
1948
- const batchedPlanWrite = process.stdout.write;
1949
- process.stdout.write = (chunk) => {
1950
- batchedPlanCaptured.push(String(chunk));
1951
- return true;
1952
- };
1953
- try {
1954
- for (let index = 1; index <= 3; index++) {
1955
- const toolUseId = `batched-create-${index}`;
1956
- const subject = `${index}. 작업 ${index}`;
1957
- updatePlanFromToolUse(batchedPlanSession, "TaskCreate", toolUseId, { subject });
1958
- updatePlanFromToolResult(
1959
- batchedPlanSession,
1960
- { name: "TaskCreate", toolUseId },
1961
- { tool_use_result: { task: { id: String(index), subject } } },
1962
- );
1963
- }
1964
- updatePlanFromToolUse(
1965
- batchedPlanSession,
1966
- "TaskUpdate",
1967
- "batched-update-1",
1968
- { taskId: "1", status: "in_progress" },
1969
- );
1970
- } finally {
1971
- process.stdout.write = batchedPlanWrite;
1972
- }
1973
- const batchedPlanEvents = batchedPlanCaptured
1974
- .join("")
1975
- .trim()
1976
- .split("\n")
1977
- .filter(Boolean)
1978
- .map((line) => JSON.parse(line))
1979
- .filter((event) => event.method === "turn/plan/updated");
1980
- if (batchedPlanEvents.length !== 1
1981
- || batchedPlanEvents[0].params?.plan?.length !== 3
1982
- || batchedPlanEvents[0].params.plan[0]?.status !== "inProgress") {
1983
- throw new Error(`Claude batched plan self-test failed: ${JSON.stringify(batchedPlanEvents)}`);
1984
- }
1985
- const usage = tokenBreakdown({
1969
+ prepareTaskPlanForCreate(restoredTasks, "1. 새 작업");
1970
+ if (restoredTasks.size !== 0) throw new Error("Claude new task plan did not reset the previous plan");
1971
+ const batchedPlanSession = {
1972
+ id: "batched-plan-self-test",
1973
+ turn: { id: "batched-plan-turn" },
1974
+ tasks: new Map(),
1975
+ planCreatePending: false,
1976
+ };
1977
+ const batchedPlanCaptured = [];
1978
+ const batchedPlanWrite = process.stdout.write;
1979
+ process.stdout.write = (chunk) => {
1980
+ batchedPlanCaptured.push(String(chunk));
1981
+ return true;
1982
+ };
1983
+ try {
1984
+ for (let index = 1; index <= 3; index++) {
1985
+ const toolUseId = `batched-create-${index}`;
1986
+ const subject = `${index}. 작업 ${index}`;
1987
+ updatePlanFromToolUse(batchedPlanSession, "TaskCreate", toolUseId, { subject });
1988
+ updatePlanFromToolResult(
1989
+ batchedPlanSession,
1990
+ { name: "TaskCreate", toolUseId },
1991
+ { tool_use_result: { task: { id: String(index), subject } } },
1992
+ );
1993
+ }
1994
+ updatePlanFromToolUse(
1995
+ batchedPlanSession,
1996
+ "TaskUpdate",
1997
+ "batched-update-1",
1998
+ { taskId: "1", status: "in_progress" },
1999
+ );
2000
+ } finally {
2001
+ process.stdout.write = batchedPlanWrite;
2002
+ }
2003
+ const batchedPlanEvents = batchedPlanCaptured
2004
+ .join("")
2005
+ .trim()
2006
+ .split("\n")
2007
+ .filter(Boolean)
2008
+ .map((line) => JSON.parse(line))
2009
+ .filter((event) => event.method === "turn/plan/updated");
2010
+ if (batchedPlanEvents.length !== 1
2011
+ || batchedPlanEvents[0].params?.plan?.length !== 3
2012
+ || batchedPlanEvents[0].params.plan[0]?.status !== "inProgress") {
2013
+ throw new Error(`Claude batched plan self-test failed: ${JSON.stringify(batchedPlanEvents)}`);
2014
+ }
2015
+ const usage = tokenBreakdown({
1986
2016
  input_tokens: 2,
1987
2017
  cache_read_input_tokens: 68_000,
1988
2018
  cache_creation_input_tokens: 500,
@@ -2189,18 +2219,51 @@ async function runSelfTest() {
2189
2219
  .filter((event) => event.method === "item/agentMessage/delta")
2190
2220
  .map((event) => event.params?.delta || "")
2191
2221
  .join("");
2192
- if (visibleLanguage !== "다음 부분을 이어서 확인하겠습니다."
2222
+ if (visibleLanguage !== ""
2223
+ || languageEvents.length
2193
2224
  || languageEvents.some((event) => JSON.stringify(event).includes("Now the tile view logic."))) {
2194
2225
  throw new Error(`Claude Korean progress normalization self-test failed: ${JSON.stringify(languageEvents)}`);
2195
2226
  }
2227
+ const keptCaptured = [];
2228
+ process.stdout.write = (chunk) => {
2229
+ keptCaptured.push(String(chunk));
2230
+ return true;
2231
+ };
2232
+ try {
2233
+ openingSession.streamBlocks.clear();
2234
+ await processStreamEvent(openingSession, {
2235
+ event: { type: "content_block_start", index: 0, content_block: { type: "text" } },
2236
+ });
2237
+ await processStreamEvent(openingSession, {
2238
+ event: { type: "content_block_delta", index: 0, delta: { text: "타일 보기 로직을 고쳤습니다." } },
2239
+ });
2240
+ await processStreamEvent(openingSession, {
2241
+ event: { type: "content_block_stop", index: 0 },
2242
+ });
2243
+ } finally {
2244
+ process.stdout.write = stdoutWrite;
2245
+ }
2246
+ const keptEvents = keptCaptured
2247
+ .join("")
2248
+ .trim()
2249
+ .split("\n")
2250
+ .filter(Boolean)
2251
+ .map((line) => JSON.parse(line));
2252
+ const keptStarted = keptEvents.findIndex((event) =>
2253
+ event.method === "item/started" && event.params?.item?.type === "agentMessage");
2254
+ const keptCompleted = keptEvents.find((event) =>
2255
+ event.method === "item/completed" && event.params?.item?.type === "agentMessage");
2256
+ if (keptStarted !== 0 || keptCompleted?.params?.item?.text !== "타일 보기 로직을 고쳤습니다.") {
2257
+ throw new Error(`Claude held Korean text self-test failed: ${JSON.stringify(keptEvents)}`);
2258
+ }
2196
2259
  const smoothText = "Claude가 👨‍👩‍👧‍👦 한 문장을 한꺼번에 보내도 부드럽게 표시합니다.";
2197
- const emitted = [];
2198
- const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
2199
- smooth.push(smoothText);
2200
- if (emitted.length !== 0) {
2201
- throw new Error(`Claude smooth stream did not batch its first frame: ${JSON.stringify(emitted)}`);
2202
- }
2203
- await smooth.finish();
2260
+ const emitted = [];
2261
+ const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
2262
+ smooth.push(smoothText);
2263
+ if (emitted.length !== 0) {
2264
+ throw new Error(`Claude smooth stream did not batch its first frame: ${JSON.stringify(emitted)}`);
2265
+ }
2266
+ await smooth.finish();
2204
2267
  if (emitted.length < 2 || emitted.join("") !== smoothText) {
2205
2268
  throw new Error(`Claude smooth stream self-test failed: ${JSON.stringify(emitted)}`);
2206
2269
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.2.13",
3
+ "version": "1.2.15",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",