devez-vibe 1.2.9 → 1.2.11

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,9 +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
- subagents: new Map(),
441
+ tools: new Map(),
442
+ tasks: new Map(),
443
+ planCreatePending: false,
444
+ subagents: new Map(),
444
445
  knownSubagents: new Map(),
445
446
  lastContextUsage: null,
446
447
  lastContextWindow: 0,
@@ -546,7 +547,7 @@ function emitOpeningNotice(session) {
546
547
  // Windows rounds larger timer delays up to the next scheduler slice. Ten
547
548
  // milliseconds lands near one terminal frame instead of visibly stepping at ~30ms.
548
549
  const SMOOTH_TEXT_INTERVAL_MS = 10;
549
- const SMOOTH_TEXT_TARGET_FRAMES = 8;
550
+ const SMOOTH_TEXT_TARGET_FRAMES = 10;
550
551
  const SMOOTH_TEXT_MAX_GRAPHEMES = 24;
551
552
  const graphemeSegmenter = typeof Intl.Segmenter === "function"
552
553
  ? new Intl.Segmenter(undefined, { granularity: "grapheme" })
@@ -581,10 +582,21 @@ class SmoothTextStream {
581
582
  this.waiters = [];
582
583
  }
583
584
 
584
- push(text) {
585
- this.pending += text;
586
- if (this.timer == null) this.drain();
587
- }
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
+ }
588
600
 
589
601
  drain() {
590
602
  if (!this.pending) {
@@ -593,14 +605,13 @@ class SmoothTextStream {
593
605
  return;
594
606
  }
595
607
  const { chunk, rest } = takeSmoothTextChunk(this.pending);
596
- this.pending = rest;
597
- this.emit(chunk);
598
- if (this.pending) {
599
- this.timer = setTimeout(() => this.drain(), this.intervalMs);
600
- } else {
601
- this.timer = null;
602
- for (const resolve of this.waiters.splice(0)) resolve();
603
- }
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
+ }
604
615
  }
605
616
 
606
617
  finish() {
@@ -780,7 +791,7 @@ function processAssistant(session, message) {
780
791
  }
781
792
  }
782
793
 
783
- function processToolUse(session, block) {
794
+ function processToolUse(session, block) {
784
795
  const name = block.name || "Tool";
785
796
  const input = block.input || {};
786
797
  if (name === "TaskCreate" || name === "TaskUpdate" || name === "TaskList") {
@@ -788,11 +799,13 @@ function processToolUse(session, block) {
788
799
  session.tools.set(block.id, { name, input, suppressed: true });
789
800
  return;
790
801
  }
791
- if (name === "AskUserQuestion") {
792
- session.tools.set(block.id, { name, input, suppressed: true });
793
- return;
794
- }
795
- const item = toolItem(session, block.id, name, input);
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);
796
809
  session.tools.set(block.id, { name, input, item });
797
810
  emitItem(session, "started", item);
798
811
  if (SUBAGENT_TOOLS.includes(name)) startSubagent(session, block);
@@ -898,21 +911,30 @@ function latestTaskPlan(tasks) {
898
911
  return new Map(entries.slice(start));
899
912
  }
900
913
 
901
- function updatePlanFromToolUse(session, name, toolUseId, input) {
914
+ function updatePlanFromToolUse(session, name, toolUseId, input) {
902
915
  const turnId = session.turn?.id;
903
916
  if (name === "TaskCreate") {
904
917
  prepareTaskPlanForCreate(session.tasks, input.subject);
905
- session.tasks.set(`pending:${toolUseId}`, {
918
+ session.tasks.set(`pending:${toolUseId}`, {
906
919
  id: `pending:${toolUseId}`,
907
920
  subject: input.subject || input.description || "작업",
908
921
  status: "pending",
909
- turnId,
910
- });
911
- } else if (name === "TaskUpdate") {
912
- applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session));
913
- }
914
- emitPlan(session);
915
- }
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
+ }
916
938
 
917
939
  function updatePlanFromToolResult(session, pending, message) {
918
940
  const value = message.tool_use_result;
@@ -1325,8 +1347,9 @@ async function runPendingPrompt(session) {
1325
1347
  }
1326
1348
  }
1327
1349
 
1328
- function finishTurn(session, error, durationMs) {
1329
- if (!session.turn) return;
1350
+ function finishTurn(session, error, durationMs) {
1351
+ if (!session.turn) return;
1352
+ flushPendingPlan(session);
1330
1353
  flushSmoothStreams(session);
1331
1354
  clearForegroundSubagents(session);
1332
1355
  const turn = { id: session.turn.id, status: error ? "failed" : "completed" };
@@ -1913,9 +1936,53 @@ async function runSelfTest() {
1913
1936
  }
1914
1937
  prepareTaskPlanForCreate(restoredTasks, "7. 추가 작업");
1915
1938
  if (restoredTasks.size !== 6) throw new Error("Claude appended task unexpectedly reset the plan");
1916
- prepareTaskPlanForCreate(restoredTasks, "1. 새 작업");
1917
- if (restoredTasks.size !== 0) throw new Error("Claude new task plan did not reset the previous plan");
1918
- const usage = tokenBreakdown({
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({
1919
1986
  input_tokens: 2,
1920
1987
  cache_read_input_tokens: 68_000,
1921
1988
  cache_creation_input_tokens: 500,
@@ -2127,10 +2194,13 @@ async function runSelfTest() {
2127
2194
  throw new Error(`Claude Korean progress normalization self-test failed: ${JSON.stringify(languageEvents)}`);
2128
2195
  }
2129
2196
  const smoothText = "Claude가 👨‍👩‍👧‍👦 한 문장을 한꺼번에 보내도 부드럽게 표시합니다.";
2130
- const emitted = [];
2131
- const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
2132
- smooth.push(smoothText);
2133
- await smooth.finish();
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();
2134
2204
  if (emitted.length < 2 || emitted.join("") !== smoothText) {
2135
2205
  throw new Error(`Claude smooth stream self-test failed: ${JSON.stringify(emitted)}`);
2136
2206
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.2.9",
3
+ "version": "1.2.11",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",