devez-vibe 1.2.17 → 1.2.19

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
  }
@@ -1559,14 +1577,20 @@ function historyState(messages) {
1559
1577
  continue;
1560
1578
  }
1561
1579
  turn.synthetic = false;
1562
- if (!turn.model && message.message?.model) {
1563
- turn.model = visibleModel(message.message.model);
1564
- const prompt = turn.items.find((item) => item.type === "userMessage");
1565
- if (prompt) prompt.model = turn.model;
1566
- }
1567
- for (const block of blocks) {
1568
- if (block.type === "text") turn.items.push({ id: `${message.uuid}-text`, type: "agentMessage", text: block.text || "", provider: "Claude" });
1569
- else if (block.type === "thinking") turn.items.push({ id: `${message.uuid}-thinking`, type: "reasoning", summary: [block.thinking || ""] });
1580
+ if (!turn.model && message.message?.model) {
1581
+ turn.model = visibleModel(message.message.model);
1582
+ const prompt = turn.items.find((item) => item.type === "userMessage");
1583
+ if (prompt) prompt.model = turn.model;
1584
+ }
1585
+ const prompt = turn.items.find((item) => item.type === "userMessage");
1586
+ const historyTurn = { koreanRequest: isKoreanPrompt(prompt?.content) };
1587
+ for (const block of blocks) {
1588
+ if (block.type === "text") {
1589
+ const visible = normalizeProgressText(historyTurn, block.text);
1590
+ if (!visible.trim() && String(block.text || "").trim()) continue;
1591
+ turn.items.push({ id: `${message.uuid}-text`, type: "agentMessage", text: visible, provider: "Claude" });
1592
+ }
1593
+ else if (block.type === "thinking") turn.items.push({ id: `${message.uuid}-thinking`, type: "reasoning", summary: [block.thinking || ""] });
1570
1594
  else if (block.type === "tool_use") {
1571
1595
  const pending = { name: block.name, input: block.input || {}, item: toolItem({}, block.id, block.name, block.input || {}) };
1572
1596
  tools.set(block.id, pending);
@@ -1574,7 +1598,7 @@ function historyState(messages) {
1574
1598
  prepareTaskPlanForCreate(tasks, block.input?.subject);
1575
1599
  tasks.set(`pending:${block.id}`, { id: `pending:${block.id}`, subject: block.input?.subject || "작업", status: "pending", turnId: turn.id });
1576
1600
  } else if (block.name === "TaskUpdate") {
1577
- applyTaskUpdate(tasks, block.input || {}, turn.id);
1601
+ applyTaskUpdate(tasks, block.input || {}, turn.id, undefined, messageTime(message));
1578
1602
  } else if (!["TaskList", "AskUserQuestion"].includes(block.name)) turn.items.push(pending.item);
1579
1603
  }
1580
1604
  }
@@ -1594,7 +1618,19 @@ function historyState(messages) {
1594
1618
  } else if (pending.name === "TaskList" && Array.isArray(message.tool_use_result?.tasks)) {
1595
1619
  const known = new Map(tasks);
1596
1620
  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 });
1621
+ for (const task of message.tool_use_result.tasks) {
1622
+ const previous = known.get(String(task.id));
1623
+ tasks.set(String(task.id), {
1624
+ id: String(task.id),
1625
+ subject: task.subject || "작업",
1626
+ status: task.status || "pending",
1627
+ turnId: previous?.turnId ?? turn.id,
1628
+ // A listing restates the plan; it does not re-run it, so the
1629
+ // timings already measured for these steps have to survive it.
1630
+ startedAt: previous?.startedAt,
1631
+ elapsedMs: previous?.elapsedMs,
1632
+ });
1633
+ }
1598
1634
  const current = latestTaskPlan(tasks);
1599
1635
  tasks.clear();
1600
1636
  for (const [id, task] of current) tasks.set(id, task);
@@ -1611,7 +1647,14 @@ function historyState(messages) {
1611
1647
  }
1612
1648
  if (turn && tasks.size) {
1613
1649
  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 });
1650
+ // The text alone cannot say how long a step took, so the measured times ride
1651
+ // alongside it and the restored plan shows its total instead of zero.
1652
+ const steps = [...tasks.values()].map((task, index) => ({
1653
+ step: numberedTaskSubject(task.subject, index),
1654
+ status: task.status || "pending",
1655
+ elapsedMs: task.elapsedMs ?? null,
1656
+ }));
1657
+ turn.items.push({ id: "claude-plan-latest", type: "plan", text, steps });
1615
1658
  }
1616
1659
  return {
1617
1660
  tasks,
@@ -1900,13 +1943,54 @@ async function runSelfTest() {
1900
1943
  content: [{ type: "tool_use", id, name, input }],
1901
1944
  },
1902
1945
  });
1903
- const taskResult = (uuid, id, toolUseResult, content) => ({
1904
- type: "user",
1905
- uuid,
1906
- message: { role: "user", content: [{ type: "tool_result", tool_use_id: id, content }] },
1907
- tool_use_result: toolUseResult,
1908
- });
1909
- const taskMessages = [user("plan", "작업을 진행해")];
1946
+ const taskResult = (uuid, id, toolUseResult, content) => ({
1947
+ type: "user",
1948
+ uuid,
1949
+ message: { role: "user", content: [{ type: "tool_result", tool_use_id: id, content }] },
1950
+ tool_use_result: toolUseResult,
1951
+ });
1952
+ const resumedProgress = historyTurns([
1953
+ user("progress-user", "환자 변경 동기화를 수정해"),
1954
+ {
1955
+ type: "assistant",
1956
+ uuid: "progress-assistant",
1957
+ message: {
1958
+ role: "assistant",
1959
+ model: "claude-opus-5",
1960
+ content: [
1961
+ { type: "text", text: "Now hook the patient-change event in CvForm." },
1962
+ { type: "tool_use", id: "progress-read", name: "Read", input: { file_path: "CvForm.cs" } },
1963
+ ],
1964
+ },
1965
+ },
1966
+ taskResult("progress-result", "progress-read", { content: "ok" }, "ok"),
1967
+ {
1968
+ type: "assistant",
1969
+ uuid: "progress-assistant-2",
1970
+ message: {
1971
+ role: "assistant",
1972
+ model: "claude-opus-5",
1973
+ content: [
1974
+ { type: "text", text: "Now the Lab-side receiver." },
1975
+ { type: "tool_use", id: "progress-read-2", name: "Read", input: { file_path: "LabReceiver.cs" } },
1976
+ ],
1977
+ },
1978
+ },
1979
+ taskResult("progress-result-2", "progress-read-2", { content: "ok" }, "ok"),
1980
+ assistant("progress-final", "claude-opus-5", "환자 변경 동기화를 수정했습니다."),
1981
+ ]);
1982
+ const resumedProgressText = resumedProgress[0]?.items
1983
+ .filter((item) => item.type === "agentMessage")
1984
+ .map((item) => item.text);
1985
+ const englishNowText = historyTurns([
1986
+ user("english-user", "Start the answer with Now"),
1987
+ assistant("english-assistant", "claude-opus-5", "Now the answer starts."),
1988
+ ])[0]?.items.find((item) => item.type === "agentMessage")?.text;
1989
+ if (JSON.stringify(resumedProgressText) !== JSON.stringify(["환자 변경 동기화를 수정했습니다."])
1990
+ || englishNowText !== "Now the answer starts.") {
1991
+ throw new Error(`Claude resumed progress normalization self-test failed: ${JSON.stringify({ resumedProgressText, englishNowText })}`);
1992
+ }
1993
+ const taskMessages = [user("plan", "작업을 진행해")];
1910
1994
  for (let index = 1; index <= 6; index++) {
1911
1995
  const id = String(24 + index);
1912
1996
  const toolId = `create-${id}`;
@@ -1937,6 +2021,20 @@ async function runSelfTest() {
1937
2021
  || [...restoredTasks.values()].filter((task) => task.status === "in_progress").length !== 1) {
1938
2022
  throw new Error(`Claude sequential task update self-test failed: ${JSON.stringify([...restoredTasks])}`);
1939
2023
  }
2024
+ // Each transcript record is stamped, so a step's own run is the span between
2025
+ // the update that started it and the one that closed it.
2026
+ const at = (message, timestamp) => ({ ...message, timestamp });
2027
+ const timedMessages = [
2028
+ at(user("timed", "작업을 진행해"), "2026-08-07T01:00:00.000Z"),
2029
+ at(taskUse("timed-create-use", "timed-create", "TaskCreate", { subject: "1. 확인" }), "2026-08-07T01:00:01.000Z"),
2030
+ at(taskResult("timed-create-result", "timed-create", { task: { id: "t1", subject: "1. 확인" } }, "Task #t1 created successfully: 1. 확인"), "2026-08-07T01:00:02.000Z"),
2031
+ at(taskUse("timed-start", "timed-start", "TaskUpdate", { taskId: "t1", status: "in_progress" }), "2026-08-07T01:00:03.000Z"),
2032
+ at(taskUse("timed-done", "timed-done", "TaskUpdate", { taskId: "t1", status: "completed" }), "2026-08-07T01:00:09.000Z"),
2033
+ ];
2034
+ const timedPlan = historyState(timedMessages).turns.at(-1)?.items.find((item) => item.type === "plan");
2035
+ if (timedPlan?.steps?.[0]?.elapsedMs !== 6000 || timedPlan.steps[0].status !== "completed") {
2036
+ throw new Error(`Claude plan step timing self-test failed: ${JSON.stringify(timedPlan)}`);
2037
+ }
1940
2038
  const skippedTasks = new Map([
1941
2039
  ["1", { id: "1", subject: "1. 조사", status: "pending" }],
1942
2040
  ["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.17",
3
+ "version": "1.2.19",
4
4
  "description": "Stable terminal UI for Codex and Claude Agent SDK",
5
5
  "keywords": [
6
6
  "codex",