devez-vibe 1.2.16 → 1.2.18

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
@@ -884,7 +884,25 @@ function prepareTaskPlanForCreate(tasks, subject) {
884
884
  if (numberedTaskIndex(subject) === 1) tasks.clear();
885
885
  }
886
886
 
887
- function applyTaskUpdate(tasks, input, turnId, onIntermediate) {
887
+ // 단계에 걸린 시간은 진행 중으로 바뀐 순간과 완료된 순간의 차이다. 기록에는
888
+ // 항목마다 시각이 남으므로, 다시 읽을 때도 같은 방식으로 되살릴 수 있다.
889
+ function markTaskStatus(task, status, at) {
890
+ if (task.status !== status && at != null) {
891
+ if (status === "in_progress") {
892
+ task.startedAt = task.startedAt ?? at;
893
+ } else if (status === "completed" && task.startedAt != null) {
894
+ task.elapsedMs = Math.max(0, at - task.startedAt);
895
+ }
896
+ }
897
+ task.status = status;
898
+ }
899
+
900
+ function messageTime(message) {
901
+ const parsed = Date.parse(message?.timestamp || "");
902
+ return Number.isNaN(parsed) ? null : parsed;
903
+ }
904
+
905
+ function applyTaskUpdate(tasks, input, turnId, onIntermediate, at) {
888
906
  const task = tasks.get(String(input.taskId));
889
907
  if (!task) return false;
890
908
  if (input.subject) task.subject = input.subject;
@@ -900,11 +918,11 @@ function applyTaskUpdate(tasks, input, turnId, onIntermediate) {
900
918
  const previous = entries[index];
901
919
  if (previous.status === "completed") continue;
902
920
  if (previous.status !== "in_progress") {
903
- previous.status = "in_progress";
921
+ markTaskStatus(previous, "in_progress", at);
904
922
  previous.turnId = turnId;
905
923
  onIntermediate?.();
906
924
  }
907
- previous.status = "completed";
925
+ markTaskStatus(previous, "completed", at);
908
926
  previous.turnId = turnId;
909
927
  onIntermediate?.();
910
928
  }
@@ -912,19 +930,19 @@ function applyTaskUpdate(tasks, input, turnId, onIntermediate) {
912
930
  for (let index = 0; index < entries.length; index++) {
913
931
  const other = entries[index];
914
932
  if (other === task || other.status !== "in_progress") continue;
915
- other.status = index < targetIndex ? "completed" : "pending";
933
+ markTaskStatus(other, index < targetIndex ? "completed" : "pending", at);
916
934
  other.turnId = turnId;
917
935
  onIntermediate?.();
918
936
  }
919
937
 
920
938
  if (status === "completed" && task.status !== "in_progress" && task.status !== "completed") {
921
- task.status = "in_progress";
939
+ markTaskStatus(task, "in_progress", at);
922
940
  task.turnId = turnId;
923
941
  onIntermediate?.();
924
942
  }
925
- task.status = status;
943
+ markTaskStatus(task, status, at);
926
944
  } else if (status) {
927
- task.status = status;
945
+ markTaskStatus(task, status, at);
928
946
  }
929
947
  task.turnId = turnId;
930
948
  return true;
@@ -955,7 +973,7 @@ function updatePlanFromToolUse(session, name, toolUseId, input) {
955
973
  return;
956
974
  } else if (name === "TaskUpdate") {
957
975
  session.planCreatePending = false;
958
- applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session));
976
+ applyTaskUpdate(session.tasks, input, turnId, () => emitPlan(session), Date.now());
959
977
  }
960
978
  if (name === "TaskUpdate") emitPlan(session);
961
979
  }
@@ -1574,7 +1592,7 @@ function historyState(messages) {
1574
1592
  prepareTaskPlanForCreate(tasks, block.input?.subject);
1575
1593
  tasks.set(`pending:${block.id}`, { id: `pending:${block.id}`, subject: block.input?.subject || "작업", status: "pending", turnId: turn.id });
1576
1594
  } else if (block.name === "TaskUpdate") {
1577
- applyTaskUpdate(tasks, block.input || {}, turn.id);
1595
+ applyTaskUpdate(tasks, block.input || {}, turn.id, undefined, messageTime(message));
1578
1596
  } else if (!["TaskList", "AskUserQuestion"].includes(block.name)) turn.items.push(pending.item);
1579
1597
  }
1580
1598
  }
@@ -1594,7 +1612,19 @@ function historyState(messages) {
1594
1612
  } else if (pending.name === "TaskList" && Array.isArray(message.tool_use_result?.tasks)) {
1595
1613
  const known = new Map(tasks);
1596
1614
  tasks.clear();
1597
- 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 });
1615
+ for (const task of message.tool_use_result.tasks) {
1616
+ const previous = known.get(String(task.id));
1617
+ tasks.set(String(task.id), {
1618
+ id: String(task.id),
1619
+ subject: task.subject || "작업",
1620
+ status: task.status || "pending",
1621
+ turnId: previous?.turnId ?? turn.id,
1622
+ // A listing restates the plan; it does not re-run it, so the
1623
+ // timings already measured for these steps have to survive it.
1624
+ startedAt: previous?.startedAt,
1625
+ elapsedMs: previous?.elapsedMs,
1626
+ });
1627
+ }
1598
1628
  const current = latestTaskPlan(tasks);
1599
1629
  tasks.clear();
1600
1630
  for (const [id, task] of current) tasks.set(id, task);
@@ -1611,7 +1641,14 @@ function historyState(messages) {
1611
1641
  }
1612
1642
  if (turn && tasks.size) {
1613
1643
  const text = [...tasks.values()].map((task, index) => `${task.status === "completed" ? "✓" : task.status === "in_progress" ? "▸" : "□"} ${numberedTaskSubject(task.subject, index)}`).join("\n");
1614
- turn.items.push({ id: "claude-plan-latest", type: "plan", text });
1644
+ // The text alone cannot say how long a step took, so the measured times ride
1645
+ // alongside it and the restored plan shows its total instead of zero.
1646
+ const steps = [...tasks.values()].map((task, index) => ({
1647
+ step: numberedTaskSubject(task.subject, index),
1648
+ status: task.status || "pending",
1649
+ elapsedMs: task.elapsedMs ?? null,
1650
+ }));
1651
+ turn.items.push({ id: "claude-plan-latest", type: "plan", text, steps });
1615
1652
  }
1616
1653
  return {
1617
1654
  tasks,
@@ -1937,6 +1974,20 @@ async function runSelfTest() {
1937
1974
  || [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
1938
1975
  throw new Error(`Claude sequential task update self-test failed: ${JSON.stringify([...restoredTasks])}`);
1939
1976
  }
1977
+ // Each transcript record is stamped, so a step's own run is the span between
1978
+ // the update that started it and the one that closed it.
1979
+ const at = (message, timestamp) => ({ ...message, timestamp });
1980
+ const timedMessages = [
1981
+ at(user("timed", "작업을 진행해"), "2026-08-07T01:00:00.000Z"),
1982
+ at(taskUse("timed-create-use", "timed-create", "TaskCreate", { subject: "1. 확인" }), "2026-08-07T01:00:01.000Z"),
1983
+ at(taskResult("timed-create-result", "timed-create", { task: { id: "t1", subject: "1. 확인" } }, "Task #t1 created successfully: 1. 확인"), "2026-08-07T01:00:02.000Z"),
1984
+ at(taskUse("timed-start", "timed-start", "TaskUpdate", { taskId: "t1", status: "in_progress" }), "2026-08-07T01:00:03.000Z"),
1985
+ at(taskUse("timed-done", "timed-done", "TaskUpdate", { taskId: "t1", status: "completed" }), "2026-08-07T01:00:09.000Z"),
1986
+ ];
1987
+ const timedPlan = historyState(timedMessages).turns.at(-1)?.items.find((item) => item.type === "plan");
1988
+ if (timedPlan?.steps?.[0]?.elapsedMs !== 6000 || timedPlan.steps[0].status !== "completed") {
1989
+ throw new Error(`Claude plan step timing self-test failed: ${JSON.stringify(timedPlan)}`);
1990
+ }
1940
1991
  const skippedTasks = new Map([
1941
1992
  ["1", { id: "1", subject: "1. 조사", status: "pending" }],
1942
1993
  ["2", { id: "2", subject: "2. 분석", status: "pending" }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devez-vibe",
3
- "version": "1.2.16",
3
+ "version": "1.2.18",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",