@kw-yeh/vibeflow 4.11.17 → 4.11.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ vibeflow
26
26
  - **看得到 agent 做了什麼**:每張卡有 diff 檢視、artifacts,以及「決策」分頁:顯示這張卡的分支新增或修改的 `spec.md`(agent 做了哪些決定、為什麼)。卡片完成後打開時預設顯示決策;本地分支刪掉後,會改從 `origin/<branch>` 或 GitHub PR 的 merge commit 讀回合併當時的內容(squash/rebase merge 的卡片讀不回來)。
27
27
  - **選你要的 agent**:Claude Code 或 Codex,每張卡可以各自指定 model、推理強度(effort)與 Auto Mode(是否免確認執行)。Effort 預設為 `auto`,由 Jev 依任務與目前模型選擇支援的級距;在「設定 → Jev 自動選擇 effort」輸入 TypeSafe Jev API key,或在啟動 VibeFlow 的環境設定 `TYPESAFE_API_KEY`。若沒有金鑰或 Jev 暫時無法使用,會顯示原因並使用模型預設的 effort。未指定 model 時,使用該 CLI 的預設模型。
28
28
  - **Web UI 與終端機 UI 共用同一個看板**:習慣瀏覽器用 Web UI,習慣終端機用 `vibeflow tui`。
29
- - **Jira 工作收件匣**:安裝 [Atlassian CLI(Windows)](https://developer.atlassian.com/cloud/acli/guides/install-windows/) 或 [Atlassian CLI(macOS)](https://developer.atlassian.com/cloud/acli/guides/install-macos/),再到設定登入,就能在「工作項目」的 Jira 分頁依狀態查看指派給自己的 ticket、開啟附件,並選擇本機專案建立 Backlog 卡片。
29
+ - **Jira 工作收件匣**:安裝 [Atlassian CLI(Windows)](https://developer.atlassian.com/cloud/acli/guides/install-windows/) 或 [Atlassian CLI(macOS)](https://developer.atlassian.com/cloud/acli/guides/install-macos/),再到設定登入,就能在「工作項目」的 Jira 分頁以固定四欄查看指派給自己的 ticket、選擇抓取範圍、開啟附件,並選擇本機專案建立 Backlog 卡片。
30
30
  - **GitHub Issues 與 PRs**:使用已登入的 `gh` 跨所有相關 repo 顯示 open 項目,即使該 repo 尚未出現在看板;建卡時可選擇本機專案資料夾。
31
31
  - **內建 skill,可自行調整**:每個人的 Library(設定 → Library)都附帶 `visual-parity`、`pr` 兩個 skill,可以直接編輯或刪除,隨時能還原成 VibeFlow 出貨的版本;沒改過的會跟著 VibeFlow 升級更新。Library 只負責保存,啟動任務時不會載入任何 Library 項目——agent 只收到你在設定填的 system prompt 與卡片內容。`visual-parity` 需要 Python 與 `pip install playwright && playwright install chromium`。
32
32
  - **也能用指令操作**:`vibeflow task create` 不開 UI 直接建卡;agent 自己也能在看板上建立後續的卡片。
package/dist/vibeflow.mjs CHANGED
@@ -1090,9 +1090,9 @@ var init_progress = __esm({
1090
1090
 
1091
1091
  // packages/tui/src/task-form.ts
1092
1092
  import { spawnSync as spawnSync2 } from "child_process";
1093
- import fs24 from "fs";
1093
+ import fs25 from "fs";
1094
1094
  import os8 from "os";
1095
- import path28 from "path";
1095
+ import path29 from "path";
1096
1096
  import React, { useEffect, useRef, useState } from "react";
1097
1097
  import { Box, Text, render as render2, useApp, useInput } from "ink";
1098
1098
  function newFormState(defaultAutoMode, agents) {
@@ -1218,17 +1218,17 @@ function editorCommand(env, platform) {
1218
1218
  return env.VISUAL?.trim() || env.EDITOR?.trim() || (platform === "win32" ? "notepad" : "vi");
1219
1219
  }
1220
1220
  function editInEditor(text) {
1221
- const dir = fs24.mkdtempSync(path28.join(os8.tmpdir(), "vibeflow-"));
1222
- const file = path28.join(dir, "description.md");
1221
+ const dir = fs25.mkdtempSync(path29.join(os8.tmpdir(), "vibeflow-"));
1222
+ const file = path29.join(dir, "description.md");
1223
1223
  try {
1224
- fs24.writeFileSync(file, text);
1224
+ fs25.writeFileSync(file, text);
1225
1225
  const cmd = editorCommand(process.env, process.platform);
1226
1226
  const r = spawnSync2(`${cmd} "${file}"`, { stdio: "inherit", shell: true });
1227
1227
  if (r.error) return { error: `\u7121\u6CD5\u555F\u52D5\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\uFF1A${r.error.message}` };
1228
1228
  if (r.status !== 0) return { error: `\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\u7D50\u675F\u78BC ${r.status}\uFF0C\u63CF\u8FF0\u672A\u8B8A\u66F4` };
1229
- return { text: fs24.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1229
+ return { text: fs25.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1230
1230
  } finally {
1231
- fs24.rmSync(dir, { recursive: true, force: true });
1231
+ fs25.rmSync(dir, { recursive: true, force: true });
1232
1232
  }
1233
1233
  }
1234
1234
  function cycle(list, current, step) {
@@ -1299,10 +1299,10 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1299
1299
  if (editing === "title") setS((prev) => ({ ...prev, title: draft }));
1300
1300
  else if (editing === "branch") setS((prev) => ({ ...prev, branch: value2 }));
1301
1301
  else if (editing === "project") {
1302
- if (value2) setProject(path28.resolve(value2.replace(/^~(?=$|\/)/, os8.homedir())));
1302
+ if (value2) setProject(path29.resolve(value2.replace(/^~(?=$|\/)/, os8.homedir())));
1303
1303
  } else if (editing === "attachments" && value2) {
1304
- const file = path28.resolve(value2.replace(/^~(?=$|\/)/, os8.homedir()));
1305
- if (!fs24.existsSync(file) || !fs24.statSync(file).isFile()) {
1304
+ const file = path29.resolve(value2.replace(/^~(?=$|\/)/, os8.homedir()));
1305
+ if (!fs25.existsSync(file) || !fs25.statSync(file).isFile()) {
1306
1306
  setMessage(`\u627E\u4E0D\u5230\u6A94\u6848\uFF1A${file}`);
1307
1307
  return;
1308
1308
  }
@@ -1385,7 +1385,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1385
1385
  case "branch":
1386
1386
  return s.branch || "\uFF08\u7559\u7A7A\u5247\u81EA\u52D5\u547D\u540D\uFF0C\u4F8B\u5982 feature/login-fix\uFF09";
1387
1387
  case "attachments":
1388
- return s.attachments.length ? s.attachments.map((a) => path28.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
1388
+ return s.attachments.length ? s.attachments.map((a) => path29.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
1389
1389
  case "description": {
1390
1390
  if (!s.description) return "\uFF08Enter \u7528\u7DE8\u8F2F\u5668\u64B0\u5BEB\uFF09";
1391
1391
  const lines = s.description.split("\n");
@@ -1420,7 +1420,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1420
1420
  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";
1421
1421
  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";
1422
1422
  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`;
1423
- if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path28.basename(s.projectPath)}`;
1423
+ if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path29.basename(s.projectPath)}`;
1424
1424
  }
1425
1425
  if (field === "branch") {
1426
1426
  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";
@@ -1549,7 +1549,7 @@ __export(src_exports, {
1549
1549
  showDiff: () => showDiff
1550
1550
  });
1551
1551
  import { spawnSync as spawnSync3 } from "child_process";
1552
- import path29 from "path";
1552
+ import path30 from "path";
1553
1553
  import React2, { useEffect as useEffect2, useState as useState2 } from "react";
1554
1554
  import { Box as Box2, Text as Text2, render as render3, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
1555
1555
  function findDetachKey(text) {
@@ -1845,7 +1845,7 @@ function showDiff(task) {
1845
1845
  });
1846
1846
  }
1847
1847
  function tmuxAttach(opts, sessionName) {
1848
- const conf = path29.join(opts.userDataDir, "tmux.conf");
1848
+ const conf = path30.join(opts.userDataDir, "tmux.conf");
1849
1849
  const inOurServer = (process.env.TMUX ?? "").split(",")[0].endsWith(`/${TMUX_SOCKET}`);
1850
1850
  const args = inOurServer ? ["-L", TMUX_SOCKET, "-f", conf, "switch-client", "-t", `=${sessionName}`] : ["-L", TMUX_SOCKET, "-f", conf, "attach-session", "-t", `=${sessionName}`];
1851
1851
  spawnSync3("tmux", args, { stdio: "inherit", env: { ...process.env, TMUX: inOurServer ? process.env.TMUX : "" } });
@@ -1976,10 +1976,11 @@ var init_src = __esm({
1976
1976
  });
1977
1977
 
1978
1978
  // packages/cli/src/main.ts
1979
- import fs25 from "fs";
1980
- import path30 from "path";
1979
+ import fs26 from "fs";
1980
+ import path31 from "path";
1981
1981
  import { fileURLToPath } from "url";
1982
1982
  import { parseArgs as parseArgs2 } from "node:util";
1983
+ import { spawn as spawn9 } from "node:child_process";
1983
1984
 
1984
1985
  // packages/core/src/lock.ts
1985
1986
  import fs from "fs";
@@ -4693,8 +4694,8 @@ async function taskCommand(argv) {
4693
4694
  }
4694
4695
 
4695
4696
  // packages/cli/src/host.ts
4696
- import fs22 from "fs";
4697
- import path26 from "path";
4697
+ import fs23 from "fs";
4698
+ import path27 from "path";
4698
4699
  import { randomBytes as randomBytes2 } from "crypto";
4699
4700
 
4700
4701
  // packages/core/src/events.ts
@@ -5928,21 +5929,25 @@ async function logoutJira(run3 = defaultAcliRunner) {
5928
5929
  var asRaw = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
5929
5930
  var nameOf = (v) => typeof v === "string" ? v : String(asRaw(v).name ?? "");
5930
5931
  var JIRA_INBOX_JQL = "assignee = currentUser() AND (statusCategory != Done OR sprint in openSprints()) ORDER BY updated DESC";
5932
+ var JIRA_SCOPE_JQL = {
5933
+ current: JIRA_INBOX_JQL,
5934
+ sprint: "assignee = currentUser() AND sprint in openSprints() ORDER BY updated DESC",
5935
+ all: "assignee = currentUser() ORDER BY updated DESC"
5936
+ };
5931
5937
  var DEFAULT_POINT_FIELDS = ["customfield_10033", "customfield_10016"];
5932
5938
  var JIRA_CACHE_TTL_MS = 5 * 60 * 1e3;
5933
- function buildSearchArgs() {
5939
+ function buildSearchArgs(scope = "current") {
5934
5940
  return [
5935
5941
  "jira",
5936
5942
  "workitem",
5937
5943
  "search",
5938
5944
  "--jql",
5939
- JIRA_INBOX_JQL,
5945
+ JIRA_SCOPE_JQL[scope],
5940
5946
  "--fields",
5941
5947
  "key,summary,status,issuetype,priority,description",
5942
5948
  "--json",
5943
5949
  "--paginate",
5944
- "--limit",
5945
- "200"
5950
+ ...scope === "all" ? [] : ["--limit", "200"]
5946
5951
  ];
5947
5952
  }
5948
5953
  function buildViewArgs(key, pointFields = DEFAULT_POINT_FIELDS) {
@@ -6039,19 +6044,22 @@ function createJiraService(options) {
6039
6044
  const run3 = options.run ?? defaultAcliRunner;
6040
6045
  const authStatus = options.authStatus ?? (() => getJiraAuthStatus(run3));
6041
6046
  const now = options.now ?? Date.now;
6042
- let cache = null;
6047
+ const cache = /* @__PURE__ */ new Map();
6043
6048
  return {
6044
6049
  clear() {
6045
- cache = null;
6050
+ cache.clear();
6046
6051
  },
6047
6052
  async inbox(opts = {}) {
6048
6053
  const auth = await authStatus();
6049
6054
  if (!auth.installed) return { status: "acli-missing", fetchedAt: 0, tickets: [] };
6050
6055
  if (!auth.authenticated || !auth.site) return { status: "unauthenticated", fetchedAt: 0, tickets: [] };
6051
- if (cache && cache.site === auth.site && !opts.force && now() - cache.at < JIRA_CACHE_TTL_MS) return cache.value;
6056
+ const scope = opts.scope ?? "current";
6057
+ const cacheKey = `${auth.site}:${scope}`;
6058
+ const cached2 = cache.get(cacheKey);
6059
+ if (cached2 && !opts.force && now() - cached2.at < JIRA_CACHE_TTL_MS) return cached2.value;
6052
6060
  const at = now();
6053
6061
  const pointFields = options.pointFields?.() ?? DEFAULT_POINT_FIELDS;
6054
- const value = run3(buildSearchArgs()).then(async (output) => {
6062
+ const value = run3(buildSearchArgs(scope)).then(async (output) => {
6055
6063
  const detail = await enrichRows(searchRows(output), run3, pointFields);
6056
6064
  return {
6057
6065
  status: "ok",
@@ -6069,7 +6077,7 @@ function createJiraService(options) {
6069
6077
  tickets: [],
6070
6078
  error: String(err.stderr || err.message || err)
6071
6079
  }));
6072
- cache = { at, site: auth.site, value };
6080
+ cache.set(cacheKey, { at, value });
6073
6081
  return value;
6074
6082
  }
6075
6083
  };
@@ -7089,7 +7097,10 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7089
7097
  },
7090
7098
  "jira:inbox": (payload) => {
7091
7099
  const p = payload === void 0 ? {} : obj(payload, "payload");
7092
- return jira.inbox({ force: p.force === true });
7100
+ if (p.scope !== void 0 && p.scope !== "current" && p.scope !== "sprint" && p.scope !== "all") {
7101
+ throw Object.assign(new Error("Invalid Jira scope"), { code: "INVALID_REQUEST" });
7102
+ }
7103
+ return jira.inbox({ force: p.force === true, scope: p.scope });
7093
7104
  },
7094
7105
  "env:detectAgents": () => detectAgents(),
7095
7106
  "agents:listModels": (payload) => {
@@ -7917,7 +7928,7 @@ function runUpdate(options) {
7917
7928
  error(`\u66F4\u65B0\u5931\u6557\uFF1A${result.error?.message ?? `npm \u7D50\u675F\u78BC ${result.status ?? "unknown"}`}`);
7918
7929
  return result.status && result.status > 0 ? result.status : 1;
7919
7930
  }
7920
- log("\u66F4\u65B0\u5B8C\u6210\u3002\u82E5 VibeFlow host \u6B63\u5728\u57F7\u884C\uFF0C\u8ACB\u81EA\u884C\u91CD\u65B0\u555F\u52D5\u4EE5\u8F09\u5165\u65B0\u7248\u672C\u3002");
7931
+ log("\u66F4\u65B0\u5B8C\u6210\u3002\u57F7\u884C\u4E2D\u7684 Web UI host \u6703\u5728\u6578\u79D2\u5167\u81EA\u52D5\u91CD\u65B0\u555F\u52D5\u6210\u65B0\u7248\u672C\uFF1BTUI host \u6216\u6709\u57F7\u884C\u4E2D session \u7684 pty host \u8ACB\u81EA\u884C\u91CD\u65B0\u555F\u52D5\u3002");
7921
7932
  return 0;
7922
7933
  }
7923
7934
 
@@ -7971,6 +7982,17 @@ function createUpdateMonitor(options) {
7971
7982
  }
7972
7983
  return next ? { status: "available", ...next } : { status: "current", currentVersion: options.currentVersion };
7973
7984
  },
7985
+ /** A newer version is on disk but the host must be restarted by hand to run it. */
7986
+ markInstalled(version, reason) {
7987
+ if (stopped || available?.installedVersion === version && available.restartBlocked === reason) return;
7988
+ available = {
7989
+ currentVersion: options.currentVersion,
7990
+ latestVersion: available && isNewerVersion(available.latestVersion, version) ? available.latestVersion : version,
7991
+ installedVersion: version,
7992
+ restartBlocked: reason
7993
+ };
7994
+ options.onChange(available);
7995
+ },
7974
7996
  stop() {
7975
7997
  stopped = true;
7976
7998
  }
@@ -8037,6 +8059,70 @@ async function openUpdateTerminal(platform = process.platform, run3 = launch) {
8037
8059
  throw new Error("\u627E\u4E0D\u5230\u53EF\u7528\u7684\u7D42\u7AEF\u6A5F\uFF0C\u8ACB\u624B\u52D5\u57F7\u884C\u66F4\u65B0\u6307\u4EE4\u3002");
8038
8060
  }
8039
8061
 
8062
+ // packages/cli/src/self-update.ts
8063
+ import fs22 from "node:fs";
8064
+ import path26 from "node:path";
8065
+ import { execFile as execFile10 } from "node:child_process";
8066
+ import { promisify as promisify7 } from "node:util";
8067
+ var execFileAsync2 = promisify7(execFile10);
8068
+ var INSTALL_POLL_INTERVAL_MS = 3e3;
8069
+ var PROBE_RETRY_MS = 3e4;
8070
+ var HOST_TOKEN_ENV = "VIBEFLOW_HOST_TOKEN";
8071
+ function readInstalledVersion(packageRoot) {
8072
+ try {
8073
+ const pkg = JSON.parse(fs22.readFileSync(path26.join(packageRoot, "package.json"), "utf8"));
8074
+ return typeof pkg.version === "string" ? pkg.version : null;
8075
+ } catch {
8076
+ return null;
8077
+ }
8078
+ }
8079
+ async function probeCli(cliEntry) {
8080
+ try {
8081
+ const { stdout } = await execFileAsync2(process.execPath, [cliEntry, "--version"], { timeout: 15e3, windowsHide: true });
8082
+ return stdout.trim();
8083
+ } catch {
8084
+ return null;
8085
+ }
8086
+ }
8087
+ function watchInstalledVersion(options) {
8088
+ const readVersion2 = options.readVersion ?? readInstalledVersion;
8089
+ const probe = options.probe ?? probeCli;
8090
+ const now = options.now ?? Date.now;
8091
+ let previous = null;
8092
+ let installed = null;
8093
+ const probedAt = /* @__PURE__ */ new Map();
8094
+ let busy = false;
8095
+ const tick = async () => {
8096
+ if (busy) return;
8097
+ busy = true;
8098
+ try {
8099
+ const version = readVersion2(options.packageRoot);
8100
+ const seenTwice = version !== null && version === previous;
8101
+ previous = version;
8102
+ if (!version || !isNewerVersion(version, options.currentVersion)) return;
8103
+ if (installed !== version) {
8104
+ const last = probedAt.get(version);
8105
+ if (!seenTwice || last !== void 0 && now() - last < PROBE_RETRY_MS) return;
8106
+ probedAt.set(version, now());
8107
+ if (await probe(options.cliEntry) !== version) return;
8108
+ installed = version;
8109
+ }
8110
+ options.onInstalled(version);
8111
+ } finally {
8112
+ busy = false;
8113
+ }
8114
+ };
8115
+ return { tick };
8116
+ }
8117
+ function relaunchArgs(cliEntry, options) {
8118
+ return [cliEntry, "web", "--no-open", "--store-path", options.userDataDir, "--port", String(options.port), "--backend", options.backend];
8119
+ }
8120
+ function takeHandedOverToken(env = process.env) {
8121
+ const token = env[HOST_TOKEN_ENV];
8122
+ delete env[HOST_TOKEN_ENV];
8123
+ return token && /^[A-Za-z0-9_-]{43}$/.test(token) ? token : void 0;
8124
+ }
8125
+
8040
8126
  // packages/cli/src/host.ts
8041
8127
  var DEFAULT_HOST_PORT = 47820;
8042
8128
  async function waitForPort(userDataDir, timeoutMs = 1e4) {
@@ -8060,7 +8146,7 @@ async function startHost(options) {
8060
8146
  const lock = {
8061
8147
  pid: process.pid,
8062
8148
  port: 0,
8063
- token: randomBytes2(32).toString("base64url"),
8149
+ token: options.token ?? randomBytes2(32).toString("base64url"),
8064
8150
  version: options.version,
8065
8151
  startedAt: Date.now()
8066
8152
  };
@@ -8079,8 +8165,10 @@ async function startHost(options) {
8079
8165
  });
8080
8166
  const core = createCore({ sessions, bus, version: options.version });
8081
8167
  let updateTimer = null;
8168
+ let installTimer = null;
8169
+ const packageRoot = options.cliEntry ? path27.dirname(path27.dirname(options.cliEntry)) : null;
8082
8170
  const updates = createUpdateMonitor({
8083
- packageName: options.sourceRoot || !options.cliEntry ? null : packageName(path26.dirname(path26.dirname(options.cliEntry))),
8171
+ packageName: options.sourceRoot || !packageRoot ? null : packageName(packageRoot),
8084
8172
  currentVersion: options.version,
8085
8173
  onChange: (update) => bus.emit("update:available", update)
8086
8174
  });
@@ -8118,19 +8206,45 @@ async function startHost(options) {
8118
8206
  updateLock(options.userDataDir, lock);
8119
8207
  const stopWatch = core.watchStore();
8120
8208
  core.warmModelCatalog();
8121
- if (updates.supported) {
8209
+ let requestRestart = () => {
8210
+ };
8211
+ const restartRequested = new Promise((resolve) => {
8212
+ requestRestart = resolve;
8213
+ });
8214
+ if (updates.supported && options.cliEntry && packageRoot) {
8122
8215
  checkUpdates();
8123
8216
  updateTimer = setInterval(checkUpdates, UPDATE_CHECK_INTERVAL_MS);
8217
+ const installs = watchInstalledVersion({
8218
+ packageRoot,
8219
+ cliEntry: options.cliEntry,
8220
+ currentVersion: options.version,
8221
+ onInstalled: (version) => {
8222
+ if (!options.autoRestart) {
8223
+ updates.markInstalled(version, "\u7D42\u7AEF\u6A5F\u4ECB\u9762\uFF08TUI\uFF09host \u4E0D\u6703\u81EA\u52D5\u91CD\u65B0\u555F\u52D5");
8224
+ return;
8225
+ }
8226
+ void (sessions.kind === "pty" ? sessions.list() : Promise.resolve([])).then((running) => {
8227
+ if (running.length > 0) {
8228
+ updates.markInstalled(version, `\u6709 ${running.length} \u500B\u57F7\u884C\u4E2D\u7684\u7D42\u7AEF\u6A5F session\uFF0C\u91CD\u65B0\u555F\u52D5\u6703\u4E2D\u65B7\u5B83\u5011\uFF1B\u5168\u90E8\u7D50\u675F\u5F8C\u6703\u81EA\u52D5\u91CD\u65B0\u555F\u52D5`);
8229
+ } else {
8230
+ requestRestart(version);
8231
+ }
8232
+ }).catch(() => {
8233
+ });
8234
+ }
8235
+ });
8236
+ installTimer = setInterval(() => void installs.tick(), INSTALL_POLL_INTERVAL_MS);
8124
8237
  }
8125
8238
  const stop = () => stopping ??= (async () => {
8126
8239
  updates.stop();
8127
8240
  if (updateTimer) clearInterval(updateTimer);
8241
+ if (installTimer) clearInterval(installTimer);
8128
8242
  stopWatch();
8129
8243
  core.shutdown();
8130
8244
  await server.close();
8131
8245
  releaseLock(options.userDataDir);
8132
8246
  })();
8133
- return { kind: "started", host: { core, server, lock, stop } };
8247
+ return { kind: "started", host: { core, server, lock, restartRequested, stop } };
8134
8248
  } catch (err) {
8135
8249
  releaseLock(options.userDataDir);
8136
8250
  throw err;
@@ -8140,7 +8254,7 @@ function loginUrl(lock, pathname = "/home/") {
8140
8254
  return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
8141
8255
  }
8142
8256
  function isWebDir(dir) {
8143
- return fs22.existsSync(path26.join(dir, "home", "index.html"));
8257
+ return fs23.existsSync(path27.join(dir, "home", "index.html"));
8144
8258
  }
8145
8259
 
8146
8260
  // packages/cli/src/remote.ts
@@ -8257,14 +8371,14 @@ function connectToHost(lock) {
8257
8371
  }
8258
8372
 
8259
8373
  // packages/cli/src/doctor.ts
8260
- import fs23 from "fs";
8261
- import path27 from "path";
8262
- import { execFile as execFile10 } from "child_process";
8263
- import { promisify as promisify7 } from "util";
8374
+ import fs24 from "fs";
8375
+ import path28 from "path";
8376
+ import { execFile as execFile11 } from "child_process";
8377
+ import { promisify as promisify8 } from "util";
8264
8378
  init_env();
8265
8379
  init_git_bash();
8266
8380
  init_tmux_backend();
8267
- var pexec6 = promisify7(execFile10);
8381
+ var pexec6 = promisify8(execFile11);
8268
8382
  function parseVersion(text) {
8269
8383
  const m = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(text);
8270
8384
  return m ? [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)] : [];
@@ -8398,10 +8512,10 @@ async function runDoctor(options) {
8398
8512
  });
8399
8513
  }
8400
8514
  try {
8401
- fs23.mkdirSync(options.userDataDir, { recursive: true });
8402
- const probe = path27.join(options.userDataDir, `.doctor-${process.pid}`);
8403
- fs23.writeFileSync(probe, "");
8404
- fs23.rmSync(probe);
8515
+ fs24.mkdirSync(options.userDataDir, { recursive: true });
8516
+ const probe = path28.join(options.userDataDir, `.doctor-${process.pid}`);
8517
+ fs24.writeFileSync(probe, "");
8518
+ fs24.rmSync(probe);
8405
8519
  checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
8406
8520
  } catch (err) {
8407
8521
  checks.push({
@@ -8441,7 +8555,7 @@ Usage:
8441
8555
  vibeflow open <path> Add a project folder to the recent list, then open the Web UI
8442
8556
  vibeflow doctor Check Node, git, tmux, node-pty and the agent CLIs
8443
8557
  (--skip agents,web,\u2026 leaves checks out, e.g. on CI)
8444
- vibeflow update Install the latest global npm release (restart the host afterward)
8558
+ vibeflow update Install the latest global npm release (a running Web UI host restarts into it)
8445
8559
  vibeflow task \u2026 Create / update cards; add artifacts (vibeflow task --help)
8446
8560
 
8447
8561
  Options:
@@ -8456,24 +8570,24 @@ Options:
8456
8570
  `.trim();
8457
8571
  function detectLayout() {
8458
8572
  const file = fileURLToPath(import.meta.url);
8459
- const here = path30.dirname(file);
8460
- const bundledWeb = path30.join(here, "web");
8461
- if (isWebDir(bundledWeb) || path30.basename(here) === "dist") {
8573
+ const here = path31.dirname(file);
8574
+ const bundledWeb = path31.join(here, "web");
8575
+ if (isWebDir(bundledWeb) || path31.basename(here) === "dist") {
8462
8576
  return {
8463
- version: readVersion(path30.join(here, "..", "package.json")),
8577
+ version: readVersion(path31.join(here, "..", "package.json")),
8464
8578
  webDir: isWebDir(bundledWeb) ? bundledWeb : null,
8465
8579
  sourceRoot: null,
8466
8580
  cliEntry: file
8467
8581
  };
8468
8582
  }
8469
- const root = path30.resolve(here, "..", "..", "..");
8583
+ const root = path31.resolve(here, "..", "..", "..");
8470
8584
  const override = process.env.VIBEFLOW_WEB_DIR;
8471
- const webDir = override && isWebDir(override) ? override : isWebDir(path30.join(root, "app")) ? path30.join(root, "app") : null;
8472
- return { version: readVersion(path30.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
8585
+ const webDir = override && isWebDir(override) ? override : isWebDir(path31.join(root, "app")) ? path31.join(root, "app") : null;
8586
+ return { version: readVersion(path31.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
8473
8587
  }
8474
8588
  function readVersion(pkgPath) {
8475
8589
  try {
8476
- return JSON.parse(fs25.readFileSync(pkgPath, "utf8")).version;
8590
+ return JSON.parse(fs26.readFileSync(pkgPath, "utf8")).version;
8477
8591
  } catch {
8478
8592
  return "0.0.0";
8479
8593
  }
@@ -8497,6 +8611,7 @@ function parse(argv) {
8497
8611
  strict: true
8498
8612
  });
8499
8613
  }
8614
+ var handedOverToken = takeHandedOverToken();
8500
8615
  function hostOptions(values, layout, userDataDir, withWeb = true) {
8501
8616
  const backend = values.backend ?? "auto";
8502
8617
  if (!["auto", "tmux", "pty"].includes(backend)) throw new Error("--backend must be auto | tmux | pty");
@@ -8509,7 +8624,8 @@ function hostOptions(values, layout, userDataDir, withWeb = true) {
8509
8624
  sourceRoot: layout.sourceRoot,
8510
8625
  cliEntry: layout.cliEntry,
8511
8626
  backend,
8512
- port
8627
+ port,
8628
+ token: handedOverToken
8513
8629
  };
8514
8630
  }
8515
8631
  async function openBrowser(url) {
@@ -8518,6 +8634,33 @@ async function openBrowser(url) {
8518
8634
  } catch {
8519
8635
  }
8520
8636
  }
8637
+ async function relaunchHost(host, cliEntry, userDataDir, version) {
8638
+ const port = host.lock.port;
8639
+ const token = host.lock.token;
8640
+ const backend = host.core.sessions.kind;
8641
+ console.log(`
8642
+ \u5DF2\u5B89\u88DD VibeFlow ${version}\uFF0C\u6B63\u5728\u91CD\u65B0\u555F\u52D5 host\u2026`);
8643
+ await host.stop();
8644
+ const child = spawn9(process.execPath, relaunchArgs(cliEntry, { userDataDir, port, backend }), {
8645
+ stdio: "inherit",
8646
+ env: { ...process.env, [HOST_TOKEN_ENV]: token }
8647
+ });
8648
+ const forward = () => {
8649
+ child.kill("SIGTERM");
8650
+ };
8651
+ process.on("SIGINT", () => {
8652
+ });
8653
+ process.on("SIGHUP", () => {
8654
+ });
8655
+ process.on("SIGTERM", forward);
8656
+ return new Promise((resolve) => {
8657
+ child.once("error", (err) => {
8658
+ console.error(`\u7121\u6CD5\u555F\u52D5\u65B0\u7248 host\uFF1A${err.message}\u3002\u8ACB\u57F7\u884C vibeflow \u91CD\u65B0\u555F\u52D5\u3002`);
8659
+ resolve(1);
8660
+ });
8661
+ child.once("exit", (code2, signal) => resolve(code2 ?? (signal ? 1 : 0)));
8662
+ });
8663
+ }
8521
8664
  function stopOnSignals(host) {
8522
8665
  return new Promise((resolve) => {
8523
8666
  const stop = () => {
@@ -8540,7 +8683,7 @@ async function cmdWeb(values, layout, userDataDir, pathname = "/home/") {
8540
8683
  console.error("\u627E\u4E0D\u5230 Web UI \u7684\u975C\u614B\u6A94\u3002\u5F9E\u539F\u59CB\u78BC\u57F7\u884C\u6642\u8ACB\u5148\u57F7\u884C\uFF1Anpm run build:web");
8541
8684
  return 1;
8542
8685
  }
8543
- const started = await startHost(hostOptions(values, layout, userDataDir));
8686
+ const started = await startHost({ ...hostOptions(values, layout, userDataDir), autoRestart: true });
8544
8687
  if (started.kind === "running") return cmdWeb(values, layout, userDataDir, pathname);
8545
8688
  const { host } = started;
8546
8689
  const url = loginUrl(host.lock, pathname);
@@ -8553,8 +8696,11 @@ async function cmdWeb(values, layout, userDataDir, pathname = "/home/") {
8553
8696
  ${formatChecks(checks)}
8554
8697
  `);
8555
8698
  if (!values["no-open"]) await openBrowser(url);
8556
- await stopOnSignals(host);
8557
- return 0;
8699
+ const ended = await Promise.race([
8700
+ stopOnSignals(host).then(() => null),
8701
+ host.restartRequested
8702
+ ]);
8703
+ return ended === null || !layout.cliEntry ? 0 : relaunchHost(host, layout.cliEntry, userDataDir, ended);
8558
8704
  }
8559
8705
  async function cmdTui(values, layout, userDataDir) {
8560
8706
  const { runTui: runTui2 } = await Promise.resolve().then(() => (init_src(), src_exports));
@@ -8679,8 +8825,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
8679
8825
  console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
8680
8826
  return 1;
8681
8827
  }
8682
- const dir = path30.resolve(target);
8683
- if (!fs25.existsSync(dir) || !fs25.statSync(dir).isDirectory()) {
8828
+ const dir = path31.resolve(target);
8829
+ if (!fs26.existsSync(dir) || !fs26.statSync(dir).isDirectory()) {
8684
8830
  console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
8685
8831
  return 1;
8686
8832
  }
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/3cafb8bddc730b60.css" as="style"/><link rel="stylesheet" href="/_next/static/css/3cafb8bddc730b60.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-33344f7100190436.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-83dfa6752c321c56.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/xoex8UtSV4DX9eSoX3lTZ/_buildManifest.js" defer=""></script><script src="/_next/static/xoex8UtSV4DX9eSoX3lTZ/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"xoex8UtSV4DX9eSoX3lTZ","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/f8f64877c884206d.css" as="style"/><link rel="stylesheet" href="/_next/static/css/f8f64877c884206d.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-33344f7100190436.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-2571a2564f593899.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/76irYAZpsrcN0YAWmxJ47/_buildManifest.js" defer=""></script><script src="/_next/static/76irYAZpsrcN0YAWmxJ47/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"76irYAZpsrcN0YAWmxJ47","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
package/dist/web/404.html CHANGED
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/3cafb8bddc730b60.css" as="style"/><link rel="stylesheet" href="/_next/static/css/3cafb8bddc730b60.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-33344f7100190436.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-83dfa6752c321c56.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/xoex8UtSV4DX9eSoX3lTZ/_buildManifest.js" defer=""></script><script src="/_next/static/xoex8UtSV4DX9eSoX3lTZ/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"xoex8UtSV4DX9eSoX3lTZ","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/f8f64877c884206d.css" as="style"/><link rel="stylesheet" href="/_next/static/css/f8f64877c884206d.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-33344f7100190436.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-2571a2564f593899.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/76irYAZpsrcN0YAWmxJ47/_buildManifest.js" defer=""></script><script src="/_next/static/76irYAZpsrcN0YAWmxJ47/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"76irYAZpsrcN0YAWmxJ47","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:0,errorRate:1e-4,numBits:0,numHashes:null,bitArray:[]},__routerFilterDynamic:{numItems:0,errorRate:1e-4,numBits:0,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-8f65b0f8749f42d2.js"],"/home":["static/chunks/9296-f476f95a89709fef.js","static/chunks/pages/home-61c688b1d20e56ff.js"],sortedPages:["/_app","/_error","/home"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
1
+ self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:0,errorRate:1e-4,numBits:0,numHashes:null,bitArray:[]},__routerFilterDynamic:{numItems:0,errorRate:1e-4,numBits:0,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-8f65b0f8749f42d2.js"],"/home":["static/chunks/9296-f476f95a89709fef.js","static/chunks/pages/home-307d0d7c49e4ee48.js"],sortedPages:["/_app","/_error","/home"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();