@kw-yeh/vibeflow 4.5.2 → 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) => {
@@ -3714,8 +4244,8 @@ async function taskCommand(argv) {
3714
4244
  }
3715
4245
 
3716
4246
  // packages/cli/src/host.ts
3717
- import fs18 from "fs";
3718
- import path22 from "path";
4247
+ import fs19 from "fs";
4248
+ import path23 from "path";
3719
4249
  import { randomBytes as randomBytes2 } from "crypto";
3720
4250
 
3721
4251
  // packages/core/src/events.ts
@@ -4253,9 +4783,9 @@ async function syncBuiltinsAtStartup() {
4253
4783
  }
4254
4784
 
4255
4785
  // packages/core/src/service.ts
4256
- import fs16 from "fs";
4257
- import os5 from "os";
4258
- import path20 from "path";
4786
+ import fs17 from "fs";
4787
+ import os6 from "os";
4788
+ import path21 from "path";
4259
4789
  import { randomUUID as randomUUID4 } from "crypto";
4260
4790
 
4261
4791
  // packages/core/src/agent-models.ts
@@ -4889,176 +5419,288 @@ function startChatSend(opts, sender) {
4889
5419
  // packages/core/src/service.ts
4890
5420
  init_attachments();
4891
5421
 
4892
- // packages/core/src/launch.ts
4893
- var ARTIFACTS_DIR_SUFFIX2 = ".artifacts";
4894
- var SCRATCH_DIR_NAME2 = "scratch";
4895
- var ARTIFACTS_FALLBACK_DIR2 = ".vibeflow-artifacts";
4896
- function pathBasename(p) {
4897
- const norm = p.replace(/\\/g, "/").replace(/\/+$/, "");
4898
- const i = norm.lastIndexOf("/");
4899
- return i >= 0 ? norm.slice(i + 1) : norm;
4900
- }
4901
- function agentArtifactsDir(worktreePath, workspacePath) {
4902
- if (!worktreePath || !workspacePath) return null;
4903
- const dir = toShellPath(workspacePath);
4904
- const ws = pathBasename(worktreePath);
4905
- return `${dir}/${ws}${ARTIFACTS_DIR_SUFFIX2}`;
4906
- }
4907
- function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR2) {
4908
- return [
4909
- "Artifact \u8A2D\u5B9A\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
4910
- `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`,
4911
- `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`,
4912
- `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`,
4913
- `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`,
4914
- "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"
4915
- ].join("\n");
4916
- }
4917
- var AUTO_PERMISSION_MODE = "auto";
4918
- function claudePermissionFlag(autoMode) {
4919
- return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
4920
- }
4921
- function shellQuote(s) {
4922
- return `'${s.replace(/'/g, `'\\''`)}'`;
4923
- }
4924
- function toShellPath(p) {
4925
- return p.replace(/\\/g, "/");
4926
- }
4927
- function boardEnvPrefix(task, opts) {
4928
- const board = opts?.boardCli;
4929
- if (!board) return "";
4930
- const entries = [
4931
- ["VIBEFLOW_TASK_ID", task.id],
4932
- ["VIBEFLOW_PROJECT_PATH", task.projectPath],
4933
- ["VIBEFLOW_BRANCH", task.branch],
4934
- ["VIBEFLOW_BASE_BRANCH", task.baseBranch],
4935
- ["VIBEFLOW_STORE_DIR", board?.storeDir],
4936
- ["VIBEFLOW_CLI", board?.cliPath],
4937
- ["VIBEFLOW_CLI_LOADER", board?.loaderPath],
4938
- ["VIBEFLOW_AUTO_MODE", opts?.autoMode ? "1" : "0"]
4939
- ];
4940
- const exports = entries.filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote(toShellPath(value))}`);
4941
- 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);
4942
5437
  }
4943
- function codexAutoFlag(autoMode) {
4944
- 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
+ }
4945
5444
  }
4946
- function claudeResumeOrFresh(sessionId, worktreePath, tail) {
4947
- if (!worktreePath) return `claude --resume ${sessionId} ${tail}\r`;
4948
- const munged = worktreePath.replace(/[^a-zA-Z0-9]/g, "-");
4949
- const sessionFile = `"$HOME/.claude/projects/${munged}/${sessionId}.jsonl"`;
4950
- 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
+ }
4951
5459
  }
4952
- var SUBAGENTS_DIR2 = ".vibeflow-subagents";
4953
- function buildClaudeSettings(worktreePath) {
4954
- const settings = { theme: "dark" };
4955
- if (worktreePath) {
4956
- const dir = `${toShellPath(worktreePath)}/${SUBAGENTS_DIR2}`;
4957
- const command = `mkdir -p "${dir}" && cat > "${dir}/$(date +%s)-$$-$RANDOM.json"`;
4958
- const taskHook = {
4959
- matcher: "Task",
4960
- 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
4961
5615
  };
4962
- settings.hooks = { PreToolUse: [taskHook], PostToolUse: [taskHook] };
4963
5616
  }
4964
- return JSON.stringify(settings);
4965
- }
4966
- function resolveSystemPrompt(custom) {
4967
- return custom && custom.trim() ? custom : "";
4968
- }
4969
- function buildPrompt(task) {
4970
- const lines = [`\u4EFB\u52D9\u6A19\u984C\uFF1A${task.title}`];
4971
- const description = task.description?.trim();
4972
- if (description) {
4973
- 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;
4974
5637
  }
4975
- return lines.join("\n");
4976
- }
4977
- function executorSessionId(taskId, runId) {
4978
- const source = runId ? `${taskId}:${runId}` : taskId;
4979
- const raw = source.replace(/[^0-9a-f]/gi, "").toLowerCase();
4980
- const hex = runId ? `${raw.slice(0, 24).padEnd(24, "0")}${namespaceHash(`executor:${source}`)}` : raw.padEnd(32, "0").slice(0, 32);
4981
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
4982
- }
4983
- function namespaceHash(namespace) {
4984
- let hash = 2166136261;
4985
- for (let i = 0; i < namespace.length; i += 1) {
4986
- hash ^= namespace.charCodeAt(i);
4987
- hash = Math.imul(hash, 16777619);
5638
+ function safeSync(entry) {
5639
+ try {
5640
+ return sync(entry);
5641
+ } catch {
5642
+ return entry.last ?? null;
5643
+ }
4988
5644
  }
4989
- return (hash >>> 0).toString(16).padStart(8, "0");
4990
- }
4991
- function assembleCommand(agent, systemPrompt, prompt, model, effort, opts, worktreePath, sessionId, workspacePath) {
4992
- let cmd;
4993
- if (agent === "claude") {
4994
- const settings = ` --settings ${shellQuote(buildClaudeSettings(worktreePath))}`;
4995
- const addDir = workspacePath ? ` --add-dir ${shellQuote(toShellPath(workspacePath))}` : "";
4996
- const modelFlag = model ? ` --model ${model}` : "";
4997
- const effortFlag = effort ? ` --effort ${effort}` : "";
4998
- const library = opts?.library;
4999
- const libraryFlags = library ? ` --plugin-dir ${shellQuote(toShellPath(library.pluginDir))} --add-dir ${shellQuote(toShellPath(library.libraryDir))}` : "";
5000
- const flags = `--chrome${claudePermissionFlag(opts?.autoMode)}${modelFlag}${effortFlag}${settings}${addDir}${libraryFlags}`;
5001
- const sysFlag = systemPrompt ? ` --append-system-prompt ${shellQuote(systemPrompt)}` : "";
5002
- const promptArg = prompt ? ` ${shellQuote(prompt)}` : "";
5003
- const tail = `${flags}${sysFlag}${promptArg}`;
5004
- cmd = sessionId && opts?.resume ? claudeResumeOrFresh(sessionId, worktreePath, tail) : `claude ${sessionId ? `--session-id ${sessionId} ` : opts?.resume ? "--continue " : ""}${tail}\r`;
5005
- } else {
5006
- const combined = [systemPrompt, prompt].filter(Boolean).join("\n\n");
5007
- const codexEffortFlag = effort ? `-c ${shellQuote(`model_reasoning_effort="${effort}"`)} ` : "";
5008
- const codexHome = opts?.library ? `CODEX_HOME=${shellQuote(toShellPath(opts.library.codexHome))} ` : "";
5009
- const promptArg = combined ? ` ${shellQuote(combined)}` : "";
5010
- 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();
5011
5649
  }
5012
- if (cmd.length > 2e5) console.warn(`[VibeFlow] launch command is ${cmd.length} bytes \u2014 approaching ARG_MAX`);
5013
- return cmd;
5014
- }
5015
- var AGENT_NAMES = {
5016
- claude: "Claude Code",
5017
- codex: "Codex CLI"
5018
- };
5019
- var DEFAULT_MODELS = {
5020
- claude: "sonnet",
5021
- codex: "gpt-5.5"
5022
- };
5023
- var LEGACY_MODEL_FALLBACKS = {
5024
- codex: {
5025
- "gpt-5-codex": "gpt-5.5",
5026
- "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);
5027
5652
  }
5028
- };
5029
- function normalizeModel(agent, model) {
5030
- return LEGACY_MODEL_FALLBACKS[agent]?.[model] ?? model;
5031
- }
5032
- function taskAgent(task) {
5033
- const agent = task.agentCli;
5034
- return agent && agent in AGENT_NAMES ? agent : "claude";
5035
- }
5036
- function taskModel(task) {
5037
- const agent = taskAgent(task);
5038
- return normalizeModel(agent, task.model || DEFAULT_MODELS[agent]);
5039
- }
5040
- function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
5041
- const agent = taskAgent(task);
5042
- const model = taskModel(task);
5043
- const artifactsDir = agentArtifactsDir(task.worktreePath, workspacePath) ?? ARTIFACTS_FALLBACK_DIR2;
5044
- const builtInPrompt = buildArtifactPrompt(artifactsDir);
5045
- const libraryPrompt = opts?.library?.promptText?.trim();
5046
- const customPrompt = resolveSystemPrompt(systemPrompt);
5047
- const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
5048
- const includeTaskPrompt = opts?.includeTaskPrompt !== false;
5049
- const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
5050
- const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
5051
- return boardEnvPrefix(task, opts) + assembleCommand(
5052
- agent,
5053
- sys,
5054
- prompt,
5055
- model,
5056
- task.effort,
5057
- opts,
5058
- task.worktreePath,
5059
- sessionId,
5060
- workspacePath
5061
- );
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
+ };
5062
5704
  }
5063
5705
 
5064
5706
  // packages/core/src/service.ts
@@ -5113,8 +5755,61 @@ function validBoard(value) {
5113
5755
  }
5114
5756
  return next;
5115
5757
  }
5116
- function createCore({ sessions, bus, version }) {
5758
+ function createCore({ sessions, bus, version, homeDir }) {
5117
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
+ }
5118
5813
  async function teardownSession(key) {
5119
5814
  await sessions.kill(key);
5120
5815
  unwatchSubAgents(key);
@@ -5153,13 +5848,13 @@ function createCore({ sessions, bus, version }) {
5153
5848
  }
5154
5849
  function claudeSessionFile(cwd, sessionId) {
5155
5850
  const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
5156
- return path20.join(os5.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
5851
+ return path21.join(os6.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
5157
5852
  }
5158
5853
  async function removeTaskWorktree(task) {
5159
5854
  if (!task.projectPath || !task.worktreePath) return;
5160
5855
  const branch = task.branch || fallbackBranchName(task.id);
5161
5856
  await removeWorktree(task.projectPath, task.worktreePath);
5162
- deleteArtifacts(task.workspacePath ?? path20.dirname(task.worktreePath), task.worktreePath);
5857
+ deleteArtifacts(task.workspacePath ?? path21.dirname(task.worktreePath), task.worktreePath);
5163
5858
  await deleteBranch(task.projectPath, branch);
5164
5859
  }
5165
5860
  function columnOf(taskId) {
@@ -5204,6 +5899,17 @@ function createCore({ sessions, bus, version }) {
5204
5899
  let folderDialog = null;
5205
5900
  const handlers = {
5206
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
+ },
5207
5913
  "app:getVersion": () => version,
5208
5914
  "vibeflow:setBoard": (board) => {
5209
5915
  setBoard(validBoard(board));
@@ -5238,8 +5944,8 @@ function createCore({ sessions, bus, version }) {
5238
5944
  "projects:listRecent": () => listRecentProjects(getStore()),
5239
5945
  // `vibeflow open <path>` while a host runs: only the host writes the store.
5240
5946
  "projects:record": (projectPath) => {
5241
- const p = path20.resolve(str(projectPath, "projectPath"));
5242
- 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}`);
5243
5949
  recordRecentProject(getStore(), p);
5244
5950
  return listRecentProjects(getStore());
5245
5951
  },
@@ -5266,7 +5972,9 @@ function createCore({ sessions, bus, version }) {
5266
5972
  return { state: getState(), task };
5267
5973
  },
5268
5974
  "vibeflow:removeTask": (taskId) => {
5269
- removeTask(requireTask(taskId).id);
5975
+ const id = requireTask(taskId).id;
5976
+ progress.untrack(id);
5977
+ removeTask(id);
5270
5978
  return getState();
5271
5979
  },
5272
5980
  // Discard everything the last run left behind — the PTY session, the
@@ -5277,6 +5985,7 @@ function createCore({ sessions, bus, version }) {
5277
5985
  const task = requireTask(taskId);
5278
5986
  if (!task.worktreePath) {
5279
5987
  await teardownTask(task.id);
5988
+ progress.untrack(task.id);
5280
5989
  updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4() });
5281
5990
  const reset2 = findTask(task.id);
5282
5991
  if (!reset2) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
@@ -5285,11 +5994,13 @@ function createCore({ sessions, bus, version }) {
5285
5994
  if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
5286
5995
  await teardownTask(task.id);
5287
5996
  await resetWorktreeToBase(task.worktreePath, task.baseBranch);
5288
- const workspacePath = task.workspacePath ?? path20.dirname(task.worktreePath);
5997
+ const workspacePath = task.workspacePath ?? path21.dirname(task.worktreePath);
5289
5998
  deleteArtifacts(workspacePath, task.worktreePath);
5290
5999
  deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
5291
6000
  resetSubAgents(task.worktreePath);
5292
- 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 } : {} });
5293
6004
  const reset = findTask(task.id);
5294
6005
  if (!reset) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
5295
6006
  bus.emit("subagents:update", { taskId: task.id, subAgents: [] });
@@ -5326,7 +6037,7 @@ function createCore({ sessions, bus, version }) {
5326
6037
  }
5327
6038
  const info = await getGitInfo(nextProject);
5328
6039
  if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
5329
- const projectName = path20.basename(nextProject);
6040
+ const projectName = path21.basename(nextProject);
5330
6041
  const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
5331
6042
  await teardownTask(existing.id);
5332
6043
  await removeTaskWorktree(existing);
@@ -5389,6 +6100,10 @@ function createCore({ sessions, bus, version }) {
5389
6100
  watchSubAgents(key, cwd, (subAgents) => {
5390
6101
  bus.emit("subagents:update", { taskId: task.id, subAgents });
5391
6102
  });
6103
+ if (p.launch) {
6104
+ const current = findTask(task.id);
6105
+ if (current) trackProgress(current);
6106
+ }
5392
6107
  return result;
5393
6108
  },
5394
6109
  // Join a session as it is: no restart, for a frontend that attaches
@@ -5416,7 +6131,7 @@ function createCore({ sessions, bus, version }) {
5416
6131
  "claude:sessionExists": (taskId) => {
5417
6132
  const task = requireTask(taskId);
5418
6133
  if (!task.worktreePath) return false;
5419
- return fs16.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
6134
+ return fs17.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
5420
6135
  },
5421
6136
  "sessions:list": async () => {
5422
6137
  const keys = await sessions.list();
@@ -5463,7 +6178,7 @@ function createCore({ sessions, bus, version }) {
5463
6178
  const task = requireTask(taskId);
5464
6179
  if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
5465
6180
  const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
5466
- if (!fs16.existsSync(dir)) return "artifacts directory does not exist yet";
6181
+ if (!fs17.existsSync(dir)) return "artifacts directory does not exist yet";
5467
6182
  return getPlatform().openPath(dir);
5468
6183
  },
5469
6184
  "task:getSpecs": async (taskId) => {
@@ -5573,24 +6288,31 @@ function createCore({ sessions, bus, version }) {
5573
6288
  if (task.worktreePath) {
5574
6289
  outcome = await captureTaskOutcome(task.worktreePath, task.baseBranch ?? "main") ?? void 0;
5575
6290
  }
6291
+ const usage = usageThroughCurrentRun(task);
6292
+ progress.untrack(task.id);
5576
6293
  if (task.projectPath && task.worktreePath) {
5577
6294
  await removeTaskWorktree(task);
5578
6295
  await syncBaseBranch(task.projectPath, task.baseBranch ?? "main");
5579
6296
  }
5580
- 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
+ });
5581
6302
  return getState();
5582
6303
  },
5583
6304
  // Delete: cleanup (PTY + worktree) AND drop the card from the board.
5584
6305
  "vibeflow:deleteTask": async (taskId) => {
5585
6306
  const task = requireTask(taskId);
5586
6307
  await teardownTask(task.id);
6308
+ progress.untrack(task.id);
5587
6309
  cancelChatSend(task.id);
5588
6310
  if (task.workspacePath) {
5589
6311
  deleteDecisions(task.workspacePath, decisionsKey(task.worktreePath, task.branch));
5590
6312
  }
5591
6313
  await removeTaskWorktree(task);
5592
6314
  if (task.workspacePath) {
5593
- fs16.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
6315
+ fs17.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
5594
6316
  }
5595
6317
  clearConversation(task.id);
5596
6318
  removeTask(task.id);
@@ -5643,9 +6365,9 @@ function createCore({ sessions, bus, version }) {
5643
6365
  // Watch the containing directory, not the file: JsonStore renames a temp
5644
6366
  // file over the target and so replaces the inode on every save.
5645
6367
  watchStore() {
5646
- const file = path20.basename(getStorePath());
6368
+ const file = path21.basename(getStorePath());
5647
6369
  let debounce = null;
5648
- storeWatcher = fs16.watch(path20.dirname(getStorePath()), (_event, filename) => {
6370
+ storeWatcher = fs17.watch(path21.dirname(getStorePath()), (_event, filename) => {
5649
6371
  if (filename !== file) return;
5650
6372
  if (debounce) clearTimeout(debounce);
5651
6373
  debounce = setTimeout(() => {
@@ -5661,6 +6383,7 @@ function createCore({ sessions, bus, version }) {
5661
6383
  sessions.shutdown();
5662
6384
  cancelAllChatSends();
5663
6385
  unwatchAllSubAgents();
6386
+ progress.untrackAll();
5664
6387
  storeWatcher?.close();
5665
6388
  }
5666
6389
  };
@@ -5677,9 +6400,9 @@ function createSessionBackend(sink, choice) {
5677
6400
  }
5678
6401
 
5679
6402
  // packages/core/src/web-server.ts
5680
- import fs17 from "fs";
6403
+ import fs18 from "fs";
5681
6404
  import http from "http";
5682
- import path21 from "path";
6405
+ import path22 from "path";
5683
6406
  import { timingSafeEqual } from "crypto";
5684
6407
  import { WebSocketServer } from "ws";
5685
6408
  var TOKEN_COOKIE = "vf_token";
@@ -5732,14 +6455,14 @@ function resolveStatic(root, urlPath) {
5732
6455
  return null;
5733
6456
  }
5734
6457
  if (decoded.includes("\0")) return null;
5735
- const rel = path21.posix.normalize(decoded).replace(/^\/+/, "");
5736
- const base = path21.resolve(root);
5737
- 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`];
5738
6461
  for (const c of candidates) {
5739
- const full = path21.resolve(base, c);
5740
- 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;
5741
6464
  try {
5742
- if (fs17.statSync(full).isFile()) return full;
6465
+ if (fs18.statSync(full).isFile()) return full;
5743
6466
  } catch {
5744
6467
  }
5745
6468
  }
@@ -5800,7 +6523,7 @@ async function startWebServer(options) {
5800
6523
  send(res, 404, "Not found");
5801
6524
  return;
5802
6525
  }
5803
- const ext = path21.extname(file).toLowerCase();
6526
+ const ext = path22.extname(file).toLowerCase();
5804
6527
  const immutable = url.pathname.startsWith("/_next/static/");
5805
6528
  res.writeHead(200, {
5806
6529
  "Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
@@ -5813,7 +6536,7 @@ async function startWebServer(options) {
5813
6536
  res.end();
5814
6537
  return;
5815
6538
  }
5816
- fs17.createReadStream(file).pipe(res);
6539
+ fs18.createReadStream(file).pipe(res);
5817
6540
  }
5818
6541
  const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
5819
6542
  function reject(socket, status, reason) {
@@ -5985,7 +6708,7 @@ function loginUrl(lock, pathname = "/home/") {
5985
6708
  return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
5986
6709
  }
5987
6710
  function isWebDir(dir) {
5988
- return fs18.existsSync(path22.join(dir, "home", "index.html"));
6711
+ return fs19.existsSync(path23.join(dir, "home", "index.html"));
5989
6712
  }
5990
6713
 
5991
6714
  // packages/cli/src/remote.ts
@@ -6102,8 +6825,8 @@ function connectToHost(lock) {
6102
6825
  }
6103
6826
 
6104
6827
  // packages/cli/src/doctor.ts
6105
- import fs19 from "fs";
6106
- import path23 from "path";
6828
+ import fs20 from "fs";
6829
+ import path24 from "path";
6107
6830
  import { execFile as execFile8 } from "child_process";
6108
6831
  import { promisify as promisify5 } from "util";
6109
6832
  init_env();
@@ -6243,10 +6966,10 @@ async function runDoctor(options) {
6243
6966
  });
6244
6967
  }
6245
6968
  try {
6246
- fs19.mkdirSync(options.userDataDir, { recursive: true });
6247
- const probe = path23.join(options.userDataDir, `.doctor-${process.pid}`);
6248
- fs19.writeFileSync(probe, "");
6249
- 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);
6250
6973
  checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
6251
6974
  } catch (err) {
6252
6975
  checks.push({
@@ -6300,24 +7023,24 @@ Options:
6300
7023
  `.trim();
6301
7024
  function detectLayout() {
6302
7025
  const file = fileURLToPath(import.meta.url);
6303
- const here = path26.dirname(file);
6304
- const bundledWeb = path26.join(here, "web");
6305
- 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") {
6306
7029
  return {
6307
- version: readVersion(path26.join(here, "..", "package.json")),
7030
+ version: readVersion(path27.join(here, "..", "package.json")),
6308
7031
  webDir: isWebDir(bundledWeb) ? bundledWeb : null,
6309
7032
  sourceRoot: null,
6310
7033
  cliEntry: file
6311
7034
  };
6312
7035
  }
6313
- const root = path26.resolve(here, "..", "..", "..");
7036
+ const root = path27.resolve(here, "..", "..", "..");
6314
7037
  const override = process.env.VIBEFLOW_WEB_DIR;
6315
- const webDir = override && isWebDir(override) ? override : isWebDir(path26.join(root, "app")) ? path26.join(root, "app") : null;
6316
- 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 };
6317
7040
  }
6318
7041
  function readVersion(pkgPath) {
6319
7042
  try {
6320
- return JSON.parse(fs21.readFileSync(pkgPath, "utf8")).version;
7043
+ return JSON.parse(fs22.readFileSync(pkgPath, "utf8")).version;
6321
7044
  } catch {
6322
7045
  return "0.0.0";
6323
7046
  }
@@ -6523,8 +7246,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
6523
7246
  console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
6524
7247
  return 1;
6525
7248
  }
6526
- const dir = path26.resolve(target);
6527
- if (!fs21.existsSync(dir) || !fs21.statSync(dir).isDirectory()) {
7249
+ const dir = path27.resolve(target);
7250
+ if (!fs22.existsSync(dir) || !fs22.statSync(dir).isDirectory()) {
6528
7251
  console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
6529
7252
  return 1;
6530
7253
  }