@kw-yeh/vibeflow 4.11.3 → 4.11.4

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
@@ -653,7 +653,8 @@ function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
653
653
  const customPrompt = resolveSystemPrompt(systemPrompt);
654
654
  const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
655
655
  const includeTaskPrompt = opts?.includeTaskPrompt !== false;
656
- const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
656
+ const prompt = includeTaskPrompt && !opts?.resume ? [buildPrompt(task), opts?.preflightPlan ? `\u524D\u7F6E\u898F\u5283\uFF08\u4F9B\u53C3\u8003\uFF0C\u8ACB\u5148\u6838\u5C0D\u7A0B\u5F0F\u78BC\u518D\u57F7\u884C\uFF09\uFF1A
657
+ ${opts.preflightPlan}` : ""].filter(Boolean).join("\n\n") : "";
657
658
  const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
658
659
  return boardEnvPrefix(task, opts) + assembleCommand(
659
660
  agent,
@@ -1102,9 +1103,9 @@ var init_progress = __esm({
1102
1103
 
1103
1104
  // packages/tui/src/task-form.ts
1104
1105
  import { spawnSync as spawnSync2 } from "child_process";
1105
- import fs23 from "fs";
1106
+ import fs24 from "fs";
1106
1107
  import os9 from "os";
1107
- import path27 from "path";
1108
+ import path28 from "path";
1108
1109
  import React, { useEffect, useRef, useState } from "react";
1109
1110
  import { Box, Text, render as render2, useApp, useInput } from "ink";
1110
1111
  function newFormState(defaultAutoMode, agents) {
@@ -1230,17 +1231,17 @@ function editorCommand(env, platform) {
1230
1231
  return env.VISUAL?.trim() || env.EDITOR?.trim() || (platform === "win32" ? "notepad" : "vi");
1231
1232
  }
1232
1233
  function editInEditor(text) {
1233
- const dir = fs23.mkdtempSync(path27.join(os9.tmpdir(), "vibeflow-"));
1234
- const file = path27.join(dir, "description.md");
1234
+ const dir = fs24.mkdtempSync(path28.join(os9.tmpdir(), "vibeflow-"));
1235
+ const file = path28.join(dir, "description.md");
1235
1236
  try {
1236
- fs23.writeFileSync(file, text);
1237
+ fs24.writeFileSync(file, text);
1237
1238
  const cmd = editorCommand(process.env, process.platform);
1238
1239
  const r = spawnSync2(`${cmd} "${file}"`, { stdio: "inherit", shell: true });
1239
1240
  if (r.error) return { error: `\u7121\u6CD5\u555F\u52D5\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\uFF1A${r.error.message}` };
1240
1241
  if (r.status !== 0) return { error: `\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\u7D50\u675F\u78BC ${r.status}\uFF0C\u63CF\u8FF0\u672A\u8B8A\u66F4` };
1241
- return { text: fs23.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1242
+ return { text: fs24.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1242
1243
  } finally {
1243
- fs23.rmSync(dir, { recursive: true, force: true });
1244
+ fs24.rmSync(dir, { recursive: true, force: true });
1244
1245
  }
1245
1246
  }
1246
1247
  function cycle(list, current, step) {
@@ -1311,10 +1312,10 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1311
1312
  if (editing === "title") setS((prev) => ({ ...prev, title: draft }));
1312
1313
  else if (editing === "branch") setS((prev) => ({ ...prev, branch: value2 }));
1313
1314
  else if (editing === "project") {
1314
- if (value2) setProject(path27.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir())));
1315
+ if (value2) setProject(path28.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir())));
1315
1316
  } else if (editing === "attachments" && value2) {
1316
- const file = path27.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir()));
1317
- if (!fs23.existsSync(file) || !fs23.statSync(file).isFile()) {
1317
+ const file = path28.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir()));
1318
+ if (!fs24.existsSync(file) || !fs24.statSync(file).isFile()) {
1318
1319
  setMessage(`\u627E\u4E0D\u5230\u6A94\u6848\uFF1A${file}`);
1319
1320
  return;
1320
1321
  }
@@ -1397,7 +1398,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1397
1398
  case "branch":
1398
1399
  return s.branch || "\uFF08\u7559\u7A7A\u5247\u81EA\u52D5\u547D\u540D\uFF0C\u4F8B\u5982 feature/login-fix\uFF09";
1399
1400
  case "attachments":
1400
- return s.attachments.length ? s.attachments.map((a) => path27.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
1401
+ return s.attachments.length ? s.attachments.map((a) => path28.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
1401
1402
  case "description": {
1402
1403
  if (!s.description) return "\uFF08Enter \u7528\u7DE8\u8F2F\u5668\u64B0\u5BEB\uFF09";
1403
1404
  const lines = s.description.split("\n");
@@ -1433,7 +1434,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1433
1434
  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";
1434
1435
  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";
1435
1436
  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`;
1436
- if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path27.basename(s.projectPath)}`;
1437
+ if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path28.basename(s.projectPath)}`;
1437
1438
  }
1438
1439
  if (field === "branch") {
1439
1440
  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";
@@ -1561,7 +1562,7 @@ __export(src_exports, {
1561
1562
  showDiff: () => showDiff
1562
1563
  });
1563
1564
  import { spawnSync as spawnSync3 } from "child_process";
1564
- import path28 from "path";
1565
+ import path29 from "path";
1565
1566
  import React2, { useEffect as useEffect2, useState as useState2 } from "react";
1566
1567
  import { Box as Box2, Text as Text2, render as render3, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
1567
1568
  function findDetachKey(text) {
@@ -1857,7 +1858,7 @@ function showDiff(task) {
1857
1858
  });
1858
1859
  }
1859
1860
  function tmuxAttach(opts, sessionName) {
1860
- const conf = path28.join(opts.userDataDir, "tmux.conf");
1861
+ const conf = path29.join(opts.userDataDir, "tmux.conf");
1861
1862
  const inOurServer = (process.env.TMUX ?? "").split(",")[0].endsWith(`/${TMUX_SOCKET}`);
1862
1863
  const args = inOurServer ? ["-L", TMUX_SOCKET, "-f", conf, "switch-client", "-t", `=${sessionName}`] : ["-L", TMUX_SOCKET, "-f", conf, "attach-session", "-t", `=${sessionName}`];
1863
1864
  spawnSync3("tmux", args, { stdio: "inherit", env: { ...process.env, TMUX: inOurServer ? process.env.TMUX : "" } });
@@ -1988,8 +1989,8 @@ var init_src = __esm({
1988
1989
  });
1989
1990
 
1990
1991
  // packages/cli/src/main.ts
1991
- import fs24 from "fs";
1992
- import path29 from "path";
1992
+ import fs25 from "fs";
1993
+ import path30 from "path";
1993
1994
  import { fileURLToPath } from "url";
1994
1995
  import { parseArgs as parseArgs2 } from "node:util";
1995
1996
 
@@ -2463,6 +2464,9 @@ function createBridge(t) {
2463
2464
  },
2464
2465
  setBoard: (board) => t.invoke("vibeflow:setBoard", board),
2465
2466
  setSettings: (patch) => t.invoke("vibeflow:setSettings", patch),
2467
+ getJevKeyStatus: () => t.invoke("settings:jevKeyStatus"),
2468
+ saveJevApiKey: (apiKey) => t.invoke("settings:saveJevApiKey", { apiKey }),
2469
+ removeJevApiKey: () => t.invoke("settings:removeJevApiKey"),
2466
2470
  getGithubAuthStatus: () => t.invoke("settings:githubAuthStatus"),
2467
2471
  startGithubAuthLogin: () => t.invoke("settings:startGithubAuthLogin"),
2468
2472
  cancelGithubAuthLogin: () => t.invoke("settings:cancelGithubAuthLogin"),
@@ -4638,8 +4642,8 @@ async function taskCommand(argv) {
4638
4642
  }
4639
4643
 
4640
4644
  // packages/cli/src/host.ts
4641
- import fs21 from "fs";
4642
- import path25 from "path";
4645
+ import fs22 from "fs";
4646
+ import path26 from "path";
4643
4647
  import { randomBytes as randomBytes2 } from "crypto";
4644
4648
 
4645
4649
  // packages/core/src/events.ts
@@ -5177,10 +5181,10 @@ async function syncBuiltinsAtStartup() {
5177
5181
  }
5178
5182
 
5179
5183
  // packages/core/src/service.ts
5180
- import fs18 from "fs";
5184
+ import fs19 from "fs";
5181
5185
  import os8 from "os";
5182
- import path22 from "path";
5183
- import { randomUUID as randomUUID4 } from "crypto";
5186
+ import path23 from "path";
5187
+ import { randomUUID as randomUUID5 } from "crypto";
5184
5188
 
5185
5189
  // packages/core/src/agent-models.ts
5186
5190
  import fs13 from "fs";
@@ -5533,6 +5537,170 @@ function createModelCatalog(opts) {
5533
5537
  };
5534
5538
  }
5535
5539
 
5540
+ // packages/core/src/jev-router.ts
5541
+ init_launch();
5542
+ var API_URL = "https://api.typesafe.ai/v1/systemone";
5543
+ var MAX_TASK_CHARS = 12e3;
5544
+ var MAX_PLAN_CHARS = 8e3;
5545
+ function tier(model, agent) {
5546
+ const name = `${model.id} ${model.label}`.toLowerCase();
5547
+ if (/haiku|mini|nano|luna|flash/.test(name)) return 0;
5548
+ if (/opus|astra|pro\b/.test(name)) return 2;
5549
+ if (agent === "codex" && /gpt-5\.5|gpt-5\.4(?!-mini)/.test(name)) return 2;
5550
+ return 1;
5551
+ }
5552
+ function routeCandidates(agent, models) {
5553
+ const valid = models.filter((m) => /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(m.id));
5554
+ const byTier = /* @__PURE__ */ new Map();
5555
+ for (const model of valid) {
5556
+ const rank = tier(model, agent);
5557
+ if (!byTier.has(rank)) byTier.set(rank, model);
5558
+ }
5559
+ return [...byTier.entries()].sort(([a], [b]) => a - b).map(([, model]) => model);
5560
+ }
5561
+ function isChoice(value) {
5562
+ return !!value && typeof value === "object" && value.type === "choice" && typeof value.choice === "string";
5563
+ }
5564
+ function createJevRouter(opts = {}) {
5565
+ const getApiKey = opts.getApiKey ?? (() => opts.apiKey ?? process.env.TYPESAFE_API_KEY?.trim());
5566
+ const fetcher = opts.fetcher ?? fetch;
5567
+ const planner = opts.planner ?? (async (agent, model, prompt, cwd) => {
5568
+ const args = agent === "claude" ? ["-p", "--model", model, "--permission-mode", "plan"] : ["exec", "--model", model, "--sandbox", "read-only", "--skip-git-repo-check", "-"];
5569
+ return (opts.printAgent ?? runAgentPrint)(agent, args, prompt, { cwd, timeout: 12e4, maxBuffer: 128 * 1024 });
5570
+ });
5571
+ async function ask(state, questions) {
5572
+ const apiKey = getApiKey();
5573
+ if (!apiKey) throw new Error("\u672A\u8A2D\u5B9A Jev API key\uFF08\u53EF\u5728\u8A2D\u5B9A\u4E2D\u8F38\u5165\u6216\u63D0\u4F9B TYPESAFE_API_KEY\uFF09");
5574
+ const response = await fetcher(API_URL, {
5575
+ method: "POST",
5576
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
5577
+ body: JSON.stringify({ model: "jev-latest", state, questions }),
5578
+ signal: AbortSignal.timeout(15e3)
5579
+ });
5580
+ if (!response.ok) throw new Error(`Jev API \u56DE\u61C9 ${response.status}`);
5581
+ const body = await response.json();
5582
+ if (!body.answers) throw new Error("Jev API \u6C92\u6709\u56DE\u50B3\u5224\u65B7");
5583
+ return body.answers;
5584
+ }
5585
+ return {
5586
+ get enabled() {
5587
+ return Boolean(getApiKey());
5588
+ },
5589
+ async route(task, catalog) {
5590
+ const agent = task.agentCli === "codex" ? "codex" : "claude";
5591
+ const candidates = routeCandidates(agent, catalog);
5592
+ if (candidates.length === 0) return { status: "fallback", reason: "\u6A21\u578B\u6E05\u55AE\u6C92\u6709\u53EF\u7528\u7684\u6A21\u578B" };
5593
+ const expensive = candidates[candidates.length - 1];
5594
+ const taskText = buildPrompt(task).slice(0, MAX_TASK_CHARS);
5595
+ try {
5596
+ const first = await ask({ task: taskText, agent }, {
5597
+ complexity: {
5598
+ type: "choice",
5599
+ instructions: "How difficult is this software task to implement correctly?",
5600
+ criteria: {
5601
+ simple: "Small, well scoped and routine",
5602
+ moderate: "Some design choices or multiple files",
5603
+ complex: "Architecture, ambiguous requirements, high risk, or many interacting parts"
5604
+ }
5605
+ },
5606
+ preparation: {
5607
+ type: "choice",
5608
+ instructions: "Would a separate read-only planning pass materially improve the implementation?",
5609
+ criteria: {
5610
+ direct: "The request is clear enough to start implementation directly",
5611
+ plan: "The request is unclear, broad or complex enough to benefit from a plan first"
5612
+ }
5613
+ }
5614
+ });
5615
+ if (!isChoice(first.complexity) || !isChoice(first.preparation) || !["simple", "moderate", "complex"].includes(first.complexity.choice) || !["direct", "plan"].includes(first.preparation.choice)) throw new Error("Jev \u56DE\u50B3\u4E0D\u53D7\u652F\u63F4\u7684\u5224\u65B7");
5616
+ let plan;
5617
+ if (first.preparation.choice === "plan") {
5618
+ if (!task.worktreePath) throw new Error("\u898F\u5283\u6642\u627E\u4E0D\u5230 worktree");
5619
+ const instruction = `Read the repository and make a concise implementation plan for the following task. Do not edit files. Identify unclear requirements, affected areas, and verification. Return only the plan.
5620
+
5621
+ ${taskText}`;
5622
+ plan = (await planner(agent, expensive.id, instruction, task.worktreePath)).trim().slice(0, MAX_PLAN_CHARS);
5623
+ if (!plan) throw new Error("\u898F\u5283\u6A21\u578B\u6C92\u6709\u56DE\u50B3\u8A08\u756B");
5624
+ }
5625
+ const options = Object.fromEntries(candidates.map((m) => [
5626
+ m.id,
5627
+ `${m.label}; relative cost tier ${tier(m, agent) + 1}/3 (${tier(m, agent) === 2 ? "highest capability" : tier(m, agent) === 0 ? "lowest cost" : "balanced"})`
5628
+ ]));
5629
+ const second = await ask({ task: taskText, complexity: first.complexity.choice, plan }, {
5630
+ model: {
5631
+ type: "choice",
5632
+ instructions: "Choose the lowest-cost available model likely to implement this task correctly. Prefer more capable models as complexity or uncertainty rises.",
5633
+ criteria: options
5634
+ }
5635
+ });
5636
+ if (!isChoice(second.model) || !candidates.some((m) => m.id === second.model.choice)) {
5637
+ throw new Error("Jev \u9078\u4E86\u4E0D\u5728\u6A21\u578B\u6E05\u55AE\u4E2D\u7684\u6A21\u578B");
5638
+ }
5639
+ return {
5640
+ status: "routed",
5641
+ model: second.model.choice,
5642
+ ...plan ? { plannerModel: expensive.id, plan } : {}
5643
+ };
5644
+ } catch (err) {
5645
+ return { status: "fallback", reason: err instanceof Error ? err.message : String(err) };
5646
+ }
5647
+ }
5648
+ };
5649
+ }
5650
+
5651
+ // packages/core/src/jev-credentials.ts
5652
+ import fs14 from "fs";
5653
+ import path18 from "path";
5654
+ import { randomUUID as randomUUID3 } from "crypto";
5655
+ function keyPath(dir) {
5656
+ return path18.join(dir, "jev-api-key");
5657
+ }
5658
+ function dataDir() {
5659
+ return getPlatform().userDataDir();
5660
+ }
5661
+ function readJevApiKey(dir = dataDir()) {
5662
+ try {
5663
+ const file = keyPath(dir);
5664
+ if (fs14.lstatSync(file).isSymbolicLink()) throw new Error("Jev key file must not be a symlink");
5665
+ const saved = fs14.readFileSync(file, "utf8").trim();
5666
+ if (saved) return saved;
5667
+ } catch (err) {
5668
+ if (err.code !== "ENOENT") throw err;
5669
+ }
5670
+ return process.env.TYPESAFE_API_KEY?.trim() || void 0;
5671
+ }
5672
+ function jevKeyStatus(dir = dataDir()) {
5673
+ try {
5674
+ const file = keyPath(dir);
5675
+ if (fs14.lstatSync(file).isSymbolicLink()) throw new Error("Jev key file must not be a symlink");
5676
+ if (fs14.readFileSync(file, "utf8").trim()) return { configured: true, source: "saved" };
5677
+ } catch (err) {
5678
+ if (err.code !== "ENOENT") throw err;
5679
+ }
5680
+ return process.env.TYPESAFE_API_KEY?.trim() ? { configured: true, source: "environment" } : { configured: false, source: "none" };
5681
+ }
5682
+ function saveJevApiKey(value, dir = dataDir()) {
5683
+ const key = value.trim();
5684
+ if (!key || key.length > 4096 || !/^[\x21-\x7e]+$/.test(key)) {
5685
+ throw new Error("Jev API key \u683C\u5F0F\u7121\u6548");
5686
+ }
5687
+ fs14.mkdirSync(dir, { recursive: true, mode: 448 });
5688
+ const file = keyPath(dir);
5689
+ const tmp = `${file}.${randomUUID3()}.tmp`;
5690
+ try {
5691
+ fs14.writeFileSync(tmp, key, { mode: 384, flag: "wx" });
5692
+ fs14.renameSync(tmp, file);
5693
+ if (process.platform !== "win32") fs14.chmodSync(file, 384);
5694
+ } finally {
5695
+ fs14.rmSync(tmp, { force: true });
5696
+ }
5697
+ return { configured: true, source: "saved" };
5698
+ }
5699
+ function removeJevApiKey(dir = dataDir()) {
5700
+ fs14.rmSync(keyPath(dir), { force: true });
5701
+ return jevKeyStatus(dir);
5702
+ }
5703
+
5536
5704
  // packages/core/src/service.ts
5537
5705
  init_effort();
5538
5706
 
@@ -6035,45 +6203,45 @@ function createJiraService(options) {
6035
6203
  }
6036
6204
 
6037
6205
  // packages/core/src/board-cli.ts
6038
- import fs14 from "fs";
6039
- import path18 from "path";
6206
+ import fs15 from "fs";
6207
+ import path19 from "path";
6040
6208
  import { pathToFileURL } from "url";
6041
6209
  async function boardCliLaunchInfo() {
6042
- const storeDir = path18.dirname(getStorePath());
6210
+ const storeDir = path19.dirname(getStorePath());
6043
6211
  const entry = getPlatform().cliEntry?.();
6044
- if (entry && fs14.existsSync(entry)) return { storeDir, cliPath: entry };
6212
+ if (entry && fs15.existsSync(entry)) return { storeDir, cliPath: entry };
6045
6213
  const root = getPlatform().sourceRoot();
6046
6214
  if (!root) return { storeDir };
6047
- const cliPath = path18.join(root, "scripts", "vibeflow.mjs");
6048
- const loaderPath = path18.join(root, "test", "support", "register.mjs");
6049
- if (!fs14.existsSync(cliPath) || !fs14.existsSync(loaderPath)) return { storeDir };
6215
+ const cliPath = path19.join(root, "scripts", "vibeflow.mjs");
6216
+ const loaderPath = path19.join(root, "test", "support", "register.mjs");
6217
+ if (!fs15.existsSync(cliPath) || !fs15.existsSync(loaderPath)) return { storeDir };
6050
6218
  return { storeDir, cliPath, loaderPath: pathToFileURL(loaderPath).href };
6051
6219
  }
6052
6220
 
6053
6221
  // packages/core/src/decisions.ts
6054
- import fs15 from "fs";
6055
- import path19 from "path";
6222
+ import fs16 from "fs";
6223
+ import path20 from "path";
6056
6224
  var DECISIONS_FILE_SUFFIX = ".DECISIONS.md";
6057
6225
  function decisionsKey(worktreePath, branch) {
6058
- return worktreePath ? path19.basename(worktreePath) : branch.replace(/\//g, "-");
6226
+ return worktreePath ? path20.basename(worktreePath) : branch.replace(/\//g, "-");
6059
6227
  }
6060
6228
  function decisionsPath(workspacePath, key) {
6061
- return path19.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
6229
+ return path20.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
6062
6230
  }
6063
6231
  function deleteDecisions(workspacePath, key) {
6064
6232
  try {
6065
- fs15.rmSync(decisionsPath(workspacePath, key), { force: true });
6233
+ fs16.rmSync(decisionsPath(workspacePath, key), { force: true });
6066
6234
  } catch {
6067
6235
  }
6068
6236
  }
6069
6237
 
6070
6238
  // packages/core/src/specs.ts
6071
- import fs16 from "fs";
6072
- import path20 from "path";
6239
+ import fs17 from "fs";
6240
+ import path21 from "path";
6073
6241
  var SPEC_FILE_NAME = "spec.md";
6074
6242
  var MAX_SPEC_BYTES = 256 * 1024;
6075
6243
  function isSpecPath(p) {
6076
- return path20.posix.basename(p) === SPEC_FILE_NAME;
6244
+ return path21.posix.basename(p) === SPEC_FILE_NAME;
6077
6245
  }
6078
6246
  function clip2(buf) {
6079
6247
  return {
@@ -6088,10 +6256,10 @@ async function readWorktreeSpecs(worktreePath, baseBranch) {
6088
6256
  for (const entry of entries) {
6089
6257
  if (entry.status === "D" || !isSpecPath(entry.path)) continue;
6090
6258
  try {
6091
- const full = path20.join(worktreePath, entry.path);
6092
- const stat = fs16.statSync(full);
6259
+ const full = path21.join(worktreePath, entry.path);
6260
+ const stat = fs17.statSync(full);
6093
6261
  if (!stat.isFile()) continue;
6094
- specs.push({ path: entry.path, ...clip2(fs16.readFileSync(full)), updatedAt: stat.mtimeMs });
6262
+ specs.push({ path: entry.path, ...clip2(fs17.readFileSync(full)), updatedAt: stat.mtimeMs });
6095
6263
  } catch {
6096
6264
  }
6097
6265
  }
@@ -6157,7 +6325,7 @@ async function readSpecsAt(cwd, ref, paths) {
6157
6325
  init_env();
6158
6326
  init_git_bash();
6159
6327
  import { spawn as spawn7 } from "child_process";
6160
- import { randomUUID as randomUUID3 } from "crypto";
6328
+ import { randomUUID as randomUUID4 } from "crypto";
6161
6329
 
6162
6330
  // packages/core/src/chat-store.ts
6163
6331
  var _chatStore = null;
@@ -6287,7 +6455,7 @@ function sendChatMessage(opts, sender) {
6287
6455
  const fullText = attachmentLines.length ? `${text}
6288
6456
 
6289
6457
  ${attachmentLines.join("\n")}` : text;
6290
- const userMsgId = randomUUID3().slice(0, 8);
6458
+ const userMsgId = randomUUID4().slice(0, 8);
6291
6459
  appendMessage(taskId, {
6292
6460
  id: userMsgId,
6293
6461
  role: "user",
@@ -6406,7 +6574,7 @@ ${attachmentLines.join("\n")}` : text;
6406
6574
  proc.on("close", (code2) => {
6407
6575
  const assistantText = accumulated || "\uFF08\u7121\u56DE\u61C9\uFF09";
6408
6576
  appendMessage(taskId, {
6409
- id: randomUUID3().slice(0, 8),
6577
+ id: randomUUID4().slice(0, 8),
6410
6578
  role: "assistant",
6411
6579
  text: assistantText,
6412
6580
  ts: Date.now()
@@ -6443,17 +6611,17 @@ init_launch();
6443
6611
  // packages/core/src/progress-tracker.ts
6444
6612
  init_progress();
6445
6613
  init_launch();
6446
- import fs17 from "fs";
6614
+ import fs18 from "fs";
6447
6615
  import os7 from "os";
6448
- import path21 from "path";
6616
+ import path22 from "path";
6449
6617
  var SINCE_SLACK_MS = 5e3;
6450
6618
  function claudeProjectDir(home, cwd) {
6451
- return path21.join(home, ".claude", "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
6619
+ return path22.join(home, ".claude", "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
6452
6620
  }
6453
6621
  function pathAliases(p) {
6454
- const resolved = path21.resolve(p);
6622
+ const resolved = path22.resolve(p);
6455
6623
  try {
6456
- const real = fs17.realpathSync(resolved);
6624
+ const real = fs18.realpathSync(resolved);
6457
6625
  return real === resolved ? [resolved] : [resolved, real];
6458
6626
  } catch {
6459
6627
  return [resolved];
@@ -6469,7 +6637,7 @@ function samePath(a, b) {
6469
6637
  }
6470
6638
  function listFiles(dir, test) {
6471
6639
  try {
6472
- return fs17.readdirSync(dir).filter(test).map((name) => path21.join(dir, name));
6640
+ return fs18.readdirSync(dir).filter(test).map((name) => path22.join(dir, name));
6473
6641
  } catch {
6474
6642
  return [];
6475
6643
  }
@@ -6477,22 +6645,22 @@ function listFiles(dir, test) {
6477
6645
  function firstLine(file) {
6478
6646
  let fd;
6479
6647
  try {
6480
- fd = fs17.openSync(file, "r");
6648
+ fd = fs18.openSync(file, "r");
6481
6649
  const buf = Buffer.alloc(64 * 1024);
6482
- const n = fs17.readSync(fd, buf, 0, buf.length, 0);
6650
+ const n = fs18.readSync(fd, buf, 0, buf.length, 0);
6483
6651
  const text = buf.subarray(0, n).toString("utf8");
6484
6652
  const nl = text.indexOf("\n");
6485
6653
  return nl === -1 ? null : text.slice(0, nl);
6486
6654
  } catch {
6487
6655
  return null;
6488
6656
  } finally {
6489
- if (fd !== void 0) fs17.closeSync(fd);
6657
+ if (fd !== void 0) fs18.closeSync(fd);
6490
6658
  }
6491
6659
  }
6492
6660
  function advance(file, cursor, reduce, initial) {
6493
6661
  let size;
6494
6662
  try {
6495
- size = fs17.statSync(file).size;
6663
+ size = fs18.statSync(file).size;
6496
6664
  } catch {
6497
6665
  return;
6498
6666
  }
@@ -6504,9 +6672,9 @@ function advance(file, cursor, reduce, initial) {
6504
6672
  if (size === cursor.offset) return;
6505
6673
  let fd;
6506
6674
  try {
6507
- fd = fs17.openSync(file, "r");
6675
+ fd = fs18.openSync(file, "r");
6508
6676
  const chunk = Buffer.alloc(size - cursor.offset);
6509
- const n = fs17.readSync(fd, chunk, 0, chunk.length, cursor.offset);
6677
+ const n = fs18.readSync(fd, chunk, 0, chunk.length, cursor.offset);
6510
6678
  cursor.offset += n;
6511
6679
  const data = Buffer.concat([cursor.rest, chunk.subarray(0, n)]);
6512
6680
  const nl = data.lastIndexOf(10);
@@ -6520,7 +6688,7 @@ function advance(file, cursor, reduce, initial) {
6520
6688
  }
6521
6689
  } catch {
6522
6690
  } finally {
6523
- if (fd !== void 0) fs17.closeSync(fd);
6691
+ if (fd !== void 0) fs18.closeSync(fd);
6524
6692
  }
6525
6693
  }
6526
6694
  function newCursor(state) {
@@ -6539,14 +6707,14 @@ function codexDayDirs(sessionsRoot, since) {
6539
6707
  const cur = new Date(start.getFullYear(), start.getMonth(), start.getDate());
6540
6708
  while (cur <= end) {
6541
6709
  const pad = (n) => String(n).padStart(2, "0");
6542
- days.push(path21.join(sessionsRoot, String(cur.getFullYear()), pad(cur.getMonth() + 1), pad(cur.getDate())));
6710
+ days.push(path22.join(sessionsRoot, String(cur.getFullYear()), pad(cur.getMonth() + 1), pad(cur.getDate())));
6543
6711
  cur.setDate(cur.getDate() + 1);
6544
6712
  }
6545
6713
  return days;
6546
6714
  }
6547
6715
  function createProgressTracker(opts) {
6548
6716
  const home = opts.homeDir ?? os7.homedir();
6549
- const codexHomes = opts.codexHomes ?? (() => [path21.join(home, ".codex")]);
6717
+ const codexHomes = opts.codexHomes ?? (() => [path22.join(home, ".codex")]);
6550
6718
  const pollMs = opts.pollMs ?? 2e3;
6551
6719
  const entries = /* @__PURE__ */ new Map();
6552
6720
  const belongsToRun = (target, startedAt) => target.since == null || startedAt != null && startedAt >= target.since - SINCE_SLACK_MS;
@@ -6555,7 +6723,7 @@ function createProgressTracker(opts) {
6555
6723
  const dirs = pathAliases(target.worktreePath).map((p) => claudeProjectDir(home, p));
6556
6724
  for (const file of dirs.flatMap((dir) => listFiles(dir, (n) => n.endsWith(".jsonl")))) {
6557
6725
  if (entry.claude.has(file) || entry.rejected.has(file)) continue;
6558
- const session = path21.basename(file, ".jsonl");
6726
+ const session = path22.basename(file, ".jsonl");
6559
6727
  if (session !== target.sessionId) {
6560
6728
  const head = firstLine(file);
6561
6729
  if (head == null) continue;
@@ -6568,8 +6736,8 @@ function createProgressTracker(opts) {
6568
6736
  entry.claude.set(file, newCursor(initialClaudeState()));
6569
6737
  }
6570
6738
  for (const file of Array.from(entry.claude.keys())) {
6571
- if (file.includes(`${path21.sep}subagents${path21.sep}`)) continue;
6572
- const sub = path21.join(file.slice(0, -".jsonl".length), "subagents");
6739
+ if (file.includes(`${path22.sep}subagents${path22.sep}`)) continue;
6740
+ const sub = path22.join(file.slice(0, -".jsonl".length), "subagents");
6573
6741
  for (const f of listFiles(sub, (n) => n.endsWith(".jsonl"))) {
6574
6742
  if (!entry.claude.has(f)) entry.claude.set(f, newCursor(initialClaudeState()));
6575
6743
  }
@@ -6578,7 +6746,7 @@ function createProgressTracker(opts) {
6578
6746
  function discoverCodex(entry) {
6579
6747
  const { target } = entry;
6580
6748
  for (const codexHome of codexHomes()) {
6581
- for (const day of codexDayDirs(path21.join(codexHome, "sessions"), target.since)) {
6749
+ for (const day of codexDayDirs(path22.join(codexHome, "sessions"), target.since)) {
6582
6750
  for (const file of listFiles(day, (n) => n.startsWith("rollout-") && n.endsWith(".jsonl"))) {
6583
6751
  if (entry.codex.has(file) || entry.rejected.has(file)) continue;
6584
6752
  const head = firstLine(file);
@@ -6595,11 +6763,11 @@ function createProgressTracker(opts) {
6595
6763
  }
6596
6764
  }
6597
6765
  function drainHooks(entry) {
6598
- const dir = path21.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
6766
+ const dir = path22.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
6599
6767
  for (const file of listFiles(dir, (n) => n.endsWith(".json")).sort()) {
6600
6768
  try {
6601
- const at = fs17.statSync(file).mtimeMs;
6602
- const event = JSON.parse(fs17.readFileSync(file, "utf8"));
6769
+ const at = fs18.statSync(file).mtimeMs;
6770
+ const event = JSON.parse(fs18.readFileSync(file, "utf8"));
6603
6771
  const foreign = entry.target.sessionId && typeof event.session_id === "string" && event.session_id !== entry.target.sessionId;
6604
6772
  const activity = foreign ? null : activityFromHook(event, at);
6605
6773
  if (activity) entry.hookActivity = latestActivity(entry.hookActivity ?? activity, activity);
@@ -6607,7 +6775,7 @@ function createProgressTracker(opts) {
6607
6775
  continue;
6608
6776
  }
6609
6777
  try {
6610
- fs17.unlinkSync(file);
6778
+ fs18.unlinkSync(file);
6611
6779
  } catch {
6612
6780
  }
6613
6781
  }
@@ -6619,8 +6787,8 @@ function createProgressTracker(opts) {
6619
6787
  if (target.agent === "claude") {
6620
6788
  for (const [file, cursor] of entry.claude) {
6621
6789
  runUsage = addUsage(runUsage, claudeUsageTotal(cursor.state));
6622
- if (file.includes(`${path21.sep}subagents${path21.sep}`)) continue;
6623
- const isExecutor = path21.basename(file, ".jsonl") === target.sessionId;
6790
+ if (file.includes(`${path22.sep}subagents${path22.sep}`)) continue;
6791
+ const isExecutor = path22.basename(file, ".jsonl") === target.sessionId;
6624
6792
  const s = cursor.state;
6625
6793
  if (isExecutor || !target.sessionId && (!primary || (s.startedAt ?? 0) > (primary.startedAt ?? 0))) {
6626
6794
  primary = { todos: s.todos, contextTokens: s.contextTokens, activity: s.activity, startedAt: s.startedAt };
@@ -6699,10 +6867,10 @@ function createProgressTracker(opts) {
6699
6867
  };
6700
6868
  entry.timer.unref?.();
6701
6869
  entries.set(target.taskId, entry);
6702
- const eventsDir = path21.join(target.worktreePath, PROGRESS_EVENTS_DIR);
6870
+ const eventsDir = path22.join(target.worktreePath, PROGRESS_EVENTS_DIR);
6703
6871
  try {
6704
- fs17.mkdirSync(eventsDir, { recursive: true });
6705
- entry.watcher = fs17.watch(eventsDir, () => {
6872
+ fs18.mkdirSync(eventsDir, { recursive: true });
6873
+ entry.watcher = fs18.watch(eventsDir, () => {
6706
6874
  if (entry.pending) return;
6707
6875
  entry.pending = setTimeout(() => {
6708
6876
  entry.pending = null;
@@ -6787,9 +6955,11 @@ function validBoard(value) {
6787
6955
  }
6788
6956
  return next;
6789
6957
  }
6790
- function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatus, acliRunner, jiraAuthStatus, modelCatalog }) {
6958
+ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatus, acliRunner, jiraAuthStatus, modelCatalog, jevRouter }) {
6791
6959
  const sink = bus.sink();
6792
6960
  const models = modelCatalog ?? createModelCatalog({ version });
6961
+ const router = jevRouter ?? createJevRouter({ getApiKey: readJevApiKey });
6962
+ const routing = /* @__PURE__ */ new Map();
6793
6963
  const standaloneTerminals = /* @__PURE__ */ new Map();
6794
6964
  let libraryCodexHome = null;
6795
6965
  void libraryRoot().then((root) => {
@@ -6799,7 +6969,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6799
6969
  const home = homeDir ?? os8.homedir();
6800
6970
  const progress = createProgressTracker({
6801
6971
  homeDir: home,
6802
- codexHomes: () => [path22.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
6972
+ codexHomes: () => [path23.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
6803
6973
  onUpdate: (taskId, value) => bus.emit("progress:update", { taskId, progress: value }),
6804
6974
  onNotify: (taskId, list) => {
6805
6975
  const prefs = resolveNotificationSettings(getSettings());
@@ -6867,36 +7037,53 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6867
7037
  requireTask(key.split(":")[0]);
6868
7038
  return key;
6869
7039
  }
6870
- function launchEffort(task) {
6871
- const model = models.findModel(taskAgent(task), taskModel(task));
7040
+ function launchEffort(task, selectedModel = taskModel(task)) {
7041
+ const model = models.findModel(taskAgent(task), selectedModel);
6872
7042
  return model?.efforts ? clampEffort(task.effort, model.efforts) : task.effort;
6873
7043
  }
6874
7044
  async function launchCommand(task, intent) {
6875
7045
  const settings = getSettings();
6876
7046
  const library = await libraryLaunchInfo(task.worktreePath);
6877
7047
  const boardCli = await boardCliLaunchInfo();
7048
+ let route = task.model?.trim() ? void 0 : task.jevRoute;
7049
+ if (!task.model?.trim() && !intent.resume && intent.includeTaskPrompt !== false) {
7050
+ let pending = routing.get(task.id);
7051
+ if (!pending) {
7052
+ pending = (async () => {
7053
+ const candidateModels = (await (router.enabled === false ? models.get(taskAgent(task)) : models.refresh(taskAgent(task)))).models;
7054
+ const selected = await router.route(task, candidateModels);
7055
+ updateTask(task.id, { jevRoute: selected });
7056
+ bus.emit("state:changed", getState());
7057
+ return selected;
7058
+ })().finally(() => routing.delete(task.id));
7059
+ routing.set(task.id, pending);
7060
+ }
7061
+ route = await pending;
7062
+ }
7063
+ const selectedModel = task.model?.trim() ? taskModel(task) : route?.model;
6878
7064
  return buildAgentCommand(
6879
- { ...task, effort: launchEffort(task) },
7065
+ { ...task, model: selectedModel, effort: launchEffort(task, selectedModel) },
6880
7066
  settings.systemPrompt ?? "",
6881
7067
  {
6882
7068
  resume: intent.resume === true,
6883
7069
  includeTaskPrompt: intent.includeTaskPrompt !== false,
6884
7070
  library: library ?? void 0,
6885
7071
  boardCli,
6886
- autoMode: task.autoMode ?? settings.autoMode
7072
+ autoMode: task.autoMode ?? settings.autoMode,
7073
+ preflightPlan: !intent.resume ? route?.plan : void 0
6887
7074
  },
6888
7075
  task.workspacePath
6889
7076
  );
6890
7077
  }
6891
7078
  function claudeSessionFile(cwd, sessionId) {
6892
7079
  const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
6893
- return path22.join(os8.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
7080
+ return path23.join(os8.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
6894
7081
  }
6895
7082
  async function removeTaskWorktree(task) {
6896
7083
  if (!task.projectPath || !task.worktreePath) return;
6897
7084
  const branch = task.branch || fallbackBranchName(task.id);
6898
7085
  await removeWorktree(task.projectPath, task.worktreePath);
6899
- deleteArtifacts(task.workspacePath ?? path22.dirname(task.worktreePath), task.worktreePath);
7086
+ deleteArtifacts(task.workspacePath ?? path23.dirname(task.worktreePath), task.worktreePath);
6900
7087
  await deleteBranch(task.projectPath, branch);
6901
7088
  }
6902
7089
  function columnOf(taskId) {
@@ -6977,6 +7164,12 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6977
7164
  setSettings(value);
6978
7165
  return getState();
6979
7166
  },
7167
+ "settings:jevKeyStatus": () => jevKeyStatus(),
7168
+ "settings:saveJevApiKey": (payload) => {
7169
+ const p = obj(payload, "payload");
7170
+ return saveJevApiKey(str(p.apiKey, "apiKey"));
7171
+ },
7172
+ "settings:removeJevApiKey": () => removeJevApiKey(),
6980
7173
  "settings:githubAuthStatus": () => getGitHubCliAuthStatus(),
6981
7174
  "settings:startGithubAuthLogin": () => {
6982
7175
  startGitHubCliLogin((payload) => bus.emit("github-auth:event", payload));
@@ -7032,8 +7225,8 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7032
7225
  "projects:listRecent": () => listRecentProjects(getStore()),
7033
7226
  // `vibeflow open <path>` while a host runs: only the host writes the store.
7034
7227
  "projects:record": (projectPath) => {
7035
- const p = path22.resolve(str(projectPath, "projectPath"));
7036
- if (!fs18.existsSync(p) || !fs18.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
7228
+ const p = path23.resolve(str(projectPath, "projectPath"));
7229
+ if (!fs19.existsSync(p) || !fs19.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
7037
7230
  recordRecentProject(getStore(), p);
7038
7231
  return listRecentProjects(getStore());
7039
7232
  },
@@ -7078,7 +7271,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7078
7271
  if (!task.worktreePath) {
7079
7272
  await teardownTask(task.id);
7080
7273
  progress.untrack(task.id);
7081
- updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4() });
7274
+ updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID5(), jevRoute: void 0 });
7082
7275
  const reset2 = findTask(task.id);
7083
7276
  if (!reset2) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
7084
7277
  return { state: getState(), task: reset2 };
@@ -7086,13 +7279,13 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7086
7279
  if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
7087
7280
  await teardownTask(task.id);
7088
7281
  await resetWorktreeToBase(task.worktreePath, task.baseBranch);
7089
- const workspacePath = task.workspacePath ?? path22.dirname(task.worktreePath);
7282
+ const workspacePath = task.workspacePath ?? path23.dirname(task.worktreePath);
7090
7283
  deleteArtifacts(workspacePath, task.worktreePath);
7091
7284
  deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
7092
7285
  resetSubAgents(task.worktreePath);
7093
7286
  const usage = usageThroughCurrentRun(task);
7094
7287
  progress.untrack(task.id);
7095
- updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4(), ...usage ? { usage } : {} });
7288
+ updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID5(), jevRoute: void 0, ...usage ? { usage } : {} });
7096
7289
  const reset = findTask(task.id);
7097
7290
  if (!reset) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
7098
7291
  bus.emit("subagents:update", { taskId: task.id, subAgents: [] });
@@ -7130,7 +7323,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7130
7323
  }
7131
7324
  const info = await getGitInfo(nextProject);
7132
7325
  if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
7133
- const projectName = path22.basename(nextProject);
7326
+ const projectName = path23.basename(nextProject);
7134
7327
  const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
7135
7328
  await teardownTask(existing.id);
7136
7329
  await removeTaskWorktree(existing);
@@ -7154,6 +7347,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7154
7347
  description: p.description?.trim() || void 0,
7155
7348
  agentCli: p.agentCli,
7156
7349
  model: p.model || void 0,
7350
+ jevRoute: void 0,
7157
7351
  effort: p.effort,
7158
7352
  autoMode: p.autoMode,
7159
7353
  ...gitPatch
@@ -7205,7 +7399,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7205
7399
  if (!/^terminal_[0-9a-f-]{36}$/.test(key)) invalid("invalid standalone terminal key");
7206
7400
  if (Boolean(p.taskId) === Boolean(p.projectPath)) invalid("provide taskId or projectPath");
7207
7401
  const cwd = str(p.taskId ? requireTask(p.taskId).projectPath : p.projectPath, "projectPath");
7208
- if (!path22.isAbsolute(cwd) || !fs18.existsSync(cwd) || !fs18.statSync(cwd).isDirectory()) {
7402
+ if (!path23.isAbsolute(cwd) || !fs19.existsSync(cwd) || !fs19.statSync(cwd).isDirectory()) {
7209
7403
  invalid("projectPath must be an existing absolute directory");
7210
7404
  }
7211
7405
  const previous = standaloneTerminals.get(key);
@@ -7244,7 +7438,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7244
7438
  "claude:sessionExists": (taskId) => {
7245
7439
  const task = requireTask(taskId);
7246
7440
  if (!task.worktreePath) return false;
7247
- return fs18.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
7441
+ return fs19.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
7248
7442
  },
7249
7443
  "sessions:list": async () => {
7250
7444
  const keys = await sessions.list();
@@ -7263,9 +7457,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7263
7457
  "git:getDiffEntries": (taskId, opts) => {
7264
7458
  const task = requireTask(taskId);
7265
7459
  if (!task.worktreePath) return [];
7266
- const fetch = opts && typeof opts === "object" ? opts.fetch : void 0;
7460
+ const fetch2 = opts && typeof opts === "object" ? opts.fetch : void 0;
7267
7461
  return getWorktreeDiffEntries(task.worktreePath, task.baseBranch ?? "HEAD", {
7268
- fetch: typeof fetch === "boolean" ? fetch : void 0
7462
+ fetch: typeof fetch2 === "boolean" ? fetch2 : void 0
7269
7463
  });
7270
7464
  },
7271
7465
  "git:getDiffFile": (taskId, filePath) => {
@@ -7291,7 +7485,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7291
7485
  const task = requireTask(taskId);
7292
7486
  if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
7293
7487
  const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
7294
- if (!fs18.existsSync(dir)) return "artifacts directory does not exist yet";
7488
+ if (!fs19.existsSync(dir)) return "artifacts directory does not exist yet";
7295
7489
  return getPlatform().openPath(dir);
7296
7490
  },
7297
7491
  "task:getSpecs": async (taskId) => {
@@ -7425,7 +7619,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7425
7619
  }
7426
7620
  await removeTaskWorktree(task);
7427
7621
  if (task.workspacePath) {
7428
- fs18.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
7622
+ fs19.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
7429
7623
  }
7430
7624
  clearConversation(task.id);
7431
7625
  removeTask(task.id);
@@ -7465,7 +7659,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7465
7659
  );
7466
7660
  },
7467
7661
  "chat:compact": (taskId) => {
7468
- const newSessionId = randomUUID4();
7662
+ const newSessionId = randomUUID5();
7469
7663
  clearMessages(requireTask(taskId).id, newSessionId);
7470
7664
  return { newSessionId };
7471
7665
  }
@@ -7478,9 +7672,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7478
7672
  // Watch the containing directory, not the file: JsonStore renames a temp
7479
7673
  // file over the target and so replaces the inode on every save.
7480
7674
  watchStore() {
7481
- const file = path22.basename(getStorePath());
7675
+ const file = path23.basename(getStorePath());
7482
7676
  let debounce = null;
7483
- storeWatcher = fs18.watch(path22.dirname(getStorePath()), (_event, filename) => {
7677
+ storeWatcher = fs19.watch(path23.dirname(getStorePath()), (_event, filename) => {
7484
7678
  if (filename !== file) return;
7485
7679
  if (debounce) clearTimeout(debounce);
7486
7680
  debounce = setTimeout(() => {
@@ -7517,9 +7711,9 @@ function createSessionBackend(sink, choice) {
7517
7711
  }
7518
7712
 
7519
7713
  // packages/core/src/web-server.ts
7520
- import fs19 from "fs";
7714
+ import fs20 from "fs";
7521
7715
  import http from "http";
7522
- import path23 from "path";
7716
+ import path24 from "path";
7523
7717
  import { timingSafeEqual } from "crypto";
7524
7718
  import { WebSocketServer } from "ws";
7525
7719
  var TOKEN_COOKIE = "vf_token";
@@ -7572,14 +7766,14 @@ function resolveStatic(root, urlPath) {
7572
7766
  return null;
7573
7767
  }
7574
7768
  if (decoded.includes("\0")) return null;
7575
- const rel = path23.posix.normalize(decoded).replace(/^\/+/, "");
7576
- const base2 = path23.resolve(root);
7577
- const candidates = [rel, path23.posix.join(rel, "index.html"), `${rel}.html`];
7769
+ const rel = path24.posix.normalize(decoded).replace(/^\/+/, "");
7770
+ const base2 = path24.resolve(root);
7771
+ const candidates = [rel, path24.posix.join(rel, "index.html"), `${rel}.html`];
7578
7772
  for (const c of candidates) {
7579
- const full = path23.resolve(base2, c);
7580
- if (full !== base2 && !full.startsWith(base2 + path23.sep)) return null;
7773
+ const full = path24.resolve(base2, c);
7774
+ if (full !== base2 && !full.startsWith(base2 + path24.sep)) return null;
7581
7775
  try {
7582
- if (fs19.statSync(full).isFile()) return full;
7776
+ if (fs20.statSync(full).isFile()) return full;
7583
7777
  } catch {
7584
7778
  }
7585
7779
  }
@@ -7640,7 +7834,7 @@ async function startWebServer(options) {
7640
7834
  send(res, 404, "Not found");
7641
7835
  return;
7642
7836
  }
7643
- const ext = path23.extname(file).toLowerCase();
7837
+ const ext = path24.extname(file).toLowerCase();
7644
7838
  const immutable = url.pathname.startsWith("/_next/static/");
7645
7839
  res.writeHead(200, {
7646
7840
  "Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
@@ -7653,7 +7847,7 @@ async function startWebServer(options) {
7653
7847
  res.end();
7654
7848
  return;
7655
7849
  }
7656
- fs19.createReadStream(file).pipe(res);
7850
+ fs20.createReadStream(file).pipe(res);
7657
7851
  }
7658
7852
  const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
7659
7853
  function reject(socket, status, reason) {
@@ -7752,8 +7946,8 @@ Content-Length: 0\r
7752
7946
  }
7753
7947
 
7754
7948
  // packages/cli/src/update-command.ts
7755
- import fs20 from "node:fs";
7756
- import path24 from "node:path";
7949
+ import fs21 from "node:fs";
7950
+ import path25 from "node:path";
7757
7951
  import { spawnSync } from "node:child_process";
7758
7952
  function npmRunner(args, inherit) {
7759
7953
  const windows = process.platform === "win32";
@@ -7766,7 +7960,7 @@ function npmRunner(args, inherit) {
7766
7960
  }
7767
7961
  function packageName(packageRoot) {
7768
7962
  try {
7769
- const pkg = JSON.parse(fs20.readFileSync(path24.join(packageRoot, "package.json"), "utf8"));
7963
+ const pkg = JSON.parse(fs21.readFileSync(path25.join(packageRoot, "package.json"), "utf8"));
7770
7964
  if (typeof pkg.name !== "string") return null;
7771
7965
  return /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(pkg.name) ? pkg.name : null;
7772
7966
  } catch {
@@ -7781,7 +7975,7 @@ function runUpdate(options) {
7781
7975
  error("vibeflow update \u53EA\u9069\u7528\u65BC\u5168\u57DF npm \u5B89\u88DD\uFF1B\u76EE\u524D\u662F\u539F\u59CB\u78BC\u7248\u672C\u3002");
7782
7976
  return 1;
7783
7977
  }
7784
- const packageRoot = path24.dirname(path24.dirname(options.cliEntry));
7978
+ const packageRoot = path25.dirname(path25.dirname(options.cliEntry));
7785
7979
  const name = packageName(packageRoot);
7786
7980
  if (!name) {
7787
7981
  error("\u7121\u6CD5\u8FA8\u8B58\u76EE\u524D VibeFlow \u7684 npm \u5957\u4EF6\u540D\u7A31\uFF0C\u5DF2\u53D6\u6D88\u66F4\u65B0\u3002");
@@ -7793,8 +7987,8 @@ function runUpdate(options) {
7793
7987
  return 1;
7794
7988
  }
7795
7989
  try {
7796
- const globalPackage = path24.join(String(root.stdout).trim(), ...name.split("/"));
7797
- if (fs20.lstatSync(globalPackage).isSymbolicLink() || fs20.realpathSync(globalPackage) !== fs20.realpathSync(packageRoot)) {
7990
+ const globalPackage = path25.join(String(root.stdout).trim(), ...name.split("/"));
7991
+ if (fs21.lstatSync(globalPackage).isSymbolicLink() || fs21.realpathSync(globalPackage) !== fs21.realpathSync(packageRoot)) {
7798
7992
  error("vibeflow update \u53EA\u9069\u7528\u65BC\u5168\u57DF npm \u5B89\u88DD\uFF1B\u76EE\u524D\u57F7\u884C\u7684\u5957\u4EF6\u4F86\u81EA\u5176\u4ED6\u4F4D\u7F6E\u3002");
7799
7993
  return 1;
7800
7994
  }
@@ -7890,7 +8084,7 @@ async function startHost(options) {
7890
8084
  let availableUpdate = null;
7891
8085
  let updateTimer = null;
7892
8086
  let stopped = false;
7893
- const installedPackage = options.sourceRoot || !options.cliEntry ? null : packageName(path25.dirname(path25.dirname(options.cliEntry)));
8087
+ const installedPackage = options.sourceRoot || !options.cliEntry ? null : packageName(path26.dirname(path26.dirname(options.cliEntry)));
7894
8088
  const checkUpdates = async () => {
7895
8089
  if (!installedPackage) return;
7896
8090
  try {
@@ -7952,7 +8146,7 @@ function loginUrl(lock, pathname = "/home/") {
7952
8146
  return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
7953
8147
  }
7954
8148
  function isWebDir(dir) {
7955
- return fs21.existsSync(path25.join(dir, "home", "index.html"));
8149
+ return fs22.existsSync(path26.join(dir, "home", "index.html"));
7956
8150
  }
7957
8151
 
7958
8152
  // packages/cli/src/remote.ts
@@ -8069,8 +8263,8 @@ function connectToHost(lock) {
8069
8263
  }
8070
8264
 
8071
8265
  // packages/cli/src/doctor.ts
8072
- import fs22 from "fs";
8073
- import path26 from "path";
8266
+ import fs23 from "fs";
8267
+ import path27 from "path";
8074
8268
  import { execFile as execFile10 } from "child_process";
8075
8269
  import { promisify as promisify7 } from "util";
8076
8270
  init_env();
@@ -8210,10 +8404,10 @@ async function runDoctor(options) {
8210
8404
  });
8211
8405
  }
8212
8406
  try {
8213
- fs22.mkdirSync(options.userDataDir, { recursive: true });
8214
- const probe = path26.join(options.userDataDir, `.doctor-${process.pid}`);
8215
- fs22.writeFileSync(probe, "");
8216
- fs22.rmSync(probe);
8407
+ fs23.mkdirSync(options.userDataDir, { recursive: true });
8408
+ const probe = path27.join(options.userDataDir, `.doctor-${process.pid}`);
8409
+ fs23.writeFileSync(probe, "");
8410
+ fs23.rmSync(probe);
8217
8411
  checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
8218
8412
  } catch (err) {
8219
8413
  checks.push({
@@ -8268,24 +8462,24 @@ Options:
8268
8462
  `.trim();
8269
8463
  function detectLayout() {
8270
8464
  const file = fileURLToPath(import.meta.url);
8271
- const here = path29.dirname(file);
8272
- const bundledWeb = path29.join(here, "web");
8273
- if (isWebDir(bundledWeb) || path29.basename(here) === "dist") {
8465
+ const here = path30.dirname(file);
8466
+ const bundledWeb = path30.join(here, "web");
8467
+ if (isWebDir(bundledWeb) || path30.basename(here) === "dist") {
8274
8468
  return {
8275
- version: readVersion(path29.join(here, "..", "package.json")),
8469
+ version: readVersion(path30.join(here, "..", "package.json")),
8276
8470
  webDir: isWebDir(bundledWeb) ? bundledWeb : null,
8277
8471
  sourceRoot: null,
8278
8472
  cliEntry: file
8279
8473
  };
8280
8474
  }
8281
- const root = path29.resolve(here, "..", "..", "..");
8475
+ const root = path30.resolve(here, "..", "..", "..");
8282
8476
  const override = process.env.VIBEFLOW_WEB_DIR;
8283
- const webDir = override && isWebDir(override) ? override : isWebDir(path29.join(root, "app")) ? path29.join(root, "app") : null;
8284
- return { version: readVersion(path29.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
8477
+ const webDir = override && isWebDir(override) ? override : isWebDir(path30.join(root, "app")) ? path30.join(root, "app") : null;
8478
+ return { version: readVersion(path30.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
8285
8479
  }
8286
8480
  function readVersion(pkgPath) {
8287
8481
  try {
8288
- return JSON.parse(fs24.readFileSync(pkgPath, "utf8")).version;
8482
+ return JSON.parse(fs25.readFileSync(pkgPath, "utf8")).version;
8289
8483
  } catch {
8290
8484
  return "0.0.0";
8291
8485
  }
@@ -8491,8 +8685,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
8491
8685
  console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
8492
8686
  return 1;
8493
8687
  }
8494
- const dir = path29.resolve(target);
8495
- if (!fs24.existsSync(dir) || !fs24.statSync(dir).isDirectory()) {
8688
+ const dir = path30.resolve(target);
8689
+ if (!fs25.existsSync(dir) || !fs25.statSync(dir).isDirectory()) {
8496
8690
  console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
8497
8691
  return 1;
8498
8692
  }