@kw-yeh/vibeflow 4.11.16 → 4.11.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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";
@@ -2457,6 +2458,7 @@ function createBridge(t) {
2457
2458
  /** Running app version (package.json version baked into the build). */
2458
2459
  getVersion: () => t.invoke("app:getVersion"),
2459
2460
  getUpdateStatus: () => t.invoke("host:updateStatus"),
2461
+ checkForUpdate: () => t.invoke("host:checkUpdate"),
2460
2462
  openUpdateTerminal: () => t.invoke("host:openUpdateTerminal"),
2461
2463
  onUpdateAvailable: (callback) => t.on("update:available", (payload) => callback(payload)),
2462
2464
  /**
@@ -4692,8 +4694,8 @@ async function taskCommand(argv) {
4692
4694
  }
4693
4695
 
4694
4696
  // packages/cli/src/host.ts
4695
- import fs22 from "fs";
4696
- import path26 from "path";
4697
+ import fs23 from "fs";
4698
+ import path27 from "path";
4697
4699
  import { randomBytes as randomBytes2 } from "crypto";
4698
4700
 
4699
4701
  // packages/core/src/events.ts
@@ -7916,7 +7918,7 @@ function runUpdate(options) {
7916
7918
  error(`\u66F4\u65B0\u5931\u6557\uFF1A${result.error?.message ?? `npm \u7D50\u675F\u78BC ${result.status ?? "unknown"}`}`);
7917
7919
  return result.status && result.status > 0 ? result.status : 1;
7918
7920
  }
7919
- 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");
7921
+ 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");
7920
7922
  return 0;
7921
7923
  }
7922
7924
 
@@ -7953,6 +7955,39 @@ async function checkForUpdate(packageName2, currentVersion, query = async (name)
7953
7955
  if (typeof latest !== "string" || !isNewerVersion(latest, currentVersion)) return null;
7954
7956
  return { currentVersion, latestVersion: latest };
7955
7957
  }
7958
+ function createUpdateMonitor(options) {
7959
+ const check = options.check ?? ((name, version) => checkForUpdate(name, version));
7960
+ let available = null;
7961
+ let stopped = false;
7962
+ return {
7963
+ supported: options.packageName !== null,
7964
+ status: () => available,
7965
+ /** Rejects when the registry cannot be reached; the last answer is kept. */
7966
+ async refresh() {
7967
+ if (!options.packageName) return { status: "unsupported", currentVersion: options.currentVersion };
7968
+ const next = await check(options.packageName, options.currentVersion);
7969
+ if (!stopped && next?.latestVersion !== available?.latestVersion) {
7970
+ available = next;
7971
+ options.onChange(next);
7972
+ }
7973
+ return next ? { status: "available", ...next } : { status: "current", currentVersion: options.currentVersion };
7974
+ },
7975
+ /** A newer version is on disk but the host must be restarted by hand to run it. */
7976
+ markInstalled(version, reason) {
7977
+ if (stopped || available?.installedVersion === version && available.restartBlocked === reason) return;
7978
+ available = {
7979
+ currentVersion: options.currentVersion,
7980
+ latestVersion: available && isNewerVersion(available.latestVersion, version) ? available.latestVersion : version,
7981
+ installedVersion: version,
7982
+ restartBlocked: reason
7983
+ };
7984
+ options.onChange(available);
7985
+ },
7986
+ stop() {
7987
+ stopped = true;
7988
+ }
7989
+ };
7990
+ }
7956
7991
 
7957
7992
  // packages/cli/src/update-terminal.ts
7958
7993
  import { spawn as spawn8 } from "node:child_process";
@@ -8014,6 +8049,70 @@ async function openUpdateTerminal(platform = process.platform, run3 = launch) {
8014
8049
  throw new Error("\u627E\u4E0D\u5230\u53EF\u7528\u7684\u7D42\u7AEF\u6A5F\uFF0C\u8ACB\u624B\u52D5\u57F7\u884C\u66F4\u65B0\u6307\u4EE4\u3002");
8015
8050
  }
8016
8051
 
8052
+ // packages/cli/src/self-update.ts
8053
+ import fs22 from "node:fs";
8054
+ import path26 from "node:path";
8055
+ import { execFile as execFile10 } from "node:child_process";
8056
+ import { promisify as promisify7 } from "node:util";
8057
+ var execFileAsync2 = promisify7(execFile10);
8058
+ var INSTALL_POLL_INTERVAL_MS = 3e3;
8059
+ var PROBE_RETRY_MS = 3e4;
8060
+ var HOST_TOKEN_ENV = "VIBEFLOW_HOST_TOKEN";
8061
+ function readInstalledVersion(packageRoot) {
8062
+ try {
8063
+ const pkg = JSON.parse(fs22.readFileSync(path26.join(packageRoot, "package.json"), "utf8"));
8064
+ return typeof pkg.version === "string" ? pkg.version : null;
8065
+ } catch {
8066
+ return null;
8067
+ }
8068
+ }
8069
+ async function probeCli(cliEntry) {
8070
+ try {
8071
+ const { stdout } = await execFileAsync2(process.execPath, [cliEntry, "--version"], { timeout: 15e3, windowsHide: true });
8072
+ return stdout.trim();
8073
+ } catch {
8074
+ return null;
8075
+ }
8076
+ }
8077
+ function watchInstalledVersion(options) {
8078
+ const readVersion2 = options.readVersion ?? readInstalledVersion;
8079
+ const probe = options.probe ?? probeCli;
8080
+ const now = options.now ?? Date.now;
8081
+ let previous = null;
8082
+ let installed = null;
8083
+ const probedAt = /* @__PURE__ */ new Map();
8084
+ let busy = false;
8085
+ const tick = async () => {
8086
+ if (busy) return;
8087
+ busy = true;
8088
+ try {
8089
+ const version = readVersion2(options.packageRoot);
8090
+ const seenTwice = version !== null && version === previous;
8091
+ previous = version;
8092
+ if (!version || !isNewerVersion(version, options.currentVersion)) return;
8093
+ if (installed !== version) {
8094
+ const last = probedAt.get(version);
8095
+ if (!seenTwice || last !== void 0 && now() - last < PROBE_RETRY_MS) return;
8096
+ probedAt.set(version, now());
8097
+ if (await probe(options.cliEntry) !== version) return;
8098
+ installed = version;
8099
+ }
8100
+ options.onInstalled(version);
8101
+ } finally {
8102
+ busy = false;
8103
+ }
8104
+ };
8105
+ return { tick };
8106
+ }
8107
+ function relaunchArgs(cliEntry, options) {
8108
+ return [cliEntry, "web", "--no-open", "--store-path", options.userDataDir, "--port", String(options.port), "--backend", options.backend];
8109
+ }
8110
+ function takeHandedOverToken(env = process.env) {
8111
+ const token = env[HOST_TOKEN_ENV];
8112
+ delete env[HOST_TOKEN_ENV];
8113
+ return token && /^[A-Za-z0-9_-]{43}$/.test(token) ? token : void 0;
8114
+ }
8115
+
8017
8116
  // packages/cli/src/host.ts
8018
8117
  var DEFAULT_HOST_PORT = 47820;
8019
8118
  async function waitForPort(userDataDir, timeoutMs = 1e4) {
@@ -8037,7 +8136,7 @@ async function startHost(options) {
8037
8136
  const lock = {
8038
8137
  pid: process.pid,
8039
8138
  port: 0,
8040
- token: randomBytes2(32).toString("base64url"),
8139
+ token: options.token ?? randomBytes2(32).toString("base64url"),
8041
8140
  version: options.version,
8042
8141
  startedAt: Date.now()
8043
8142
  };
@@ -8055,22 +8154,16 @@ async function startHost(options) {
8055
8154
  stateDir: options.userDataDir
8056
8155
  });
8057
8156
  const core = createCore({ sessions, bus, version: options.version });
8058
- let availableUpdate = null;
8059
8157
  let updateTimer = null;
8060
- let stopped = false;
8061
- const installedPackage = options.sourceRoot || !options.cliEntry ? null : packageName(path26.dirname(path26.dirname(options.cliEntry)));
8062
- const checkUpdates = async () => {
8063
- if (!installedPackage) return;
8064
- try {
8065
- const next = await checkForUpdate(installedPackage, options.version);
8066
- if (stopped) return;
8067
- if (next?.latestVersion !== availableUpdate?.latestVersion) {
8068
- availableUpdate = next;
8069
- bus.emit("update:available", next);
8070
- }
8071
- } catch {
8072
- }
8073
- };
8158
+ let installTimer = null;
8159
+ const packageRoot = options.cliEntry ? path27.dirname(path27.dirname(options.cliEntry)) : null;
8160
+ const updates = createUpdateMonitor({
8161
+ packageName: options.sourceRoot || !packageRoot ? null : packageName(packageRoot),
8162
+ currentVersion: options.version,
8163
+ onChange: (update) => bus.emit("update:available", update)
8164
+ });
8165
+ const checkUpdates = () => void updates.refresh().catch(() => {
8166
+ });
8074
8167
  let stopping = null;
8075
8168
  const handlers = {
8076
8169
  ...core.handlers,
@@ -8084,9 +8177,10 @@ async function startHost(options) {
8084
8177
  backend: sessions.kind,
8085
8178
  startedAt: lock.startedAt
8086
8179
  }),
8087
- "host:updateStatus": () => availableUpdate,
8180
+ "host:updateStatus": () => updates.status(),
8181
+ "host:checkUpdate": () => updates.refresh(),
8088
8182
  "host:openUpdateTerminal": () => {
8089
- if (!installedPackage || !availableUpdate) throw new Error("\u76EE\u524D\u6C92\u6709\u53EF\u5F9E\u5168\u57DF npm \u5B89\u88DD\u66F4\u65B0\u7684\u7248\u672C\u3002");
8183
+ if (!updates.status()) throw new Error("\u76EE\u524D\u6C92\u6709\u53EF\u5F9E\u5168\u57DF npm \u5B89\u88DD\u66F4\u65B0\u7684\u7248\u672C\u3002");
8090
8184
  return openUpdateTerminal();
8091
8185
  }
8092
8186
  };
@@ -8102,19 +8196,45 @@ async function startHost(options) {
8102
8196
  updateLock(options.userDataDir, lock);
8103
8197
  const stopWatch = core.watchStore();
8104
8198
  core.warmModelCatalog();
8105
- if (installedPackage) {
8106
- void checkUpdates();
8107
- updateTimer = setInterval(() => void checkUpdates(), UPDATE_CHECK_INTERVAL_MS);
8199
+ let requestRestart = () => {
8200
+ };
8201
+ const restartRequested = new Promise((resolve) => {
8202
+ requestRestart = resolve;
8203
+ });
8204
+ if (updates.supported && options.cliEntry && packageRoot) {
8205
+ checkUpdates();
8206
+ updateTimer = setInterval(checkUpdates, UPDATE_CHECK_INTERVAL_MS);
8207
+ const installs = watchInstalledVersion({
8208
+ packageRoot,
8209
+ cliEntry: options.cliEntry,
8210
+ currentVersion: options.version,
8211
+ onInstalled: (version) => {
8212
+ if (!options.autoRestart) {
8213
+ updates.markInstalled(version, "\u7D42\u7AEF\u6A5F\u4ECB\u9762\uFF08TUI\uFF09host \u4E0D\u6703\u81EA\u52D5\u91CD\u65B0\u555F\u52D5");
8214
+ return;
8215
+ }
8216
+ void (sessions.kind === "pty" ? sessions.list() : Promise.resolve([])).then((running) => {
8217
+ if (running.length > 0) {
8218
+ 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`);
8219
+ } else {
8220
+ requestRestart(version);
8221
+ }
8222
+ }).catch(() => {
8223
+ });
8224
+ }
8225
+ });
8226
+ installTimer = setInterval(() => void installs.tick(), INSTALL_POLL_INTERVAL_MS);
8108
8227
  }
8109
8228
  const stop = () => stopping ??= (async () => {
8110
- stopped = true;
8229
+ updates.stop();
8111
8230
  if (updateTimer) clearInterval(updateTimer);
8231
+ if (installTimer) clearInterval(installTimer);
8112
8232
  stopWatch();
8113
8233
  core.shutdown();
8114
8234
  await server.close();
8115
8235
  releaseLock(options.userDataDir);
8116
8236
  })();
8117
- return { kind: "started", host: { core, server, lock, stop } };
8237
+ return { kind: "started", host: { core, server, lock, restartRequested, stop } };
8118
8238
  } catch (err) {
8119
8239
  releaseLock(options.userDataDir);
8120
8240
  throw err;
@@ -8124,7 +8244,7 @@ function loginUrl(lock, pathname = "/home/") {
8124
8244
  return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
8125
8245
  }
8126
8246
  function isWebDir(dir) {
8127
- return fs22.existsSync(path26.join(dir, "home", "index.html"));
8247
+ return fs23.existsSync(path27.join(dir, "home", "index.html"));
8128
8248
  }
8129
8249
 
8130
8250
  // packages/cli/src/remote.ts
@@ -8241,14 +8361,14 @@ function connectToHost(lock) {
8241
8361
  }
8242
8362
 
8243
8363
  // packages/cli/src/doctor.ts
8244
- import fs23 from "fs";
8245
- import path27 from "path";
8246
- import { execFile as execFile10 } from "child_process";
8247
- import { promisify as promisify7 } from "util";
8364
+ import fs24 from "fs";
8365
+ import path28 from "path";
8366
+ import { execFile as execFile11 } from "child_process";
8367
+ import { promisify as promisify8 } from "util";
8248
8368
  init_env();
8249
8369
  init_git_bash();
8250
8370
  init_tmux_backend();
8251
- var pexec6 = promisify7(execFile10);
8371
+ var pexec6 = promisify8(execFile11);
8252
8372
  function parseVersion(text) {
8253
8373
  const m = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(text);
8254
8374
  return m ? [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)] : [];
@@ -8382,10 +8502,10 @@ async function runDoctor(options) {
8382
8502
  });
8383
8503
  }
8384
8504
  try {
8385
- fs23.mkdirSync(options.userDataDir, { recursive: true });
8386
- const probe = path27.join(options.userDataDir, `.doctor-${process.pid}`);
8387
- fs23.writeFileSync(probe, "");
8388
- fs23.rmSync(probe);
8505
+ fs24.mkdirSync(options.userDataDir, { recursive: true });
8506
+ const probe = path28.join(options.userDataDir, `.doctor-${process.pid}`);
8507
+ fs24.writeFileSync(probe, "");
8508
+ fs24.rmSync(probe);
8389
8509
  checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
8390
8510
  } catch (err) {
8391
8511
  checks.push({
@@ -8425,7 +8545,7 @@ Usage:
8425
8545
  vibeflow open <path> Add a project folder to the recent list, then open the Web UI
8426
8546
  vibeflow doctor Check Node, git, tmux, node-pty and the agent CLIs
8427
8547
  (--skip agents,web,\u2026 leaves checks out, e.g. on CI)
8428
- vibeflow update Install the latest global npm release (restart the host afterward)
8548
+ vibeflow update Install the latest global npm release (a running Web UI host restarts into it)
8429
8549
  vibeflow task \u2026 Create / update cards; add artifacts (vibeflow task --help)
8430
8550
 
8431
8551
  Options:
@@ -8440,24 +8560,24 @@ Options:
8440
8560
  `.trim();
8441
8561
  function detectLayout() {
8442
8562
  const file = fileURLToPath(import.meta.url);
8443
- const here = path30.dirname(file);
8444
- const bundledWeb = path30.join(here, "web");
8445
- if (isWebDir(bundledWeb) || path30.basename(here) === "dist") {
8563
+ const here = path31.dirname(file);
8564
+ const bundledWeb = path31.join(here, "web");
8565
+ if (isWebDir(bundledWeb) || path31.basename(here) === "dist") {
8446
8566
  return {
8447
- version: readVersion(path30.join(here, "..", "package.json")),
8567
+ version: readVersion(path31.join(here, "..", "package.json")),
8448
8568
  webDir: isWebDir(bundledWeb) ? bundledWeb : null,
8449
8569
  sourceRoot: null,
8450
8570
  cliEntry: file
8451
8571
  };
8452
8572
  }
8453
- const root = path30.resolve(here, "..", "..", "..");
8573
+ const root = path31.resolve(here, "..", "..", "..");
8454
8574
  const override = process.env.VIBEFLOW_WEB_DIR;
8455
- const webDir = override && isWebDir(override) ? override : isWebDir(path30.join(root, "app")) ? path30.join(root, "app") : null;
8456
- return { version: readVersion(path30.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
8575
+ const webDir = override && isWebDir(override) ? override : isWebDir(path31.join(root, "app")) ? path31.join(root, "app") : null;
8576
+ return { version: readVersion(path31.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
8457
8577
  }
8458
8578
  function readVersion(pkgPath) {
8459
8579
  try {
8460
- return JSON.parse(fs25.readFileSync(pkgPath, "utf8")).version;
8580
+ return JSON.parse(fs26.readFileSync(pkgPath, "utf8")).version;
8461
8581
  } catch {
8462
8582
  return "0.0.0";
8463
8583
  }
@@ -8481,6 +8601,7 @@ function parse(argv) {
8481
8601
  strict: true
8482
8602
  });
8483
8603
  }
8604
+ var handedOverToken = takeHandedOverToken();
8484
8605
  function hostOptions(values, layout, userDataDir, withWeb = true) {
8485
8606
  const backend = values.backend ?? "auto";
8486
8607
  if (!["auto", "tmux", "pty"].includes(backend)) throw new Error("--backend must be auto | tmux | pty");
@@ -8493,7 +8614,8 @@ function hostOptions(values, layout, userDataDir, withWeb = true) {
8493
8614
  sourceRoot: layout.sourceRoot,
8494
8615
  cliEntry: layout.cliEntry,
8495
8616
  backend,
8496
- port
8617
+ port,
8618
+ token: handedOverToken
8497
8619
  };
8498
8620
  }
8499
8621
  async function openBrowser(url) {
@@ -8502,6 +8624,33 @@ async function openBrowser(url) {
8502
8624
  } catch {
8503
8625
  }
8504
8626
  }
8627
+ async function relaunchHost(host, cliEntry, userDataDir, version) {
8628
+ const port = host.lock.port;
8629
+ const token = host.lock.token;
8630
+ const backend = host.core.sessions.kind;
8631
+ console.log(`
8632
+ \u5DF2\u5B89\u88DD VibeFlow ${version}\uFF0C\u6B63\u5728\u91CD\u65B0\u555F\u52D5 host\u2026`);
8633
+ await host.stop();
8634
+ const child = spawn9(process.execPath, relaunchArgs(cliEntry, { userDataDir, port, backend }), {
8635
+ stdio: "inherit",
8636
+ env: { ...process.env, [HOST_TOKEN_ENV]: token }
8637
+ });
8638
+ const forward = () => {
8639
+ child.kill("SIGTERM");
8640
+ };
8641
+ process.on("SIGINT", () => {
8642
+ });
8643
+ process.on("SIGHUP", () => {
8644
+ });
8645
+ process.on("SIGTERM", forward);
8646
+ return new Promise((resolve) => {
8647
+ child.once("error", (err) => {
8648
+ console.error(`\u7121\u6CD5\u555F\u52D5\u65B0\u7248 host\uFF1A${err.message}\u3002\u8ACB\u57F7\u884C vibeflow \u91CD\u65B0\u555F\u52D5\u3002`);
8649
+ resolve(1);
8650
+ });
8651
+ child.once("exit", (code2, signal) => resolve(code2 ?? (signal ? 1 : 0)));
8652
+ });
8653
+ }
8505
8654
  function stopOnSignals(host) {
8506
8655
  return new Promise((resolve) => {
8507
8656
  const stop = () => {
@@ -8524,7 +8673,7 @@ async function cmdWeb(values, layout, userDataDir, pathname = "/home/") {
8524
8673
  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");
8525
8674
  return 1;
8526
8675
  }
8527
- const started = await startHost(hostOptions(values, layout, userDataDir));
8676
+ const started = await startHost({ ...hostOptions(values, layout, userDataDir), autoRestart: true });
8528
8677
  if (started.kind === "running") return cmdWeb(values, layout, userDataDir, pathname);
8529
8678
  const { host } = started;
8530
8679
  const url = loginUrl(host.lock, pathname);
@@ -8537,8 +8686,11 @@ async function cmdWeb(values, layout, userDataDir, pathname = "/home/") {
8537
8686
  ${formatChecks(checks)}
8538
8687
  `);
8539
8688
  if (!values["no-open"]) await openBrowser(url);
8540
- await stopOnSignals(host);
8541
- return 0;
8689
+ const ended = await Promise.race([
8690
+ stopOnSignals(host).then(() => null),
8691
+ host.restartRequested
8692
+ ]);
8693
+ return ended === null || !layout.cliEntry ? 0 : relaunchHost(host, layout.cliEntry, userDataDir, ended);
8542
8694
  }
8543
8695
  async function cmdTui(values, layout, userDataDir) {
8544
8696
  const { runTui: runTui2 } = await Promise.resolve().then(() => (init_src(), src_exports));
@@ -8663,8 +8815,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
8663
8815
  console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
8664
8816
  return 1;
8665
8817
  }
8666
- const dir = path30.resolve(target);
8667
- if (!fs25.existsSync(dir) || !fs25.statSync(dir).isDirectory()) {
8818
+ const dir = path31.resolve(target);
8819
+ if (!fs26.existsSync(dir) || !fs26.statSync(dir).isDirectory()) {
8668
8820
  console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
8669
8821
  return 1;
8670
8822
  }
@@ -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-0c60752b4c05d52e.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/TTJDd4vO-NmQBjEis26sl/_buildManifest.js" defer=""></script><script src="/_next/static/TTJDd4vO-NmQBjEis26sl/_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":"TTJDd4vO-NmQBjEis26sl","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/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-2571a2564f593899.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/wiyRNf4HeW4AW-LhhYSsf/_buildManifest.js" defer=""></script><script src="/_next/static/wiyRNf4HeW4AW-LhhYSsf/_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":"wiyRNf4HeW4AW-LhhYSsf","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-0c60752b4c05d52e.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/TTJDd4vO-NmQBjEis26sl/_buildManifest.js" defer=""></script><script src="/_next/static/TTJDd4vO-NmQBjEis26sl/_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":"TTJDd4vO-NmQBjEis26sl","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/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-2571a2564f593899.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/wiyRNf4HeW4AW-LhhYSsf/_buildManifest.js" defer=""></script><script src="/_next/static/wiyRNf4HeW4AW-LhhYSsf/_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":"wiyRNf4HeW4AW-LhhYSsf","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>