devez-vibe 1.2.4 β†’ 1.2.6

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
@@ -435,23 +435,25 @@ async function createSession(params, resumeId) {
435
435
  turnSequence: 1,
436
436
  itemSequence: 1,
437
437
  streamBlocks: new Map(),
438
- tools: new Map(),
439
- tasks: new Map(),
440
- subagents: new Map(),
441
- lastContextUsage: null,
438
+ tools: new Map(),
439
+ tasks: new Map(),
440
+ subagents: new Map(),
441
+ knownSubagents: new Map(),
442
+ lastContextUsage: null,
442
443
  lastContextWindow: 0,
443
444
  };
444
445
  const agentQuery = await startAgentQuery(queue, makeOptions(params, id, resumeId));
445
446
  session.query = agentQuery;
446
447
  sessions.set(id, session);
447
- const consumer = consume(session).catch((error) => {
448
+ const consumer = consume(session).catch((error) => {
448
449
  notify("error", {
449
450
  threadId: id,
450
451
  provider: "Claude",
451
452
  error: { message: error instanceof Error ? error.message : String(error) },
452
- willRetry: false,
453
- });
454
- if (session.turn) finishTurn(session, error);
453
+ willRetry: false,
454
+ });
455
+ clearSubagents(session);
456
+ if (session.turn) finishTurn(session, error);
455
457
  });
456
458
  session.consumer = consumer;
457
459
  let initialization;
@@ -495,9 +497,87 @@ function emitItem(session, phase, item) {
495
497
  notify(`item/${phase}`, { threadId: session.id, turnId: session.turn?.id, item });
496
498
  }
497
499
 
498
- function emitDelta(session, method, itemId, delta) {
499
- notify(method, { threadId: session.id, turnId: session.turn?.id, itemId, delta, provider: "Claude" });
500
- }
500
+ function emitDelta(session, method, itemId, delta) {
501
+ notify(method, { threadId: session.id, turnId: session.turn?.id, itemId, delta, provider: "Claude" });
502
+ }
503
+
504
+ // Windows rounds larger timer delays up to the next scheduler slice. Ten
505
+ // milliseconds lands near one terminal frame instead of visibly stepping at ~30ms.
506
+ const SMOOTH_TEXT_INTERVAL_MS = 10;
507
+ const SMOOTH_TEXT_TARGET_FRAMES = 8;
508
+ const SMOOTH_TEXT_MAX_GRAPHEMES = 24;
509
+ const graphemeSegmenter = typeof Intl.Segmenter === "function"
510
+ ? new Intl.Segmenter(undefined, { granularity: "grapheme" })
511
+ : null;
512
+
513
+ function splitGraphemes(text) {
514
+ return graphemeSegmenter
515
+ ? Array.from(graphemeSegmenter.segment(text), ({ segment }) => segment)
516
+ : Array.from(text);
517
+ }
518
+
519
+ // Claude can deliver a whole phrase in one SDK event. Drain roughly one visual
520
+ // frame's share at a time, catching up quickly when a large backlog arrives.
521
+ function takeSmoothTextChunk(text) {
522
+ const graphemes = splitGraphemes(text);
523
+ const size = Math.min(
524
+ SMOOTH_TEXT_MAX_GRAPHEMES,
525
+ Math.max(1, Math.ceil(graphemes.length / SMOOTH_TEXT_TARGET_FRAMES)),
526
+ );
527
+ return {
528
+ chunk: graphemes.slice(0, size).join(""),
529
+ rest: graphemes.slice(size).join(""),
530
+ };
531
+ }
532
+
533
+ class SmoothTextStream {
534
+ constructor(emit, intervalMs = SMOOTH_TEXT_INTERVAL_MS) {
535
+ this.emit = emit;
536
+ this.intervalMs = intervalMs;
537
+ this.pending = "";
538
+ this.timer = null;
539
+ this.waiters = [];
540
+ }
541
+
542
+ push(text) {
543
+ this.pending += text;
544
+ if (this.timer == null) this.drain();
545
+ }
546
+
547
+ drain() {
548
+ if (!this.pending) {
549
+ this.timer = null;
550
+ for (const resolve of this.waiters.splice(0)) resolve();
551
+ return;
552
+ }
553
+ const { chunk, rest } = takeSmoothTextChunk(this.pending);
554
+ this.pending = rest;
555
+ this.emit(chunk);
556
+ if (this.pending) {
557
+ this.timer = setTimeout(() => this.drain(), this.intervalMs);
558
+ } else {
559
+ this.timer = null;
560
+ for (const resolve of this.waiters.splice(0)) resolve();
561
+ }
562
+ }
563
+
564
+ finish() {
565
+ if (!this.pending && this.timer == null) return Promise.resolve();
566
+ return new Promise((resolve) => this.waiters.push(resolve));
567
+ }
568
+
569
+ flush() {
570
+ if (this.timer != null) clearTimeout(this.timer);
571
+ this.timer = null;
572
+ if (this.pending) this.emit(this.pending);
573
+ this.pending = "";
574
+ for (const resolve of this.waiters.splice(0)) resolve();
575
+ }
576
+ }
577
+
578
+ function flushSmoothStreams(session) {
579
+ for (const block of session.streamBlocks.values()) block.smooth?.flush();
580
+ }
501
581
 
502
582
  function tokenBreakdown(usage) {
503
583
  if (!usage) return null;
@@ -545,41 +625,44 @@ function historyTokenUsage(messages, models, model) {
545
625
  };
546
626
  }
547
627
 
548
- function processStreamEvent(session, message) {
549
- if (!session.turn || message.parent_tool_use_id) return;
550
- const event = message.event || {};
551
- if (event.type === "message_start") session.streamBlocks.clear();
552
- if (event.type === "content_block_start") {
553
- const block = event.content_block || {};
554
- if (block.type !== "text" && block.type !== "thinking") return;
555
- const id = nextItemId(session, block.type);
628
+ async function processStreamEvent(session, message) {
629
+ if (!session.turn || message.parent_tool_use_id) return;
630
+ const event = message.event || {};
631
+ if (event.type === "message_start") {
632
+ flushSmoothStreams(session);
633
+ session.streamBlocks.clear();
634
+ }
635
+ if (event.type === "content_block_start") {
636
+ const block = event.content_block || {};
637
+ if (block.type !== "text" && block.type !== "thinking") return;
638
+ const id = nextItemId(session, block.type);
556
639
  const item = block.type === "text"
557
640
  ? { id, type: "agentMessage", text: "", provider: "Claude" }
558
641
  : { id, type: "reasoning", summary: [] };
559
- session.streamBlocks.set(event.index, { id, type: block.type, text: "" });
560
- emitItem(session, "started", item);
561
- return;
562
- }
642
+ const smooth = block.type === "text"
643
+ ? new SmoothTextStream((delta) => emitDelta(session, "item/agentMessage/delta", id, delta))
644
+ : null;
645
+ session.streamBlocks.set(event.index, { id, type: block.type, text: "", smooth });
646
+ emitItem(session, "started", item);
647
+ return;
648
+ }
563
649
  if (event.type === "content_block_delta") {
564
650
  const current = session.streamBlocks.get(event.index);
565
651
  if (!current) return;
566
- const delta = event.delta?.text || event.delta?.thinking || "";
567
- if (!delta) return;
568
- current.text += delta;
569
- emitDelta(
570
- session,
571
- current.type === "text" ? "item/agentMessage/delta" : "item/reasoning/summaryTextDelta",
572
- current.id,
573
- delta,
574
- );
575
- return;
576
- }
577
- if (event.type === "content_block_stop") {
578
- const current = session.streamBlocks.get(event.index);
579
- if (!current) return;
580
- const item = current.type === "text"
581
- ? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
582
- : { id: current.id, type: "reasoning", summary: [current.text] };
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
+ }
659
+ if (event.type === "content_block_stop") {
660
+ const current = session.streamBlocks.get(event.index);
661
+ if (!current) return;
662
+ await current.smooth?.finish();
663
+ const item = current.type === "text"
664
+ ? { id: current.id, type: "agentMessage", text: current.text, provider: "Claude" }
665
+ : { id: current.id, type: "reasoning", summary: [current.text] };
583
666
  emitItem(session, "completed", item);
584
667
  session.streamBlocks.delete(event.index);
585
668
  }
@@ -668,34 +751,52 @@ function fileChanges(name, input) {
668
751
  return [{ path, kind: { type: name === "Write" ? "add" : "update" }, diff: `@@ -0,0 +1 @@\n${additions}` }];
669
752
  }
670
753
 
671
- // ClaudeλŠ” Codex의 update_plan처럼 κ³„νš 전체λ₯Ό λ‹€μ‹œ 보내지 μ•ŠμœΌλ―€λ‘œ, μƒˆ κ³„νšμ΄ μ‹œμž‘λ  λ•Œ
672
- // 이전 ν„΄μ—μ„œ 이미 λλ‚œ μž‘μ—…μ„ 직접 κ±·μ–΄λ‚΄μ•Ό λͺ©λ‘μ΄ ν„΄λ§ˆλ‹€ μŒ“μ΄μ§€ μ•ŠλŠ”λ‹€.
673
- function pruneFinishedTasks(tasks, turnId) {
674
- for (const [key, task] of tasks) {
675
- if (task.status === "completed" && task.turnId !== turnId) tasks.delete(key);
676
- }
677
- }
754
+ function numberedTaskIndex(subject) {
755
+ const match = String(subject || "").trim().match(/^(\d+)[.)]\s+/);
756
+ return match ? Number(match[1]) : null;
757
+ }
758
+
759
+ // Claude의 Task idλŠ” μ„Έμ…˜ μ „μ²΄μ—μ„œ λˆ„μ λœλ‹€. 제λͺ© λ²ˆν˜Έκ°€ λ‹€μ‹œ 1λΆ€ν„°
760
+ // μ‹œμž‘ν•  λ•Œλ§Œ μƒˆ κ³„νšμ΄λ©°, 3Β·4번만 κ°±μ‹ λ˜λŠ” 턴은 κΈ°μ‘΄ 1~6λ²ˆμ„ 지킨닀.
761
+ function prepareTaskPlanForCreate(tasks, subject) {
762
+ if (numberedTaskIndex(subject) === 1) tasks.clear();
763
+ }
764
+
765
+ function applyTaskUpdate(tasks, input, turnId) {
766
+ const task = tasks.get(String(input.taskId));
767
+ if (!task) return false;
768
+ if (input.subject) task.subject = input.subject;
769
+ if (input.status) task.status = input.status;
770
+ task.turnId = turnId;
771
+ return true;
772
+ }
773
+
774
+ // TaskListμ—λŠ” μ˜ˆμ „ κ³„νšκΉŒμ§€ ν•¨κ»˜ λ“€μ–΄μ˜¬ 수 μžˆλ‹€. λ§ˆμ§€λ§‰μœΌλ‘œ λ²ˆν˜Έκ°€ 1λΆ€ν„°
775
+ // μ‹œμž‘ν•œ 묢음만 ν˜„μž¬ κ³„νšμœΌλ‘œ μ‚Όλ˜, λ²ˆν˜Έκ°€ μ—†λŠ” λͺ©λ‘μ€ 손싀 없이 κ·ΈλŒ€λ‘œ λ‘”λ‹€.
776
+ function latestTaskPlan(tasks) {
777
+ const entries = [...tasks.entries()];
778
+ let start = 0;
779
+ for (let index = 0; index < entries.length; index++) {
780
+ if (numberedTaskIndex(entries[index][1].subject) === 1) start = index;
781
+ }
782
+ return new Map(entries.slice(start));
783
+ }
678
784
 
679
785
  function updatePlanFromToolUse(session, name, toolUseId, input) {
680
- const turnId = session.turn?.id;
681
- if (name === "TaskCreate") {
682
- pruneFinishedTasks(session.tasks, turnId);
683
- session.tasks.set(`pending:${toolUseId}`, {
684
- id: `pending:${toolUseId}`,
685
- subject: input.subject || input.description || "μž‘μ—…",
786
+ const turnId = session.turn?.id;
787
+ if (name === "TaskCreate") {
788
+ prepareTaskPlanForCreate(session.tasks, input.subject);
789
+ session.tasks.set(`pending:${toolUseId}`, {
790
+ id: `pending:${toolUseId}`,
791
+ subject: input.subject || input.description || "μž‘μ—…",
686
792
  status: "pending",
687
- turnId,
688
- });
689
- } else if (name === "TaskUpdate") {
690
- const task = session.tasks.get(String(input.taskId));
691
- if (task) {
692
- if (input.subject) task.subject = input.subject;
693
- if (input.status) task.status = input.status;
694
- task.turnId = turnId;
695
- }
696
- }
697
- emitPlan(session);
698
- }
793
+ turnId,
794
+ });
795
+ } else if (name === "TaskUpdate") {
796
+ applyTaskUpdate(session.tasks, input, turnId);
797
+ }
798
+ emitPlan(session);
799
+ }
699
800
 
700
801
  function updatePlanFromToolResult(session, pending, message) {
701
802
  const value = message.tool_use_result;
@@ -723,14 +824,10 @@ function updatePlanFromToolResult(session, pending, message) {
723
824
  turnId: previous.get(id)?.turnId ?? turnId,
724
825
  });
725
826
  }
726
- // TaskListλŠ” μ„Έμ…˜ 전체 λͺ©λ‘μ„ λŒλ €μ£Όλ―€λ‘œ, μ§€λ‚œ 턴에 λλ‚œ μž‘μ—…κΉŒμ§€ λ˜μ‚΄μ•„λ‚˜μ§€ μ•Šκ²Œ κ±·μ–΄λ‚Έλ‹€.
727
- // λ‹€λ§Œ μ „λΆ€ κ±·μ–΄λ‚΄ λͺ©λ‘μ΄ λΉ„λ©΄ κ³„νš μΉ΄λ“œκ°€ ν†΅μ§Έλ‘œ μ‚¬λΌμ§€λ―€λ‘œ, κ·Έλ•ŒλŠ” 정리λ₯Ό λ‹€μŒ TaskCreate에 λ§‘κΈ΄λ‹€.
728
- const listed = new Map(session.tasks);
729
- pruneFinishedTasks(session.tasks, turnId);
730
- if (session.tasks.size === 0) session.tasks = listed;
731
- emitPlan(session);
732
- }
733
- }
827
+ session.tasks = latestTaskPlan(session.tasks);
828
+ emitPlan(session);
829
+ }
830
+ }
734
831
 
735
832
  function taskCreatedResult(structured, content) {
736
833
  const created = structured?.task || structured;
@@ -771,22 +868,78 @@ function numberedTaskSubject(subject, index) {
771
868
  // κ·Έ ID둜 λ¬Άμ–΄ 두면 μ§€κΈˆ μ–΄λ–€ μ—μ΄μ „νŠΈκ°€ 무슨 도ꡬλ₯Ό λŒλ¦¬λŠ”μ§€ κ·ΈλŒ€λ‘œ 볡원할 수 μžˆλ‹€.
772
869
  const SUBAGENT_TOOLS = ["Agent", "Task"];
773
870
 
774
- function startSubagent(session, block) {
775
- const input = block.input || {};
776
- session.subagents.set(block.id, {
777
- id: block.id,
778
- name: firstLine(input.subagent_type || input.agentType || "agent", 40),
871
+ function startSubagent(session, block) {
872
+ const input = block.input || {};
873
+ session.subagents.set(block.id, {
874
+ id: block.id,
875
+ taskId: "",
876
+ background: false,
877
+ name: firstLine(input.subagent_type || input.agentType || "agent", 40),
779
878
  description: firstLine(input.description || input.prompt || "", 120),
780
879
  tool: "",
781
880
  startedAt: Date.now(),
782
881
  });
783
- emitSubagents(session);
784
- }
882
+ emitSubagents(session);
883
+ }
884
+
885
+ function isBackgroundSubagentResult(result) {
886
+ return result?.isAsync === true || result?.status === "async_launched";
887
+ }
888
+
889
+ // Claude Code treats an async Agent result as a launch receipt. The agent remains
890
+ // live until its later task-notification names the same task or tool-use id.
891
+ function keepBackgroundSubagent(session, toolUseId, result) {
892
+ if (!isBackgroundSubagentResult(result)) return false;
893
+ const running = session.subagents.get(toolUseId);
894
+ if (!running) return false;
895
+ running.background = true;
896
+ running.taskId = firstLine(result?.agentId || result?.taskId || "", 80);
897
+ if (running.taskId) {
898
+ session.knownSubagents.set(running.taskId, {
899
+ name: running.name,
900
+ description: running.description,
901
+ });
902
+ }
903
+ return true;
904
+ }
905
+
906
+ // SendMessage can wake a completed agent from its transcript. Its new parent
907
+ // tool-use id owns this run, while the stable agent id lets later notifications
908
+ // and further resumes recover the original label.
909
+ function resumeBackgroundSubagent(session, toolUseId, pending, result) {
910
+ const taskId = firstLine(result?.resumedAgentId || "", 80);
911
+ if (!taskId) return false;
912
+ const existing = [...session.subagents.values()].find((agent) => agent.taskId === taskId);
913
+ if (existing) return true;
914
+ const known = session.knownSubagents.get(taskId);
915
+ const input = pending?.input || {};
916
+ const running = {
917
+ id: toolUseId,
918
+ taskId,
919
+ background: true,
920
+ name: known?.name || "agent",
921
+ description: known?.description || firstLine(input.summary || input.message || "", 120),
922
+ tool: "",
923
+ startedAt: Date.now(),
924
+ };
925
+ session.subagents.set(toolUseId, running);
926
+ session.knownSubagents.set(taskId, {
927
+ name: running.name,
928
+ description: running.description,
929
+ });
930
+ emitSubagents(session);
931
+ return true;
932
+ }
933
+
934
+ function findSubagent(session, id) {
935
+ return session.subagents.get(id)
936
+ || [...session.subagents.values()].find((agent) => agent.taskId === id);
937
+ }
785
938
 
786
939
  // μ„œλΈŒμ—μ΄μ „νŠΈκ°€ μ‹€μ œλ‘œ 무엇을 ν–ˆλŠ”μ§€λŠ” μžμ‹ λ©”μ‹œμ§€μ—λ§Œ λ‚¨λŠ”λ‹€. μ—΄λžŒμš© 기둝은 μ—¬κΈ°μ„œ
787
940
  // ν•œ 쀄씩 ν˜λ €λ³΄λ‚΄κ³ , λͺ©λ‘ 행에 μ“Έ ν˜„μž¬ λ„κ΅¬λ§Œ λ”°λ‘œ κ°±μ‹ ν•œλ‹€.
788
- function recordSubagentMessage(session, message) {
789
- const running = session.subagents.get(message.parent_tool_use_id);
941
+ function recordSubagentMessage(session, message) {
942
+ const running = findSubagent(session, message.parent_tool_use_id);
790
943
  if (!running) return;
791
944
  const content = Array.isArray(message.message?.content) ? message.message.content : [];
792
945
  let toolChanged = false;
@@ -807,8 +960,8 @@ function recordSubagentMessage(session, message) {
807
960
  if (toolChanged) emitSubagents(session);
808
961
  }
809
962
 
810
- function recordSubagentResult(session, message) {
811
- const running = session.subagents.get(message.parent_tool_use_id);
963
+ function recordSubagentResult(session, message) {
964
+ const running = findSubagent(session, message.parent_tool_use_id);
812
965
  if (!running) return;
813
966
  const content = Array.isArray(message.message?.content) ? message.message.content : [];
814
967
  for (const block of content) {
@@ -844,13 +997,32 @@ function subagentToolLabel(block) {
844
997
  return text ? `${name}(${text})` : name;
845
998
  }
846
999
 
847
- function finishSubagent(session, toolUseId) {
848
- if (!session.subagents.delete(toolUseId)) return;
849
- emitSubagents(session);
850
- }
851
-
852
- function clearSubagents(session) {
853
- if (!session.subagents.size) return;
1000
+ function finishSubagent(session, toolUseId) {
1001
+ if (!session.subagents.delete(toolUseId)) return;
1002
+ emitSubagents(session);
1003
+ }
1004
+
1005
+ function finishSubagentTask(session, taskId) {
1006
+ if (!taskId) return false;
1007
+ const entry = [...session.subagents.entries()].find(([, agent]) => agent.taskId === taskId);
1008
+ if (!entry) return false;
1009
+ session.subagents.delete(entry[0]);
1010
+ emitSubagents(session);
1011
+ return true;
1012
+ }
1013
+
1014
+ function clearForegroundSubagents(session) {
1015
+ let changed = false;
1016
+ for (const [id, agent] of session.subagents) {
1017
+ if (agent.background) continue;
1018
+ session.subagents.delete(id);
1019
+ changed = true;
1020
+ }
1021
+ if (changed) emitSubagents(session);
1022
+ }
1023
+
1024
+ function clearSubagents(session) {
1025
+ if (!session.subagents.size) return;
854
1026
  session.subagents.clear();
855
1027
  emitSubagents(session);
856
1028
  }
@@ -870,22 +1042,87 @@ function emitSubagents(session) {
870
1042
  tool: agent.tool,
871
1043
  elapsedMs: Date.now() - agent.startedAt,
872
1044
  })),
873
- });
874
- }
875
-
876
- function processUser(session, message) {
1045
+ });
1046
+ }
1047
+
1048
+ function messageTextParts(message) {
1049
+ const content = message.message?.content;
1050
+ if (typeof content === "string") return [content];
1051
+ if (!Array.isArray(content)) return [];
1052
+ return content
1053
+ .filter((block) => block?.type === "text" && typeof block.text === "string")
1054
+ .map((block) => block.text);
1055
+ }
1056
+
1057
+ function notificationTag(body, name) {
1058
+ const match = body.match(new RegExp(`<${name}>([\\s\\S]*?)</${name}>`));
1059
+ return match?.[1]?.trim() || "";
1060
+ }
1061
+
1062
+ function taskNotifications(message) {
1063
+ if (message.origin?.kind !== "task-notification"
1064
+ && !messageTextParts(message).some((text) => text.includes("<task-notification>"))) {
1065
+ return [];
1066
+ }
1067
+ const notifications = [];
1068
+ for (const text of messageTextParts(message)) {
1069
+ for (const match of text.matchAll(/<task-notification>([\s\S]*?)<\/task-notification>/g)) {
1070
+ const body = match[1];
1071
+ notifications.push({
1072
+ taskId: notificationTag(body, "task-id"),
1073
+ toolUseId: notificationTag(body, "tool-use-id"),
1074
+ status: notificationTag(body, "status"),
1075
+ summary: notificationTag(body, "summary"),
1076
+ });
1077
+ }
1078
+ }
1079
+ return notifications;
1080
+ }
1081
+
1082
+ function finishNotifiedSubagents(session, notifications) {
1083
+ for (const notification of notifications) {
1084
+ const byToolUse = notification.toolUseId
1085
+ ? session.subagents.get(notification.toolUseId)
1086
+ : null;
1087
+ const running = byToolUse || (notification.taskId
1088
+ ? [...session.subagents.values()].find((agent) => agent.taskId === notification.taskId)
1089
+ : null);
1090
+ if (!running) continue;
1091
+ emitSubagentLine(session, running.id, {
1092
+ kind: notification.status === "completed" ? "result" : "error",
1093
+ text: notification.summary || notification.status || "μ™„λ£Œλ¨",
1094
+ });
1095
+ session.subagents.delete(running.id);
1096
+ emitSubagents(session);
1097
+ }
1098
+ }
1099
+
1100
+ function processUser(session, message) {
877
1101
  // μžμ‹ tool_result의 tool_use_idλŠ” λΆ€λͺ¨ μ„Έμ…˜μ˜ 것과 λ‹€λ₯Έ κ³΅κ°„μ΄λ―€λ‘œ, λΆ€λͺ¨ 흐름에
878
1102
  // μ„žμ΄κΈ° 전에 μ„œλΈŒμ—μ΄μ „νŠΈ 기둝으둜 보낸닀.
879
1103
  if (message.parent_tool_use_id) {
880
- recordSubagentResult(session, message);
881
- return;
882
- }
883
- const content = Array.isArray(message.message?.content) ? message.message.content : [];
884
- for (const block of content) {
885
- if (block.type !== "tool_result") continue;
886
- finishSubagent(session, block.tool_use_id);
887
- const pending = session.tools.get(block.tool_use_id);
888
- if (!pending) continue;
1104
+ recordSubagentResult(session, message);
1105
+ return;
1106
+ }
1107
+ const notifications = taskNotifications(message);
1108
+ if (notifications.length) {
1109
+ if (!session.turn) beginTurn(session);
1110
+ finishNotifiedSubagents(session, notifications);
1111
+ return;
1112
+ }
1113
+ const content = Array.isArray(message.message?.content) ? message.message.content : [];
1114
+ for (const block of content) {
1115
+ if (block.type !== "tool_result") continue;
1116
+ const pending = session.tools.get(block.tool_use_id);
1117
+ const staysInBackground = SUBAGENT_TOOLS.includes(pending?.name)
1118
+ && keepBackgroundSubagent(session, block.tool_use_id, message.tool_use_result);
1119
+ if (!staysInBackground) finishSubagent(session, block.tool_use_id);
1120
+ if (pending?.name === "SendMessage") {
1121
+ resumeBackgroundSubagent(session, block.tool_use_id, pending, message.tool_use_result);
1122
+ } else if (pending?.name === "TaskStop" && message.tool_use_result?.success !== false) {
1123
+ finishSubagentTask(session, firstLine(pending.input?.task_id || "", 80));
1124
+ }
1125
+ if (!pending) continue;
889
1126
  pending.toolUseId = block.tool_use_id;
890
1127
  if (pending.suppressed) {
891
1128
  updatePlanFromToolResult(session, pending, message);
@@ -972,9 +1209,10 @@ async function runPendingPrompt(session) {
972
1209
  }
973
1210
  }
974
1211
 
975
- function finishTurn(session, error, durationMs) {
976
- if (!session.turn) return;
977
- clearSubagents(session);
1212
+ function finishTurn(session, error, durationMs) {
1213
+ if (!session.turn) return;
1214
+ flushSmoothStreams(session);
1215
+ clearForegroundSubagents(session);
978
1216
  const turn = { id: session.turn.id, status: error ? "failed" : "completed" };
979
1217
  if (error) turn.error = { message: error instanceof Error ? error.message : error.message || String(error) };
980
1218
  if (durationMs != null) turn.durationMs = durationMs;
@@ -990,7 +1228,7 @@ async function consume(session) {
990
1228
  if (message.event?.type === "content_block_delta" && (message.event?.delta?.text || message.event?.delta?.thinking)) {
991
1229
  if (session.turn) session.turn.sawStreamText = true;
992
1230
  }
993
- processStreamEvent(session, message);
1231
+ await processStreamEvent(session, message);
994
1232
  } else if (message.type === "assistant") processAssistant(session, message);
995
1233
  else if (message.type === "user") processUser(session, message);
996
1234
  else if (message.type === "result") await processResult(session, message);
@@ -1060,7 +1298,7 @@ async function startPrompt(params) {
1060
1298
  return runPrompt(session, params);
1061
1299
  }
1062
1300
 
1063
- async function runPrompt(session, params) {
1301
+ async function runPrompt(session, params) {
1064
1302
  const id = session.id;
1065
1303
  if (params.model) {
1066
1304
  const model = stripClaudeModel(params.model);
@@ -1073,10 +1311,7 @@ async function runPrompt(session, params) {
1073
1311
  }
1074
1312
  session.effort = effort;
1075
1313
  await applyPermissionMode(session, params.permissionMode);
1076
- const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
1077
- session.turn = { id: turnId, sawStreamText: false };
1078
- session.lastContextUsage = null;
1079
- notify("turn/started", { threadId: id, turn: { id: turnId } });
1314
+ const turnId = beginTurn(session);
1080
1315
  session.queue.push({
1081
1316
  type: "user",
1082
1317
  message: { role: "user", content: await inputContent(params.input, params.handoffContext) },
@@ -1084,8 +1319,18 @@ async function runPrompt(session, params) {
1084
1319
  session_id: id,
1085
1320
  origin: { kind: "human" },
1086
1321
  });
1087
- return { turn: { id: turnId } };
1088
- }
1322
+ return { turn: { id: turnId } };
1323
+ }
1324
+
1325
+ // A background task notification is an internal user message that starts its
1326
+ // own Claude response even though the host did not submit a new prompt.
1327
+ function beginTurn(session) {
1328
+ const turnId = `claude-turn-${session.turnSequence++}-${randomUUID()}`;
1329
+ session.turn = { id: turnId, sawStreamText: false };
1330
+ session.lastContextUsage = null;
1331
+ notify("turn/started", { threadId: session.id, turn: { id: turnId } });
1332
+ return turnId;
1333
+ }
1089
1334
 
1090
1335
  function contentBlocks(message) {
1091
1336
  const content = message?.content;
@@ -1110,7 +1355,7 @@ function isInternalHistoryText(message, text) {
1110
1355
  ].includes(tag);
1111
1356
  }
1112
1357
 
1113
- function historyTurns(messages) {
1358
+ function historyState(messages) {
1114
1359
  const turns = [];
1115
1360
  let turn = null;
1116
1361
  const tools = new Map();
@@ -1146,15 +1391,14 @@ function historyTurns(messages) {
1146
1391
  if (block.type === "text") turn.items.push({ id: `${message.uuid}-text`, type: "agentMessage", text: block.text || "", provider: "Claude" });
1147
1392
  else if (block.type === "thinking") turn.items.push({ id: `${message.uuid}-thinking`, type: "reasoning", summary: [block.thinking || ""] });
1148
1393
  else if (block.type === "tool_use") {
1149
- const pending = { name: block.name, input: block.input || {}, item: toolItem({}, block.id, block.name, block.input || {}) };
1150
- tools.set(block.id, pending);
1151
- if (block.name === "TaskCreate") {
1152
- pruneFinishedTasks(tasks, turn.id);
1153
- tasks.set(`pending:${block.id}`, { id: `pending:${block.id}`, subject: block.input?.subject || "μž‘μ—…", status: "pending", turnId: turn.id });
1154
- } else if (block.name === "TaskUpdate") {
1155
- const task = tasks.get(String(block.input?.taskId));
1156
- if (task) Object.assign(task, block.input?.subject ? { subject: block.input.subject } : {}, block.input?.status ? { status: block.input.status } : {}, { turnId: turn.id });
1157
- } else if (!["TaskList", "AskUserQuestion"].includes(block.name)) turn.items.push(pending.item);
1394
+ const pending = { name: block.name, input: block.input || {}, item: toolItem({}, block.id, block.name, block.input || {}) };
1395
+ tools.set(block.id, pending);
1396
+ if (block.name === "TaskCreate") {
1397
+ prepareTaskPlanForCreate(tasks, block.input?.subject);
1398
+ tasks.set(`pending:${block.id}`, { id: `pending:${block.id}`, subject: block.input?.subject || "μž‘μ—…", status: "pending", turnId: turn.id });
1399
+ } else if (block.name === "TaskUpdate") {
1400
+ applyTaskUpdate(tasks, block.input || {}, turn.id);
1401
+ } else if (!["TaskList", "AskUserQuestion"].includes(block.name)) turn.items.push(pending.item);
1158
1402
  }
1159
1403
  }
1160
1404
  } else if (message.type === "user") {
@@ -1171,12 +1415,12 @@ function historyTurns(messages) {
1171
1415
  tasks.set(temporary.id, temporary);
1172
1416
  }
1173
1417
  } else if (pending.name === "TaskList" && Array.isArray(message.tool_use_result?.tasks)) {
1174
- const known = new Map(tasks);
1175
- tasks.clear();
1176
- for (const task of message.tool_use_result.tasks) tasks.set(String(task.id), { id: String(task.id), subject: task.subject || "μž‘μ—…", status: task.status || "pending", turnId: known.get(String(task.id))?.turnId ?? turn.id });
1177
- const listed = new Map(tasks);
1178
- pruneFinishedTasks(tasks, turn.id);
1179
- if (tasks.size === 0) for (const [id, task] of listed) tasks.set(id, task);
1418
+ const known = new Map(tasks);
1419
+ tasks.clear();
1420
+ for (const task of message.tool_use_result.tasks) tasks.set(String(task.id), { id: String(task.id), subject: task.subject || "μž‘μ—…", status: task.status || "pending", turnId: known.get(String(task.id))?.turnId ?? turn.id });
1421
+ const current = latestTaskPlan(tasks);
1422
+ tasks.clear();
1423
+ for (const [id, task] of current) tasks.set(id, task);
1180
1424
  } else if (pending.item) {
1181
1425
  const output = toolOutput(block.content, message.tool_use_result);
1182
1426
  Object.assign(pending.item, pending.item.type === "commandExecution"
@@ -1192,10 +1436,17 @@ function historyTurns(messages) {
1192
1436
  const text = [...tasks.values()].map((task, index) => `${task.status === "completed" ? "βœ“" : task.status === "in_progress" ? "β–Έ" : "β–‘"} ${numberedTaskSubject(task.subject, index)}`).join("\n");
1193
1437
  turn.items.push({ id: "claude-plan-latest", type: "plan", text });
1194
1438
  }
1195
- return turns
1196
- .filter((candidate) => !candidate.synthetic)
1197
- .map(({ synthetic: _, ...candidate }) => candidate);
1198
- }
1439
+ return {
1440
+ tasks,
1441
+ turns: turns
1442
+ .filter((candidate) => !candidate.synthetic)
1443
+ .map(({ synthetic: _, ...candidate }) => candidate),
1444
+ };
1445
+ }
1446
+
1447
+ function historyTurns(messages) {
1448
+ return historyState(messages).turns;
1449
+ }
1199
1450
 
1200
1451
  async function dispatch(method, params = {}) {
1201
1452
  if (method === "model/list") return loadModelCatalog(params);
@@ -1220,10 +1471,11 @@ async function dispatch(method, params = {}) {
1220
1471
  }
1221
1472
  if (method === "session/resume") {
1222
1473
  const id = liveSessionId(params.sessionId);
1223
- const existing = sessions.get(id);
1224
- if (existing) {
1225
- const messages = await getSessionMessages(id, { dir: existing.cwd, includeSystemMessages: true });
1226
- return {
1474
+ const existing = sessions.get(id);
1475
+ if (existing) {
1476
+ const messages = await getSessionMessages(id, { dir: existing.cwd, includeSystemMessages: true });
1477
+ if (!existing.tasks.size) existing.tasks = historyState(messages).tasks;
1478
+ return {
1227
1479
  id,
1228
1480
  thread: { id, turns: [] },
1229
1481
  initialTurnsPage: { data: [], nextCursor: null },
@@ -1247,10 +1499,11 @@ async function dispatch(method, params = {}) {
1247
1499
  model: params.model || lastModel || params.fallbackModel,
1248
1500
  effort: params.effort || params.fallbackEffort,
1249
1501
  }, id);
1250
- const tokenUsage = historyTokenUsage(messages, session.models, session.model);
1251
- // Seed the live session so the next turn keeps reporting a full context.
1252
- session.lastContextUsage = tokenUsage?.last || null;
1253
- session.lastContextWindow = tokenUsage?.modelContextWindow || 0;
1502
+ const tokenUsage = historyTokenUsage(messages, session.models, session.model);
1503
+ // Seed the live session so the next turn keeps reporting a full context.
1504
+ session.lastContextUsage = tokenUsage?.last || null;
1505
+ session.lastContextWindow = tokenUsage?.modelContextWindow || 0;
1506
+ session.tasks = historyState(messages).tasks;
1254
1507
  return {
1255
1508
  id,
1256
1509
  thread: { id, turns: [] },
@@ -1352,7 +1605,7 @@ async function dispatch(method, params = {}) {
1352
1605
  throw new Error(`μ§€μ›ν•˜μ§€ μ•ŠλŠ” Claude λΈŒλ¦¬μ§€ λ©”μ„œλ“œ: ${method}`);
1353
1606
  }
1354
1607
 
1355
- function runSelfTest() {
1608
+ async function runSelfTest() {
1356
1609
  const user = (uuid, text) => ({
1357
1610
  type: "user",
1358
1611
  uuid,
@@ -1383,12 +1636,70 @@ function runSelfTest() {
1383
1636
  ["say hello", "claude:claude-sonnet-5"],
1384
1637
  ["hay zzz", "claude:claude-haiku-4-5-20251001"],
1385
1638
  ];
1386
- if (turns.length !== expected.length
1387
- || prompts.some((prompt, index) => prompt?.content?.[0]?.text !== expected[index][0]
1388
- || prompt.model !== expected[index][1])) {
1389
- throw new Error(`Claude history self-test failed: ${JSON.stringify(turns)}`);
1390
- }
1391
- const usage = tokenBreakdown({
1639
+ if (turns.length !== expected.length
1640
+ || prompts.some((prompt, index) => prompt?.content?.[0]?.text !== expected[index][0]
1641
+ || prompt.model !== expected[index][1])) {
1642
+ throw new Error(`Claude history self-test failed: ${JSON.stringify(turns)}`);
1643
+ }
1644
+ const taskUse = (uuid, id, name, input) => ({
1645
+ type: "assistant",
1646
+ uuid,
1647
+ message: {
1648
+ role: "assistant",
1649
+ model: "claude-opus-5",
1650
+ content: [{ type: "tool_use", id, name, input }],
1651
+ },
1652
+ });
1653
+ const taskResult = (uuid, id, toolUseResult, content) => ({
1654
+ type: "user",
1655
+ uuid,
1656
+ message: { role: "user", content: [{ type: "tool_result", tool_use_id: id, content }] },
1657
+ tool_use_result: toolUseResult,
1658
+ });
1659
+ const taskMessages = [user("plan", "μž‘μ—…μ„ μ§„ν–‰ν•΄")];
1660
+ for (let index = 1; index <= 6; index++) {
1661
+ const id = String(24 + index);
1662
+ const toolId = `create-${id}`;
1663
+ const subject = `${index}. μž‘μ—… ${index}`;
1664
+ taskMessages.push(
1665
+ taskUse(`create-use-${id}`, toolId, "TaskCreate", { subject }),
1666
+ taskResult(`create-result-${id}`, toolId, { task: { id, subject } }, `Task #${id} created successfully: ${subject}`),
1667
+ );
1668
+ }
1669
+ taskMessages.push(
1670
+ taskUse("update-25-start", "update-25-start", "TaskUpdate", { taskId: "25", status: "in_progress" }),
1671
+ taskUse("update-25-done", "update-25-done", "TaskUpdate", { taskId: "25", status: "completed" }),
1672
+ taskUse("update-26-start", "update-26-start", "TaskUpdate", { taskId: "26", status: "in_progress" }),
1673
+ );
1674
+ const restoredTasks = historyState(taskMessages).tasks;
1675
+ if ([...restoredTasks.keys()].join(",") !== "25,26,27,28,29,30"
1676
+ || restoredTasks.get("25")?.status !== "completed"
1677
+ || restoredTasks.get("26")?.status !== "in_progress"
1678
+ || [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
1679
+ throw new Error(`Claude task resume self-test failed: ${JSON.stringify([...restoredTasks])}`);
1680
+ }
1681
+ applyTaskUpdate(restoredTasks, { taskId: "26", status: "completed" }, "resumed-turn");
1682
+ applyTaskUpdate(restoredTasks, { taskId: "27", status: "in_progress" }, "resumed-turn");
1683
+ if (restoredTasks.size !== 6
1684
+ || restoredTasks.get("25")?.status !== "completed"
1685
+ || restoredTasks.get("26")?.status !== "completed"
1686
+ || restoredTasks.get("27")?.status !== "in_progress"
1687
+ || [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
1688
+ throw new Error(`Claude sequential task update self-test failed: ${JSON.stringify([...restoredTasks])}`);
1689
+ }
1690
+ const mixedPlans = new Map([
1691
+ ["old-1", { subject: "1. 이전 μž‘μ—…", status: "completed" }],
1692
+ ["old-2", { subject: "2. 이전 검증", status: "completed" }],
1693
+ ...[...restoredTasks],
1694
+ ]);
1695
+ if ([...latestTaskPlan(mixedPlans).keys()].join(",") !== "25,26,27,28,29,30") {
1696
+ throw new Error(`Claude latest task plan self-test failed: ${JSON.stringify([...mixedPlans])}`);
1697
+ }
1698
+ prepareTaskPlanForCreate(restoredTasks, "7. μΆ”κ°€ μž‘μ—…");
1699
+ if (restoredTasks.size !== 6) throw new Error("Claude appended task unexpectedly reset the plan");
1700
+ prepareTaskPlanForCreate(restoredTasks, "1. μƒˆ μž‘μ—…");
1701
+ if (restoredTasks.size !== 0) throw new Error("Claude new task plan did not reset the previous plan");
1702
+ const usage = tokenBreakdown({
1392
1703
  input_tokens: 2,
1393
1704
  cache_read_input_tokens: 68_000,
1394
1705
  cache_creation_input_tokens: 500,
@@ -1402,16 +1713,148 @@ function runSelfTest() {
1402
1713
  catalogEntry({ value: "sonnet", resolvedModel: "claude-sonnet-5" }, "").contextWindow,
1403
1714
  catalogEntry({ value: "haiku", resolvedModel: "x", contextWindow: 300_000 }, "").contextWindow,
1404
1715
  ];
1405
- if (windows.join(",") !== "1000000,200000,300000") {
1406
- throw new Error(`Claude context window self-test failed: ${windows.join(",")}`);
1407
- }
1408
- process.stdout.write("Claude bridge self-test passed\n");
1409
- }
1410
-
1411
- if (process.argv.includes("--self-test")) {
1412
- runSelfTest();
1413
- process.exit(0);
1414
- }
1716
+ if (windows.join(",") !== "1000000,200000,300000") {
1717
+ throw new Error(`Claude context window self-test failed: ${windows.join(",")}`);
1718
+ }
1719
+ const notification = taskNotifications({
1720
+ origin: { kind: "task-notification" },
1721
+ message: {
1722
+ content: `<task-notification>
1723
+ <task-id>agent-1</task-id>
1724
+ <tool-use-id>toolu_1</tool-use-id>
1725
+ <status>completed</status>
1726
+ <summary>Agent "Explore" finished</summary>
1727
+ <result>done</result>
1728
+ </task-notification>`,
1729
+ },
1730
+ });
1731
+ if (notification.length !== 1
1732
+ || notification[0].taskId !== "agent-1"
1733
+ || notification[0].toolUseId !== "toolu_1"
1734
+ || notification[0].status !== "completed"
1735
+ || notification[0].summary !== 'Agent "Explore" finished') {
1736
+ throw new Error(`Claude task notification self-test failed: ${JSON.stringify(notification)}`);
1737
+ }
1738
+ const lifecycleSession = {
1739
+ id: "self-test-session",
1740
+ turn: { id: "parent-turn", sawStreamText: false },
1741
+ turnSequence: 1,
1742
+ streamBlocks: new Map(),
1743
+ tools: new Map([[
1744
+ "toolu_1",
1745
+ {
1746
+ name: "Agent",
1747
+ input: { subagent_type: "Explore", description: "Inspect files" },
1748
+ item: { id: "toolu_1", type: "collabAgentToolCall", tool: { name: "Agent", arguments: {} } },
1749
+ },
1750
+ ]]),
1751
+ subagents: new Map([[
1752
+ "toolu_1",
1753
+ {
1754
+ id: "toolu_1",
1755
+ taskId: "",
1756
+ background: false,
1757
+ name: "Explore",
1758
+ description: "Inspect files",
1759
+ tool: "",
1760
+ startedAt: Date.now(),
1761
+ },
1762
+ ]]),
1763
+ knownSubagents: new Map(),
1764
+ lastContextUsage: null,
1765
+ };
1766
+ const captured = [];
1767
+ const stdoutWrite = process.stdout.write;
1768
+ process.stdout.write = (chunk) => {
1769
+ captured.push(String(chunk));
1770
+ return true;
1771
+ };
1772
+ try {
1773
+ processUser(lifecycleSession, {
1774
+ message: { content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "launched" }] },
1775
+ tool_use_result: { isAsync: true, status: "async_launched", agentId: "agent-1" },
1776
+ });
1777
+ finishTurn(lifecycleSession, null, 1);
1778
+ if (!lifecycleSession.subagents.has("toolu_1") || lifecycleSession.turn !== null) {
1779
+ throw new Error("Claude background subagent did not survive its parent turn");
1780
+ }
1781
+ processUser(lifecycleSession, {
1782
+ origin: { kind: "task-notification" },
1783
+ message: { content: notification[0] && `<task-notification>
1784
+ <task-id>agent-1</task-id><tool-use-id>toolu_1</tool-use-id>
1785
+ <status>completed</status><summary>Agent finished</summary>
1786
+ </task-notification>` },
1787
+ });
1788
+ if (lifecycleSession.subagents.size !== 0 || lifecycleSession.turn === null) {
1789
+ throw new Error("Claude task notification did not finish the agent in an automatic turn");
1790
+ }
1791
+ finishTurn(lifecycleSession, null, 1);
1792
+
1793
+ beginTurn(lifecycleSession);
1794
+ lifecycleSession.tools.set("toolu_2", {
1795
+ name: "SendMessage",
1796
+ input: { to: "agent-1", summary: "Continue inspection" },
1797
+ item: { id: "toolu_2", type: "dynamicToolCall", tool: "SendMessage", arguments: {} },
1798
+ });
1799
+ processUser(lifecycleSession, {
1800
+ message: { content: [{ type: "tool_result", tool_use_id: "toolu_2", content: "resumed" }] },
1801
+ tool_use_result: { success: true, resumedAgentId: "agent-1" },
1802
+ });
1803
+ const resumed = lifecycleSession.subagents.get("toolu_2");
1804
+ if (!resumed?.background || resumed.taskId !== "agent-1" || resumed.name !== "Explore") {
1805
+ throw new Error(`Claude resumed subagent self-test failed: ${JSON.stringify(resumed)}`);
1806
+ }
1807
+ finishTurn(lifecycleSession, null, 1);
1808
+ processUser(lifecycleSession, {
1809
+ origin: { kind: "task-notification" },
1810
+ message: { content: `<task-notification>
1811
+ <task-id>agent-1</task-id><tool-use-id>toolu_2</tool-use-id>
1812
+ <status>failed</status><summary>Agent failed</summary>
1813
+ </task-notification>` },
1814
+ });
1815
+ if (lifecycleSession.subagents.size !== 0 || lifecycleSession.turn === null) {
1816
+ throw new Error("Claude failed task notification did not finish the resumed agent");
1817
+ }
1818
+ finishTurn(lifecycleSession, null, 1);
1819
+ } finally {
1820
+ process.stdout.write = stdoutWrite;
1821
+ }
1822
+ const lifecycleEvents = captured
1823
+ .join("")
1824
+ .trim()
1825
+ .split("\n")
1826
+ .filter(Boolean)
1827
+ .map((line) => JSON.parse(line));
1828
+ const lifecycleMethods = lifecycleEvents.map((event) => event.method);
1829
+ if (!lifecycleMethods.includes("turn/subagents/updated")
1830
+ || lifecycleMethods.filter((method) => method === "turn/started").length < 3
1831
+ || !lifecycleEvents.some((event) => event.method === "turn/subagent/line"
1832
+ && event.params?.line?.kind === "error")) {
1833
+ throw new Error(`Claude subagent lifecycle events self-test failed: ${lifecycleMethods}`);
1834
+ }
1835
+ const smoothText = "Claudeκ°€ πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ ν•œ λ¬Έμž₯을 ν•œκΊΌλ²ˆμ— 보내도 λΆ€λ“œλŸ½κ²Œ ν‘œμ‹œν•©λ‹ˆλ‹€.";
1836
+ const emitted = [];
1837
+ const smooth = new SmoothTextStream((chunk) => emitted.push(chunk), 0);
1838
+ smooth.push(smoothText);
1839
+ await smooth.finish();
1840
+ if (emitted.length < 2 || emitted.join("") !== smoothText) {
1841
+ throw new Error(`Claude smooth stream self-test failed: ${JSON.stringify(emitted)}`);
1842
+ }
1843
+ const flushed = [];
1844
+ const interrupted = new SmoothTextStream((chunk) => flushed.push(chunk), 1000);
1845
+ interrupted.push(smoothText);
1846
+ interrupted.flush();
1847
+ await interrupted.finish();
1848
+ if (flushed.join("") !== smoothText) {
1849
+ throw new Error(`Claude smooth stream flush self-test failed: ${JSON.stringify(flushed)}`);
1850
+ }
1851
+ process.stdout.write("Claude bridge self-test passed\n");
1852
+ }
1853
+
1854
+ if (process.argv.includes("--self-test")) {
1855
+ await runSelfTest();
1856
+ process.exit(0);
1857
+ }
1415
1858
 
1416
1859
  const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
1417
1860
  lines.on("line", async (line) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",