@kw-yeh/vibeflow 4.11.2 → 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
@@ -257,6 +257,9 @@ var init_pty = __esm({
257
257
  scrollback(key) {
258
258
  return this.scrollbacks.get(key) ?? null;
259
259
  }
260
+ discardScrollback(key) {
261
+ this.scrollbacks.delete(key);
262
+ }
260
263
  async isScrolledBack() {
261
264
  return false;
262
265
  }
@@ -650,7 +653,8 @@ function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
650
653
  const customPrompt = resolveSystemPrompt(systemPrompt);
651
654
  const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
652
655
  const includeTaskPrompt = opts?.includeTaskPrompt !== false;
653
- 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") : "";
654
658
  const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
655
659
  return boardEnvPrefix(task, opts) + assembleCommand(
656
660
  agent,
@@ -1099,9 +1103,9 @@ var init_progress = __esm({
1099
1103
 
1100
1104
  // packages/tui/src/task-form.ts
1101
1105
  import { spawnSync as spawnSync2 } from "child_process";
1102
- import fs23 from "fs";
1106
+ import fs24 from "fs";
1103
1107
  import os9 from "os";
1104
- import path27 from "path";
1108
+ import path28 from "path";
1105
1109
  import React, { useEffect, useRef, useState } from "react";
1106
1110
  import { Box, Text, render as render2, useApp, useInput } from "ink";
1107
1111
  function newFormState(defaultAutoMode, agents) {
@@ -1227,17 +1231,17 @@ function editorCommand(env, platform) {
1227
1231
  return env.VISUAL?.trim() || env.EDITOR?.trim() || (platform === "win32" ? "notepad" : "vi");
1228
1232
  }
1229
1233
  function editInEditor(text) {
1230
- const dir = fs23.mkdtempSync(path27.join(os9.tmpdir(), "vibeflow-"));
1231
- 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");
1232
1236
  try {
1233
- fs23.writeFileSync(file, text);
1237
+ fs24.writeFileSync(file, text);
1234
1238
  const cmd = editorCommand(process.env, process.platform);
1235
1239
  const r = spawnSync2(`${cmd} "${file}"`, { stdio: "inherit", shell: true });
1236
1240
  if (r.error) return { error: `\u7121\u6CD5\u555F\u52D5\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\uFF1A${r.error.message}` };
1237
1241
  if (r.status !== 0) return { error: `\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\u7D50\u675F\u78BC ${r.status}\uFF0C\u63CF\u8FF0\u672A\u8B8A\u66F4` };
1238
- return { text: fs23.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1242
+ return { text: fs24.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1239
1243
  } finally {
1240
- fs23.rmSync(dir, { recursive: true, force: true });
1244
+ fs24.rmSync(dir, { recursive: true, force: true });
1241
1245
  }
1242
1246
  }
1243
1247
  function cycle(list, current, step) {
@@ -1308,10 +1312,10 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1308
1312
  if (editing === "title") setS((prev) => ({ ...prev, title: draft }));
1309
1313
  else if (editing === "branch") setS((prev) => ({ ...prev, branch: value2 }));
1310
1314
  else if (editing === "project") {
1311
- if (value2) setProject(path27.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir())));
1315
+ if (value2) setProject(path28.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir())));
1312
1316
  } else if (editing === "attachments" && value2) {
1313
- const file = path27.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir()));
1314
- 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()) {
1315
1319
  setMessage(`\u627E\u4E0D\u5230\u6A94\u6848\uFF1A${file}`);
1316
1320
  return;
1317
1321
  }
@@ -1394,7 +1398,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1394
1398
  case "branch":
1395
1399
  return s.branch || "\uFF08\u7559\u7A7A\u5247\u81EA\u52D5\u547D\u540D\uFF0C\u4F8B\u5982 feature/login-fix\uFF09";
1396
1400
  case "attachments":
1397
- 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";
1398
1402
  case "description": {
1399
1403
  if (!s.description) return "\uFF08Enter \u7528\u7DE8\u8F2F\u5668\u64B0\u5BEB\uFF09";
1400
1404
  const lines = s.description.split("\n");
@@ -1430,7 +1434,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1430
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";
1431
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";
1432
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`;
1433
- 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)}`;
1434
1438
  }
1435
1439
  if (field === "branch") {
1436
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";
@@ -1558,7 +1562,7 @@ __export(src_exports, {
1558
1562
  showDiff: () => showDiff
1559
1563
  });
1560
1564
  import { spawnSync as spawnSync3 } from "child_process";
1561
- import path28 from "path";
1565
+ import path29 from "path";
1562
1566
  import React2, { useEffect as useEffect2, useState as useState2 } from "react";
1563
1567
  import { Box as Box2, Text as Text2, render as render3, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
1564
1568
  function findDetachKey(text) {
@@ -1854,7 +1858,7 @@ function showDiff(task) {
1854
1858
  });
1855
1859
  }
1856
1860
  function tmuxAttach(opts, sessionName) {
1857
- const conf = path28.join(opts.userDataDir, "tmux.conf");
1861
+ const conf = path29.join(opts.userDataDir, "tmux.conf");
1858
1862
  const inOurServer = (process.env.TMUX ?? "").split(",")[0].endsWith(`/${TMUX_SOCKET}`);
1859
1863
  const args = inOurServer ? ["-L", TMUX_SOCKET, "-f", conf, "switch-client", "-t", `=${sessionName}`] : ["-L", TMUX_SOCKET, "-f", conf, "attach-session", "-t", `=${sessionName}`];
1860
1864
  spawnSync3("tmux", args, { stdio: "inherit", env: { ...process.env, TMUX: inOurServer ? process.env.TMUX : "" } });
@@ -1985,8 +1989,8 @@ var init_src = __esm({
1985
1989
  });
1986
1990
 
1987
1991
  // packages/cli/src/main.ts
1988
- import fs24 from "fs";
1989
- import path29 from "path";
1992
+ import fs25 from "fs";
1993
+ import path30 from "path";
1990
1994
  import { fileURLToPath } from "url";
1991
1995
  import { parseArgs as parseArgs2 } from "node:util";
1992
1996
 
@@ -2460,6 +2464,9 @@ function createBridge(t) {
2460
2464
  },
2461
2465
  setBoard: (board) => t.invoke("vibeflow:setBoard", board),
2462
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"),
2463
2470
  getGithubAuthStatus: () => t.invoke("settings:githubAuthStatus"),
2464
2471
  startGithubAuthLogin: () => t.invoke("settings:startGithubAuthLogin"),
2465
2472
  cancelGithubAuthLogin: () => t.invoke("settings:cancelGithubAuthLogin"),
@@ -2576,6 +2583,7 @@ function createBridge(t) {
2576
2583
  * `sessionKey` defaults to `taskId`.
2577
2584
  */
2578
2585
  start: (payload) => t.invoke("pty:start", payload),
2586
+ startStandalone: (payload) => t.invoke("terminal:start", payload),
2579
2587
  /** Join a running session without restarting it. */
2580
2588
  peek: (sessionKey) => t.invoke("pty:peek", sessionKey),
2581
2589
  /** Which session backend runs terminals, and the sessions it has running. */
@@ -4634,8 +4642,8 @@ async function taskCommand(argv) {
4634
4642
  }
4635
4643
 
4636
4644
  // packages/cli/src/host.ts
4637
- import fs21 from "fs";
4638
- import path25 from "path";
4645
+ import fs22 from "fs";
4646
+ import path26 from "path";
4639
4647
  import { randomBytes as randomBytes2 } from "crypto";
4640
4648
 
4641
4649
  // packages/core/src/events.ts
@@ -5173,10 +5181,10 @@ async function syncBuiltinsAtStartup() {
5173
5181
  }
5174
5182
 
5175
5183
  // packages/core/src/service.ts
5176
- import fs18 from "fs";
5184
+ import fs19 from "fs";
5177
5185
  import os8 from "os";
5178
- import path22 from "path";
5179
- import { randomUUID as randomUUID4 } from "crypto";
5186
+ import path23 from "path";
5187
+ import { randomUUID as randomUUID5 } from "crypto";
5180
5188
 
5181
5189
  // packages/core/src/agent-models.ts
5182
5190
  import fs13 from "fs";
@@ -5529,6 +5537,170 @@ function createModelCatalog(opts) {
5529
5537
  };
5530
5538
  }
5531
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
+
5532
5704
  // packages/core/src/service.ts
5533
5705
  init_effort();
5534
5706
 
@@ -6031,45 +6203,45 @@ function createJiraService(options) {
6031
6203
  }
6032
6204
 
6033
6205
  // packages/core/src/board-cli.ts
6034
- import fs14 from "fs";
6035
- import path18 from "path";
6206
+ import fs15 from "fs";
6207
+ import path19 from "path";
6036
6208
  import { pathToFileURL } from "url";
6037
6209
  async function boardCliLaunchInfo() {
6038
- const storeDir = path18.dirname(getStorePath());
6210
+ const storeDir = path19.dirname(getStorePath());
6039
6211
  const entry = getPlatform().cliEntry?.();
6040
- if (entry && fs14.existsSync(entry)) return { storeDir, cliPath: entry };
6212
+ if (entry && fs15.existsSync(entry)) return { storeDir, cliPath: entry };
6041
6213
  const root = getPlatform().sourceRoot();
6042
6214
  if (!root) return { storeDir };
6043
- const cliPath = path18.join(root, "scripts", "vibeflow.mjs");
6044
- const loaderPath = path18.join(root, "test", "support", "register.mjs");
6045
- 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 };
6046
6218
  return { storeDir, cliPath, loaderPath: pathToFileURL(loaderPath).href };
6047
6219
  }
6048
6220
 
6049
6221
  // packages/core/src/decisions.ts
6050
- import fs15 from "fs";
6051
- import path19 from "path";
6222
+ import fs16 from "fs";
6223
+ import path20 from "path";
6052
6224
  var DECISIONS_FILE_SUFFIX = ".DECISIONS.md";
6053
6225
  function decisionsKey(worktreePath, branch) {
6054
- return worktreePath ? path19.basename(worktreePath) : branch.replace(/\//g, "-");
6226
+ return worktreePath ? path20.basename(worktreePath) : branch.replace(/\//g, "-");
6055
6227
  }
6056
6228
  function decisionsPath(workspacePath, key) {
6057
- return path19.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
6229
+ return path20.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
6058
6230
  }
6059
6231
  function deleteDecisions(workspacePath, key) {
6060
6232
  try {
6061
- fs15.rmSync(decisionsPath(workspacePath, key), { force: true });
6233
+ fs16.rmSync(decisionsPath(workspacePath, key), { force: true });
6062
6234
  } catch {
6063
6235
  }
6064
6236
  }
6065
6237
 
6066
6238
  // packages/core/src/specs.ts
6067
- import fs16 from "fs";
6068
- import path20 from "path";
6239
+ import fs17 from "fs";
6240
+ import path21 from "path";
6069
6241
  var SPEC_FILE_NAME = "spec.md";
6070
6242
  var MAX_SPEC_BYTES = 256 * 1024;
6071
6243
  function isSpecPath(p) {
6072
- return path20.posix.basename(p) === SPEC_FILE_NAME;
6244
+ return path21.posix.basename(p) === SPEC_FILE_NAME;
6073
6245
  }
6074
6246
  function clip2(buf) {
6075
6247
  return {
@@ -6084,10 +6256,10 @@ async function readWorktreeSpecs(worktreePath, baseBranch) {
6084
6256
  for (const entry of entries) {
6085
6257
  if (entry.status === "D" || !isSpecPath(entry.path)) continue;
6086
6258
  try {
6087
- const full = path20.join(worktreePath, entry.path);
6088
- const stat = fs16.statSync(full);
6259
+ const full = path21.join(worktreePath, entry.path);
6260
+ const stat = fs17.statSync(full);
6089
6261
  if (!stat.isFile()) continue;
6090
- 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 });
6091
6263
  } catch {
6092
6264
  }
6093
6265
  }
@@ -6153,7 +6325,7 @@ async function readSpecsAt(cwd, ref, paths) {
6153
6325
  init_env();
6154
6326
  init_git_bash();
6155
6327
  import { spawn as spawn7 } from "child_process";
6156
- import { randomUUID as randomUUID3 } from "crypto";
6328
+ import { randomUUID as randomUUID4 } from "crypto";
6157
6329
 
6158
6330
  // packages/core/src/chat-store.ts
6159
6331
  var _chatStore = null;
@@ -6283,7 +6455,7 @@ function sendChatMessage(opts, sender) {
6283
6455
  const fullText = attachmentLines.length ? `${text}
6284
6456
 
6285
6457
  ${attachmentLines.join("\n")}` : text;
6286
- const userMsgId = randomUUID3().slice(0, 8);
6458
+ const userMsgId = randomUUID4().slice(0, 8);
6287
6459
  appendMessage(taskId, {
6288
6460
  id: userMsgId,
6289
6461
  role: "user",
@@ -6402,7 +6574,7 @@ ${attachmentLines.join("\n")}` : text;
6402
6574
  proc.on("close", (code2) => {
6403
6575
  const assistantText = accumulated || "\uFF08\u7121\u56DE\u61C9\uFF09";
6404
6576
  appendMessage(taskId, {
6405
- id: randomUUID3().slice(0, 8),
6577
+ id: randomUUID4().slice(0, 8),
6406
6578
  role: "assistant",
6407
6579
  text: assistantText,
6408
6580
  ts: Date.now()
@@ -6439,17 +6611,17 @@ init_launch();
6439
6611
  // packages/core/src/progress-tracker.ts
6440
6612
  init_progress();
6441
6613
  init_launch();
6442
- import fs17 from "fs";
6614
+ import fs18 from "fs";
6443
6615
  import os7 from "os";
6444
- import path21 from "path";
6616
+ import path22 from "path";
6445
6617
  var SINCE_SLACK_MS = 5e3;
6446
6618
  function claudeProjectDir(home, cwd) {
6447
- 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, "-"));
6448
6620
  }
6449
6621
  function pathAliases(p) {
6450
- const resolved = path21.resolve(p);
6622
+ const resolved = path22.resolve(p);
6451
6623
  try {
6452
- const real = fs17.realpathSync(resolved);
6624
+ const real = fs18.realpathSync(resolved);
6453
6625
  return real === resolved ? [resolved] : [resolved, real];
6454
6626
  } catch {
6455
6627
  return [resolved];
@@ -6465,7 +6637,7 @@ function samePath(a, b) {
6465
6637
  }
6466
6638
  function listFiles(dir, test) {
6467
6639
  try {
6468
- 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));
6469
6641
  } catch {
6470
6642
  return [];
6471
6643
  }
@@ -6473,22 +6645,22 @@ function listFiles(dir, test) {
6473
6645
  function firstLine(file) {
6474
6646
  let fd;
6475
6647
  try {
6476
- fd = fs17.openSync(file, "r");
6648
+ fd = fs18.openSync(file, "r");
6477
6649
  const buf = Buffer.alloc(64 * 1024);
6478
- const n = fs17.readSync(fd, buf, 0, buf.length, 0);
6650
+ const n = fs18.readSync(fd, buf, 0, buf.length, 0);
6479
6651
  const text = buf.subarray(0, n).toString("utf8");
6480
6652
  const nl = text.indexOf("\n");
6481
6653
  return nl === -1 ? null : text.slice(0, nl);
6482
6654
  } catch {
6483
6655
  return null;
6484
6656
  } finally {
6485
- if (fd !== void 0) fs17.closeSync(fd);
6657
+ if (fd !== void 0) fs18.closeSync(fd);
6486
6658
  }
6487
6659
  }
6488
6660
  function advance(file, cursor, reduce, initial) {
6489
6661
  let size;
6490
6662
  try {
6491
- size = fs17.statSync(file).size;
6663
+ size = fs18.statSync(file).size;
6492
6664
  } catch {
6493
6665
  return;
6494
6666
  }
@@ -6500,9 +6672,9 @@ function advance(file, cursor, reduce, initial) {
6500
6672
  if (size === cursor.offset) return;
6501
6673
  let fd;
6502
6674
  try {
6503
- fd = fs17.openSync(file, "r");
6675
+ fd = fs18.openSync(file, "r");
6504
6676
  const chunk = Buffer.alloc(size - cursor.offset);
6505
- const n = fs17.readSync(fd, chunk, 0, chunk.length, cursor.offset);
6677
+ const n = fs18.readSync(fd, chunk, 0, chunk.length, cursor.offset);
6506
6678
  cursor.offset += n;
6507
6679
  const data = Buffer.concat([cursor.rest, chunk.subarray(0, n)]);
6508
6680
  const nl = data.lastIndexOf(10);
@@ -6516,7 +6688,7 @@ function advance(file, cursor, reduce, initial) {
6516
6688
  }
6517
6689
  } catch {
6518
6690
  } finally {
6519
- if (fd !== void 0) fs17.closeSync(fd);
6691
+ if (fd !== void 0) fs18.closeSync(fd);
6520
6692
  }
6521
6693
  }
6522
6694
  function newCursor(state) {
@@ -6535,14 +6707,14 @@ function codexDayDirs(sessionsRoot, since) {
6535
6707
  const cur = new Date(start.getFullYear(), start.getMonth(), start.getDate());
6536
6708
  while (cur <= end) {
6537
6709
  const pad = (n) => String(n).padStart(2, "0");
6538
- 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())));
6539
6711
  cur.setDate(cur.getDate() + 1);
6540
6712
  }
6541
6713
  return days;
6542
6714
  }
6543
6715
  function createProgressTracker(opts) {
6544
6716
  const home = opts.homeDir ?? os7.homedir();
6545
- const codexHomes = opts.codexHomes ?? (() => [path21.join(home, ".codex")]);
6717
+ const codexHomes = opts.codexHomes ?? (() => [path22.join(home, ".codex")]);
6546
6718
  const pollMs = opts.pollMs ?? 2e3;
6547
6719
  const entries = /* @__PURE__ */ new Map();
6548
6720
  const belongsToRun = (target, startedAt) => target.since == null || startedAt != null && startedAt >= target.since - SINCE_SLACK_MS;
@@ -6551,7 +6723,7 @@ function createProgressTracker(opts) {
6551
6723
  const dirs = pathAliases(target.worktreePath).map((p) => claudeProjectDir(home, p));
6552
6724
  for (const file of dirs.flatMap((dir) => listFiles(dir, (n) => n.endsWith(".jsonl")))) {
6553
6725
  if (entry.claude.has(file) || entry.rejected.has(file)) continue;
6554
- const session = path21.basename(file, ".jsonl");
6726
+ const session = path22.basename(file, ".jsonl");
6555
6727
  if (session !== target.sessionId) {
6556
6728
  const head = firstLine(file);
6557
6729
  if (head == null) continue;
@@ -6564,8 +6736,8 @@ function createProgressTracker(opts) {
6564
6736
  entry.claude.set(file, newCursor(initialClaudeState()));
6565
6737
  }
6566
6738
  for (const file of Array.from(entry.claude.keys())) {
6567
- if (file.includes(`${path21.sep}subagents${path21.sep}`)) continue;
6568
- 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");
6569
6741
  for (const f of listFiles(sub, (n) => n.endsWith(".jsonl"))) {
6570
6742
  if (!entry.claude.has(f)) entry.claude.set(f, newCursor(initialClaudeState()));
6571
6743
  }
@@ -6574,7 +6746,7 @@ function createProgressTracker(opts) {
6574
6746
  function discoverCodex(entry) {
6575
6747
  const { target } = entry;
6576
6748
  for (const codexHome of codexHomes()) {
6577
- for (const day of codexDayDirs(path21.join(codexHome, "sessions"), target.since)) {
6749
+ for (const day of codexDayDirs(path22.join(codexHome, "sessions"), target.since)) {
6578
6750
  for (const file of listFiles(day, (n) => n.startsWith("rollout-") && n.endsWith(".jsonl"))) {
6579
6751
  if (entry.codex.has(file) || entry.rejected.has(file)) continue;
6580
6752
  const head = firstLine(file);
@@ -6591,11 +6763,11 @@ function createProgressTracker(opts) {
6591
6763
  }
6592
6764
  }
6593
6765
  function drainHooks(entry) {
6594
- const dir = path21.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
6766
+ const dir = path22.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
6595
6767
  for (const file of listFiles(dir, (n) => n.endsWith(".json")).sort()) {
6596
6768
  try {
6597
- const at = fs17.statSync(file).mtimeMs;
6598
- const event = JSON.parse(fs17.readFileSync(file, "utf8"));
6769
+ const at = fs18.statSync(file).mtimeMs;
6770
+ const event = JSON.parse(fs18.readFileSync(file, "utf8"));
6599
6771
  const foreign = entry.target.sessionId && typeof event.session_id === "string" && event.session_id !== entry.target.sessionId;
6600
6772
  const activity = foreign ? null : activityFromHook(event, at);
6601
6773
  if (activity) entry.hookActivity = latestActivity(entry.hookActivity ?? activity, activity);
@@ -6603,7 +6775,7 @@ function createProgressTracker(opts) {
6603
6775
  continue;
6604
6776
  }
6605
6777
  try {
6606
- fs17.unlinkSync(file);
6778
+ fs18.unlinkSync(file);
6607
6779
  } catch {
6608
6780
  }
6609
6781
  }
@@ -6615,8 +6787,8 @@ function createProgressTracker(opts) {
6615
6787
  if (target.agent === "claude") {
6616
6788
  for (const [file, cursor] of entry.claude) {
6617
6789
  runUsage = addUsage(runUsage, claudeUsageTotal(cursor.state));
6618
- if (file.includes(`${path21.sep}subagents${path21.sep}`)) continue;
6619
- 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;
6620
6792
  const s = cursor.state;
6621
6793
  if (isExecutor || !target.sessionId && (!primary || (s.startedAt ?? 0) > (primary.startedAt ?? 0))) {
6622
6794
  primary = { todos: s.todos, contextTokens: s.contextTokens, activity: s.activity, startedAt: s.startedAt };
@@ -6695,10 +6867,10 @@ function createProgressTracker(opts) {
6695
6867
  };
6696
6868
  entry.timer.unref?.();
6697
6869
  entries.set(target.taskId, entry);
6698
- const eventsDir = path21.join(target.worktreePath, PROGRESS_EVENTS_DIR);
6870
+ const eventsDir = path22.join(target.worktreePath, PROGRESS_EVENTS_DIR);
6699
6871
  try {
6700
- fs17.mkdirSync(eventsDir, { recursive: true });
6701
- entry.watcher = fs17.watch(eventsDir, () => {
6872
+ fs18.mkdirSync(eventsDir, { recursive: true });
6873
+ entry.watcher = fs18.watch(eventsDir, () => {
6702
6874
  if (entry.pending) return;
6703
6875
  entry.pending = setTimeout(() => {
6704
6876
  entry.pending = null;
@@ -6783,9 +6955,12 @@ function validBoard(value) {
6783
6955
  }
6784
6956
  return next;
6785
6957
  }
6786
- function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatus, acliRunner, jiraAuthStatus, modelCatalog }) {
6958
+ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatus, acliRunner, jiraAuthStatus, modelCatalog, jevRouter }) {
6787
6959
  const sink = bus.sink();
6788
6960
  const models = modelCatalog ?? createModelCatalog({ version });
6961
+ const router = jevRouter ?? createJevRouter({ getApiKey: readJevApiKey });
6962
+ const routing = /* @__PURE__ */ new Map();
6963
+ const standaloneTerminals = /* @__PURE__ */ new Map();
6789
6964
  let libraryCodexHome = null;
6790
6965
  void libraryRoot().then((root) => {
6791
6966
  libraryCodexHome = libraryPaths(root).codexHome;
@@ -6794,7 +6969,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6794
6969
  const home = homeDir ?? os8.homedir();
6795
6970
  const progress = createProgressTracker({
6796
6971
  homeDir: home,
6797
- codexHomes: () => [path22.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
6972
+ codexHomes: () => [path23.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
6798
6973
  onUpdate: (taskId, value) => bus.emit("progress:update", { taskId, progress: value }),
6799
6974
  onNotify: (taskId, list) => {
6800
6975
  const prefs = resolveNotificationSettings(getSettings());
@@ -6840,7 +7015,10 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6840
7015
  } catch {
6841
7016
  }
6842
7017
  async function teardownSession(key) {
7018
+ const standalone = standaloneTerminals.has(key);
6843
7019
  await sessions.kill(key);
7020
+ if (standalone) sessions.discardScrollback?.(key);
7021
+ standaloneTerminals.delete(key);
6844
7022
  unwatchSubAgents(key);
6845
7023
  }
6846
7024
  async function teardownTask(taskId) {
@@ -6855,39 +7033,57 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6855
7033
  }
6856
7034
  function requireSessionKey(sessionKey) {
6857
7035
  const key = str(sessionKey, "sessionKey");
7036
+ if (standaloneTerminals.has(key)) return key;
6858
7037
  requireTask(key.split(":")[0]);
6859
7038
  return key;
6860
7039
  }
6861
- function launchEffort(task) {
6862
- const model = models.findModel(taskAgent(task), taskModel(task));
7040
+ function launchEffort(task, selectedModel = taskModel(task)) {
7041
+ const model = models.findModel(taskAgent(task), selectedModel);
6863
7042
  return model?.efforts ? clampEffort(task.effort, model.efforts) : task.effort;
6864
7043
  }
6865
7044
  async function launchCommand(task, intent) {
6866
7045
  const settings = getSettings();
6867
7046
  const library = await libraryLaunchInfo(task.worktreePath);
6868
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;
6869
7064
  return buildAgentCommand(
6870
- { ...task, effort: launchEffort(task) },
7065
+ { ...task, model: selectedModel, effort: launchEffort(task, selectedModel) },
6871
7066
  settings.systemPrompt ?? "",
6872
7067
  {
6873
7068
  resume: intent.resume === true,
6874
7069
  includeTaskPrompt: intent.includeTaskPrompt !== false,
6875
7070
  library: library ?? void 0,
6876
7071
  boardCli,
6877
- autoMode: task.autoMode ?? settings.autoMode
7072
+ autoMode: task.autoMode ?? settings.autoMode,
7073
+ preflightPlan: !intent.resume ? route?.plan : void 0
6878
7074
  },
6879
7075
  task.workspacePath
6880
7076
  );
6881
7077
  }
6882
7078
  function claudeSessionFile(cwd, sessionId) {
6883
7079
  const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
6884
- return path22.join(os8.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
7080
+ return path23.join(os8.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
6885
7081
  }
6886
7082
  async function removeTaskWorktree(task) {
6887
7083
  if (!task.projectPath || !task.worktreePath) return;
6888
7084
  const branch = task.branch || fallbackBranchName(task.id);
6889
7085
  await removeWorktree(task.projectPath, task.worktreePath);
6890
- deleteArtifacts(task.workspacePath ?? path22.dirname(task.worktreePath), task.worktreePath);
7086
+ deleteArtifacts(task.workspacePath ?? path23.dirname(task.worktreePath), task.worktreePath);
6891
7087
  await deleteBranch(task.projectPath, branch);
6892
7088
  }
6893
7089
  function columnOf(taskId) {
@@ -6968,6 +7164,12 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6968
7164
  setSettings(value);
6969
7165
  return getState();
6970
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(),
6971
7173
  "settings:githubAuthStatus": () => getGitHubCliAuthStatus(),
6972
7174
  "settings:startGithubAuthLogin": () => {
6973
7175
  startGitHubCliLogin((payload) => bus.emit("github-auth:event", payload));
@@ -7023,8 +7225,8 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7023
7225
  "projects:listRecent": () => listRecentProjects(getStore()),
7024
7226
  // `vibeflow open <path>` while a host runs: only the host writes the store.
7025
7227
  "projects:record": (projectPath) => {
7026
- const p = path22.resolve(str(projectPath, "projectPath"));
7027
- 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}`);
7028
7230
  recordRecentProject(getStore(), p);
7029
7231
  return listRecentProjects(getStore());
7030
7232
  },
@@ -7069,7 +7271,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7069
7271
  if (!task.worktreePath) {
7070
7272
  await teardownTask(task.id);
7071
7273
  progress.untrack(task.id);
7072
- updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4() });
7274
+ updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID5(), jevRoute: void 0 });
7073
7275
  const reset2 = findTask(task.id);
7074
7276
  if (!reset2) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
7075
7277
  return { state: getState(), task: reset2 };
@@ -7077,13 +7279,13 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7077
7279
  if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
7078
7280
  await teardownTask(task.id);
7079
7281
  await resetWorktreeToBase(task.worktreePath, task.baseBranch);
7080
- const workspacePath = task.workspacePath ?? path22.dirname(task.worktreePath);
7282
+ const workspacePath = task.workspacePath ?? path23.dirname(task.worktreePath);
7081
7283
  deleteArtifacts(workspacePath, task.worktreePath);
7082
7284
  deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
7083
7285
  resetSubAgents(task.worktreePath);
7084
7286
  const usage = usageThroughCurrentRun(task);
7085
7287
  progress.untrack(task.id);
7086
- updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4(), ...usage ? { usage } : {} });
7288
+ updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID5(), jevRoute: void 0, ...usage ? { usage } : {} });
7087
7289
  const reset = findTask(task.id);
7088
7290
  if (!reset) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
7089
7291
  bus.emit("subagents:update", { taskId: task.id, subAgents: [] });
@@ -7121,7 +7323,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7121
7323
  }
7122
7324
  const info = await getGitInfo(nextProject);
7123
7325
  if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
7124
- const projectName = path22.basename(nextProject);
7326
+ const projectName = path23.basename(nextProject);
7125
7327
  const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
7126
7328
  await teardownTask(existing.id);
7127
7329
  await removeTaskWorktree(existing);
@@ -7145,6 +7347,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7145
7347
  description: p.description?.trim() || void 0,
7146
7348
  agentCli: p.agentCli,
7147
7349
  model: p.model || void 0,
7350
+ jevRoute: void 0,
7148
7351
  effort: p.effort,
7149
7352
  autoMode: p.autoMode,
7150
7353
  ...gitPatch
@@ -7190,6 +7393,26 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7190
7393
  }
7191
7394
  return result;
7192
7395
  },
7396
+ "terminal:start": async (payload) => {
7397
+ const p = obj(payload, "payload");
7398
+ const key = str(p.sessionKey, "sessionKey");
7399
+ if (!/^terminal_[0-9a-f-]{36}$/.test(key)) invalid("invalid standalone terminal key");
7400
+ if (Boolean(p.taskId) === Boolean(p.projectPath)) invalid("provide taskId or projectPath");
7401
+ const cwd = str(p.taskId ? requireTask(p.taskId).projectPath : p.projectPath, "projectPath");
7402
+ if (!path23.isAbsolute(cwd) || !fs19.existsSync(cwd) || !fs19.statSync(cwd).isDirectory()) {
7403
+ invalid("projectPath must be an existing absolute directory");
7404
+ }
7405
+ const previous = standaloneTerminals.get(key);
7406
+ if (previous && previous !== cwd) invalid("terminal key already belongs to another directory");
7407
+ const result = await sessions.start(key, {
7408
+ cwd,
7409
+ cols: typeof p.cols === "number" ? p.cols : void 0,
7410
+ rows: typeof p.rows === "number" ? p.rows : void 0
7411
+ });
7412
+ standaloneTerminals.set(key, cwd);
7413
+ if (!p.taskId) recordRecentProject(getStore(), cwd);
7414
+ return result;
7415
+ },
7193
7416
  // Join a session as it is: no restart, for a frontend that attaches
7194
7417
  // after it started (the TUI's terminal view).
7195
7418
  "pty:peek": async (sessionKey) => {
@@ -7215,7 +7438,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7215
7438
  "claude:sessionExists": (taskId) => {
7216
7439
  const task = requireTask(taskId);
7217
7440
  if (!task.worktreePath) return false;
7218
- return fs18.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
7441
+ return fs19.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
7219
7442
  },
7220
7443
  "sessions:list": async () => {
7221
7444
  const keys = await sessions.list();
@@ -7234,9 +7457,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7234
7457
  "git:getDiffEntries": (taskId, opts) => {
7235
7458
  const task = requireTask(taskId);
7236
7459
  if (!task.worktreePath) return [];
7237
- const fetch = opts && typeof opts === "object" ? opts.fetch : void 0;
7460
+ const fetch2 = opts && typeof opts === "object" ? opts.fetch : void 0;
7238
7461
  return getWorktreeDiffEntries(task.worktreePath, task.baseBranch ?? "HEAD", {
7239
- fetch: typeof fetch === "boolean" ? fetch : void 0
7462
+ fetch: typeof fetch2 === "boolean" ? fetch2 : void 0
7240
7463
  });
7241
7464
  },
7242
7465
  "git:getDiffFile": (taskId, filePath) => {
@@ -7262,7 +7485,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7262
7485
  const task = requireTask(taskId);
7263
7486
  if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
7264
7487
  const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
7265
- if (!fs18.existsSync(dir)) return "artifacts directory does not exist yet";
7488
+ if (!fs19.existsSync(dir)) return "artifacts directory does not exist yet";
7266
7489
  return getPlatform().openPath(dir);
7267
7490
  },
7268
7491
  "task:getSpecs": async (taskId) => {
@@ -7396,7 +7619,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7396
7619
  }
7397
7620
  await removeTaskWorktree(task);
7398
7621
  if (task.workspacePath) {
7399
- fs18.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
7622
+ fs19.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
7400
7623
  }
7401
7624
  clearConversation(task.id);
7402
7625
  removeTask(task.id);
@@ -7436,7 +7659,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7436
7659
  );
7437
7660
  },
7438
7661
  "chat:compact": (taskId) => {
7439
- const newSessionId = randomUUID4();
7662
+ const newSessionId = randomUUID5();
7440
7663
  clearMessages(requireTask(taskId).id, newSessionId);
7441
7664
  return { newSessionId };
7442
7665
  }
@@ -7449,9 +7672,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7449
7672
  // Watch the containing directory, not the file: JsonStore renames a temp
7450
7673
  // file over the target and so replaces the inode on every save.
7451
7674
  watchStore() {
7452
- const file = path22.basename(getStorePath());
7675
+ const file = path23.basename(getStorePath());
7453
7676
  let debounce = null;
7454
- storeWatcher = fs18.watch(path22.dirname(getStorePath()), (_event, filename) => {
7677
+ storeWatcher = fs19.watch(path23.dirname(getStorePath()), (_event, filename) => {
7455
7678
  if (filename !== file) return;
7456
7679
  if (debounce) clearTimeout(debounce);
7457
7680
  debounce = setTimeout(() => {
@@ -7488,9 +7711,9 @@ function createSessionBackend(sink, choice) {
7488
7711
  }
7489
7712
 
7490
7713
  // packages/core/src/web-server.ts
7491
- import fs19 from "fs";
7714
+ import fs20 from "fs";
7492
7715
  import http from "http";
7493
- import path23 from "path";
7716
+ import path24 from "path";
7494
7717
  import { timingSafeEqual } from "crypto";
7495
7718
  import { WebSocketServer } from "ws";
7496
7719
  var TOKEN_COOKIE = "vf_token";
@@ -7543,14 +7766,14 @@ function resolveStatic(root, urlPath) {
7543
7766
  return null;
7544
7767
  }
7545
7768
  if (decoded.includes("\0")) return null;
7546
- const rel = path23.posix.normalize(decoded).replace(/^\/+/, "");
7547
- const base2 = path23.resolve(root);
7548
- 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`];
7549
7772
  for (const c of candidates) {
7550
- const full = path23.resolve(base2, c);
7551
- 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;
7552
7775
  try {
7553
- if (fs19.statSync(full).isFile()) return full;
7776
+ if (fs20.statSync(full).isFile()) return full;
7554
7777
  } catch {
7555
7778
  }
7556
7779
  }
@@ -7611,7 +7834,7 @@ async function startWebServer(options) {
7611
7834
  send(res, 404, "Not found");
7612
7835
  return;
7613
7836
  }
7614
- const ext = path23.extname(file).toLowerCase();
7837
+ const ext = path24.extname(file).toLowerCase();
7615
7838
  const immutable = url.pathname.startsWith("/_next/static/");
7616
7839
  res.writeHead(200, {
7617
7840
  "Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
@@ -7624,7 +7847,7 @@ async function startWebServer(options) {
7624
7847
  res.end();
7625
7848
  return;
7626
7849
  }
7627
- fs19.createReadStream(file).pipe(res);
7850
+ fs20.createReadStream(file).pipe(res);
7628
7851
  }
7629
7852
  const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
7630
7853
  function reject(socket, status, reason) {
@@ -7723,8 +7946,8 @@ Content-Length: 0\r
7723
7946
  }
7724
7947
 
7725
7948
  // packages/cli/src/update-command.ts
7726
- import fs20 from "node:fs";
7727
- import path24 from "node:path";
7949
+ import fs21 from "node:fs";
7950
+ import path25 from "node:path";
7728
7951
  import { spawnSync } from "node:child_process";
7729
7952
  function npmRunner(args, inherit) {
7730
7953
  const windows = process.platform === "win32";
@@ -7737,7 +7960,7 @@ function npmRunner(args, inherit) {
7737
7960
  }
7738
7961
  function packageName(packageRoot) {
7739
7962
  try {
7740
- 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"));
7741
7964
  if (typeof pkg.name !== "string") return null;
7742
7965
  return /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(pkg.name) ? pkg.name : null;
7743
7966
  } catch {
@@ -7752,7 +7975,7 @@ function runUpdate(options) {
7752
7975
  error("vibeflow update \u53EA\u9069\u7528\u65BC\u5168\u57DF npm \u5B89\u88DD\uFF1B\u76EE\u524D\u662F\u539F\u59CB\u78BC\u7248\u672C\u3002");
7753
7976
  return 1;
7754
7977
  }
7755
- const packageRoot = path24.dirname(path24.dirname(options.cliEntry));
7978
+ const packageRoot = path25.dirname(path25.dirname(options.cliEntry));
7756
7979
  const name = packageName(packageRoot);
7757
7980
  if (!name) {
7758
7981
  error("\u7121\u6CD5\u8FA8\u8B58\u76EE\u524D VibeFlow \u7684 npm \u5957\u4EF6\u540D\u7A31\uFF0C\u5DF2\u53D6\u6D88\u66F4\u65B0\u3002");
@@ -7764,8 +7987,8 @@ function runUpdate(options) {
7764
7987
  return 1;
7765
7988
  }
7766
7989
  try {
7767
- const globalPackage = path24.join(String(root.stdout).trim(), ...name.split("/"));
7768
- 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)) {
7769
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");
7770
7993
  return 1;
7771
7994
  }
@@ -7861,7 +8084,7 @@ async function startHost(options) {
7861
8084
  let availableUpdate = null;
7862
8085
  let updateTimer = null;
7863
8086
  let stopped = false;
7864
- 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)));
7865
8088
  const checkUpdates = async () => {
7866
8089
  if (!installedPackage) return;
7867
8090
  try {
@@ -7923,7 +8146,7 @@ function loginUrl(lock, pathname = "/home/") {
7923
8146
  return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
7924
8147
  }
7925
8148
  function isWebDir(dir) {
7926
- return fs21.existsSync(path25.join(dir, "home", "index.html"));
8149
+ return fs22.existsSync(path26.join(dir, "home", "index.html"));
7927
8150
  }
7928
8151
 
7929
8152
  // packages/cli/src/remote.ts
@@ -8040,8 +8263,8 @@ function connectToHost(lock) {
8040
8263
  }
8041
8264
 
8042
8265
  // packages/cli/src/doctor.ts
8043
- import fs22 from "fs";
8044
- import path26 from "path";
8266
+ import fs23 from "fs";
8267
+ import path27 from "path";
8045
8268
  import { execFile as execFile10 } from "child_process";
8046
8269
  import { promisify as promisify7 } from "util";
8047
8270
  init_env();
@@ -8181,10 +8404,10 @@ async function runDoctor(options) {
8181
8404
  });
8182
8405
  }
8183
8406
  try {
8184
- fs22.mkdirSync(options.userDataDir, { recursive: true });
8185
- const probe = path26.join(options.userDataDir, `.doctor-${process.pid}`);
8186
- fs22.writeFileSync(probe, "");
8187
- 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);
8188
8411
  checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
8189
8412
  } catch (err) {
8190
8413
  checks.push({
@@ -8239,24 +8462,24 @@ Options:
8239
8462
  `.trim();
8240
8463
  function detectLayout() {
8241
8464
  const file = fileURLToPath(import.meta.url);
8242
- const here = path29.dirname(file);
8243
- const bundledWeb = path29.join(here, "web");
8244
- 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") {
8245
8468
  return {
8246
- version: readVersion(path29.join(here, "..", "package.json")),
8469
+ version: readVersion(path30.join(here, "..", "package.json")),
8247
8470
  webDir: isWebDir(bundledWeb) ? bundledWeb : null,
8248
8471
  sourceRoot: null,
8249
8472
  cliEntry: file
8250
8473
  };
8251
8474
  }
8252
- const root = path29.resolve(here, "..", "..", "..");
8475
+ const root = path30.resolve(here, "..", "..", "..");
8253
8476
  const override = process.env.VIBEFLOW_WEB_DIR;
8254
- const webDir = override && isWebDir(override) ? override : isWebDir(path29.join(root, "app")) ? path29.join(root, "app") : null;
8255
- 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 };
8256
8479
  }
8257
8480
  function readVersion(pkgPath) {
8258
8481
  try {
8259
- return JSON.parse(fs24.readFileSync(pkgPath, "utf8")).version;
8482
+ return JSON.parse(fs25.readFileSync(pkgPath, "utf8")).version;
8260
8483
  } catch {
8261
8484
  return "0.0.0";
8262
8485
  }
@@ -8462,8 +8685,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
8462
8685
  console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
8463
8686
  return 1;
8464
8687
  }
8465
- const dir = path29.resolve(target);
8466
- if (!fs24.existsSync(dir) || !fs24.statSync(dir).isDirectory()) {
8688
+ const dir = path30.resolve(target);
8689
+ if (!fs25.existsSync(dir) || !fs25.statSync(dir).isDirectory()) {
8467
8690
  console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
8468
8691
  return 1;
8469
8692
  }