@kw-yeh/vibeflow 4.5.1 → 4.6.0

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/dist/vibeflow.mjs CHANGED
@@ -599,11 +599,293 @@ var init_attachments = __esm({
599
599
  }
600
600
  });
601
601
 
602
+ // packages/core/src/progress.ts
603
+ function emptyUsage() {
604
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 };
605
+ }
606
+ function addUsage(a, b) {
607
+ return {
608
+ input: a.input + b.input,
609
+ output: a.output + b.output,
610
+ cacheRead: a.cacheRead + b.cacheRead,
611
+ cacheWrite: a.cacheWrite + b.cacheWrite,
612
+ reasoning: a.reasoning + b.reasoning
613
+ };
614
+ }
615
+ function totalTokens(u) {
616
+ return u.input + u.output + u.cacheRead + u.cacheWrite;
617
+ }
618
+ function todoStatus(v) {
619
+ return v === "pending" || v === "in_progress" || v === "completed" ? v : null;
620
+ }
621
+ function parseTime(v) {
622
+ if (typeof v !== "string") return void 0;
623
+ const t = Date.parse(v);
624
+ return Number.isNaN(t) ? void 0 : t;
625
+ }
626
+ function parseLine(line) {
627
+ try {
628
+ const value = JSON.parse(line);
629
+ return value && typeof value === "object" ? value : null;
630
+ } catch {
631
+ return null;
632
+ }
633
+ }
634
+ function initialClaudeState() {
635
+ return { todos: null, usageById: {}, pendingTasks: {}, activity: { state: "unknown" } };
636
+ }
637
+ function claudeUsage(u) {
638
+ const details = u.output_tokens_details;
639
+ return {
640
+ input: num(u.input_tokens),
641
+ output: num(u.output_tokens),
642
+ cacheRead: num(u.cache_read_input_tokens),
643
+ cacheWrite: num(u.cache_creation_input_tokens),
644
+ reasoning: num(details?.thinking_tokens)
645
+ };
646
+ }
647
+ function todoWriteItems(input) {
648
+ if (!Array.isArray(input.todos)) return null;
649
+ const items = [];
650
+ for (const raw of input.todos) {
651
+ if (!raw || typeof raw !== "object") continue;
652
+ const t = raw;
653
+ const status = todoStatus(t.status);
654
+ if (typeof t.content !== "string" || !status) continue;
655
+ items.push({
656
+ content: t.content,
657
+ status,
658
+ ...typeof t.activeForm === "string" ? { activeForm: t.activeForm } : {}
659
+ });
660
+ }
661
+ return items;
662
+ }
663
+ function applyClaudeTask(todos, name, input, result) {
664
+ if (name === "TaskCreate") {
665
+ const task = result?.task;
666
+ const id2 = task && (typeof task.id === "string" || typeof task.id === "number") ? String(task.id) : void 0;
667
+ const content = typeof input.subject === "string" ? input.subject : typeof task?.subject === "string" ? task.subject : null;
668
+ if (!content) return todos;
669
+ const item = {
670
+ ...id2 ? { id: id2 } : {},
671
+ content,
672
+ status: "pending",
673
+ ...typeof input.activeForm === "string" ? { activeForm: input.activeForm } : {}
674
+ };
675
+ return [...todos ?? [], item];
676
+ }
677
+ if (result && result.success === false) return todos;
678
+ const id = typeof input.taskId === "string" || typeof input.taskId === "number" ? String(input.taskId) : null;
679
+ if (!id || !todos) return todos;
680
+ if (input.status === "deleted") return todos.filter((t) => t.id !== id);
681
+ const status = todoStatus(input.status);
682
+ return todos.map((t) => {
683
+ if (t.id !== id) return t;
684
+ return {
685
+ ...t,
686
+ ...status ? { status } : {},
687
+ ...typeof input.subject === "string" ? { content: input.subject } : {},
688
+ ...typeof input.activeForm === "string" ? { activeForm: input.activeForm } : {}
689
+ };
690
+ });
691
+ }
692
+ function reduceClaudeLine(state, line) {
693
+ const entry = parseLine(line);
694
+ if (!entry) return state;
695
+ const at = parseTime(entry.timestamp);
696
+ const next = { ...state, startedAt: state.startedAt ?? at };
697
+ const message = entry.message;
698
+ const content = Array.isArray(message?.content) ? message.content : [];
699
+ if (entry.type === "assistant" && message) {
700
+ const usage = message.usage;
701
+ if (usage && typeof message.id === "string") {
702
+ const u = claudeUsage(usage);
703
+ next.usageById = { ...state.usageById, [message.id]: u };
704
+ next.contextTokens = u.input + u.cacheRead + u.cacheWrite;
705
+ }
706
+ let tool;
707
+ for (const block of content) {
708
+ if (block?.type !== "tool_use" || typeof block.name !== "string") continue;
709
+ tool = block.name;
710
+ const input = block.input && typeof block.input === "object" ? block.input : {};
711
+ if (block.name === "TodoWrite") {
712
+ const items = todoWriteItems(input);
713
+ if (items) next.todos = items;
714
+ } else if ((block.name === "TaskCreate" || block.name === "TaskUpdate") && typeof block.id === "string") {
715
+ next.pendingTasks = { ...next.pendingTasks, [block.id]: { name: block.name, input } };
716
+ }
717
+ }
718
+ if (tool) next.activity = { state: "working", tool, at };
719
+ else if (message.stop_reason === "end_turn") next.activity = { state: "waiting", waitingFor: "input", at };
720
+ else next.activity = { state: "working", at };
721
+ return next;
722
+ }
723
+ if (entry.type === "user" && message) {
724
+ const result = entry.toolUseResult && typeof entry.toolUseResult === "object" ? entry.toolUseResult : void 0;
725
+ let pending = next.pendingTasks;
726
+ for (const block of content) {
727
+ if (block?.type !== "tool_result" || typeof block.tool_use_id !== "string") continue;
728
+ const call = pending[block.tool_use_id];
729
+ if (!call) continue;
730
+ next.todos = applyClaudeTask(next.todos, call.name, call.input, result);
731
+ pending = { ...pending };
732
+ delete pending[block.tool_use_id];
733
+ }
734
+ next.pendingTasks = pending;
735
+ next.activity = { state: "working", at };
736
+ return next;
737
+ }
738
+ return next;
739
+ }
740
+ function claudeUsageTotal(state) {
741
+ return Object.values(state.usageById).reduce(addUsage, emptyUsage());
742
+ }
743
+ function initialCodexState() {
744
+ return { todos: null, usage: emptyUsage(), activity: { state: "unknown" } };
745
+ }
746
+ function codexUsage(u) {
747
+ const cached2 = num(u.cached_input_tokens);
748
+ return {
749
+ input: Math.max(0, num(u.input_tokens) - cached2),
750
+ output: num(u.output_tokens),
751
+ cacheRead: cached2,
752
+ cacheWrite: num(u.cache_write_input_tokens),
753
+ reasoning: num(u.reasoning_output_tokens)
754
+ };
755
+ }
756
+ function planItems(argumentsJson) {
757
+ if (typeof argumentsJson !== "string") return null;
758
+ const args = parseLine(argumentsJson);
759
+ if (!args || !Array.isArray(args.plan)) return null;
760
+ const items = [];
761
+ for (const raw of args.plan) {
762
+ if (!raw || typeof raw !== "object") continue;
763
+ const p = raw;
764
+ const status = todoStatus(p.status);
765
+ if (typeof p.step === "string" && status) items.push({ content: p.step, status });
766
+ }
767
+ return items;
768
+ }
769
+ function reduceCodexLine(state, line) {
770
+ const entry = parseLine(line);
771
+ if (!entry) return state;
772
+ const at = parseTime(entry.timestamp);
773
+ const payload = entry.payload && typeof entry.payload === "object" ? entry.payload : {};
774
+ if (entry.type === "session_meta") {
775
+ return {
776
+ ...state,
777
+ cwd: typeof payload.cwd === "string" ? payload.cwd : state.cwd,
778
+ startedAt: parseTime(payload.timestamp) ?? at ?? state.startedAt
779
+ };
780
+ }
781
+ if (entry.type === "response_item" && (payload.type === "function_call" || payload.type === "custom_tool_call")) {
782
+ const name = typeof payload.name === "string" ? payload.name : void 0;
783
+ const next = { ...state, activity: { state: "working", tool: name, at } };
784
+ if (name === "update_plan") {
785
+ const items = planItems(payload.arguments);
786
+ if (items) next.todos = items;
787
+ }
788
+ return next;
789
+ }
790
+ if (entry.type === "event_msg") {
791
+ if (payload.type === "token_count") {
792
+ const info = payload.info;
793
+ const total = info?.total_token_usage;
794
+ if (!total) return state;
795
+ const last = info?.last_token_usage;
796
+ return { ...state, usage: codexUsage(total), contextTokens: last ? num(last.input_tokens) : state.contextTokens };
797
+ }
798
+ if (payload.type === "task_complete") {
799
+ return { ...state, activity: { state: "waiting", waitingFor: "input", at } };
800
+ }
801
+ if (payload.type === "task_started" || payload.type === "user_message") {
802
+ return { ...state, activity: { state: "working", at } };
803
+ }
804
+ }
805
+ return state;
806
+ }
807
+ function activityFromHook(event, at) {
808
+ if (!event || typeof event !== "object") return null;
809
+ const e = event;
810
+ switch (e.hook_event_name) {
811
+ case "Notification":
812
+ return e.notification_type === "idle_prompt" ? { state: "waiting", waitingFor: "input", at } : { state: "waiting", waitingFor: "permission", at };
813
+ case "Stop":
814
+ return { state: "waiting", waitingFor: "input", at };
815
+ case "UserPromptSubmit":
816
+ case "PostToolUse":
817
+ return { state: "working", at };
818
+ default:
819
+ return null;
820
+ }
821
+ }
822
+ function latestActivity(a, b) {
823
+ if (!b) return a;
824
+ if (a.at == null) return b;
825
+ if (b.at == null) return a;
826
+ return b.at >= a.at ? b : a;
827
+ }
828
+ function sameItem(a, b) {
829
+ return a.id != null || b.id != null ? a.id === b.id : a.content === b.content;
830
+ }
831
+ function diffProgress(prev, next) {
832
+ if (!prev) return [];
833
+ const out = [];
834
+ const todos = next.todos ?? [];
835
+ const before = prev.todos ?? [];
836
+ const done = todos.filter((t) => t.status === "completed").length;
837
+ for (const item of todos) {
838
+ if (item.status !== "completed") continue;
839
+ const old = before.find((b) => sameItem(b, item));
840
+ if (old?.status === "completed") continue;
841
+ out.push({ kind: "step_completed", item: item.content, done, total: todos.length });
842
+ }
843
+ const allDone = (list) => list.length > 0 && list.every((t) => t.status === "completed");
844
+ if (allDone(todos) && !allDone(before)) {
845
+ out.push({ kind: "all_completed", done, total: todos.length });
846
+ }
847
+ if (next.activity.state === "waiting" && prev.activity.state !== "waiting") {
848
+ out.push({ kind: "waiting_input", waitingFor: next.activity.waitingFor ?? "input" });
849
+ }
850
+ return out;
851
+ }
852
+ function formatTokens(n) {
853
+ if (n < 1e3) return String(n);
854
+ if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
855
+ return `${(n / 1e6).toFixed(2)}M`;
856
+ }
857
+ function describeNotification(n) {
858
+ switch (n.kind) {
859
+ case "step_completed":
860
+ return `\u5B8C\u6210 ${n.done}/${n.total} \u2014 ${n.item ?? ""}`;
861
+ case "all_completed":
862
+ return "\u6240\u6709\u6B65\u9A5F\u5DF2\u5B8C\u6210";
863
+ case "waiting_input":
864
+ return n.waitingFor === "permission" ? "\u7B49\u5F85\u4F60\u5141\u8A31\u6B0A\u9650" : "\u7B49\u5F85\u4F60\u7684\u8F38\u5165";
865
+ }
866
+ }
867
+ function progressSummary(progress, usage) {
868
+ const parts = [];
869
+ const todos = progress?.todos;
870
+ if (todos && todos.length) parts.push(`${todos.filter((t) => t.status === "completed").length}/${todos.length}`);
871
+ const total = progress?.totalUsage ?? usage;
872
+ if (total && totalTokens(total) > 0) parts.push(`${formatTokens(totalTokens(total))} tok`);
873
+ if (progress?.activity.state === "waiting") parts.push(progress.activity.waitingFor === "permission" ? "\u7B49\u5F85\u6B0A\u9650" : "\u7B49\u5F85\u8F38\u5165");
874
+ return parts.join(" \xB7 ");
875
+ }
876
+ var num;
877
+ var init_progress = __esm({
878
+ "packages/core/src/progress.ts"() {
879
+ "use strict";
880
+ num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
881
+ }
882
+ });
883
+
602
884
  // packages/tui/src/task-form.ts
603
885
  import { spawnSync } from "child_process";
604
- import fs20 from "fs";
605
- import os6 from "os";
606
- import path24 from "path";
886
+ import fs21 from "fs";
887
+ import os7 from "os";
888
+ import path25 from "path";
607
889
  import React, { useEffect, useRef, useState } from "react";
608
890
  import { Box, Text, render, useApp, useInput } from "ink";
609
891
  function newFormState(defaultAutoMode, agents) {
@@ -722,17 +1004,17 @@ function editorCommand(env, platform) {
722
1004
  return env.VISUAL?.trim() || env.EDITOR?.trim() || (platform === "win32" ? "notepad" : "vi");
723
1005
  }
724
1006
  function editInEditor(text) {
725
- const dir = fs20.mkdtempSync(path24.join(os6.tmpdir(), "vibeflow-"));
726
- const file = path24.join(dir, "description.md");
1007
+ const dir = fs21.mkdtempSync(path25.join(os7.tmpdir(), "vibeflow-"));
1008
+ const file = path25.join(dir, "description.md");
727
1009
  try {
728
- fs20.writeFileSync(file, text);
1010
+ fs21.writeFileSync(file, text);
729
1011
  const cmd = editorCommand(process.env, process.platform);
730
1012
  const r = spawnSync(`${cmd} "${file}"`, { stdio: "inherit", shell: true });
731
1013
  if (r.error) return { error: `\u7121\u6CD5\u555F\u52D5\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\uFF1A${r.error.message}` };
732
1014
  if (r.status !== 0) return { error: `\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\u7D50\u675F\u78BC ${r.status}\uFF0C\u63CF\u8FF0\u672A\u8B8A\u66F4` };
733
- return { text: fs20.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1015
+ return { text: fs21.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
734
1016
  } finally {
735
- fs20.rmSync(dir, { recursive: true, force: true });
1017
+ fs21.rmSync(dir, { recursive: true, force: true });
736
1018
  }
737
1019
  }
738
1020
  function cycle(list, current, step) {
@@ -803,10 +1085,10 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
803
1085
  if (editing === "title") setS((prev) => ({ ...prev, title: draft }));
804
1086
  else if (editing === "branch") setS((prev) => ({ ...prev, branch: value2 }));
805
1087
  else if (editing === "project") {
806
- if (value2) setProject(path24.resolve(value2.replace(/^~(?=$|\/)/, os6.homedir())));
1088
+ if (value2) setProject(path25.resolve(value2.replace(/^~(?=$|\/)/, os7.homedir())));
807
1089
  } else if (editing === "attachments" && value2) {
808
- const file = path24.resolve(value2.replace(/^~(?=$|\/)/, os6.homedir()));
809
- if (!fs20.existsSync(file) || !fs20.statSync(file).isFile()) {
1090
+ const file = path25.resolve(value2.replace(/^~(?=$|\/)/, os7.homedir()));
1091
+ if (!fs21.existsSync(file) || !fs21.statSync(file).isFile()) {
810
1092
  setMessage(`\u627E\u4E0D\u5230\u6A94\u6848\uFF1A${file}`);
811
1093
  return;
812
1094
  }
@@ -886,7 +1168,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
886
1168
  case "branch":
887
1169
  return s.branch || "\uFF08\u7559\u7A7A\u5247\u81EA\u52D5\u547D\u540D\uFF0C\u4F8B\u5982 feature/login-fix\uFF09";
888
1170
  case "attachments":
889
- return s.attachments.length ? s.attachments.map((a) => path24.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
1171
+ return s.attachments.length ? s.attachments.map((a) => path25.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
890
1172
  case "description": {
891
1173
  if (!s.description) return "\uFF08Enter \u7528\u7DE8\u8F2F\u5668\u64B0\u5BEB\uFF09";
892
1174
  const lines = s.description.split("\n");
@@ -917,7 +1199,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
917
1199
  if (info && !info.isRepo) return "\u9019\u500B\u8CC7\u6599\u593E\u4E0D\u662F Git repository\u3002\u8ACB\u6539\u9078\u4E00\u500B Git \u5C08\u6848\uFF0C\u6216\u5728\u4E0B\u65B9\u521D\u59CB\u5316\u3002";
918
1200
  if (s.mode === "edit" && projectChanged(s)) return "\u5206\u652F\u6703\u5728\u65B0\u5C08\u6848\u3001\u5361\u7247\u958B\u59CB\u57F7\u884C\u6642\u624D\u5EFA\u7ACB\u3002";
919
1201
  if (info?.isRepo && !info.hasRemote) return `\u6B64 repository \u6C92\u6709 remote\uFF0C\u5C07\u4EE5\u76EE\u524D\u5206\u652F (${info.currentBranch ?? "HEAD"}) \u70BA\u57FA\u6E96\u5EFA\u7ACB\u672C\u5730 worktree\u3002`;
920
- if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path24.basename(s.projectPath)}`;
1202
+ if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path25.basename(s.projectPath)}`;
921
1203
  }
922
1204
  if (field === "branch") {
923
1205
  const when = info?.hasRemote ? "\u958B\u59CB\u57F7\u884C\u6642\u6703\u5EFA\u7ACB\u540C\u540D\u5206\u652F\u4E26\u63A8\u5230 origin\uFF1B\u82E5 remote \u5DF2\u6709\u9019\u500B\u5206\u652F\uFF0C\u5247\u76F4\u63A5\u53D6\u56DE\u4E26\u63A5\u7E8C\u4E0A\u9762\u7684\u5DE5\u4F5C\u3002" : "\u958B\u59CB\u57F7\u884C\u6642\u6703\u5EFA\u7ACB\u540C\u540D\u5206\u652F\u3002";
@@ -1037,7 +1319,7 @@ __export(src_exports, {
1037
1319
  showDiff: () => showDiff
1038
1320
  });
1039
1321
  import { spawnSync as spawnSync2 } from "child_process";
1040
- import path25 from "path";
1322
+ import path26 from "path";
1041
1323
  import React2, { useEffect as useEffect2, useState as useState2 } from "react";
1042
1324
  import { Box as Box2, Text as Text2, render as render2, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
1043
1325
  function findDetachKey(text) {
@@ -1092,10 +1374,18 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
1092
1374
  const [message, setMessage] = useState2(initialMessage ?? "");
1093
1375
  const [confirm, setConfirm] = useState2(null);
1094
1376
  const [help, setHelp] = useState2(false);
1377
+ const [progress, setProgress] = useState2({});
1095
1378
  useEffect2(() => {
1096
1379
  let alive = true;
1097
1380
  void api.getState().then((s) => alive && setBoard2(s.board));
1098
1381
  const off = api.onStateChanged((s) => setBoard2(s.board));
1382
+ const offProgress = api.onProgressUpdate(({ taskId, progress: p }) => {
1383
+ setProgress((prev) => ({ ...prev, [taskId]: p }));
1384
+ });
1385
+ const offNotify = api.onProgressNotify(({ title, notifications }) => {
1386
+ const last = notifications[notifications.length - 1];
1387
+ if (last) setMessage(`\u300C${title}\u300D${describeNotification(last)}`);
1388
+ });
1099
1389
  const poll = async () => {
1100
1390
  try {
1101
1391
  const info = await api.term.list();
@@ -1110,12 +1400,27 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
1110
1400
  return () => {
1111
1401
  alive = false;
1112
1402
  off();
1403
+ offProgress();
1404
+ offNotify();
1113
1405
  clearInterval(timer);
1114
1406
  };
1115
1407
  }, [api]);
1116
1408
  useEffect2(() => {
1117
1409
  if (board) setSel((s) => clampSelection(board, s));
1118
1410
  }, [board]);
1411
+ const runningIds = board ? board.in_progress.map((t) => t.id).join(",") : "";
1412
+ useEffect2(() => {
1413
+ let alive = true;
1414
+ for (const id of runningIds ? runningIds.split(",") : []) {
1415
+ void api.getProgress(id).then((p) => {
1416
+ if (alive && p) setProgress((prev) => prev[id] ? prev : { ...prev, [id]: p });
1417
+ }).catch(() => {
1418
+ });
1419
+ }
1420
+ return () => {
1421
+ alive = false;
1422
+ };
1423
+ }, [api, runningIds]);
1119
1424
  const selected = board ? board[COLUMNS2[sel.column]][sel.index] : void 0;
1120
1425
  const leave = (action) => {
1121
1426
  onAction(action, sel);
@@ -1259,10 +1564,12 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
1259
1564
  ...tasks.length === 0 ? [h2(Text2, { key: "empty", dimColor: true }, "\u2014")] : tasks.map((t, ti) => {
1260
1565
  const isSel = active && ti === sel.index;
1261
1566
  const mark = running.has(t.id) ? "\u25CF " : " ";
1567
+ const summary = col === "backlog" ? "" : progressSummary(col === "in_progress" ? progress[t.id] : null, t.usage);
1262
1568
  return h2(
1263
1569
  Box2,
1264
1570
  { key: t.id, flexDirection: "column" },
1265
1571
  h2(Text2, { inverse: isSel, color: running.has(t.id) ? "green" : void 0 }, truncate2(`${mark}${t.title}`, colWidth - 4)),
1572
+ summary ? h2(Text2, { dimColor: !isSel, color: isSel ? "cyan" : void 0 }, truncate2(` ${summary}`, colWidth - 4)) : null,
1266
1573
  isSel ? h2(Text2, { dimColor: true }, truncate2(` ${t.projectName ?? ""} \xB7 ${t.branch}`, colWidth - 4)) : null
1267
1574
  );
1268
1575
  })
@@ -1308,7 +1615,7 @@ function showDiff(task) {
1308
1615
  });
1309
1616
  }
1310
1617
  function tmuxAttach(opts, sessionName) {
1311
- const conf = path25.join(opts.userDataDir, "tmux.conf");
1618
+ const conf = path26.join(opts.userDataDir, "tmux.conf");
1312
1619
  const inOurServer = (process.env.TMUX ?? "").split(",")[0].endsWith(`/${TMUX_SOCKET}`);
1313
1620
  const args = inOurServer ? ["-L", TMUX_SOCKET, "-f", conf, "switch-client", "-t", `=${sessionName}`] : ["-L", TMUX_SOCKET, "-f", conf, "attach-session", "-t", `=${sessionName}`];
1314
1621
  spawnSync2("tmux", args, { stdio: "inherit", env: { ...process.env, TMUX: inOurServer ? process.env.TMUX : "" } });
@@ -1422,6 +1729,7 @@ var h2, COLUMNS2, COLUMN_TITLES, DETACH_KEY, RESET_TERMINAL_MODES, CLEAR_SCREEN;
1422
1729
  var init_src = __esm({
1423
1730
  "packages/tui/src/index.ts"() {
1424
1731
  "use strict";
1732
+ init_progress();
1425
1733
  init_tmux_backend();
1426
1734
  init_task_form();
1427
1735
  h2 = React2.createElement;
@@ -1438,8 +1746,8 @@ var init_src = __esm({
1438
1746
  });
1439
1747
 
1440
1748
  // packages/cli/src/main.ts
1441
- import fs21 from "fs";
1442
- import path26 from "path";
1749
+ import fs22 from "fs";
1750
+ import path27 from "path";
1443
1751
  import { fileURLToPath } from "url";
1444
1752
  import { parseArgs as parseArgs2 } from "node:util";
1445
1753
 
@@ -1775,6 +2083,21 @@ function recordRecentProject(store, projectPath) {
1775
2083
  }
1776
2084
 
1777
2085
  // packages/core/src/store.ts
2086
+ var DEFAULT_NOTIFICATION_SETTINGS = {
2087
+ enabled: true,
2088
+ stepCompleted: false,
2089
+ allCompleted: true,
2090
+ waitingInput: true,
2091
+ desktop: false
2092
+ };
2093
+ function resolveNotificationSettings(settings) {
2094
+ const stored = settings?.notifications ?? {};
2095
+ const out = { ...DEFAULT_NOTIFICATION_SETTINGS };
2096
+ for (const key of Object.keys(out)) {
2097
+ if (typeof stored[key] === "boolean") out[key] = stored[key];
2098
+ }
2099
+ return out;
2100
+ }
1778
2101
  function resolveWorkstationPath(settings) {
1779
2102
  const p = settings?.workstationPath?.trim();
1780
2103
  return p && p.length > 0 ? p : join(homedir2(), "Desktop");
@@ -1832,6 +2155,11 @@ function getSettings() {
1832
2155
  }
1833
2156
  function setSettings(patch) {
1834
2157
  const next = { ...getSettings(), ...patch };
2158
+ if (patch.notifications !== void 0) {
2159
+ next.notifications = resolveNotificationSettings({
2160
+ notifications: { ...resolveNotificationSettings(getSettings()), ...patch.notifications ?? {} }
2161
+ });
2162
+ }
1835
2163
  if (typeof next.systemPrompt === "string" && next.systemPrompt.trim() === "") {
1836
2164
  delete next.systemPrompt;
1837
2165
  }
@@ -1962,6 +2290,16 @@ function createBridge(t) {
1962
2290
  return t.on("subagents:update", (payload) => callback(payload));
1963
2291
  },
1964
2292
  deleteTask: (taskId) => t.invoke("vibeflow:deleteTask", taskId),
2293
+ /** A running card's progress now (todos, usage, activity); null = nothing to show yet. */
2294
+ getProgress: (taskId) => t.invoke("progress:get", taskId),
2295
+ /** Every change to a card's progress, read from its agent's transcript. */
2296
+ onProgressUpdate: (callback) => {
2297
+ return t.on("progress:update", (payload) => callback(payload));
2298
+ },
2299
+ /** Stage notifications, already filtered by the user's notification settings. */
2300
+ onProgressNotify: (callback) => {
2301
+ return t.on("progress:notify", (payload) => callback(payload));
2302
+ },
1965
2303
  attachments: {
1966
2304
  write: (payload) => t.invoke("attachments:write", payload)
1967
2305
  },
@@ -2357,103 +2695,287 @@ function unwatchAllSubAgents() {
2357
2695
  }
2358
2696
  }
2359
2697
 
2360
- // packages/core/src/git.ts
2361
- init_env();
2362
- init_attachments();
2363
-
2364
- // packages/core/src/artifacts.ts
2365
- import fs7 from "fs";
2366
- import path11 from "path";
2698
+ // packages/core/src/launch.ts
2367
2699
  var ARTIFACTS_DIR_SUFFIX = ".artifacts";
2368
2700
  var SCRATCH_DIR_NAME = "scratch";
2369
2701
  var ARTIFACTS_FALLBACK_DIR = ".vibeflow-artifacts";
2370
- var IMAGE_MIME = {
2371
- ".png": "image/png",
2372
- ".jpg": "image/jpeg",
2373
- ".jpeg": "image/jpeg",
2374
- ".gif": "image/gif",
2375
- ".webp": "image/webp",
2376
- ".avif": "image/avif",
2377
- ".bmp": "image/bmp",
2378
- ".svg": "image/svg+xml"
2379
- };
2380
- var VIDEO_MIME = {
2381
- ".mp4": "video/mp4",
2382
- ".m4v": "video/mp4",
2383
- ".webm": "video/webm",
2384
- ".mov": "video/mp4"
2385
- };
2386
- var MAX_VIDEO_BYTES = 20 * 1024 * 1024;
2387
- var MAX_TEXT_BYTES = 256 * 1024;
2388
- var MAX_DEPTH = 3;
2389
- var MAX_ARTIFACTS = 200;
2390
- var SNIFF_BYTES = 8 * 1024;
2391
- function agentArtifactsPath(baseDir, worktreePath) {
2392
- return path11.join(baseDir, `${path11.basename(worktreePath)}${ARTIFACTS_DIR_SUFFIX}`);
2702
+ function pathBasename(p) {
2703
+ const norm = p.replace(/\\/g, "/").replace(/\/+$/, "");
2704
+ const i = norm.lastIndexOf("/");
2705
+ return i >= 0 ? norm.slice(i + 1) : norm;
2393
2706
  }
2394
- function mediaKind(filePath, size) {
2395
- const extension = path11.extname(filePath).toLowerCase();
2396
- const imageMime = IMAGE_MIME[extension];
2397
- if (imageMime) return { kind: "image", mime: imageMime };
2398
- const videoMime = VIDEO_MIME[extension];
2399
- if (videoMime) {
2400
- return size > MAX_VIDEO_BYTES ? { kind: "binary" } : { kind: "video", mime: videoMime };
2707
+ function agentArtifactsDir(worktreePath, workspacePath) {
2708
+ if (!worktreePath || !workspacePath) return null;
2709
+ const dir = toShellPath(workspacePath);
2710
+ const ws = pathBasename(worktreePath);
2711
+ return `${dir}/${ws}${ARTIFACTS_DIR_SUFFIX}`;
2712
+ }
2713
+ function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR) {
2714
+ return [
2715
+ "Artifact \u8A2D\u5B9A\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
2716
+ `1. \u672C\u4EFB\u52D9\u7684 Artifact \u8CC7\u6599\u593E\u662F ${artifactsDir}/\uFF1B\u9700\u8981\u4FDD\u5B58\u7D66\u4F7F\u7528\u8005\u7684\u9A57\u8B49\u8B49\u64DA\u6216\u5831\u544A\u6642\uFF0C\u8ACB\u5BEB\u5165\u6B64\u8CC7\u6599\u593E\uFF08\u4E0D\u5B58\u5728\u8ACB\u5EFA\u7ACB\uFF09\u3002`,
2717
+ `2. \u4F7F\u7528\u8005\u6703\u5728 VibeFlow \u7684 Artifacts \u5206\u9801\u6AA2\u8996\u6839\u76EE\u9304\u5167\u5BB9\uFF1B\u53EA\u653E\u4F60\u6703\u4E3B\u52D5\u8ACB\u4F7F\u7528\u8005\u904E\u76EE\u7684\u6A94\u6848\u3002`,
2718
+ `3. \u4F60\u81EA\u5DF1\u7684\u5DE5\u4F5C\u66AB\u5B58\uFF08\u4E00\u6B21\u6027 script\u3001log\u3001\u4E2D\u9593\u8F38\u51FA\u3001debug \u6A94\uFF09\u653E\u5728 ${artifactsDir}/${SCRATCH_DIR_NAME}/\u3002`,
2719
+ `4. \u82E5\u5DE5\u5177\u5148\u8F38\u51FA\u5230 /tmp\u3001/private/tmp\u3001$TMPDIR \u6216 ~/Downloads\uFF0C\u8ACB\u628A\u6700\u7D42\u8981\u5448\u73FE\u7684\u6A94\u6848\u8907\u88FD\u5230 ${artifactsDir}/ \u4E26\u78BA\u8A8D\u5B58\u5728\u3002`,
2720
+ "5. Artifact \u8CC7\u6599\u593E\u4F4D\u65BC worktree \u4E4B\u5916\uFF1B\u4E0D\u8981\u628A\u5176\u4E2D\u5167\u5BB9\u52A0\u5165 git commit\uFF0C\u4E5F\u4E0D\u8981\u628A\u6700\u7D42\u4EA4\u4ED8\u7269\u53EA\u7559\u5728\u9019\u88E1\u3002"
2721
+ ].join("\n");
2722
+ }
2723
+ var AUTO_PERMISSION_MODE = "auto";
2724
+ function claudePermissionFlag(autoMode) {
2725
+ return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
2726
+ }
2727
+ function shellQuote(s) {
2728
+ return `'${s.replace(/'/g, `'\\''`)}'`;
2729
+ }
2730
+ function toShellPath(p) {
2731
+ return p.replace(/\\/g, "/");
2732
+ }
2733
+ function boardEnvPrefix(task, opts) {
2734
+ const board = opts?.boardCli;
2735
+ if (!board) return "";
2736
+ const entries = [
2737
+ ["VIBEFLOW_TASK_ID", task.id],
2738
+ ["VIBEFLOW_PROJECT_PATH", task.projectPath],
2739
+ ["VIBEFLOW_BRANCH", task.branch],
2740
+ ["VIBEFLOW_BASE_BRANCH", task.baseBranch],
2741
+ ["VIBEFLOW_STORE_DIR", board?.storeDir],
2742
+ ["VIBEFLOW_CLI", board?.cliPath],
2743
+ ["VIBEFLOW_CLI_LOADER", board?.loaderPath],
2744
+ ["VIBEFLOW_AUTO_MODE", opts?.autoMode ? "1" : "0"]
2745
+ ];
2746
+ const exports = entries.filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote(toShellPath(value))}`);
2747
+ return exports.length ? `export ${exports.join(" ")}; ` : "";
2748
+ }
2749
+ function codexAutoFlag(autoMode) {
2750
+ return autoMode ? "--dangerously-bypass-approvals-and-sandbox " : "";
2751
+ }
2752
+ function claudeResumeOrFresh(sessionId, worktreePath, tail) {
2753
+ if (!worktreePath) return `claude --resume ${sessionId} ${tail}\r`;
2754
+ const munged = worktreePath.replace(/[^a-zA-Z0-9]/g, "-");
2755
+ const sessionFile = `"$HOME/.claude/projects/${munged}/${sessionId}.jsonl"`;
2756
+ return `if [ -f ${sessionFile} ]; then claude --resume ${sessionId} ${tail}; else claude --session-id ${sessionId} ${tail}; fi\r`;
2757
+ }
2758
+ var SUBAGENTS_DIR2 = ".vibeflow-subagents";
2759
+ var PROGRESS_EVENTS_DIR = ".vibeflow-events";
2760
+ var TODO_TOOLS = "TodoWrite|TaskCreate|TaskUpdate";
2761
+ function buildClaudeSettings(worktreePath) {
2762
+ const settings = { theme: "dark" };
2763
+ if (worktreePath) {
2764
+ const dir = `${toShellPath(worktreePath)}/${SUBAGENTS_DIR2}`;
2765
+ const command = `mkdir -p "${dir}" && cat > "${dir}/$(date +%s)-$$-$RANDOM.json"`;
2766
+ const taskHook = {
2767
+ matcher: "Task",
2768
+ hooks: [{ type: "command", command }]
2769
+ };
2770
+ const eventsDir = `${toShellPath(worktreePath)}/${PROGRESS_EVENTS_DIR}`;
2771
+ const progress = {
2772
+ hooks: [{ type: "command", command: `mkdir -p "${eventsDir}" && cat > "${eventsDir}/$(date +%s)-$$-$RANDOM.json"; exit 0` }]
2773
+ };
2774
+ settings.hooks = {
2775
+ PreToolUse: [taskHook],
2776
+ PostToolUse: [taskHook, { matcher: TODO_TOOLS, ...progress }],
2777
+ Stop: [progress],
2778
+ Notification: [progress],
2779
+ UserPromptSubmit: [progress]
2780
+ };
2401
2781
  }
2402
- return null;
2782
+ return JSON.stringify(settings);
2403
2783
  }
2404
- function sniffKind(filePath) {
2405
- let fd;
2406
- try {
2407
- fd = fs7.openSync(filePath, "r");
2408
- const buf = Buffer.alloc(SNIFF_BYTES);
2409
- const read = fs7.readSync(fd, buf, 0, SNIFF_BYTES, 0);
2410
- return buf.subarray(0, read).includes(0) ? "binary" : "text";
2411
- } catch {
2412
- return "binary";
2413
- } finally {
2414
- if (fd !== void 0) {
2415
- try {
2416
- fs7.closeSync(fd);
2417
- } catch {
2418
- }
2419
- }
2784
+ function resolveSystemPrompt(custom) {
2785
+ return custom && custom.trim() ? custom : "";
2786
+ }
2787
+ function buildPrompt(task) {
2788
+ const lines = [`\u4EFB\u52D9\u6A19\u984C\uFF1A${task.title}`];
2789
+ const description = task.description?.trim();
2790
+ if (description) {
2791
+ lines.push("", "\u4EFB\u52D9\u63CF\u8FF0\uFF1A", description);
2420
2792
  }
2793
+ return lines.join("\n");
2421
2794
  }
2422
- function resolveInside(dir, name) {
2423
- let root;
2424
- let target;
2425
- try {
2426
- root = fs7.realpathSync(path11.resolve(dir));
2427
- target = fs7.realpathSync(path11.resolve(root, name));
2428
- } catch {
2429
- return null;
2795
+ function executorSessionId(taskId, runId) {
2796
+ const source = runId ? `${taskId}:${runId}` : taskId;
2797
+ const raw = source.replace(/[^0-9a-f]/gi, "").toLowerCase();
2798
+ const hex = runId ? `${raw.slice(0, 24).padEnd(24, "0")}${namespaceHash(`executor:${source}`)}` : raw.padEnd(32, "0").slice(0, 32);
2799
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
2800
+ }
2801
+ function namespaceHash(namespace) {
2802
+ let hash = 2166136261;
2803
+ for (let i = 0; i < namespace.length; i += 1) {
2804
+ hash ^= namespace.charCodeAt(i);
2805
+ hash = Math.imul(hash, 16777619);
2430
2806
  }
2431
- if (target !== root && !target.startsWith(root + path11.sep)) return null;
2432
- return target;
2807
+ return (hash >>> 0).toString(16).padStart(8, "0");
2433
2808
  }
2434
- function listArtifacts(dir) {
2435
- const found2 = [];
2436
- const walk = (current, prefix, depth, scratch) => {
2437
- if (found2.length >= MAX_ARTIFACTS) return;
2438
- let dirents;
2439
- try {
2440
- dirents = fs7.readdirSync(current, { withFileTypes: true });
2441
- } catch {
2442
- return;
2443
- }
2444
- for (const dirent of dirents) {
2445
- if (found2.length >= MAX_ARTIFACTS) return;
2446
- const name = prefix ? `${prefix}/${dirent.name}` : dirent.name;
2447
- const full = path11.join(current, dirent.name);
2448
- if (dirent.isDirectory()) {
2449
- if (!scratch && depth === 1 && dirent.name === SCRATCH_DIR_NAME) continue;
2450
- if (depth < MAX_DEPTH) walk(full, name, depth + 1, scratch);
2451
- continue;
2452
- }
2453
- if (!dirent.isFile()) continue;
2454
- let stat;
2455
- try {
2456
- stat = fs7.statSync(full);
2809
+ function assembleCommand(agent, systemPrompt, prompt, model, effort, opts, worktreePath, sessionId, workspacePath) {
2810
+ let cmd;
2811
+ if (agent === "claude") {
2812
+ const settings = ` --settings ${shellQuote(buildClaudeSettings(worktreePath))}`;
2813
+ const addDir = workspacePath ? ` --add-dir ${shellQuote(toShellPath(workspacePath))}` : "";
2814
+ const modelFlag = model ? ` --model ${model}` : "";
2815
+ const effortFlag = effort ? ` --effort ${effort}` : "";
2816
+ const library = opts?.library;
2817
+ const libraryFlags = library ? ` --plugin-dir ${shellQuote(toShellPath(library.pluginDir))} --add-dir ${shellQuote(toShellPath(library.libraryDir))}` : "";
2818
+ const flags = `--chrome${claudePermissionFlag(opts?.autoMode)}${modelFlag}${effortFlag}${settings}${addDir}${libraryFlags}`;
2819
+ const sysFlag = systemPrompt ? ` --append-system-prompt ${shellQuote(systemPrompt)}` : "";
2820
+ const promptArg = prompt ? ` ${shellQuote(prompt)}` : "";
2821
+ const tail = `${flags}${sysFlag}${promptArg}`;
2822
+ cmd = sessionId && opts?.resume ? claudeResumeOrFresh(sessionId, worktreePath, tail) : `claude ${sessionId ? `--session-id ${sessionId} ` : opts?.resume ? "--continue " : ""}${tail}\r`;
2823
+ } else {
2824
+ const combined = [systemPrompt, prompt].filter(Boolean).join("\n\n");
2825
+ const codexEffortFlag = effort ? `-c ${shellQuote(`model_reasoning_effort="${effort}"`)} ` : "";
2826
+ const codexHome = opts?.library ? `CODEX_HOME=${shellQuote(toShellPath(opts.library.codexHome))} ` : "";
2827
+ const promptArg = combined ? ` ${shellQuote(combined)}` : "";
2828
+ cmd = `${codexHome}codex ${codexAutoFlag(opts?.autoMode)}${codexEffortFlag}--model ${model}${promptArg}\r`;
2829
+ }
2830
+ if (cmd.length > 2e5) console.warn(`[VibeFlow] launch command is ${cmd.length} bytes \u2014 approaching ARG_MAX`);
2831
+ return cmd;
2832
+ }
2833
+ var AGENT_NAMES = {
2834
+ claude: "Claude Code",
2835
+ codex: "Codex CLI"
2836
+ };
2837
+ var DEFAULT_MODELS = {
2838
+ claude: "sonnet",
2839
+ codex: "gpt-5.5"
2840
+ };
2841
+ var LEGACY_MODEL_FALLBACKS = {
2842
+ codex: {
2843
+ "gpt-5-codex": "gpt-5.5",
2844
+ "gpt-5": "gpt-5.5"
2845
+ }
2846
+ };
2847
+ function normalizeModel(agent, model) {
2848
+ return LEGACY_MODEL_FALLBACKS[agent]?.[model] ?? model;
2849
+ }
2850
+ function taskAgent(task) {
2851
+ const agent = task.agentCli;
2852
+ return agent && agent in AGENT_NAMES ? agent : "claude";
2853
+ }
2854
+ function taskModel(task) {
2855
+ const agent = taskAgent(task);
2856
+ return normalizeModel(agent, task.model || DEFAULT_MODELS[agent]);
2857
+ }
2858
+ function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
2859
+ const agent = taskAgent(task);
2860
+ const model = taskModel(task);
2861
+ const artifactsDir = agentArtifactsDir(task.worktreePath, workspacePath) ?? ARTIFACTS_FALLBACK_DIR;
2862
+ const builtInPrompt = buildArtifactPrompt(artifactsDir);
2863
+ const libraryPrompt = opts?.library?.promptText?.trim();
2864
+ const customPrompt = resolveSystemPrompt(systemPrompt);
2865
+ const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
2866
+ const includeTaskPrompt = opts?.includeTaskPrompt !== false;
2867
+ const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
2868
+ const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
2869
+ return boardEnvPrefix(task, opts) + assembleCommand(
2870
+ agent,
2871
+ sys,
2872
+ prompt,
2873
+ model,
2874
+ task.effort,
2875
+ opts,
2876
+ task.worktreePath,
2877
+ sessionId,
2878
+ workspacePath
2879
+ );
2880
+ }
2881
+
2882
+ // packages/core/src/git.ts
2883
+ init_env();
2884
+ init_attachments();
2885
+
2886
+ // packages/core/src/artifacts.ts
2887
+ import fs7 from "fs";
2888
+ import path11 from "path";
2889
+ var ARTIFACTS_DIR_SUFFIX2 = ".artifacts";
2890
+ var SCRATCH_DIR_NAME2 = "scratch";
2891
+ var ARTIFACTS_FALLBACK_DIR2 = ".vibeflow-artifacts";
2892
+ var IMAGE_MIME = {
2893
+ ".png": "image/png",
2894
+ ".jpg": "image/jpeg",
2895
+ ".jpeg": "image/jpeg",
2896
+ ".gif": "image/gif",
2897
+ ".webp": "image/webp",
2898
+ ".avif": "image/avif",
2899
+ ".bmp": "image/bmp",
2900
+ ".svg": "image/svg+xml"
2901
+ };
2902
+ var VIDEO_MIME = {
2903
+ ".mp4": "video/mp4",
2904
+ ".m4v": "video/mp4",
2905
+ ".webm": "video/webm",
2906
+ ".mov": "video/mp4"
2907
+ };
2908
+ var MAX_VIDEO_BYTES = 20 * 1024 * 1024;
2909
+ var MAX_TEXT_BYTES = 256 * 1024;
2910
+ var MAX_DEPTH = 3;
2911
+ var MAX_ARTIFACTS = 200;
2912
+ var SNIFF_BYTES = 8 * 1024;
2913
+ function agentArtifactsPath(baseDir, worktreePath) {
2914
+ return path11.join(baseDir, `${path11.basename(worktreePath)}${ARTIFACTS_DIR_SUFFIX2}`);
2915
+ }
2916
+ function mediaKind(filePath, size) {
2917
+ const extension = path11.extname(filePath).toLowerCase();
2918
+ const imageMime = IMAGE_MIME[extension];
2919
+ if (imageMime) return { kind: "image", mime: imageMime };
2920
+ const videoMime = VIDEO_MIME[extension];
2921
+ if (videoMime) {
2922
+ return size > MAX_VIDEO_BYTES ? { kind: "binary" } : { kind: "video", mime: videoMime };
2923
+ }
2924
+ return null;
2925
+ }
2926
+ function sniffKind(filePath) {
2927
+ let fd;
2928
+ try {
2929
+ fd = fs7.openSync(filePath, "r");
2930
+ const buf = Buffer.alloc(SNIFF_BYTES);
2931
+ const read = fs7.readSync(fd, buf, 0, SNIFF_BYTES, 0);
2932
+ return buf.subarray(0, read).includes(0) ? "binary" : "text";
2933
+ } catch {
2934
+ return "binary";
2935
+ } finally {
2936
+ if (fd !== void 0) {
2937
+ try {
2938
+ fs7.closeSync(fd);
2939
+ } catch {
2940
+ }
2941
+ }
2942
+ }
2943
+ }
2944
+ function resolveInside(dir, name) {
2945
+ let root;
2946
+ let target;
2947
+ try {
2948
+ root = fs7.realpathSync(path11.resolve(dir));
2949
+ target = fs7.realpathSync(path11.resolve(root, name));
2950
+ } catch {
2951
+ return null;
2952
+ }
2953
+ if (target !== root && !target.startsWith(root + path11.sep)) return null;
2954
+ return target;
2955
+ }
2956
+ function listArtifacts(dir) {
2957
+ const found2 = [];
2958
+ const walk = (current, prefix, depth, scratch) => {
2959
+ if (found2.length >= MAX_ARTIFACTS) return;
2960
+ let dirents;
2961
+ try {
2962
+ dirents = fs7.readdirSync(current, { withFileTypes: true });
2963
+ } catch {
2964
+ return;
2965
+ }
2966
+ for (const dirent of dirents) {
2967
+ if (found2.length >= MAX_ARTIFACTS) return;
2968
+ const name = prefix ? `${prefix}/${dirent.name}` : dirent.name;
2969
+ const full = path11.join(current, dirent.name);
2970
+ if (dirent.isDirectory()) {
2971
+ if (!scratch && depth === 1 && dirent.name === SCRATCH_DIR_NAME2) continue;
2972
+ if (depth < MAX_DEPTH) walk(full, name, depth + 1, scratch);
2973
+ continue;
2974
+ }
2975
+ if (!dirent.isFile()) continue;
2976
+ let stat;
2977
+ try {
2978
+ stat = fs7.statSync(full);
2457
2979
  } catch {
2458
2980
  continue;
2459
2981
  }
@@ -2470,7 +2992,7 @@ function listArtifacts(dir) {
2470
2992
  }
2471
2993
  };
2472
2994
  walk(dir, "", 1, false);
2473
- walk(path11.join(dir, SCRATCH_DIR_NAME), SCRATCH_DIR_NAME, 2, true);
2995
+ walk(path11.join(dir, SCRATCH_DIR_NAME2), SCRATCH_DIR_NAME2, 2, true);
2474
2996
  return found2.sort(
2475
2997
  (a, b) => Number(a.scratch) - Number(b.scratch) || b.modifiedAt - a.modifiedAt
2476
2998
  );
@@ -2637,7 +3159,13 @@ async function ensureLocalExclude(projectPath) {
2637
3159
  );
2638
3160
  await appendLineIfMissing(
2639
3161
  path12.join(infoDir, "exclude"),
2640
- `${ARTIFACTS_FALLBACK_DIR}/`,
3162
+ `${PROGRESS_EVENTS_DIR}/`,
3163
+ "# VibeFlow progress hook events (runtime-only)",
3164
+ { mkdir: true }
3165
+ );
3166
+ await appendLineIfMissing(
3167
+ path12.join(infoDir, "exclude"),
3168
+ `${ARTIFACTS_FALLBACK_DIR2}/`,
2641
3169
  "# VibeFlow task artifacts (runtime-only)",
2642
3170
  { mkdir: true }
2643
3171
  );
@@ -2727,7 +3255,7 @@ async function resolveBranchName(projectPath, workspacePath, taskId, preferredBr
2727
3255
  }
2728
3256
  return preferredBranch;
2729
3257
  }
2730
- var RUNTIME_ARTIFACT_DENYLIST = /* @__PURE__ */ new Set([SUBAGENTS_DIR]);
3258
+ var RUNTIME_ARTIFACT_DENYLIST = /* @__PURE__ */ new Set([SUBAGENTS_DIR, PROGRESS_EVENTS_DIR]);
2731
3259
  var MAX_IGNORED_COPY_BYTES = 10 * 1024 * 1024;
2732
3260
  var MAX_IGNORED_COPY_FILES = 200;
2733
3261
  var HEAVY_IGNORED_DIR_NAMES = /* @__PURE__ */ new Set([
@@ -2998,7 +3526,7 @@ async function resetWorktreeToBase(worktreePath, baseBranch) {
2998
3526
  const baseRef = await resolveBaseRef(worktreePath, baseBranch);
2999
3527
  await git(worktreePath, ["reset", "--hard", baseRef]);
3000
3528
  await git(worktreePath, ["clean", "-fd"]);
3001
- await fs8.rm(path12.join(worktreePath, ARTIFACTS_FALLBACK_DIR), {
3529
+ await fs8.rm(path12.join(worktreePath, ARTIFACTS_FALLBACK_DIR2), {
3002
3530
  recursive: true,
3003
3531
  force: true
3004
3532
  });
@@ -3082,7 +3610,9 @@ async function collectDiffEntries(worktreePath, baseRef) {
3082
3610
  }
3083
3611
  }
3084
3612
  const entries = Array.from(entryMap.values()).filter(
3085
- (e) => e.path !== SUBAGENTS_DIR && !e.path.startsWith(`${SUBAGENTS_DIR}/`)
3613
+ (e) => ![SUBAGENTS_DIR, PROGRESS_EVENTS_DIR].some(
3614
+ (dir) => e.path === dir || e.path.startsWith(`${dir}/`)
3615
+ )
3086
3616
  );
3087
3617
  await Promise.all(
3088
3618
  entries.map(async (entry) => {
@@ -3465,7 +3995,8 @@ async function provisionTaskWorktree(task) {
3465
3995
  branch: result.branch,
3466
3996
  worktreePath: result.worktreePath,
3467
3997
  baseBranch: result.baseBranch,
3468
- pushed: result.pushed
3998
+ pushed: result.pushed,
3999
+ provisionedAt: Date.now()
3469
4000
  };
3470
4001
  } catch (err) {
3471
4002
  throw Object.assign(
@@ -3713,8 +4244,8 @@ async function taskCommand(argv) {
3713
4244
  }
3714
4245
 
3715
4246
  // packages/cli/src/host.ts
3716
- import fs18 from "fs";
3717
- import path22 from "path";
4247
+ import fs19 from "fs";
4248
+ import path23 from "path";
3718
4249
  import { randomBytes as randomBytes2 } from "crypto";
3719
4250
 
3720
4251
  // packages/core/src/events.ts
@@ -4252,9 +4783,9 @@ async function syncBuiltinsAtStartup() {
4252
4783
  }
4253
4784
 
4254
4785
  // packages/core/src/service.ts
4255
- import fs16 from "fs";
4256
- import os5 from "os";
4257
- import path20 from "path";
4786
+ import fs17 from "fs";
4787
+ import os6 from "os";
4788
+ import path21 from "path";
4258
4789
  import { randomUUID as randomUUID4 } from "crypto";
4259
4790
 
4260
4791
  // packages/core/src/agent-models.ts
@@ -4888,176 +5419,288 @@ function startChatSend(opts, sender) {
4888
5419
  // packages/core/src/service.ts
4889
5420
  init_attachments();
4890
5421
 
4891
- // packages/core/src/launch.ts
4892
- var ARTIFACTS_DIR_SUFFIX2 = ".artifacts";
4893
- var SCRATCH_DIR_NAME2 = "scratch";
4894
- var ARTIFACTS_FALLBACK_DIR2 = ".vibeflow-artifacts";
4895
- function pathBasename(p) {
4896
- const norm = p.replace(/\\/g, "/").replace(/\/+$/, "");
4897
- const i = norm.lastIndexOf("/");
4898
- return i >= 0 ? norm.slice(i + 1) : norm;
4899
- }
4900
- function agentArtifactsDir(worktreePath, workspacePath) {
4901
- if (!worktreePath || !workspacePath) return null;
4902
- const dir = toShellPath(workspacePath);
4903
- const ws = pathBasename(worktreePath);
4904
- return `${dir}/${ws}${ARTIFACTS_DIR_SUFFIX2}`;
4905
- }
4906
- function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR2) {
4907
- return [
4908
- "Artifact \u8A2D\u5B9A\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
4909
- `1. \u672C\u4EFB\u52D9\u7684 Artifact \u8CC7\u6599\u593E\u662F ${artifactsDir}/\uFF1B\u9700\u8981\u4FDD\u5B58\u7D66\u4F7F\u7528\u8005\u7684\u9A57\u8B49\u8B49\u64DA\u6216\u5831\u544A\u6642\uFF0C\u8ACB\u5BEB\u5165\u6B64\u8CC7\u6599\u593E\uFF08\u4E0D\u5B58\u5728\u8ACB\u5EFA\u7ACB\uFF09\u3002`,
4910
- `2. \u4F7F\u7528\u8005\u6703\u5728 VibeFlow \u7684 Artifacts \u5206\u9801\u6AA2\u8996\u6839\u76EE\u9304\u5167\u5BB9\uFF1B\u53EA\u653E\u4F60\u6703\u4E3B\u52D5\u8ACB\u4F7F\u7528\u8005\u904E\u76EE\u7684\u6A94\u6848\u3002`,
4911
- `3. \u4F60\u81EA\u5DF1\u7684\u5DE5\u4F5C\u66AB\u5B58\uFF08\u4E00\u6B21\u6027 script\u3001log\u3001\u4E2D\u9593\u8F38\u51FA\u3001debug \u6A94\uFF09\u653E\u5728 ${artifactsDir}/${SCRATCH_DIR_NAME2}/\u3002`,
4912
- `4. \u82E5\u5DE5\u5177\u5148\u8F38\u51FA\u5230 /tmp\u3001/private/tmp\u3001$TMPDIR \u6216 ~/Downloads\uFF0C\u8ACB\u628A\u6700\u7D42\u8981\u5448\u73FE\u7684\u6A94\u6848\u8907\u88FD\u5230 ${artifactsDir}/ \u4E26\u78BA\u8A8D\u5B58\u5728\u3002`,
4913
- "5. Artifact \u8CC7\u6599\u593E\u4F4D\u65BC worktree \u4E4B\u5916\uFF1B\u4E0D\u8981\u628A\u5176\u4E2D\u5167\u5BB9\u52A0\u5165 git commit\uFF0C\u4E5F\u4E0D\u8981\u628A\u6700\u7D42\u4EA4\u4ED8\u7269\u53EA\u7559\u5728\u9019\u88E1\u3002"
4914
- ].join("\n");
4915
- }
4916
- var AUTO_PERMISSION_MODE = "auto";
4917
- function claudePermissionFlag(autoMode) {
4918
- return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
4919
- }
4920
- function shellQuote(s) {
4921
- return `'${s.replace(/'/g, `'\\''`)}'`;
4922
- }
4923
- function toShellPath(p) {
4924
- return p.replace(/\\/g, "/");
4925
- }
4926
- function boardEnvPrefix(task, opts) {
4927
- const board = opts?.boardCli;
4928
- if (!board) return "";
4929
- const entries = [
4930
- ["VIBEFLOW_TASK_ID", task.id],
4931
- ["VIBEFLOW_PROJECT_PATH", task.projectPath],
4932
- ["VIBEFLOW_BRANCH", task.branch],
4933
- ["VIBEFLOW_BASE_BRANCH", task.baseBranch],
4934
- ["VIBEFLOW_STORE_DIR", board?.storeDir],
4935
- ["VIBEFLOW_CLI", board?.cliPath],
4936
- ["VIBEFLOW_CLI_LOADER", board?.loaderPath],
4937
- ["VIBEFLOW_AUTO_MODE", opts?.autoMode ? "1" : "0"]
4938
- ];
4939
- const exports = entries.filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote(toShellPath(value))}`);
4940
- return exports.length ? `export ${exports.join(" ")}; ` : "";
5422
+ // packages/core/src/progress-tracker.ts
5423
+ init_progress();
5424
+ import fs16 from "fs";
5425
+ import os5 from "os";
5426
+ import path20 from "path";
5427
+ var SINCE_SLACK_MS = 5e3;
5428
+ function claudeProjectDir(home, cwd) {
5429
+ return path20.join(home, ".claude", "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
5430
+ }
5431
+ function samePath(a, b) {
5432
+ const norm = (p) => {
5433
+ const r = path20.resolve(p).replace(/\\/g, "/").replace(/\/+$/, "");
5434
+ return process.platform === "win32" ? r.toLowerCase() : r;
5435
+ };
5436
+ return norm(a) === norm(b);
4941
5437
  }
4942
- function codexAutoFlag(autoMode) {
4943
- return autoMode ? "--dangerously-bypass-approvals-and-sandbox " : "";
5438
+ function listFiles(dir, test) {
5439
+ try {
5440
+ return fs16.readdirSync(dir).filter(test).map((name) => path20.join(dir, name));
5441
+ } catch {
5442
+ return [];
5443
+ }
4944
5444
  }
4945
- function claudeResumeOrFresh(sessionId, worktreePath, tail) {
4946
- if (!worktreePath) return `claude --resume ${sessionId} ${tail}\r`;
4947
- const munged = worktreePath.replace(/[^a-zA-Z0-9]/g, "-");
4948
- const sessionFile = `"$HOME/.claude/projects/${munged}/${sessionId}.jsonl"`;
4949
- return `if [ -f ${sessionFile} ]; then claude --resume ${sessionId} ${tail}; else claude --session-id ${sessionId} ${tail}; fi\r`;
5445
+ function firstLine(file) {
5446
+ let fd;
5447
+ try {
5448
+ fd = fs16.openSync(file, "r");
5449
+ const buf = Buffer.alloc(64 * 1024);
5450
+ const n = fs16.readSync(fd, buf, 0, buf.length, 0);
5451
+ const text = buf.subarray(0, n).toString("utf8");
5452
+ const nl = text.indexOf("\n");
5453
+ return nl === -1 ? null : text.slice(0, nl);
5454
+ } catch {
5455
+ return null;
5456
+ } finally {
5457
+ if (fd !== void 0) fs16.closeSync(fd);
5458
+ }
4950
5459
  }
4951
- var SUBAGENTS_DIR2 = ".vibeflow-subagents";
4952
- function buildClaudeSettings(worktreePath) {
4953
- const settings = { theme: "dark" };
4954
- if (worktreePath) {
4955
- const dir = `${toShellPath(worktreePath)}/${SUBAGENTS_DIR2}`;
4956
- const command = `mkdir -p "${dir}" && cat > "${dir}/$(date +%s)-$$-$RANDOM.json"`;
4957
- const taskHook = {
4958
- matcher: "Task",
4959
- hooks: [{ type: "command", command }]
5460
+ function advance(file, cursor, reduce, initial) {
5461
+ let size;
5462
+ try {
5463
+ size = fs16.statSync(file).size;
5464
+ } catch {
5465
+ return;
5466
+ }
5467
+ if (size < cursor.offset) {
5468
+ cursor.offset = 0;
5469
+ cursor.rest = Buffer.alloc(0);
5470
+ cursor.state = initial();
5471
+ }
5472
+ if (size === cursor.offset) return;
5473
+ let fd;
5474
+ try {
5475
+ fd = fs16.openSync(file, "r");
5476
+ const chunk = Buffer.alloc(size - cursor.offset);
5477
+ const n = fs16.readSync(fd, chunk, 0, chunk.length, cursor.offset);
5478
+ cursor.offset += n;
5479
+ const data = Buffer.concat([cursor.rest, chunk.subarray(0, n)]);
5480
+ const nl = data.lastIndexOf(10);
5481
+ if (nl === -1) {
5482
+ cursor.rest = data;
5483
+ return;
5484
+ }
5485
+ cursor.rest = data.subarray(nl + 1);
5486
+ for (const line of data.subarray(0, nl).toString("utf8").split("\n")) {
5487
+ if (line.trim()) cursor.state = reduce(cursor.state, line);
5488
+ }
5489
+ } catch {
5490
+ } finally {
5491
+ if (fd !== void 0) fs16.closeSync(fd);
5492
+ }
5493
+ }
5494
+ function newCursor(state) {
5495
+ return { offset: 0, rest: Buffer.alloc(0), state };
5496
+ }
5497
+ function codexDayDirs(sessionsRoot, since) {
5498
+ const days = [];
5499
+ const end = /* @__PURE__ */ new Date();
5500
+ const start = since != null ? new Date(since - 24 * 36e5) : null;
5501
+ if (!start) {
5502
+ for (const y of listFiles(sessionsRoot, () => true))
5503
+ for (const m of listFiles(y, () => true))
5504
+ for (const d of listFiles(m, () => true)) days.push(d);
5505
+ return days;
5506
+ }
5507
+ const cur = new Date(start.getFullYear(), start.getMonth(), start.getDate());
5508
+ while (cur <= end) {
5509
+ const pad = (n) => String(n).padStart(2, "0");
5510
+ days.push(path20.join(sessionsRoot, String(cur.getFullYear()), pad(cur.getMonth() + 1), pad(cur.getDate())));
5511
+ cur.setDate(cur.getDate() + 1);
5512
+ }
5513
+ return days;
5514
+ }
5515
+ function createProgressTracker(opts) {
5516
+ const home = opts.homeDir ?? os5.homedir();
5517
+ const codexHomes = opts.codexHomes ?? (() => [path20.join(home, ".codex")]);
5518
+ const pollMs = opts.pollMs ?? 2e3;
5519
+ const entries = /* @__PURE__ */ new Map();
5520
+ const belongsToRun = (target, startedAt) => target.since == null || startedAt != null && startedAt >= target.since - SINCE_SLACK_MS;
5521
+ function discoverClaude(entry) {
5522
+ const { target } = entry;
5523
+ const dir = claudeProjectDir(home, target.worktreePath);
5524
+ for (const file of listFiles(dir, (n) => n.endsWith(".jsonl"))) {
5525
+ if (entry.claude.has(file) || entry.rejected.has(file)) continue;
5526
+ const session = path20.basename(file, ".jsonl");
5527
+ if (session !== target.sessionId) {
5528
+ const head = firstLine(file);
5529
+ if (head == null) continue;
5530
+ const started = reduceClaudeLine(initialClaudeState(), head).startedAt;
5531
+ if (!belongsToRun(target, started)) {
5532
+ entry.rejected.add(file);
5533
+ continue;
5534
+ }
5535
+ }
5536
+ entry.claude.set(file, newCursor(initialClaudeState()));
5537
+ }
5538
+ for (const file of Array.from(entry.claude.keys())) {
5539
+ if (file.includes(`${path20.sep}subagents${path20.sep}`)) continue;
5540
+ const sub = path20.join(file.slice(0, -".jsonl".length), "subagents");
5541
+ for (const f of listFiles(sub, (n) => n.endsWith(".jsonl"))) {
5542
+ if (!entry.claude.has(f)) entry.claude.set(f, newCursor(initialClaudeState()));
5543
+ }
5544
+ }
5545
+ }
5546
+ function discoverCodex(entry) {
5547
+ const { target } = entry;
5548
+ for (const codexHome of codexHomes()) {
5549
+ for (const day of codexDayDirs(path20.join(codexHome, "sessions"), target.since)) {
5550
+ for (const file of listFiles(day, (n) => n.startsWith("rollout-") && n.endsWith(".jsonl"))) {
5551
+ if (entry.codex.has(file) || entry.rejected.has(file)) continue;
5552
+ const head = firstLine(file);
5553
+ if (head == null) continue;
5554
+ const meta = reduceCodexLine(initialCodexState(), head);
5555
+ if (!meta.cwd) continue;
5556
+ if (!samePath(meta.cwd, target.worktreePath) || !belongsToRun(target, meta.startedAt)) {
5557
+ entry.rejected.add(file);
5558
+ continue;
5559
+ }
5560
+ entry.codex.set(file, newCursor(initialCodexState()));
5561
+ }
5562
+ }
5563
+ }
5564
+ }
5565
+ function drainHooks(entry) {
5566
+ const dir = path20.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
5567
+ for (const file of listFiles(dir, (n) => n.endsWith(".json")).sort()) {
5568
+ try {
5569
+ const at = fs16.statSync(file).mtimeMs;
5570
+ const event = JSON.parse(fs16.readFileSync(file, "utf8"));
5571
+ const foreign = entry.target.sessionId && typeof event.session_id === "string" && event.session_id !== entry.target.sessionId;
5572
+ const activity = foreign ? null : activityFromHook(event, at);
5573
+ if (activity) entry.hookActivity = latestActivity(entry.hookActivity ?? activity, activity);
5574
+ } catch {
5575
+ continue;
5576
+ }
5577
+ try {
5578
+ fs16.unlinkSync(file);
5579
+ } catch {
5580
+ }
5581
+ }
5582
+ }
5583
+ function compose(entry) {
5584
+ const { target } = entry;
5585
+ let runUsage = emptyUsage();
5586
+ let primary = null;
5587
+ if (target.agent === "claude") {
5588
+ for (const [file, cursor] of entry.claude) {
5589
+ runUsage = addUsage(runUsage, claudeUsageTotal(cursor.state));
5590
+ if (file.includes(`${path20.sep}subagents${path20.sep}`)) continue;
5591
+ const isExecutor = path20.basename(file, ".jsonl") === target.sessionId;
5592
+ const s = cursor.state;
5593
+ if (isExecutor || !target.sessionId && (!primary || (s.startedAt ?? 0) > (primary.startedAt ?? 0))) {
5594
+ primary = { todos: s.todos, contextTokens: s.contextTokens, activity: s.activity, startedAt: s.startedAt };
5595
+ }
5596
+ }
5597
+ } else {
5598
+ for (const cursor of entry.codex.values()) {
5599
+ const s = cursor.state;
5600
+ runUsage = addUsage(runUsage, s.usage);
5601
+ if (!primary || (s.startedAt ?? 0) >= (primary.startedAt ?? 0)) {
5602
+ primary = { todos: s.todos, contextTokens: s.contextTokens, activity: s.activity, startedAt: s.startedAt };
5603
+ }
5604
+ }
5605
+ }
5606
+ if (!primary) return null;
5607
+ const hook = entry.hookActivity;
5608
+ const activity = hook?.state === "working" && primary.activity.state === "working" ? primary.activity : latestActivity(primary.activity, hook);
5609
+ return {
5610
+ todos: primary.todos,
5611
+ runUsage,
5612
+ totalUsage: addUsage(target.priorUsage ?? emptyUsage(), runUsage),
5613
+ ...primary.contextTokens != null ? { contextTokens: primary.contextTokens } : {},
5614
+ activity
4960
5615
  };
4961
- settings.hooks = { PreToolUse: [taskHook], PostToolUse: [taskHook] };
4962
5616
  }
4963
- return JSON.stringify(settings);
4964
- }
4965
- function resolveSystemPrompt(custom) {
4966
- return custom && custom.trim() ? custom : "";
4967
- }
4968
- function buildPrompt(task) {
4969
- const lines = [`\u4EFB\u52D9\u6A19\u984C\uFF1A${task.title}`];
4970
- const description = task.description?.trim();
4971
- if (description) {
4972
- lines.push("", "\u4EFB\u52D9\u63CF\u8FF0\uFF1A", description);
5617
+ function sync(entry) {
5618
+ drainHooks(entry);
5619
+ if (entry.target.agent === "claude") {
5620
+ discoverClaude(entry);
5621
+ for (const [file, cursor] of entry.claude) advance(file, cursor, reduceClaudeLine, initialClaudeState);
5622
+ } else {
5623
+ discoverCodex(entry);
5624
+ for (const [file, cursor] of entry.codex) advance(file, cursor, reduceCodexLine, initialCodexState);
5625
+ }
5626
+ const next = compose(entry);
5627
+ if (!next) return null;
5628
+ const json = JSON.stringify(next);
5629
+ if (json !== entry.lastJson) {
5630
+ const notifications = diffProgress(entry.last, next);
5631
+ entry.last = next;
5632
+ entry.lastJson = json;
5633
+ opts.onUpdate(entry.target.taskId, next);
5634
+ if (notifications.length) opts.onNotify(entry.target.taskId, notifications);
5635
+ }
5636
+ return next;
4973
5637
  }
4974
- return lines.join("\n");
4975
- }
4976
- function executorSessionId(taskId, runId) {
4977
- const source = runId ? `${taskId}:${runId}` : taskId;
4978
- const raw = source.replace(/[^0-9a-f]/gi, "").toLowerCase();
4979
- const hex = runId ? `${raw.slice(0, 24).padEnd(24, "0")}${namespaceHash(`executor:${source}`)}` : raw.padEnd(32, "0").slice(0, 32);
4980
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
4981
- }
4982
- function namespaceHash(namespace) {
4983
- let hash = 2166136261;
4984
- for (let i = 0; i < namespace.length; i += 1) {
4985
- hash ^= namespace.charCodeAt(i);
4986
- hash = Math.imul(hash, 16777619);
5638
+ function safeSync(entry) {
5639
+ try {
5640
+ return sync(entry);
5641
+ } catch {
5642
+ return entry.last ?? null;
5643
+ }
4987
5644
  }
4988
- return (hash >>> 0).toString(16).padStart(8, "0");
4989
- }
4990
- function assembleCommand(agent, systemPrompt, prompt, model, effort, opts, worktreePath, sessionId, workspacePath) {
4991
- let cmd;
4992
- if (agent === "claude") {
4993
- const settings = ` --settings ${shellQuote(buildClaudeSettings(worktreePath))}`;
4994
- const addDir = workspacePath ? ` --add-dir ${shellQuote(toShellPath(workspacePath))}` : "";
4995
- const modelFlag = model ? ` --model ${model}` : "";
4996
- const effortFlag = effort ? ` --effort ${effort}` : "";
4997
- const library = opts?.library;
4998
- const libraryFlags = library ? ` --plugin-dir ${shellQuote(toShellPath(library.pluginDir))} --add-dir ${shellQuote(toShellPath(library.libraryDir))}` : "";
4999
- const flags = `--chrome${claudePermissionFlag(opts?.autoMode)}${modelFlag}${effortFlag}${settings}${addDir}${libraryFlags}`;
5000
- const sysFlag = systemPrompt ? ` --append-system-prompt ${shellQuote(systemPrompt)}` : "";
5001
- const promptArg = prompt ? ` ${shellQuote(prompt)}` : "";
5002
- const tail = `${flags}${sysFlag}${promptArg}`;
5003
- cmd = sessionId && opts?.resume ? claudeResumeOrFresh(sessionId, worktreePath, tail) : `claude ${sessionId ? `--session-id ${sessionId} ` : opts?.resume ? "--continue " : ""}${tail}\r`;
5004
- } else {
5005
- const combined = [systemPrompt, prompt].filter(Boolean).join("\n\n");
5006
- const codexEffortFlag = effort ? `-c ${shellQuote(`model_reasoning_effort="${effort}"`)} ` : "";
5007
- const codexHome = opts?.library ? `CODEX_HOME=${shellQuote(toShellPath(opts.library.codexHome))} ` : "";
5008
- const promptArg = combined ? ` ${shellQuote(combined)}` : "";
5009
- cmd = `${codexHome}codex ${codexAutoFlag(opts?.autoMode)}${codexEffortFlag}--model ${model}${promptArg}\r`;
5645
+ function stop(entry) {
5646
+ clearInterval(entry.timer);
5647
+ if (entry.pending) clearTimeout(entry.pending);
5648
+ entry.watcher?.close();
5010
5649
  }
5011
- if (cmd.length > 2e5) console.warn(`[VibeFlow] launch command is ${cmd.length} bytes \u2014 approaching ARG_MAX`);
5012
- return cmd;
5013
- }
5014
- var AGENT_NAMES = {
5015
- claude: "Claude Code",
5016
- codex: "Codex CLI"
5017
- };
5018
- var DEFAULT_MODELS = {
5019
- claude: "sonnet",
5020
- codex: "gpt-5.5"
5021
- };
5022
- var LEGACY_MODEL_FALLBACKS = {
5023
- codex: {
5024
- "gpt-5-codex": "gpt-5.5",
5025
- "gpt-5": "gpt-5.5"
5650
+ function sameTarget(a, b) {
5651
+ return a.agent === b.agent && a.worktreePath === b.worktreePath && a.sessionId === b.sessionId && a.since === b.since && JSON.stringify(a.priorUsage ?? null) === JSON.stringify(b.priorUsage ?? null);
5026
5652
  }
5027
- };
5028
- function normalizeModel(agent, model) {
5029
- return LEGACY_MODEL_FALLBACKS[agent]?.[model] ?? model;
5030
- }
5031
- function taskAgent(task) {
5032
- const agent = task.agentCli;
5033
- return agent && agent in AGENT_NAMES ? agent : "claude";
5034
- }
5035
- function taskModel(task) {
5036
- const agent = taskAgent(task);
5037
- return normalizeModel(agent, task.model || DEFAULT_MODELS[agent]);
5038
- }
5039
- function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
5040
- const agent = taskAgent(task);
5041
- const model = taskModel(task);
5042
- const artifactsDir = agentArtifactsDir(task.worktreePath, workspacePath) ?? ARTIFACTS_FALLBACK_DIR2;
5043
- const builtInPrompt = buildArtifactPrompt(artifactsDir);
5044
- const libraryPrompt = opts?.library?.promptText?.trim();
5045
- const customPrompt = resolveSystemPrompt(systemPrompt);
5046
- const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
5047
- const includeTaskPrompt = opts?.includeTaskPrompt !== false;
5048
- const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
5049
- const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
5050
- return boardEnvPrefix(task, opts) + assembleCommand(
5051
- agent,
5052
- sys,
5053
- prompt,
5054
- model,
5055
- task.effort,
5056
- opts,
5057
- task.worktreePath,
5058
- sessionId,
5059
- workspacePath
5060
- );
5653
+ return {
5654
+ track(target) {
5655
+ const existing = entries.get(target.taskId);
5656
+ if (existing && sameTarget(existing.target, target)) return;
5657
+ if (existing) stop(existing);
5658
+ const entry = {
5659
+ target,
5660
+ claude: /* @__PURE__ */ new Map(),
5661
+ codex: /* @__PURE__ */ new Map(),
5662
+ rejected: /* @__PURE__ */ new Set(),
5663
+ hookActivity: null,
5664
+ timer: setInterval(() => safeSync(entry), pollMs),
5665
+ watcher: null,
5666
+ pending: null
5667
+ };
5668
+ entry.timer.unref?.();
5669
+ entries.set(target.taskId, entry);
5670
+ const eventsDir = path20.join(target.worktreePath, PROGRESS_EVENTS_DIR);
5671
+ try {
5672
+ fs16.mkdirSync(eventsDir, { recursive: true });
5673
+ entry.watcher = fs16.watch(eventsDir, () => {
5674
+ if (entry.pending) return;
5675
+ entry.pending = setTimeout(() => {
5676
+ entry.pending = null;
5677
+ safeSync(entry);
5678
+ }, 50);
5679
+ });
5680
+ entry.watcher.on("error", () => entry.watcher?.close());
5681
+ entry.watcher.unref?.();
5682
+ } catch {
5683
+ }
5684
+ safeSync(entry);
5685
+ },
5686
+ untrack(taskId) {
5687
+ const entry = entries.get(taskId);
5688
+ if (!entry) return;
5689
+ stop(entry);
5690
+ entries.delete(taskId);
5691
+ },
5692
+ untrackAll() {
5693
+ for (const entry of entries.values()) stop(entry);
5694
+ entries.clear();
5695
+ },
5696
+ snapshot(taskId) {
5697
+ const entry = entries.get(taskId);
5698
+ return entry ? safeSync(entry) : null;
5699
+ },
5700
+ isTracking(taskId) {
5701
+ return entries.has(taskId);
5702
+ }
5703
+ };
5061
5704
  }
5062
5705
 
5063
5706
  // packages/core/src/service.ts
@@ -5112,8 +5755,61 @@ function validBoard(value) {
5112
5755
  }
5113
5756
  return next;
5114
5757
  }
5115
- function createCore({ sessions, bus, version }) {
5758
+ function createCore({ sessions, bus, version, homeDir }) {
5116
5759
  const sink = bus.sink();
5760
+ let libraryCodexHome = null;
5761
+ void libraryRoot().then((root) => {
5762
+ libraryCodexHome = libraryPaths(root).codexHome;
5763
+ }).catch(() => {
5764
+ });
5765
+ const home = homeDir ?? os6.homedir();
5766
+ const progress = createProgressTracker({
5767
+ homeDir: home,
5768
+ codexHomes: () => [path21.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
5769
+ onUpdate: (taskId, value) => bus.emit("progress:update", { taskId, progress: value }),
5770
+ onNotify: (taskId, list) => {
5771
+ const prefs = resolveNotificationSettings(getSettings());
5772
+ if (!prefs.enabled) return;
5773
+ const wanted = {
5774
+ step_completed: prefs.stepCompleted,
5775
+ all_completed: prefs.allCompleted,
5776
+ waiting_input: prefs.waitingInput
5777
+ };
5778
+ const notifications = list.filter((n) => wanted[n.kind]);
5779
+ const task = findTask(taskId);
5780
+ if (!task || !notifications.length) return;
5781
+ bus.emit("progress:notify", { taskId, title: task.title, notifications });
5782
+ }
5783
+ });
5784
+ function progressTarget(task) {
5785
+ if (!task.worktreePath) return null;
5786
+ const agent = taskAgent(task);
5787
+ return {
5788
+ taskId: task.id,
5789
+ agent,
5790
+ worktreePath: task.worktreePath,
5791
+ sessionId: agent === "claude" ? executorSessionId(task.id, task.runId) : void 0,
5792
+ since: task.launchedAt,
5793
+ priorUsage: task.usage
5794
+ };
5795
+ }
5796
+ function trackProgress(task) {
5797
+ const target = progressTarget(task);
5798
+ if (target) progress.track(target);
5799
+ }
5800
+ function usageThroughCurrentRun(task) {
5801
+ const target = progressTarget(task);
5802
+ if (!target) return task.usage;
5803
+ const wasTracking = progress.isTracking(task.id);
5804
+ progress.track(target);
5805
+ const snapshot = progress.snapshot(task.id);
5806
+ if (!wasTracking) progress.untrack(task.id);
5807
+ return snapshot?.totalUsage ?? task.usage;
5808
+ }
5809
+ try {
5810
+ for (const task of getState().board.in_progress) trackProgress(task);
5811
+ } catch {
5812
+ }
5117
5813
  async function teardownSession(key) {
5118
5814
  await sessions.kill(key);
5119
5815
  unwatchSubAgents(key);
@@ -5152,13 +5848,13 @@ function createCore({ sessions, bus, version }) {
5152
5848
  }
5153
5849
  function claudeSessionFile(cwd, sessionId) {
5154
5850
  const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
5155
- return path20.join(os5.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
5851
+ return path21.join(os6.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
5156
5852
  }
5157
5853
  async function removeTaskWorktree(task) {
5158
5854
  if (!task.projectPath || !task.worktreePath) return;
5159
5855
  const branch = task.branch || fallbackBranchName(task.id);
5160
5856
  await removeWorktree(task.projectPath, task.worktreePath);
5161
- deleteArtifacts(task.workspacePath ?? path20.dirname(task.worktreePath), task.worktreePath);
5857
+ deleteArtifacts(task.workspacePath ?? path21.dirname(task.worktreePath), task.worktreePath);
5162
5858
  await deleteBranch(task.projectPath, branch);
5163
5859
  }
5164
5860
  function columnOf(taskId) {
@@ -5203,6 +5899,17 @@ function createCore({ sessions, bus, version }) {
5203
5899
  let folderDialog = null;
5204
5900
  const handlers = {
5205
5901
  "vibeflow:getState": () => getState(),
5902
+ // A card's progress right now (todo list, usage, activity), for a frontend
5903
+ // that just connected; 'progress:update' carries every later change. Null
5904
+ // when the card has no transcript yet or is not running.
5905
+ "progress:get": (taskId) => {
5906
+ const task = requireTask(taskId);
5907
+ if (!progress.isTracking(task.id)) {
5908
+ if (columnOf(task.id) !== "in_progress") return null;
5909
+ trackProgress(task);
5910
+ }
5911
+ return progress.snapshot(task.id);
5912
+ },
5206
5913
  "app:getVersion": () => version,
5207
5914
  "vibeflow:setBoard": (board) => {
5208
5915
  setBoard(validBoard(board));
@@ -5237,8 +5944,8 @@ function createCore({ sessions, bus, version }) {
5237
5944
  "projects:listRecent": () => listRecentProjects(getStore()),
5238
5945
  // `vibeflow open <path>` while a host runs: only the host writes the store.
5239
5946
  "projects:record": (projectPath) => {
5240
- const p = path20.resolve(str(projectPath, "projectPath"));
5241
- if (!fs16.existsSync(p) || !fs16.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
5947
+ const p = path21.resolve(str(projectPath, "projectPath"));
5948
+ if (!fs17.existsSync(p) || !fs17.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
5242
5949
  recordRecentProject(getStore(), p);
5243
5950
  return listRecentProjects(getStore());
5244
5951
  },
@@ -5265,7 +5972,9 @@ function createCore({ sessions, bus, version }) {
5265
5972
  return { state: getState(), task };
5266
5973
  },
5267
5974
  "vibeflow:removeTask": (taskId) => {
5268
- removeTask(requireTask(taskId).id);
5975
+ const id = requireTask(taskId).id;
5976
+ progress.untrack(id);
5977
+ removeTask(id);
5269
5978
  return getState();
5270
5979
  },
5271
5980
  // Discard everything the last run left behind — the PTY session, the
@@ -5276,6 +5985,7 @@ function createCore({ sessions, bus, version }) {
5276
5985
  const task = requireTask(taskId);
5277
5986
  if (!task.worktreePath) {
5278
5987
  await teardownTask(task.id);
5988
+ progress.untrack(task.id);
5279
5989
  updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4() });
5280
5990
  const reset2 = findTask(task.id);
5281
5991
  if (!reset2) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
@@ -5284,11 +5994,13 @@ function createCore({ sessions, bus, version }) {
5284
5994
  if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
5285
5995
  await teardownTask(task.id);
5286
5996
  await resetWorktreeToBase(task.worktreePath, task.baseBranch);
5287
- const workspacePath = task.workspacePath ?? path20.dirname(task.worktreePath);
5997
+ const workspacePath = task.workspacePath ?? path21.dirname(task.worktreePath);
5288
5998
  deleteArtifacts(workspacePath, task.worktreePath);
5289
5999
  deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
5290
6000
  resetSubAgents(task.worktreePath);
5291
- updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4() });
6001
+ const usage = usageThroughCurrentRun(task);
6002
+ progress.untrack(task.id);
6003
+ updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4(), ...usage ? { usage } : {} });
5292
6004
  const reset = findTask(task.id);
5293
6005
  if (!reset) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
5294
6006
  bus.emit("subagents:update", { taskId: task.id, subAgents: [] });
@@ -5325,7 +6037,7 @@ function createCore({ sessions, bus, version }) {
5325
6037
  }
5326
6038
  const info = await getGitInfo(nextProject);
5327
6039
  if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
5328
- const projectName = path20.basename(nextProject);
6040
+ const projectName = path21.basename(nextProject);
5329
6041
  const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
5330
6042
  await teardownTask(existing.id);
5331
6043
  await removeTaskWorktree(existing);
@@ -5339,6 +6051,7 @@ function createCore({ sessions, bus, version }) {
5339
6051
  ...branchChanged ? { branch: nextBranch, branchExplicit: true } : {},
5340
6052
  worktreePath: void 0,
5341
6053
  pushed: void 0,
6054
+ provisionedAt: void 0,
5342
6055
  launchedAt: void 0
5343
6056
  };
5344
6057
  }
@@ -5387,6 +6100,10 @@ function createCore({ sessions, bus, version }) {
5387
6100
  watchSubAgents(key, cwd, (subAgents) => {
5388
6101
  bus.emit("subagents:update", { taskId: task.id, subAgents });
5389
6102
  });
6103
+ if (p.launch) {
6104
+ const current = findTask(task.id);
6105
+ if (current) trackProgress(current);
6106
+ }
5390
6107
  return result;
5391
6108
  },
5392
6109
  // Join a session as it is: no restart, for a frontend that attaches
@@ -5414,7 +6131,7 @@ function createCore({ sessions, bus, version }) {
5414
6131
  "claude:sessionExists": (taskId) => {
5415
6132
  const task = requireTask(taskId);
5416
6133
  if (!task.worktreePath) return false;
5417
- return fs16.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
6134
+ return fs17.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
5418
6135
  },
5419
6136
  "sessions:list": async () => {
5420
6137
  const keys = await sessions.list();
@@ -5461,16 +6178,17 @@ function createCore({ sessions, bus, version }) {
5461
6178
  const task = requireTask(taskId);
5462
6179
  if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
5463
6180
  const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
5464
- if (!fs16.existsSync(dir)) return "artifacts directory does not exist yet";
6181
+ if (!fs17.existsSync(dir)) return "artifacts directory does not exist yet";
5465
6182
  return getPlatform().openPath(dir);
5466
6183
  },
5467
6184
  "task:getSpecs": async (taskId) => {
5468
6185
  const task = requireTask(taskId);
5469
6186
  const base = task.baseBranch ?? "main";
5470
6187
  if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base);
5471
- if (!task.projectPath || !task.launchedAt) return [];
6188
+ const since = task.provisionedAt ?? task.launchedAt ?? (task.pushed !== void 0 ? task.createdAt : void 0);
6189
+ if (!task.projectPath || !since) return [];
5472
6190
  const changed = task.outcome?.files.filter((f) => f.status !== "D").map((f) => f.path);
5473
- return readBranchSpecs(task.projectPath, task.branch, base, changed, task.launchedAt);
6191
+ return readBranchSpecs(task.projectPath, task.branch, base, changed, since);
5474
6192
  },
5475
6193
  "board:getCliLaunchInfo": () => boardCliLaunchInfo(),
5476
6194
  "library:list": async () => listLibrary(await libraryRoot(), builtinSkillsDir()),
@@ -5570,24 +6288,31 @@ function createCore({ sessions, bus, version }) {
5570
6288
  if (task.worktreePath) {
5571
6289
  outcome = await captureTaskOutcome(task.worktreePath, task.baseBranch ?? "main") ?? void 0;
5572
6290
  }
6291
+ const usage = usageThroughCurrentRun(task);
6292
+ progress.untrack(task.id);
5573
6293
  if (task.projectPath && task.worktreePath) {
5574
6294
  await removeTaskWorktree(task);
5575
6295
  await syncBaseBranch(task.projectPath, task.baseBranch ?? "main");
5576
6296
  }
5577
- updateTask(task.id, outcome ? { worktreePath: void 0, outcome } : { worktreePath: void 0 });
6297
+ updateTask(task.id, {
6298
+ worktreePath: void 0,
6299
+ ...outcome ? { outcome } : {},
6300
+ ...usage ? { usage } : {}
6301
+ });
5578
6302
  return getState();
5579
6303
  },
5580
6304
  // Delete: cleanup (PTY + worktree) AND drop the card from the board.
5581
6305
  "vibeflow:deleteTask": async (taskId) => {
5582
6306
  const task = requireTask(taskId);
5583
6307
  await teardownTask(task.id);
6308
+ progress.untrack(task.id);
5584
6309
  cancelChatSend(task.id);
5585
6310
  if (task.workspacePath) {
5586
6311
  deleteDecisions(task.workspacePath, decisionsKey(task.worktreePath, task.branch));
5587
6312
  }
5588
6313
  await removeTaskWorktree(task);
5589
6314
  if (task.workspacePath) {
5590
- fs16.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
6315
+ fs17.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
5591
6316
  }
5592
6317
  clearConversation(task.id);
5593
6318
  removeTask(task.id);
@@ -5640,9 +6365,9 @@ function createCore({ sessions, bus, version }) {
5640
6365
  // Watch the containing directory, not the file: JsonStore renames a temp
5641
6366
  // file over the target and so replaces the inode on every save.
5642
6367
  watchStore() {
5643
- const file = path20.basename(getStorePath());
6368
+ const file = path21.basename(getStorePath());
5644
6369
  let debounce = null;
5645
- storeWatcher = fs16.watch(path20.dirname(getStorePath()), (_event, filename) => {
6370
+ storeWatcher = fs17.watch(path21.dirname(getStorePath()), (_event, filename) => {
5646
6371
  if (filename !== file) return;
5647
6372
  if (debounce) clearTimeout(debounce);
5648
6373
  debounce = setTimeout(() => {
@@ -5658,6 +6383,7 @@ function createCore({ sessions, bus, version }) {
5658
6383
  sessions.shutdown();
5659
6384
  cancelAllChatSends();
5660
6385
  unwatchAllSubAgents();
6386
+ progress.untrackAll();
5661
6387
  storeWatcher?.close();
5662
6388
  }
5663
6389
  };
@@ -5674,9 +6400,9 @@ function createSessionBackend(sink, choice) {
5674
6400
  }
5675
6401
 
5676
6402
  // packages/core/src/web-server.ts
5677
- import fs17 from "fs";
6403
+ import fs18 from "fs";
5678
6404
  import http from "http";
5679
- import path21 from "path";
6405
+ import path22 from "path";
5680
6406
  import { timingSafeEqual } from "crypto";
5681
6407
  import { WebSocketServer } from "ws";
5682
6408
  var TOKEN_COOKIE = "vf_token";
@@ -5729,14 +6455,14 @@ function resolveStatic(root, urlPath) {
5729
6455
  return null;
5730
6456
  }
5731
6457
  if (decoded.includes("\0")) return null;
5732
- const rel = path21.posix.normalize(decoded).replace(/^\/+/, "");
5733
- const base = path21.resolve(root);
5734
- const candidates = [rel, path21.posix.join(rel, "index.html"), `${rel}.html`];
6458
+ const rel = path22.posix.normalize(decoded).replace(/^\/+/, "");
6459
+ const base = path22.resolve(root);
6460
+ const candidates = [rel, path22.posix.join(rel, "index.html"), `${rel}.html`];
5735
6461
  for (const c of candidates) {
5736
- const full = path21.resolve(base, c);
5737
- if (full !== base && !full.startsWith(base + path21.sep)) return null;
6462
+ const full = path22.resolve(base, c);
6463
+ if (full !== base && !full.startsWith(base + path22.sep)) return null;
5738
6464
  try {
5739
- if (fs17.statSync(full).isFile()) return full;
6465
+ if (fs18.statSync(full).isFile()) return full;
5740
6466
  } catch {
5741
6467
  }
5742
6468
  }
@@ -5797,7 +6523,7 @@ async function startWebServer(options) {
5797
6523
  send(res, 404, "Not found");
5798
6524
  return;
5799
6525
  }
5800
- const ext = path21.extname(file).toLowerCase();
6526
+ const ext = path22.extname(file).toLowerCase();
5801
6527
  const immutable = url.pathname.startsWith("/_next/static/");
5802
6528
  res.writeHead(200, {
5803
6529
  "Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
@@ -5810,7 +6536,7 @@ async function startWebServer(options) {
5810
6536
  res.end();
5811
6537
  return;
5812
6538
  }
5813
- fs17.createReadStream(file).pipe(res);
6539
+ fs18.createReadStream(file).pipe(res);
5814
6540
  }
5815
6541
  const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
5816
6542
  function reject(socket, status, reason) {
@@ -5982,7 +6708,7 @@ function loginUrl(lock, pathname = "/home/") {
5982
6708
  return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
5983
6709
  }
5984
6710
  function isWebDir(dir) {
5985
- return fs18.existsSync(path22.join(dir, "home", "index.html"));
6711
+ return fs19.existsSync(path23.join(dir, "home", "index.html"));
5986
6712
  }
5987
6713
 
5988
6714
  // packages/cli/src/remote.ts
@@ -6099,8 +6825,8 @@ function connectToHost(lock) {
6099
6825
  }
6100
6826
 
6101
6827
  // packages/cli/src/doctor.ts
6102
- import fs19 from "fs";
6103
- import path23 from "path";
6828
+ import fs20 from "fs";
6829
+ import path24 from "path";
6104
6830
  import { execFile as execFile8 } from "child_process";
6105
6831
  import { promisify as promisify5 } from "util";
6106
6832
  init_env();
@@ -6240,10 +6966,10 @@ async function runDoctor(options) {
6240
6966
  });
6241
6967
  }
6242
6968
  try {
6243
- fs19.mkdirSync(options.userDataDir, { recursive: true });
6244
- const probe = path23.join(options.userDataDir, `.doctor-${process.pid}`);
6245
- fs19.writeFileSync(probe, "");
6246
- fs19.rmSync(probe);
6969
+ fs20.mkdirSync(options.userDataDir, { recursive: true });
6970
+ const probe = path24.join(options.userDataDir, `.doctor-${process.pid}`);
6971
+ fs20.writeFileSync(probe, "");
6972
+ fs20.rmSync(probe);
6247
6973
  checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
6248
6974
  } catch (err) {
6249
6975
  checks.push({
@@ -6297,24 +7023,24 @@ Options:
6297
7023
  `.trim();
6298
7024
  function detectLayout() {
6299
7025
  const file = fileURLToPath(import.meta.url);
6300
- const here = path26.dirname(file);
6301
- const bundledWeb = path26.join(here, "web");
6302
- if (isWebDir(bundledWeb) || path26.basename(here) === "dist") {
7026
+ const here = path27.dirname(file);
7027
+ const bundledWeb = path27.join(here, "web");
7028
+ if (isWebDir(bundledWeb) || path27.basename(here) === "dist") {
6303
7029
  return {
6304
- version: readVersion(path26.join(here, "..", "package.json")),
7030
+ version: readVersion(path27.join(here, "..", "package.json")),
6305
7031
  webDir: isWebDir(bundledWeb) ? bundledWeb : null,
6306
7032
  sourceRoot: null,
6307
7033
  cliEntry: file
6308
7034
  };
6309
7035
  }
6310
- const root = path26.resolve(here, "..", "..", "..");
7036
+ const root = path27.resolve(here, "..", "..", "..");
6311
7037
  const override = process.env.VIBEFLOW_WEB_DIR;
6312
- const webDir = override && isWebDir(override) ? override : isWebDir(path26.join(root, "app")) ? path26.join(root, "app") : null;
6313
- return { version: readVersion(path26.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
7038
+ const webDir = override && isWebDir(override) ? override : isWebDir(path27.join(root, "app")) ? path27.join(root, "app") : null;
7039
+ return { version: readVersion(path27.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
6314
7040
  }
6315
7041
  function readVersion(pkgPath) {
6316
7042
  try {
6317
- return JSON.parse(fs21.readFileSync(pkgPath, "utf8")).version;
7043
+ return JSON.parse(fs22.readFileSync(pkgPath, "utf8")).version;
6318
7044
  } catch {
6319
7045
  return "0.0.0";
6320
7046
  }
@@ -6520,8 +7246,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
6520
7246
  console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
6521
7247
  return 1;
6522
7248
  }
6523
- const dir = path26.resolve(target);
6524
- if (!fs21.existsSync(dir) || !fs21.statSync(dir).isDirectory()) {
7249
+ const dir = path27.resolve(target);
7250
+ if (!fs22.existsSync(dir) || !fs22.statSync(dir).isDirectory()) {
6525
7251
  console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
6526
7252
  return 1;
6527
7253
  }