@kw-yeh/vibeflow 4.11.28 → 4.11.30

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
@@ -1107,9 +1107,9 @@ var init_progress = __esm({
1107
1107
 
1108
1108
  // packages/tui/src/task-form.ts
1109
1109
  import { spawnSync as spawnSync2 } from "child_process";
1110
- import fs26 from "fs";
1111
- import os8 from "os";
1112
- import path29 from "path";
1110
+ import fs27 from "fs";
1111
+ import os9 from "os";
1112
+ import path30 from "path";
1113
1113
  import React, { useEffect, useRef, useState } from "react";
1114
1114
  import { Box, Text, render as render2, useApp, useInput } from "ink";
1115
1115
  function newFormState(defaultAutoMode, agents) {
@@ -1235,17 +1235,17 @@ function editorCommand(env, platform) {
1235
1235
  return env.VISUAL?.trim() || env.EDITOR?.trim() || (platform === "win32" ? "notepad" : "vi");
1236
1236
  }
1237
1237
  function editInEditor(text) {
1238
- const dir = fs26.mkdtempSync(path29.join(os8.tmpdir(), "vibeflow-"));
1239
- const file = path29.join(dir, "description.md");
1238
+ const dir = fs27.mkdtempSync(path30.join(os9.tmpdir(), "vibeflow-"));
1239
+ const file = path30.join(dir, "description.md");
1240
1240
  try {
1241
- fs26.writeFileSync(file, text);
1241
+ fs27.writeFileSync(file, text);
1242
1242
  const cmd = editorCommand(process.env, process.platform);
1243
1243
  const r = spawnSync2(`${cmd} "${file}"`, { stdio: "inherit", shell: true });
1244
1244
  if (r.error) return { error: `\u7121\u6CD5\u555F\u52D5\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\uFF1A${r.error.message}` };
1245
1245
  if (r.status !== 0) return { error: `\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\u7D50\u675F\u78BC ${r.status}\uFF0C\u63CF\u8FF0\u672A\u8B8A\u66F4` };
1246
- return { text: fs26.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1246
+ return { text: fs27.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1247
1247
  } finally {
1248
- fs26.rmSync(dir, { recursive: true, force: true });
1248
+ fs27.rmSync(dir, { recursive: true, force: true });
1249
1249
  }
1250
1250
  }
1251
1251
  function cycle(list, current, step) {
@@ -1316,10 +1316,10 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1316
1316
  if (editing === "title") setS((prev) => ({ ...prev, title: draft }));
1317
1317
  else if (editing === "branch") setS((prev) => ({ ...prev, branch: value2 }));
1318
1318
  else if (editing === "project") {
1319
- if (value2) setProject(path29.resolve(value2.replace(/^~(?=$|\/)/, os8.homedir())));
1319
+ if (value2) setProject(path30.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir())));
1320
1320
  } else if (editing === "attachments" && value2) {
1321
- const file = path29.resolve(value2.replace(/^~(?=$|\/)/, os8.homedir()));
1322
- if (!fs26.existsSync(file) || !fs26.statSync(file).isFile()) {
1321
+ const file = path30.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir()));
1322
+ if (!fs27.existsSync(file) || !fs27.statSync(file).isFile()) {
1323
1323
  setMessage(`\u627E\u4E0D\u5230\u6A94\u6848\uFF1A${file}`);
1324
1324
  return;
1325
1325
  }
@@ -1402,7 +1402,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1402
1402
  case "branch":
1403
1403
  return s.branch || "\uFF08\u7559\u7A7A\u5247\u81EA\u52D5\u547D\u540D\uFF0C\u4F8B\u5982 feature/login-fix\uFF09";
1404
1404
  case "attachments":
1405
- return s.attachments.length ? s.attachments.map((a) => path29.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
1405
+ return s.attachments.length ? s.attachments.map((a) => path30.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
1406
1406
  case "description": {
1407
1407
  if (!s.description) return "\uFF08Enter \u7528\u7DE8\u8F2F\u5668\u64B0\u5BEB\uFF09";
1408
1408
  const lines = s.description.split("\n");
@@ -1437,7 +1437,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1437
1437
  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";
1438
1438
  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";
1439
1439
  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`;
1440
- if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path29.basename(s.projectPath)}`;
1440
+ if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path30.basename(s.projectPath)}`;
1441
1441
  }
1442
1442
  if (field === "branch") {
1443
1443
  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";
@@ -1566,7 +1566,7 @@ __export(src_exports, {
1566
1566
  showDiff: () => showDiff
1567
1567
  });
1568
1568
  import { spawnSync as spawnSync3 } from "child_process";
1569
- import path30 from "path";
1569
+ import path31 from "path";
1570
1570
  import React2, { useEffect as useEffect2, useState as useState2 } from "react";
1571
1571
  import { Box as Box2, Text as Text2, render as render3, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
1572
1572
  function findDetachKey(text) {
@@ -1862,7 +1862,7 @@ function showDiff(task) {
1862
1862
  });
1863
1863
  }
1864
1864
  function tmuxAttach(opts, sessionName) {
1865
- const conf = path30.join(opts.userDataDir, "tmux.conf");
1865
+ const conf = path31.join(opts.userDataDir, "tmux.conf");
1866
1866
  const inOurServer = (process.env.TMUX ?? "").split(",")[0].endsWith(`/${TMUX_SOCKET}`);
1867
1867
  const args = inOurServer ? ["-L", TMUX_SOCKET, "-f", conf, "switch-client", "-t", `=${sessionName}`] : ["-L", TMUX_SOCKET, "-f", conf, "attach-session", "-t", `=${sessionName}`];
1868
1868
  spawnSync3("tmux", args, { stdio: "inherit", env: { ...process.env, TMUX: inOurServer ? process.env.TMUX : "" } });
@@ -1993,9 +1993,9 @@ var init_src = __esm({
1993
1993
  });
1994
1994
 
1995
1995
  // packages/cli/src/main.ts
1996
- import fs27 from "fs";
1997
- import path31 from "path";
1998
- import { fileURLToPath } from "url";
1996
+ import fs28 from "fs";
1997
+ import path32 from "path";
1998
+ import { fileURLToPath as fileURLToPath2 } from "url";
1999
1999
  import { parseArgs as parseArgs2 } from "node:util";
2000
2000
  import { spawn as spawn9 } from "node:child_process";
2001
2001
 
@@ -2601,6 +2601,8 @@ function createBridge(t) {
2601
2601
  },
2602
2602
  // Interactive terminal bridge (Phase 3).
2603
2603
  term: {
2604
+ readFile: (sessionKey, path33) => t.invoke("terminal:readFile", { sessionKey, path: path33 }),
2605
+ openFile: (sessionKey, path33) => t.invoke("terminal:openFile", { sessionKey, path: path33 }),
2604
2606
  /**
2605
2607
  * Start a terminal for a task: its agent when `launch` is given, else an
2606
2608
  * interactive shell. Core resolves the cwd and builds the command.
@@ -4714,8 +4716,8 @@ async function taskCommand(argv) {
4714
4716
  }
4715
4717
 
4716
4718
  // packages/cli/src/host.ts
4717
- import fs24 from "fs";
4718
- import path27 from "path";
4719
+ import fs25 from "fs";
4720
+ import path28 from "path";
4719
4721
  import { randomBytes as randomBytes2 } from "crypto";
4720
4722
 
4721
4723
  // packages/core/src/events.ts
@@ -5122,21 +5124,98 @@ async function syncBuiltinsAtStartup() {
5122
5124
  }
5123
5125
 
5124
5126
  // packages/core/src/service.ts
5125
- import fs20 from "fs";
5126
- import os7 from "os";
5127
- import path23 from "path";
5127
+ import fs21 from "fs";
5128
+
5129
+ // packages/core/src/terminal-files.ts
5130
+ import { constants } from "fs";
5131
+ import fs13 from "fs/promises";
5132
+ import os3 from "os";
5133
+ import path16 from "path";
5134
+ import { fileURLToPath } from "url";
5135
+ var MAX_PREVIEW_IMAGE_BYTES = 20 * 1024 * 1024;
5136
+ var MAX_TEXT_BYTES2 = 256 * 1024;
5137
+ var IMAGE_MIME2 = {
5138
+ ".png": "image/png",
5139
+ ".jpg": "image/jpeg",
5140
+ ".jpeg": "image/jpeg",
5141
+ ".gif": "image/gif",
5142
+ ".webp": "image/webp",
5143
+ ".avif": "image/avif",
5144
+ ".bmp": "image/bmp",
5145
+ ".svg": "image/svg+xml"
5146
+ };
5147
+ function resolveTerminalFile(cwd, input) {
5148
+ if (!input || input.includes("\0") || input.length > 32768) throw new Error("\u6A94\u6848\u8DEF\u5F91\u7121\u6548\u3002");
5149
+ let target = input;
5150
+ if (/^file:/i.test(target)) {
5151
+ const url = new URL(target);
5152
+ if (url.hostname && url.hostname !== "localhost") throw new Error("\u53EA\u652F\u63F4\u672C\u6A5F\u6A94\u6848\u3002");
5153
+ target = fileURLToPath(url);
5154
+ } else if (/^[a-z][a-z\d+.-]*:/i.test(target) && !/^[a-z]:[\\/]/i.test(target)) {
5155
+ throw new Error("\u53EA\u652F\u63F4\u672C\u6A5F\u6A94\u6848\u8DEF\u5F91\u3002");
5156
+ }
5157
+ if (target.startsWith("~/") || target.startsWith("~\\")) target = path16.join(os3.homedir(), target.slice(2));
5158
+ if (target.includes("\0")) throw new Error("\u6A94\u6848\u8DEF\u5F91\u7121\u6548\u3002");
5159
+ return path16.resolve(cwd, target);
5160
+ }
5161
+ async function readTerminalFile(cwd, input, metadataOnly = false) {
5162
+ let target;
5163
+ try {
5164
+ target = resolveTerminalFile(cwd, input);
5165
+ } catch (error) {
5166
+ return { ok: false, error: "invalid_path", message: error instanceof Error ? error.message : "\u6A94\u6848\u8DEF\u5F91\u7121\u6548\u3002" };
5167
+ }
5168
+ let handle;
5169
+ try {
5170
+ if (!(await fs13.stat(target)).isFile()) return { ok: false, error: "not_file", message: "\u9019\u500B\u8DEF\u5F91\u4E0D\u662F\u4E00\u822C\u6A94\u6848\u3002" };
5171
+ handle = await fs13.open(target, constants.O_RDONLY | constants.O_NONBLOCK);
5172
+ const stat = await handle.stat();
5173
+ if (!stat.isFile()) return { ok: false, error: "not_file", message: "\u9019\u500B\u8DEF\u5F91\u4E0D\u662F\u4E00\u822C\u6A94\u6848\u3002" };
5174
+ if (metadataOnly) return { ok: true, path: target, kind: "binary", reason: "" };
5175
+ const mime = IMAGE_MIME2[path16.extname(target).toLowerCase()];
5176
+ if (mime && stat.size > MAX_PREVIEW_IMAGE_BYTES) {
5177
+ return { ok: true, path: target, kind: "binary", reason: "\u5716\u7247\u8D85\u904E 20 MB \u9810\u89BD\u4E0A\u9650\u3002" };
5178
+ }
5179
+ const limit = mime ? MAX_PREVIEW_IMAGE_BYTES : MAX_TEXT_BYTES2;
5180
+ const buffer = Buffer.alloc(Math.min(stat.size, limit + 1));
5181
+ let bytes = 0;
5182
+ while (bytes < buffer.length) {
5183
+ const { bytesRead } = await handle.read(buffer, bytes, buffer.length - bytes, bytes);
5184
+ if (!bytesRead) break;
5185
+ bytes += bytesRead;
5186
+ }
5187
+ if (mime) {
5188
+ if (bytes > limit) return { ok: true, path: target, kind: "binary", reason: "\u5716\u7247\u8D85\u904E 20 MB \u9810\u89BD\u4E0A\u9650\u3002" };
5189
+ return { ok: true, path: target, kind: "image", dataUrl: `data:${mime};base64,${buffer.subarray(0, bytes).toString("base64")}` };
5190
+ }
5191
+ const sample = buffer.subarray(0, Math.min(bytes, 8192));
5192
+ if (/\.(pdf|zip|gz|tar|exe|dll|woff2?|mp4|mov|webm|mp3|wav)$/i.test(target) || sample.some((b) => b === 0 || (b < 9 || b > 13 && b < 32))) {
5193
+ return { ok: true, path: target, kind: "binary", reason: "\u6B64\u683C\u5F0F\u7121\u6CD5\u5728 VibeFlow \u5167\u9810\u89BD\u3002" };
5194
+ }
5195
+ return { ok: true, path: target, kind: "text", text: buffer.subarray(0, Math.min(bytes, limit)).toString("utf8"), truncated: bytes > limit };
5196
+ } catch (error) {
5197
+ const code2 = error.code;
5198
+ return code2 === "ENOENT" || code2 === "ENOTDIR" ? { ok: false, error: "not_found", message: "\u627E\u4E0D\u5230\u9019\u500B\u6A94\u6848\uFF0C\u53EF\u80FD\u5DF2\u88AB\u79FB\u52D5\u6216\u522A\u9664\u3002" } : { ok: false, error: "unreadable", message: "\u7121\u6CD5\u8B80\u53D6\u9019\u500B\u6A94\u6848\uFF0C\u8ACB\u78BA\u8A8D\u5B58\u53D6\u6B0A\u9650\u3002" };
5199
+ } finally {
5200
+ await handle?.close();
5201
+ }
5202
+ }
5203
+
5204
+ // packages/core/src/service.ts
5205
+ import os8 from "os";
5206
+ import path24 from "path";
5128
5207
  import { randomUUID as randomUUID5 } from "crypto";
5129
5208
 
5130
5209
  // packages/core/src/agent-models.ts
5131
- import fs14 from "fs";
5132
- import os5 from "os";
5133
- import path17 from "path";
5210
+ import fs15 from "fs";
5211
+ import os6 from "os";
5212
+ import path18 from "path";
5134
5213
 
5135
5214
  // packages/core/src/claude-models.ts
5136
5215
  init_effort();
5137
- import fs13 from "fs";
5138
- import os3 from "os";
5139
- import path16 from "path";
5216
+ import fs14 from "fs";
5217
+ import os4 from "os";
5218
+ import path17 from "path";
5140
5219
 
5141
5220
  // packages/core/src/json-lines-process.ts
5142
5221
  import { spawn as spawn4 } from "child_process";
@@ -5240,11 +5319,11 @@ var CLAUDE_MODELS_TIMEOUT_MS = 15e3;
5240
5319
  var REQUEST_ID = "vibeflow-models";
5241
5320
  var CLAUDE_ARGS = ["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"];
5242
5321
  function openClaude() {
5243
- const cwd = fs13.mkdtempSync(path16.join(os3.tmpdir(), "vf-claude-models-"));
5322
+ const cwd = fs14.mkdtempSync(path17.join(os4.tmpdir(), "vf-claude-models-"));
5244
5323
  return spawnJsonLines("claude", CLAUDE_ARGS, {
5245
5324
  cwd,
5246
5325
  timeoutMs: CLAUDE_MODELS_TIMEOUT_MS,
5247
- onExit: () => fs13.rmSync(cwd, { recursive: true, force: true })
5326
+ onExit: () => fs14.rmSync(cwd, { recursive: true, force: true })
5248
5327
  });
5249
5328
  }
5250
5329
  function isRecord(value) {
@@ -5291,7 +5370,7 @@ async function fetchClaudeModels(open = openClaude) {
5291
5370
  // packages/core/src/codex-models.ts
5292
5371
  init_effort();
5293
5372
  init_env();
5294
- import os4 from "os";
5373
+ import os5 from "os";
5295
5374
  var CODEX_MODELS_TIMEOUT_MS = 2e4;
5296
5375
  var MAX_PAGES = 20;
5297
5376
  var CodexLoginRequiredError = class extends Error {
@@ -5304,7 +5383,7 @@ function codexAppServerCommand() {
5304
5383
  return {
5305
5384
  bin: "codex",
5306
5385
  args: ["app-server", "--listen", "stdio://"],
5307
- cwd: os4.homedir(),
5386
+ cwd: os5.homedir(),
5308
5387
  env: execEnv()
5309
5388
  };
5310
5389
  }
@@ -5393,7 +5472,7 @@ function builtinModels(id) {
5393
5472
  }
5394
5473
  function defaultCodexHome() {
5395
5474
  const configured = process.env.CODEX_HOME?.trim();
5396
- return configured ? configured : path17.join(os5.homedir(), ".codex");
5475
+ return configured ? configured : path18.join(os6.homedir(), ".codex");
5397
5476
  }
5398
5477
  function parseCodexCatalog(raw) {
5399
5478
  const body = JSON.parse(raw);
@@ -5419,9 +5498,9 @@ function errorMessage(err) {
5419
5498
  return err instanceof Error ? err.message : String(err);
5420
5499
  }
5421
5500
  async function readCodexCache(codexHome) {
5422
- const cachePath = path17.join(codexHome, "models_cache.json");
5501
+ const cachePath = path18.join(codexHome, "models_cache.json");
5423
5502
  try {
5424
- const raw = await fs14.promises.readFile(cachePath, "utf8");
5503
+ const raw = await fs15.promises.readFile(cachePath, "utf8");
5425
5504
  return { models: parseCodexCatalog(raw), source: "codex-cache" };
5426
5505
  } catch (err) {
5427
5506
  const missing = err.code === "ENOENT";
@@ -5543,11 +5622,11 @@ function effortDescription(level) {
5543
5622
  }
5544
5623
 
5545
5624
  // packages/core/src/jev-credentials.ts
5546
- import fs15 from "fs";
5547
- import path18 from "path";
5625
+ import fs16 from "fs";
5626
+ import path19 from "path";
5548
5627
  import { randomUUID as randomUUID3 } from "crypto";
5549
5628
  function keyPath(dir) {
5550
- return path18.join(dir, "jev-api-key");
5629
+ return path19.join(dir, "jev-api-key");
5551
5630
  }
5552
5631
  function dataDir() {
5553
5632
  return getPlatform().userDataDir();
@@ -5555,8 +5634,8 @@ function dataDir() {
5555
5634
  function readJevApiKey(dir = dataDir()) {
5556
5635
  try {
5557
5636
  const file = keyPath(dir);
5558
- if (fs15.lstatSync(file).isSymbolicLink()) throw new Error("Jev key file must not be a symlink");
5559
- const saved = fs15.readFileSync(file, "utf8").trim();
5637
+ if (fs16.lstatSync(file).isSymbolicLink()) throw new Error("Jev key file must not be a symlink");
5638
+ const saved = fs16.readFileSync(file, "utf8").trim();
5560
5639
  if (saved) return saved;
5561
5640
  } catch (err) {
5562
5641
  if (err.code !== "ENOENT") throw err;
@@ -5566,8 +5645,8 @@ function readJevApiKey(dir = dataDir()) {
5566
5645
  function jevKeyStatus(dir = dataDir()) {
5567
5646
  try {
5568
5647
  const file = keyPath(dir);
5569
- if (fs15.lstatSync(file).isSymbolicLink()) throw new Error("Jev key file must not be a symlink");
5570
- if (fs15.readFileSync(file, "utf8").trim()) return { configured: true, source: "saved" };
5648
+ if (fs16.lstatSync(file).isSymbolicLink()) throw new Error("Jev key file must not be a symlink");
5649
+ if (fs16.readFileSync(file, "utf8").trim()) return { configured: true, source: "saved" };
5571
5650
  } catch (err) {
5572
5651
  if (err.code !== "ENOENT") throw err;
5573
5652
  }
@@ -5578,20 +5657,20 @@ function saveJevApiKey(value, dir = dataDir()) {
5578
5657
  if (!key || key.length > 4096 || !/^[\x21-\x7e]+$/.test(key)) {
5579
5658
  throw new Error("Jev API key \u683C\u5F0F\u7121\u6548");
5580
5659
  }
5581
- fs15.mkdirSync(dir, { recursive: true, mode: 448 });
5660
+ fs16.mkdirSync(dir, { recursive: true, mode: 448 });
5582
5661
  const file = keyPath(dir);
5583
5662
  const tmp = `${file}.${randomUUID3()}.tmp`;
5584
5663
  try {
5585
- fs15.writeFileSync(tmp, key, { mode: 384, flag: "wx" });
5586
- fs15.renameSync(tmp, file);
5587
- if (process.platform !== "win32") fs15.chmodSync(file, 384);
5664
+ fs16.writeFileSync(tmp, key, { mode: 384, flag: "wx" });
5665
+ fs16.renameSync(tmp, file);
5666
+ if (process.platform !== "win32") fs16.chmodSync(file, 384);
5588
5667
  } finally {
5589
- fs15.rmSync(tmp, { force: true });
5668
+ fs16.rmSync(tmp, { force: true });
5590
5669
  }
5591
5670
  return { configured: true, source: "saved" };
5592
5671
  }
5593
5672
  function removeJevApiKey(dir = dataDir()) {
5594
- fs15.rmSync(keyPath(dir), { force: true });
5673
+ fs16.rmSync(keyPath(dir), { force: true });
5595
5674
  return jevKeyStatus(dir);
5596
5675
  }
5597
5676
 
@@ -6103,45 +6182,45 @@ function createJiraService(options) {
6103
6182
  }
6104
6183
 
6105
6184
  // packages/core/src/board-cli.ts
6106
- import fs16 from "fs";
6107
- import path19 from "path";
6185
+ import fs17 from "fs";
6186
+ import path20 from "path";
6108
6187
  import { pathToFileURL } from "url";
6109
6188
  async function boardCliLaunchInfo() {
6110
- const storeDir = path19.dirname(getStorePath());
6189
+ const storeDir = path20.dirname(getStorePath());
6111
6190
  const entry = getPlatform().cliEntry?.();
6112
- if (entry && fs16.existsSync(entry)) return { storeDir, cliPath: entry };
6191
+ if (entry && fs17.existsSync(entry)) return { storeDir, cliPath: entry };
6113
6192
  const root = getPlatform().sourceRoot();
6114
6193
  if (!root) return { storeDir };
6115
- const cliPath = path19.join(root, "scripts", "vibeflow.mjs");
6116
- const loaderPath = path19.join(root, "test", "support", "register.mjs");
6117
- if (!fs16.existsSync(cliPath) || !fs16.existsSync(loaderPath)) return { storeDir };
6194
+ const cliPath = path20.join(root, "scripts", "vibeflow.mjs");
6195
+ const loaderPath = path20.join(root, "test", "support", "register.mjs");
6196
+ if (!fs17.existsSync(cliPath) || !fs17.existsSync(loaderPath)) return { storeDir };
6118
6197
  return { storeDir, cliPath, loaderPath: pathToFileURL(loaderPath).href };
6119
6198
  }
6120
6199
 
6121
6200
  // packages/core/src/decisions.ts
6122
- import fs17 from "fs";
6123
- import path20 from "path";
6201
+ import fs18 from "fs";
6202
+ import path21 from "path";
6124
6203
  var DECISIONS_FILE_SUFFIX = ".DECISIONS.md";
6125
6204
  function decisionsKey(worktreePath, branch) {
6126
- return worktreePath ? path20.basename(worktreePath) : branch.replace(/\//g, "-");
6205
+ return worktreePath ? path21.basename(worktreePath) : branch.replace(/\//g, "-");
6127
6206
  }
6128
6207
  function decisionsPath(workspacePath, key) {
6129
- return path20.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
6208
+ return path21.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
6130
6209
  }
6131
6210
  function deleteDecisions(workspacePath, key) {
6132
6211
  try {
6133
- fs17.rmSync(decisionsPath(workspacePath, key), { force: true });
6212
+ fs18.rmSync(decisionsPath(workspacePath, key), { force: true });
6134
6213
  } catch {
6135
6214
  }
6136
6215
  }
6137
6216
 
6138
6217
  // packages/core/src/specs.ts
6139
- import fs18 from "fs";
6140
- import path21 from "path";
6218
+ import fs19 from "fs";
6219
+ import path22 from "path";
6141
6220
  var SPEC_FILE_NAME = "spec.md";
6142
6221
  var MAX_SPEC_BYTES = 256 * 1024;
6143
6222
  function isSpecPath(p) {
6144
- return path21.posix.basename(p) === SPEC_FILE_NAME;
6223
+ return path22.posix.basename(p) === SPEC_FILE_NAME;
6145
6224
  }
6146
6225
  function clip2(buf) {
6147
6226
  return {
@@ -6156,10 +6235,10 @@ async function readWorktreeSpecs(worktreePath, baseBranch) {
6156
6235
  for (const entry of entries) {
6157
6236
  if (entry.status === "D" || !isSpecPath(entry.path)) continue;
6158
6237
  try {
6159
- const full = path21.join(worktreePath, entry.path);
6160
- const stat = fs18.statSync(full);
6238
+ const full = path22.join(worktreePath, entry.path);
6239
+ const stat = fs19.statSync(full);
6161
6240
  if (!stat.isFile()) continue;
6162
- specs.push({ path: entry.path, ...clip2(fs18.readFileSync(full)), updatedAt: stat.mtimeMs });
6241
+ specs.push({ path: entry.path, ...clip2(fs19.readFileSync(full)), updatedAt: stat.mtimeMs });
6163
6242
  } catch {
6164
6243
  }
6165
6244
  }
@@ -6511,17 +6590,17 @@ init_launch();
6511
6590
  // packages/core/src/progress-tracker.ts
6512
6591
  init_progress();
6513
6592
  init_launch();
6514
- import fs19 from "fs";
6515
- import os6 from "os";
6516
- import path22 from "path";
6593
+ import fs20 from "fs";
6594
+ import os7 from "os";
6595
+ import path23 from "path";
6517
6596
  var SINCE_SLACK_MS = 5e3;
6518
6597
  function claudeProjectDir(home, cwd) {
6519
- return path22.join(home, ".claude", "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
6598
+ return path23.join(home, ".claude", "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
6520
6599
  }
6521
6600
  function pathAliases(p) {
6522
- const resolved = path22.resolve(p);
6601
+ const resolved = path23.resolve(p);
6523
6602
  try {
6524
- const real = fs19.realpathSync(resolved);
6603
+ const real = fs20.realpathSync(resolved);
6525
6604
  return real === resolved ? [resolved] : [resolved, real];
6526
6605
  } catch {
6527
6606
  return [resolved];
@@ -6537,7 +6616,7 @@ function samePath(a, b) {
6537
6616
  }
6538
6617
  function listFiles(dir, test) {
6539
6618
  try {
6540
- return fs19.readdirSync(dir).filter(test).map((name) => path22.join(dir, name));
6619
+ return fs20.readdirSync(dir).filter(test).map((name) => path23.join(dir, name));
6541
6620
  } catch {
6542
6621
  return [];
6543
6622
  }
@@ -6545,22 +6624,22 @@ function listFiles(dir, test) {
6545
6624
  function firstLine(file) {
6546
6625
  let fd;
6547
6626
  try {
6548
- fd = fs19.openSync(file, "r");
6627
+ fd = fs20.openSync(file, "r");
6549
6628
  const buf = Buffer.alloc(64 * 1024);
6550
- const n = fs19.readSync(fd, buf, 0, buf.length, 0);
6629
+ const n = fs20.readSync(fd, buf, 0, buf.length, 0);
6551
6630
  const text = buf.subarray(0, n).toString("utf8");
6552
6631
  const nl = text.indexOf("\n");
6553
6632
  return nl === -1 ? null : text.slice(0, nl);
6554
6633
  } catch {
6555
6634
  return null;
6556
6635
  } finally {
6557
- if (fd !== void 0) fs19.closeSync(fd);
6636
+ if (fd !== void 0) fs20.closeSync(fd);
6558
6637
  }
6559
6638
  }
6560
6639
  function advance(file, cursor, reduce, initial) {
6561
6640
  let size;
6562
6641
  try {
6563
- size = fs19.statSync(file).size;
6642
+ size = fs20.statSync(file).size;
6564
6643
  } catch {
6565
6644
  return;
6566
6645
  }
@@ -6572,9 +6651,9 @@ function advance(file, cursor, reduce, initial) {
6572
6651
  if (size === cursor.offset) return;
6573
6652
  let fd;
6574
6653
  try {
6575
- fd = fs19.openSync(file, "r");
6654
+ fd = fs20.openSync(file, "r");
6576
6655
  const chunk = Buffer.alloc(size - cursor.offset);
6577
- const n = fs19.readSync(fd, chunk, 0, chunk.length, cursor.offset);
6656
+ const n = fs20.readSync(fd, chunk, 0, chunk.length, cursor.offset);
6578
6657
  cursor.offset += n;
6579
6658
  const data = Buffer.concat([cursor.rest, chunk.subarray(0, n)]);
6580
6659
  const nl = data.lastIndexOf(10);
@@ -6588,7 +6667,7 @@ function advance(file, cursor, reduce, initial) {
6588
6667
  }
6589
6668
  } catch {
6590
6669
  } finally {
6591
- if (fd !== void 0) fs19.closeSync(fd);
6670
+ if (fd !== void 0) fs20.closeSync(fd);
6592
6671
  }
6593
6672
  }
6594
6673
  function newCursor(state) {
@@ -6607,14 +6686,14 @@ function codexDayDirs(sessionsRoot, since) {
6607
6686
  const cur = new Date(start.getFullYear(), start.getMonth(), start.getDate());
6608
6687
  while (cur <= end) {
6609
6688
  const pad = (n) => String(n).padStart(2, "0");
6610
- days.push(path22.join(sessionsRoot, String(cur.getFullYear()), pad(cur.getMonth() + 1), pad(cur.getDate())));
6689
+ days.push(path23.join(sessionsRoot, String(cur.getFullYear()), pad(cur.getMonth() + 1), pad(cur.getDate())));
6611
6690
  cur.setDate(cur.getDate() + 1);
6612
6691
  }
6613
6692
  return days;
6614
6693
  }
6615
6694
  function createProgressTracker(opts) {
6616
- const home = opts.homeDir ?? os6.homedir();
6617
- const codexHomes = opts.codexHomes ?? (() => [path22.join(home, ".codex")]);
6695
+ const home = opts.homeDir ?? os7.homedir();
6696
+ const codexHomes = opts.codexHomes ?? (() => [path23.join(home, ".codex")]);
6618
6697
  const pollMs = opts.pollMs ?? 2e3;
6619
6698
  const entries = /* @__PURE__ */ new Map();
6620
6699
  const belongsToRun = (target, startedAt) => target.since == null || startedAt != null && startedAt >= target.since - SINCE_SLACK_MS;
@@ -6623,7 +6702,7 @@ function createProgressTracker(opts) {
6623
6702
  const dirs = pathAliases(target.worktreePath).map((p) => claudeProjectDir(home, p));
6624
6703
  for (const file of dirs.flatMap((dir) => listFiles(dir, (n) => n.endsWith(".jsonl")))) {
6625
6704
  if (entry.claude.has(file) || entry.rejected.has(file)) continue;
6626
- const session = path22.basename(file, ".jsonl");
6705
+ const session = path23.basename(file, ".jsonl");
6627
6706
  if (session !== target.sessionId) {
6628
6707
  const head = firstLine(file);
6629
6708
  if (head == null) continue;
@@ -6636,8 +6715,8 @@ function createProgressTracker(opts) {
6636
6715
  entry.claude.set(file, newCursor(initialClaudeState()));
6637
6716
  }
6638
6717
  for (const file of Array.from(entry.claude.keys())) {
6639
- if (file.includes(`${path22.sep}subagents${path22.sep}`)) continue;
6640
- const sub = path22.join(file.slice(0, -".jsonl".length), "subagents");
6718
+ if (file.includes(`${path23.sep}subagents${path23.sep}`)) continue;
6719
+ const sub = path23.join(file.slice(0, -".jsonl".length), "subagents");
6641
6720
  for (const f of listFiles(sub, (n) => n.endsWith(".jsonl"))) {
6642
6721
  if (!entry.claude.has(f)) entry.claude.set(f, newCursor(initialClaudeState()));
6643
6722
  }
@@ -6646,7 +6725,7 @@ function createProgressTracker(opts) {
6646
6725
  function discoverCodex(entry) {
6647
6726
  const { target } = entry;
6648
6727
  for (const codexHome of codexHomes()) {
6649
- for (const day of codexDayDirs(path22.join(codexHome, "sessions"), target.since)) {
6728
+ for (const day of codexDayDirs(path23.join(codexHome, "sessions"), target.since)) {
6650
6729
  for (const file of listFiles(day, (n) => n.startsWith("rollout-") && n.endsWith(".jsonl"))) {
6651
6730
  if (entry.codex.has(file) || entry.rejected.has(file)) continue;
6652
6731
  const head = firstLine(file);
@@ -6663,11 +6742,11 @@ function createProgressTracker(opts) {
6663
6742
  }
6664
6743
  }
6665
6744
  function drainHooks(entry) {
6666
- const dir = path22.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
6745
+ const dir = path23.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
6667
6746
  for (const file of listFiles(dir, (n) => n.endsWith(".json")).sort()) {
6668
6747
  try {
6669
- const at = fs19.statSync(file).mtimeMs;
6670
- const event = JSON.parse(fs19.readFileSync(file, "utf8"));
6748
+ const at = fs20.statSync(file).mtimeMs;
6749
+ const event = JSON.parse(fs20.readFileSync(file, "utf8"));
6671
6750
  const foreign = entry.target.sessionId && typeof event.session_id === "string" && event.session_id !== entry.target.sessionId;
6672
6751
  const activity = foreign ? null : activityFromHook(event, at);
6673
6752
  if (activity) entry.hookActivity = latestActivity(entry.hookActivity ?? activity, activity);
@@ -6675,7 +6754,7 @@ function createProgressTracker(opts) {
6675
6754
  continue;
6676
6755
  }
6677
6756
  try {
6678
- fs19.unlinkSync(file);
6757
+ fs20.unlinkSync(file);
6679
6758
  } catch {
6680
6759
  }
6681
6760
  }
@@ -6687,8 +6766,8 @@ function createProgressTracker(opts) {
6687
6766
  if (target.agent === "claude") {
6688
6767
  for (const [file, cursor] of entry.claude) {
6689
6768
  runUsage = addUsage(runUsage, claudeUsageTotal(cursor.state));
6690
- if (file.includes(`${path22.sep}subagents${path22.sep}`)) continue;
6691
- const isExecutor = path22.basename(file, ".jsonl") === target.sessionId;
6769
+ if (file.includes(`${path23.sep}subagents${path23.sep}`)) continue;
6770
+ const isExecutor = path23.basename(file, ".jsonl") === target.sessionId;
6692
6771
  const s = cursor.state;
6693
6772
  if (isExecutor || !target.sessionId && (!primary || (s.startedAt ?? 0) > (primary.startedAt ?? 0))) {
6694
6773
  primary = { todos: s.todos, contextTokens: s.contextTokens, activity: s.activity, startedAt: s.startedAt };
@@ -6767,10 +6846,10 @@ function createProgressTracker(opts) {
6767
6846
  };
6768
6847
  entry.timer.unref?.();
6769
6848
  entries.set(target.taskId, entry);
6770
- const eventsDir = path22.join(target.worktreePath, PROGRESS_EVENTS_DIR);
6849
+ const eventsDir = path23.join(target.worktreePath, PROGRESS_EVENTS_DIR);
6771
6850
  try {
6772
- fs19.mkdirSync(eventsDir, { recursive: true });
6773
- entry.watcher = fs19.watch(eventsDir, () => {
6851
+ fs20.mkdirSync(eventsDir, { recursive: true });
6852
+ entry.watcher = fs20.watch(eventsDir, () => {
6774
6853
  if (entry.pending) return;
6775
6854
  entry.pending = setTimeout(() => {
6776
6855
  entry.pending = null;
@@ -6862,15 +6941,16 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6862
6941
  const router = jevRouter ?? createJevRouter({ getApiKey: readJevApiKey });
6863
6942
  const routing = /* @__PURE__ */ new Map();
6864
6943
  const standaloneTerminals = /* @__PURE__ */ new Map();
6944
+ const terminalDirectories = /* @__PURE__ */ new Map();
6865
6945
  let libraryCodexHome = null;
6866
6946
  void libraryRoot().then((root) => {
6867
6947
  libraryCodexHome = libraryPaths(root).codexHome;
6868
6948
  }).catch(() => {
6869
6949
  });
6870
- const home = homeDir ?? os7.homedir();
6950
+ const home = homeDir ?? os8.homedir();
6871
6951
  const progress = createProgressTracker({
6872
6952
  homeDir: home,
6873
- codexHomes: () => [path23.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
6953
+ codexHomes: () => [path24.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
6874
6954
  onUpdate: (taskId, value) => bus.emit("progress:update", { taskId, progress: value }),
6875
6955
  onNotify: (taskId, list) => {
6876
6956
  const prefs = resolveNotificationSettings(getSettings());
@@ -6923,6 +7003,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6923
7003
  removeTerminals([key]);
6924
7004
  }
6925
7005
  standaloneTerminals.delete(key);
7006
+ terminalDirectories.delete(key);
6926
7007
  unwatchSubAgents(key);
6927
7008
  }
6928
7009
  function isStandaloneKey(key) {
@@ -6944,6 +7025,21 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6944
7025
  requireTask(key.split(":")[0]);
6945
7026
  return key;
6946
7027
  }
7028
+ function terminalDirectory(sessionKey) {
7029
+ const key = requireSessionKey(sessionKey);
7030
+ const known = standaloneTerminals.get(key) ?? terminalDirectories.get(key);
7031
+ if (known) return known;
7032
+ if (isStandaloneKey(key)) {
7033
+ const saved = listTerminals().find((item) => item.sessionKey === key);
7034
+ if (saved?.taskId) {
7035
+ const task2 = requireTask(saved.taskId);
7036
+ return str(task2.worktreePath ?? task2.projectPath, "directory");
7037
+ }
7038
+ return saved?.projectPath ?? os8.homedir();
7039
+ }
7040
+ const task = requireTask(key.split(":")[0]);
7041
+ return str(task.worktreePath ?? task.projectPath, "directory");
7042
+ }
6947
7043
  function launchEffort(task, route) {
6948
7044
  const effort = task.effort === "auto" ? route?.effort : task.effort;
6949
7045
  const model = models.findModel(taskAgent(task), taskModel(task));
@@ -6988,13 +7084,13 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6988
7084
  }
6989
7085
  function claudeSessionFile(cwd, sessionId) {
6990
7086
  const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
6991
- return path23.join(os7.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
7087
+ return path24.join(os8.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
6992
7088
  }
6993
7089
  async function removeTaskWorktree(task) {
6994
7090
  if (!task.projectPath || !task.worktreePath) return;
6995
7091
  const branch = task.branch || fallbackBranchName(task.id);
6996
7092
  await removeWorktree(task.projectPath, task.worktreePath);
6997
- deleteArtifacts(task.workspacePath ?? path23.dirname(task.worktreePath), task.worktreePath);
7093
+ deleteArtifacts(task.workspacePath ?? path24.dirname(task.worktreePath), task.worktreePath);
6998
7094
  await deleteBranch(task.projectPath, branch);
6999
7095
  }
7000
7096
  function columnOf(taskId) {
@@ -7139,8 +7235,8 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7139
7235
  "projects:listRecent": () => listRecentProjects(getStore()),
7140
7236
  // `vibeflow open <path>` while a host runs: only the host writes the store.
7141
7237
  "projects:record": (projectPath) => {
7142
- const p = path23.resolve(str(projectPath, "projectPath"));
7143
- if (!fs20.existsSync(p) || !fs20.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
7238
+ const p = path24.resolve(str(projectPath, "projectPath"));
7239
+ if (!fs21.existsSync(p) || !fs21.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
7144
7240
  recordRecentProject(getStore(), p);
7145
7241
  return listRecentProjects(getStore());
7146
7242
  },
@@ -7193,7 +7289,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7193
7289
  if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
7194
7290
  await teardownTask(task.id);
7195
7291
  await resetWorktreeToBase(task.worktreePath, task.baseBranch);
7196
- const workspacePath = task.workspacePath ?? path23.dirname(task.worktreePath);
7292
+ const workspacePath = task.workspacePath ?? path24.dirname(task.worktreePath);
7197
7293
  deleteArtifacts(workspacePath, task.worktreePath);
7198
7294
  deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
7199
7295
  resetSubAgents(task.worktreePath);
@@ -7237,7 +7333,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7237
7333
  }
7238
7334
  const info = await getGitInfo(nextProject);
7239
7335
  if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
7240
- const projectName = path23.basename(nextProject);
7336
+ const projectName = path24.basename(nextProject);
7241
7337
  const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
7242
7338
  await teardownTask(existing.id);
7243
7339
  await removeTaskWorktree(existing);
@@ -7299,6 +7395,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7299
7395
  // Session ended (natural exit included), so stop its watcher.
7300
7396
  onExit: () => unwatchSubAgents(key)
7301
7397
  });
7398
+ terminalDirectories.set(key, cwd);
7302
7399
  watchSubAgents(key, cwd, (subAgents) => {
7303
7400
  bus.emit("subagents:update", { taskId: task.id, subAgents });
7304
7401
  });
@@ -7312,7 +7409,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7312
7409
  const p = obj(payload, "payload");
7313
7410
  const key = str(p.sessionKey, "sessionKey");
7314
7411
  if (!STANDALONE_KEY.test(key)) invalid("invalid standalone terminal key");
7315
- if (Boolean(p.taskId) === Boolean(p.projectPath)) invalid("provide taskId or projectPath");
7412
+ if (p.taskId && p.projectPath) invalid("provide taskId or projectPath, not both");
7316
7413
  const saved = listTerminals().find((t) => t.sessionKey === key);
7317
7414
  if (saved && (saved.taskId ? saved.taskId !== p.taskId : saved.projectPath !== p.projectPath)) {
7318
7415
  invalid("terminal key already belongs to another directory");
@@ -7325,17 +7422,24 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7325
7422
  const task = column === "in_progress" && !requested.worktreePath ? await ensureProvisioned(requested) : requested;
7326
7423
  cwd = str(column === "backlog" ? task.projectPath : task.worktreePath ?? task.projectPath, "directory");
7327
7424
  projectPath = task.projectPath ?? cwd;
7328
- } else {
7425
+ } else if (p.projectPath) {
7329
7426
  cwd = str(p.projectPath, "projectPath");
7330
7427
  projectPath = cwd;
7428
+ } else {
7429
+ cwd = os8.homedir();
7331
7430
  }
7332
- if (!path23.isAbsolute(cwd) || !fs20.existsSync(cwd) || !fs20.statSync(cwd).isDirectory()) {
7431
+ if (!path24.isAbsolute(cwd) || !fs21.existsSync(cwd) || !fs21.statSync(cwd).isDirectory()) {
7333
7432
  invalid("terminal directory must be an existing absolute directory");
7334
7433
  }
7335
- const title = (typeof p.title === "string" ? p.title.trim() : "") || saved?.title || path23.basename(projectPath);
7336
- saveTerminal({ sessionKey: key, title, projectPath, ...p.taskId ? { taskId: p.taskId } : {} });
7434
+ const title = (typeof p.title === "string" ? p.title.trim() : "") || saved?.title || (projectPath ? path24.basename(projectPath) : "~");
7435
+ saveTerminal({
7436
+ sessionKey: key,
7437
+ title,
7438
+ ...projectPath ? { projectPath } : {},
7439
+ ...p.taskId ? { taskId: p.taskId } : {}
7440
+ });
7337
7441
  standaloneTerminals.set(key, cwd);
7338
- if (!p.taskId) recordRecentProject(getStore(), cwd);
7442
+ if (!p.taskId && projectPath) recordRecentProject(getStore(), cwd);
7339
7443
  const cols = typeof p.cols === "number" ? p.cols : void 0;
7340
7444
  const rows = typeof p.rows === "number" ? p.rows : void 0;
7341
7445
  if (sessions.kind === "pty" && await sessions.isAlive(key)) {
@@ -7344,6 +7448,21 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7344
7448
  }
7345
7449
  return sessions.start(key, { cwd, cols, rows });
7346
7450
  },
7451
+ "terminal:readFile": async (payload) => {
7452
+ const p = obj(payload, "payload");
7453
+ return readTerminalFile(terminalDirectory(p.sessionKey), str(p.path, "path"));
7454
+ },
7455
+ "terminal:openFile": async (payload) => {
7456
+ const p = obj(payload, "payload");
7457
+ const file = await readTerminalFile(terminalDirectory(p.sessionKey), str(p.path, "path"), true);
7458
+ if (!file.ok) return { ok: false, message: file.message };
7459
+ try {
7460
+ const message = await getPlatform().openPath(file.path);
7461
+ return message ? { ok: false, message } : { ok: true };
7462
+ } catch (error) {
7463
+ return { ok: false, message: error instanceof Error ? error.message : "\u7121\u6CD5\u958B\u555F\u6A94\u6848\u3002" };
7464
+ }
7465
+ },
7347
7466
  "terminal:rename": (payload) => {
7348
7467
  const p = obj(payload, "payload");
7349
7468
  const key = str(p.sessionKey, "sessionKey");
@@ -7382,7 +7501,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7382
7501
  "claude:sessionExists": (taskId) => {
7383
7502
  const task = requireTask(taskId);
7384
7503
  if (!task.worktreePath) return false;
7385
- return fs20.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
7504
+ return fs21.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
7386
7505
  },
7387
7506
  "sessions:list": async () => {
7388
7507
  const keys = await sessions.list();
@@ -7429,7 +7548,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7429
7548
  const task = requireTask(taskId);
7430
7549
  if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
7431
7550
  const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
7432
- if (!fs20.existsSync(dir)) return "artifacts directory does not exist yet";
7551
+ if (!fs21.existsSync(dir)) return "artifacts directory does not exist yet";
7433
7552
  return getPlatform().openPath(dir);
7434
7553
  },
7435
7554
  "task:getSpecs": async (taskId) => {
@@ -7564,7 +7683,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7564
7683
  }
7565
7684
  await removeTaskWorktree(task);
7566
7685
  if (task.workspacePath) {
7567
- fs20.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
7686
+ fs21.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
7568
7687
  }
7569
7688
  clearConversation(task.id);
7570
7689
  removeTask(task.id);
@@ -7617,9 +7736,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7617
7736
  // Watch the containing directory, not the file: JsonStore renames a temp
7618
7737
  // file over the target and so replaces the inode on every save.
7619
7738
  watchStore() {
7620
- const file = path23.basename(getStorePath());
7739
+ const file = path24.basename(getStorePath());
7621
7740
  let debounce = null;
7622
- storeWatcher = fs20.watch(path23.dirname(getStorePath()), (_event, filename) => {
7741
+ storeWatcher = fs21.watch(path24.dirname(getStorePath()), (_event, filename) => {
7623
7742
  if (filename !== file) return;
7624
7743
  if (debounce) clearTimeout(debounce);
7625
7744
  debounce = setTimeout(() => {
@@ -7656,9 +7775,9 @@ function createSessionBackend(sink, choice) {
7656
7775
  }
7657
7776
 
7658
7777
  // packages/core/src/web-server.ts
7659
- import fs21 from "fs";
7778
+ import fs22 from "fs";
7660
7779
  import http from "http";
7661
- import path24 from "path";
7780
+ import path25 from "path";
7662
7781
  import { timingSafeEqual } from "crypto";
7663
7782
  import { WebSocketServer } from "ws";
7664
7783
  var TOKEN_COOKIE = "vf_token";
@@ -7713,14 +7832,14 @@ function resolveStatic(root, urlPath) {
7713
7832
  return null;
7714
7833
  }
7715
7834
  if (decoded.includes("\0")) return null;
7716
- const rel = path24.posix.normalize(decoded).replace(/^\/+/, "");
7717
- const base2 = path24.resolve(root);
7718
- const candidates = [rel, path24.posix.join(rel, "index.html"), `${rel}.html`];
7835
+ const rel = path25.posix.normalize(decoded).replace(/^\/+/, "");
7836
+ const base2 = path25.resolve(root);
7837
+ const candidates = [rel, path25.posix.join(rel, "index.html"), `${rel}.html`];
7719
7838
  for (const c of candidates) {
7720
- const full = path24.resolve(base2, c);
7721
- if (full !== base2 && !full.startsWith(base2 + path24.sep)) return null;
7839
+ const full = path25.resolve(base2, c);
7840
+ if (full !== base2 && !full.startsWith(base2 + path25.sep)) return null;
7722
7841
  try {
7723
- if (fs21.statSync(full).isFile()) return full;
7842
+ if (fs22.statSync(full).isFile()) return full;
7724
7843
  } catch {
7725
7844
  }
7726
7845
  }
@@ -7781,7 +7900,7 @@ async function startWebServer(options) {
7781
7900
  send(res, 404, "Not found");
7782
7901
  return;
7783
7902
  }
7784
- const ext = path24.extname(file).toLowerCase();
7903
+ const ext = path25.extname(file).toLowerCase();
7785
7904
  const immutable = url.pathname.startsWith("/_next/static/");
7786
7905
  res.writeHead(200, {
7787
7906
  "Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
@@ -7794,7 +7913,7 @@ async function startWebServer(options) {
7794
7913
  res.end();
7795
7914
  return;
7796
7915
  }
7797
- fs21.createReadStream(file).pipe(res);
7916
+ fs22.createReadStream(file).pipe(res);
7798
7917
  }
7799
7918
  const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
7800
7919
  function reject(socket, status, reason) {
@@ -7893,8 +8012,8 @@ Content-Length: 0\r
7893
8012
  }
7894
8013
 
7895
8014
  // packages/cli/src/update-command.ts
7896
- import fs22 from "node:fs";
7897
- import path25 from "node:path";
8015
+ import fs23 from "node:fs";
8016
+ import path26 from "node:path";
7898
8017
  import { spawnSync } from "node:child_process";
7899
8018
  function npmRunner(args, inherit) {
7900
8019
  const spawnOpts = {
@@ -7906,7 +8025,7 @@ function npmRunner(args, inherit) {
7906
8025
  }
7907
8026
  function packageName(packageRoot) {
7908
8027
  try {
7909
- const pkg = JSON.parse(fs22.readFileSync(path25.join(packageRoot, "package.json"), "utf8"));
8028
+ const pkg = JSON.parse(fs23.readFileSync(path26.join(packageRoot, "package.json"), "utf8"));
7910
8029
  if (typeof pkg.name !== "string") return null;
7911
8030
  return /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(pkg.name) ? pkg.name : null;
7912
8031
  } catch {
@@ -7921,7 +8040,7 @@ function runUpdate(options) {
7921
8040
  error("vibeflow update \u53EA\u9069\u7528\u65BC\u5168\u57DF npm \u5B89\u88DD\uFF1B\u76EE\u524D\u662F\u539F\u59CB\u78BC\u7248\u672C\u3002");
7922
8041
  return 1;
7923
8042
  }
7924
- const packageRoot = path25.dirname(path25.dirname(options.cliEntry));
8043
+ const packageRoot = path26.dirname(path26.dirname(options.cliEntry));
7925
8044
  const name = packageName(packageRoot);
7926
8045
  if (!name) {
7927
8046
  error("\u7121\u6CD5\u8FA8\u8B58\u76EE\u524D VibeFlow \u7684 npm \u5957\u4EF6\u540D\u7A31\uFF0C\u5DF2\u53D6\u6D88\u66F4\u65B0\u3002");
@@ -7933,8 +8052,8 @@ function runUpdate(options) {
7933
8052
  return 1;
7934
8053
  }
7935
8054
  try {
7936
- const globalPackage = path25.join(String(root.stdout).trim(), ...name.split("/"));
7937
- if (fs22.lstatSync(globalPackage).isSymbolicLink() || fs22.realpathSync(globalPackage) !== fs22.realpathSync(packageRoot)) {
8055
+ const globalPackage = path26.join(String(root.stdout).trim(), ...name.split("/"));
8056
+ if (fs23.lstatSync(globalPackage).isSymbolicLink() || fs23.realpathSync(globalPackage) !== fs23.realpathSync(packageRoot)) {
7938
8057
  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");
7939
8058
  return 1;
7940
8059
  }
@@ -8079,8 +8198,8 @@ async function openUpdateTerminal(platform = process.platform, run3 = launch) {
8079
8198
  }
8080
8199
 
8081
8200
  // packages/cli/src/self-update.ts
8082
- import fs23 from "node:fs";
8083
- import path26 from "node:path";
8201
+ import fs24 from "node:fs";
8202
+ import path27 from "node:path";
8084
8203
  import { execFile as execFile10 } from "node:child_process";
8085
8204
  import { promisify as promisify7 } from "node:util";
8086
8205
  var execFileAsync2 = promisify7(execFile10);
@@ -8089,7 +8208,7 @@ var PROBE_RETRY_MS = 3e4;
8089
8208
  var HOST_TOKEN_ENV = "VIBEFLOW_HOST_TOKEN";
8090
8209
  function readInstalledVersion(packageRoot) {
8091
8210
  try {
8092
- const pkg = JSON.parse(fs23.readFileSync(path26.join(packageRoot, "package.json"), "utf8"));
8211
+ const pkg = JSON.parse(fs24.readFileSync(path27.join(packageRoot, "package.json"), "utf8"));
8093
8212
  return typeof pkg.version === "string" ? pkg.version : null;
8094
8213
  } catch {
8095
8214
  return null;
@@ -8185,7 +8304,7 @@ async function startHost(options) {
8185
8304
  const core = createCore({ sessions, bus, version: options.version });
8186
8305
  let updateTimer = null;
8187
8306
  let installTimer = null;
8188
- const packageRoot = options.cliEntry ? path27.dirname(path27.dirname(options.cliEntry)) : null;
8307
+ const packageRoot = options.cliEntry ? path28.dirname(path28.dirname(options.cliEntry)) : null;
8189
8308
  const updates = createUpdateMonitor({
8190
8309
  packageName: options.sourceRoot || !packageRoot ? null : packageName(packageRoot),
8191
8310
  currentVersion: options.version,
@@ -8273,7 +8392,7 @@ function loginUrl(lock, pathname = "/home/") {
8273
8392
  return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
8274
8393
  }
8275
8394
  function isWebDir(dir) {
8276
- return fs24.existsSync(path27.join(dir, "home", "index.html"));
8395
+ return fs25.existsSync(path28.join(dir, "home", "index.html"));
8277
8396
  }
8278
8397
 
8279
8398
  // packages/cli/src/remote.ts
@@ -8390,8 +8509,8 @@ function connectToHost(lock) {
8390
8509
  }
8391
8510
 
8392
8511
  // packages/cli/src/doctor.ts
8393
- import fs25 from "fs";
8394
- import path28 from "path";
8512
+ import fs26 from "fs";
8513
+ import path29 from "path";
8395
8514
  import { execFile as execFile11 } from "child_process";
8396
8515
  import { promisify as promisify8 } from "util";
8397
8516
  init_env();
@@ -8531,10 +8650,10 @@ async function runDoctor(options) {
8531
8650
  });
8532
8651
  }
8533
8652
  try {
8534
- fs25.mkdirSync(options.userDataDir, { recursive: true });
8535
- const probe = path28.join(options.userDataDir, `.doctor-${process.pid}`);
8536
- fs25.writeFileSync(probe, "");
8537
- fs25.rmSync(probe);
8653
+ fs26.mkdirSync(options.userDataDir, { recursive: true });
8654
+ const probe = path29.join(options.userDataDir, `.doctor-${process.pid}`);
8655
+ fs26.writeFileSync(probe, "");
8656
+ fs26.rmSync(probe);
8538
8657
  checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
8539
8658
  } catch (err) {
8540
8659
  checks.push({
@@ -8588,25 +8707,25 @@ Options:
8588
8707
  -h, --help Show this help
8589
8708
  `.trim();
8590
8709
  function detectLayout() {
8591
- const file = fileURLToPath(import.meta.url);
8592
- const here = path31.dirname(file);
8593
- const bundledWeb = path31.join(here, "web");
8594
- if (isWebDir(bundledWeb) || path31.basename(here) === "dist") {
8710
+ const file = fileURLToPath2(import.meta.url);
8711
+ const here = path32.dirname(file);
8712
+ const bundledWeb = path32.join(here, "web");
8713
+ if (isWebDir(bundledWeb) || path32.basename(here) === "dist") {
8595
8714
  return {
8596
- version: readVersion(path31.join(here, "..", "package.json")),
8715
+ version: readVersion(path32.join(here, "..", "package.json")),
8597
8716
  webDir: isWebDir(bundledWeb) ? bundledWeb : null,
8598
8717
  sourceRoot: null,
8599
8718
  cliEntry: file
8600
8719
  };
8601
8720
  }
8602
- const root = path31.resolve(here, "..", "..", "..");
8721
+ const root = path32.resolve(here, "..", "..", "..");
8603
8722
  const override = process.env.VIBEFLOW_WEB_DIR;
8604
- const webDir = override && isWebDir(override) ? override : isWebDir(path31.join(root, "app")) ? path31.join(root, "app") : null;
8605
- return { version: readVersion(path31.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
8723
+ const webDir = override && isWebDir(override) ? override : isWebDir(path32.join(root, "app")) ? path32.join(root, "app") : null;
8724
+ return { version: readVersion(path32.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
8606
8725
  }
8607
8726
  function readVersion(pkgPath) {
8608
8727
  try {
8609
- return JSON.parse(fs27.readFileSync(pkgPath, "utf8")).version;
8728
+ return JSON.parse(fs28.readFileSync(pkgPath, "utf8")).version;
8610
8729
  } catch {
8611
8730
  return "0.0.0";
8612
8731
  }
@@ -8844,8 +8963,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
8844
8963
  console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
8845
8964
  return 1;
8846
8965
  }
8847
- const dir = path31.resolve(target);
8848
- if (!fs27.existsSync(dir) || !fs27.statSync(dir).isDirectory()) {
8966
+ const dir = path32.resolve(target);
8967
+ if (!fs28.existsSync(dir) || !fs28.statSync(dir).isDirectory()) {
8849
8968
  console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
8850
8969
  return 1;
8851
8970
  }