@kw-yeh/vibeflow 4.6.2 → 4.8.0

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
@@ -499,6 +499,196 @@ var init_tmux_backend = __esm({
499
499
  }
500
500
  });
501
501
 
502
+ // packages/core/src/launch.ts
503
+ function pathBasename(p) {
504
+ const norm = p.replace(/\\/g, "/").replace(/\/+$/, "");
505
+ const i = norm.lastIndexOf("/");
506
+ return i >= 0 ? norm.slice(i + 1) : norm;
507
+ }
508
+ function agentArtifactsDir(worktreePath, workspacePath) {
509
+ if (!worktreePath || !workspacePath) return null;
510
+ const dir = toShellPath(workspacePath);
511
+ const ws = pathBasename(worktreePath);
512
+ return `${dir}/${ws}${ARTIFACTS_DIR_SUFFIX}`;
513
+ }
514
+ function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR) {
515
+ return [
516
+ "Artifact \u8A2D\u5B9A\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
517
+ `1. \u672C\u4EFB\u52D9\u7684 Artifact \u8CC7\u6599\u593E\u662F ${artifactsDir}/\uFF1B\u9700\u8981\u4FDD\u5B58\u7D66\u4F7F\u7528\u8005\u7684\u9A57\u8B49\u8B49\u64DA\u6216\u5831\u544A\u6642\uFF0C\u8ACB\u5BEB\u5165\u6B64\u8CC7\u6599\u593E\uFF08\u4E0D\u5B58\u5728\u8ACB\u5EFA\u7ACB\uFF09\u3002`,
518
+ `2. \u4F7F\u7528\u8005\u6703\u5728 VibeFlow \u7684 Artifacts \u5206\u9801\u6AA2\u8996\u6839\u76EE\u9304\u5167\u5BB9\uFF1B\u53EA\u653E\u4F60\u6703\u4E3B\u52D5\u8ACB\u4F7F\u7528\u8005\u904E\u76EE\u7684\u6A94\u6848\u3002`,
519
+ `3. \u4F60\u81EA\u5DF1\u7684\u5DE5\u4F5C\u66AB\u5B58\uFF08\u4E00\u6B21\u6027 script\u3001log\u3001\u4E2D\u9593\u8F38\u51FA\u3001debug \u6A94\uFF09\u653E\u5728 ${artifactsDir}/${SCRATCH_DIR_NAME}/\u3002`,
520
+ `4. \u82E5\u5DE5\u5177\u5148\u8F38\u51FA\u5230 /tmp\u3001/private/tmp\u3001$TMPDIR \u6216 ~/Downloads\uFF0C\u8ACB\u628A\u6700\u7D42\u8981\u5448\u73FE\u7684\u6A94\u6848\u8907\u88FD\u5230 ${artifactsDir}/ \u4E26\u78BA\u8A8D\u5B58\u5728\u3002`,
521
+ "5. Artifact \u8CC7\u6599\u593E\u4F4D\u65BC worktree \u4E4B\u5916\uFF1B\u4E0D\u8981\u628A\u5176\u4E2D\u5167\u5BB9\u52A0\u5165 git commit\uFF0C\u4E5F\u4E0D\u8981\u628A\u6700\u7D42\u4EA4\u4ED8\u7269\u53EA\u7559\u5728\u9019\u88E1\u3002"
522
+ ].join("\n");
523
+ }
524
+ function claudePermissionFlag(autoMode) {
525
+ return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
526
+ }
527
+ function shellQuote(s) {
528
+ return `'${s.replace(/'/g, `'\\''`)}'`;
529
+ }
530
+ function toShellPath(p) {
531
+ return p.replace(/\\/g, "/");
532
+ }
533
+ function boardEnvPrefix(task, opts) {
534
+ const board = opts?.boardCli;
535
+ if (!board) return "";
536
+ const entries = [
537
+ ["VIBEFLOW_TASK_ID", task.id],
538
+ ["VIBEFLOW_PROJECT_PATH", task.projectPath],
539
+ ["VIBEFLOW_BRANCH", task.branch],
540
+ ["VIBEFLOW_BASE_BRANCH", task.baseBranch],
541
+ ["VIBEFLOW_STORE_DIR", board?.storeDir],
542
+ ["VIBEFLOW_CLI", board?.cliPath],
543
+ ["VIBEFLOW_CLI_LOADER", board?.loaderPath],
544
+ ["VIBEFLOW_AUTO_MODE", opts?.autoMode ? "1" : "0"]
545
+ ];
546
+ const exports = entries.filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote(toShellPath(value))}`);
547
+ return exports.length ? `export ${exports.join(" ")}; ` : "";
548
+ }
549
+ function codexAutoFlag(autoMode) {
550
+ return autoMode ? "--dangerously-bypass-approvals-and-sandbox " : "";
551
+ }
552
+ function claudeResumeOrFresh(sessionId, worktreePath, tail) {
553
+ if (!worktreePath) return `claude --resume ${sessionId} ${tail}\r`;
554
+ const munged = worktreePath.replace(/[^a-zA-Z0-9]/g, "-");
555
+ const sessionFile = `"$HOME/.claude/projects/${munged}/${sessionId}.jsonl"`;
556
+ return `if [ -f ${sessionFile} ]; then claude --resume ${sessionId} ${tail}; else claude --session-id ${sessionId} ${tail}; fi\r`;
557
+ }
558
+ function buildClaudeSettings(worktreePath) {
559
+ const settings = { theme: "dark" };
560
+ if (worktreePath) {
561
+ const dir = `${toShellPath(worktreePath)}/${SUBAGENTS_DIR2}`;
562
+ const command = `mkdir -p "${dir}" && cat > "${dir}/$(date +%s)-$$-$RANDOM.json"`;
563
+ const taskHook = {
564
+ matcher: "Task",
565
+ hooks: [{ type: "command", command }]
566
+ };
567
+ const eventsDir = `${toShellPath(worktreePath)}/${PROGRESS_EVENTS_DIR}`;
568
+ const progress = {
569
+ hooks: [{ type: "command", command: `mkdir -p "${eventsDir}" && cat > "${eventsDir}/$(date +%s)-$$-$RANDOM.json"; exit 0` }]
570
+ };
571
+ settings.hooks = {
572
+ PreToolUse: [taskHook],
573
+ PostToolUse: [taskHook, { matcher: TODO_TOOLS, ...progress }],
574
+ Stop: [progress],
575
+ Notification: [progress],
576
+ UserPromptSubmit: [progress]
577
+ };
578
+ }
579
+ return JSON.stringify(settings);
580
+ }
581
+ function resolveSystemPrompt(custom) {
582
+ return custom && custom.trim() ? custom : "";
583
+ }
584
+ function buildPrompt(task) {
585
+ const lines = [`\u4EFB\u52D9\u6A19\u984C\uFF1A${task.title}`];
586
+ const description = task.description?.trim();
587
+ if (description) {
588
+ lines.push("", "\u4EFB\u52D9\u63CF\u8FF0\uFF1A", description);
589
+ }
590
+ return lines.join("\n");
591
+ }
592
+ function executorSessionId(taskId, runId) {
593
+ const source = runId ? `${taskId}:${runId}` : taskId;
594
+ const raw = source.replace(/[^0-9a-f]/gi, "").toLowerCase();
595
+ const hex = runId ? `${raw.slice(0, 24).padEnd(24, "0")}${namespaceHash(`executor:${source}`)}` : raw.padEnd(32, "0").slice(0, 32);
596
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
597
+ }
598
+ function namespaceHash(namespace) {
599
+ let hash = 2166136261;
600
+ for (let i = 0; i < namespace.length; i += 1) {
601
+ hash ^= namespace.charCodeAt(i);
602
+ hash = Math.imul(hash, 16777619);
603
+ }
604
+ return (hash >>> 0).toString(16).padStart(8, "0");
605
+ }
606
+ function assembleCommand(agent, systemPrompt, prompt, model, effort, opts, worktreePath, sessionId, workspacePath) {
607
+ let cmd;
608
+ if (agent === "claude") {
609
+ const settings = ` --settings ${shellQuote(buildClaudeSettings(worktreePath))}`;
610
+ const addDir = workspacePath ? ` --add-dir ${shellQuote(toShellPath(workspacePath))}` : "";
611
+ const modelFlag = model ? ` --model ${model}` : "";
612
+ const effortFlag = effort ? ` --effort ${effort}` : "";
613
+ const library = opts?.library;
614
+ const libraryFlags = library ? ` --plugin-dir ${shellQuote(toShellPath(library.pluginDir))} --add-dir ${shellQuote(toShellPath(library.libraryDir))}` : "";
615
+ const flags = `--chrome${claudePermissionFlag(opts?.autoMode)}${modelFlag}${effortFlag}${settings}${addDir}${libraryFlags}`;
616
+ const sysFlag = systemPrompt ? ` --append-system-prompt ${shellQuote(systemPrompt)}` : "";
617
+ const promptArg = prompt ? ` ${shellQuote(prompt)}` : "";
618
+ const tail = `${flags}${sysFlag}${promptArg}`;
619
+ cmd = sessionId && opts?.resume ? claudeResumeOrFresh(sessionId, worktreePath, tail) : `claude ${sessionId ? `--session-id ${sessionId} ` : opts?.resume ? "--continue " : ""}${tail}\r`;
620
+ } else {
621
+ const combined = [systemPrompt, prompt].filter(Boolean).join("\n\n");
622
+ const codexEffortFlag = effort ? `-c ${shellQuote(`model_reasoning_effort="${effort}"`)} ` : "";
623
+ const codexHome = opts?.library ? `CODEX_HOME=${shellQuote(toShellPath(opts.library.codexHome))} ` : "";
624
+ const promptArg = combined ? ` ${shellQuote(combined)}` : "";
625
+ cmd = `${codexHome}codex ${codexAutoFlag(opts?.autoMode)}${codexEffortFlag}--model ${model}${promptArg}\r`;
626
+ }
627
+ if (cmd.length > 2e5) console.warn(`[VibeFlow] launch command is ${cmd.length} bytes \u2014 approaching ARG_MAX`);
628
+ return cmd;
629
+ }
630
+ function normalizeModel(agent, model) {
631
+ return LEGACY_MODEL_FALLBACKS[agent]?.[model] ?? model;
632
+ }
633
+ function taskAgent(task) {
634
+ const agent = task.agentCli;
635
+ return agent && agent in AGENT_NAMES ? agent : "claude";
636
+ }
637
+ function taskModel(task) {
638
+ const agent = taskAgent(task);
639
+ return normalizeModel(agent, task.model || DEFAULT_MODELS[agent]);
640
+ }
641
+ function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
642
+ const agent = taskAgent(task);
643
+ const model = taskModel(task);
644
+ const artifactsDir = agentArtifactsDir(task.worktreePath, workspacePath) ?? ARTIFACTS_FALLBACK_DIR;
645
+ const builtInPrompt = buildArtifactPrompt(artifactsDir);
646
+ const libraryPrompt = opts?.library?.promptText?.trim();
647
+ const customPrompt = resolveSystemPrompt(systemPrompt);
648
+ const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
649
+ const includeTaskPrompt = opts?.includeTaskPrompt !== false;
650
+ const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
651
+ const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
652
+ return boardEnvPrefix(task, opts) + assembleCommand(
653
+ agent,
654
+ sys,
655
+ prompt,
656
+ model,
657
+ task.effort,
658
+ opts,
659
+ task.worktreePath,
660
+ sessionId,
661
+ workspacePath
662
+ );
663
+ }
664
+ var ARTIFACTS_DIR_SUFFIX, SCRATCH_DIR_NAME, ARTIFACTS_FALLBACK_DIR, AUTO_PERMISSION_MODE, SUBAGENTS_DIR2, PROGRESS_EVENTS_DIR, TODO_TOOLS, AGENT_NAMES, DEFAULT_MODELS, LEGACY_MODEL_FALLBACKS;
665
+ var init_launch = __esm({
666
+ "packages/core/src/launch.ts"() {
667
+ "use strict";
668
+ ARTIFACTS_DIR_SUFFIX = ".artifacts";
669
+ SCRATCH_DIR_NAME = "scratch";
670
+ ARTIFACTS_FALLBACK_DIR = ".vibeflow-artifacts";
671
+ AUTO_PERMISSION_MODE = "auto";
672
+ SUBAGENTS_DIR2 = ".vibeflow-subagents";
673
+ PROGRESS_EVENTS_DIR = ".vibeflow-events";
674
+ TODO_TOOLS = "TodoWrite|TaskCreate|TaskUpdate";
675
+ AGENT_NAMES = {
676
+ claude: "Claude Code",
677
+ codex: "Codex CLI"
678
+ };
679
+ DEFAULT_MODELS = {
680
+ claude: "sonnet",
681
+ codex: "gpt-5.5"
682
+ };
683
+ LEGACY_MODEL_FALLBACKS = {
684
+ codex: {
685
+ "gpt-5-codex": "gpt-5.5",
686
+ "gpt-5": "gpt-5.5"
687
+ }
688
+ };
689
+ }
690
+ });
691
+
502
692
  // packages/core/src/attachments.ts
503
693
  import { randomUUID } from "crypto";
504
694
  import { execFileSync as execFileSync3 } from "child_process";
@@ -522,9 +712,9 @@ function truncateUtf8(value, maxBytes) {
522
712
  return result;
523
713
  }
524
714
  function safeAttachmentName(name) {
525
- const basename2 = name.replace(/\\/g, "/").split("/").pop() ?? "";
715
+ const basename3 = name.replace(/\\/g, "/").split("/").pop() ?? "";
526
716
  const sanitized = truncateUtf8(
527
- basename2.replace(/[\u0000-\u001f\u007f]/g, "").trim(),
717
+ basename3.replace(/[\u0000-\u001f\u007f]/g, "").trim(),
528
718
  MAX_FILENAME_BYTES
529
719
  );
530
720
  return sanitized && sanitized !== "." && sanitized !== ".." ? sanitized : "attachment";
@@ -599,6 +789,33 @@ var init_attachments = __esm({
599
789
  }
600
790
  });
601
791
 
792
+ // packages/core/src/effort.ts
793
+ function isAgentEffort(value) {
794
+ return typeof value === "string" && AGENT_EFFORTS.includes(value);
795
+ }
796
+ function allowedEfforts(model) {
797
+ return model?.efforts ?? null;
798
+ }
799
+ function selectableEfforts(model) {
800
+ return allowedEfforts(model) ?? UNKNOWN_MODEL_EFFORTS;
801
+ }
802
+ function clampEffort(effort, allowed) {
803
+ if (allowed.length === 0) return void 0;
804
+ if (effort === void 0 || allowed.includes(effort)) return effort;
805
+ const rank = AGENT_EFFORTS.indexOf(effort);
806
+ const ordered = AGENT_EFFORTS.filter((level) => allowed.includes(level));
807
+ const notDeeper = ordered.filter((level) => AGENT_EFFORTS.indexOf(level) <= rank);
808
+ return notDeeper.length > 0 ? notDeeper[notDeeper.length - 1] : ordered[0];
809
+ }
810
+ var AGENT_EFFORTS, UNKNOWN_MODEL_EFFORTS;
811
+ var init_effort = __esm({
812
+ "packages/core/src/effort.ts"() {
813
+ "use strict";
814
+ AGENT_EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"];
815
+ UNKNOWN_MODEL_EFFORTS = ["low", "medium", "high", "xhigh"];
816
+ }
817
+ });
818
+
602
819
  // packages/core/src/progress.ts
603
820
  function emptyUsage() {
604
821
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 };
@@ -883,9 +1100,9 @@ var init_progress = __esm({
883
1100
 
884
1101
  // packages/tui/src/task-form.ts
885
1102
  import { spawnSync } from "child_process";
886
- import fs21 from "fs";
887
- import os7 from "os";
888
- import path25 from "path";
1103
+ import fs22 from "fs";
1104
+ import os9 from "os";
1105
+ import path26 from "path";
889
1106
  import React, { useEffect, useRef, useState } from "react";
890
1107
  import { Box, Text, render, useApp, useInput } from "ink";
891
1108
  function newFormState(defaultAutoMode, agents) {
@@ -951,11 +1168,19 @@ function visibleFields(s, ctx) {
951
1168
  return fields;
952
1169
  }
953
1170
  function modelOptions(ctx, agentCli, current) {
954
- const listed = ctx.models[agentCli] ?? [];
1171
+ const listed = (ctx.models[agentCli] ?? []).map((m) => m.id);
955
1172
  const options = ["", ...listed];
956
1173
  if (current && !listed.includes(current)) options.push(current);
957
1174
  return options;
958
1175
  }
1176
+ function effortOptions(ctx, agentCli, model) {
1177
+ const id = taskModel({ agentCli, model });
1178
+ return selectableEfforts(ctx.models[agentCli]?.find((m) => m.id === id));
1179
+ }
1180
+ function fitEffort(ctx, s) {
1181
+ const effort = clampEffort(s.effort, effortOptions(ctx, s.agentCli, s.model)) ?? s.effort;
1182
+ return effort === s.effort ? s : { ...s, effort };
1183
+ }
959
1184
  function blockReason(s, ctx, loadingGit) {
960
1185
  if (!s.title.trim()) return "\u8ACB\u8F38\u5165\u4EFB\u52D9\u6A19\u984C";
961
1186
  if (loadingGit) return "\u5075\u6E2C Git \u72C0\u614B\u4E2D\u2026";
@@ -1004,17 +1229,17 @@ function editorCommand(env, platform) {
1004
1229
  return env.VISUAL?.trim() || env.EDITOR?.trim() || (platform === "win32" ? "notepad" : "vi");
1005
1230
  }
1006
1231
  function editInEditor(text) {
1007
- const dir = fs21.mkdtempSync(path25.join(os7.tmpdir(), "vibeflow-"));
1008
- const file = path25.join(dir, "description.md");
1232
+ const dir = fs22.mkdtempSync(path26.join(os9.tmpdir(), "vibeflow-"));
1233
+ const file = path26.join(dir, "description.md");
1009
1234
  try {
1010
- fs21.writeFileSync(file, text);
1235
+ fs22.writeFileSync(file, text);
1011
1236
  const cmd = editorCommand(process.env, process.platform);
1012
1237
  const r = spawnSync(`${cmd} "${file}"`, { stdio: "inherit", shell: true });
1013
1238
  if (r.error) return { error: `\u7121\u6CD5\u555F\u52D5\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\uFF1A${r.error.message}` };
1014
1239
  if (r.status !== 0) return { error: `\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\u7D50\u675F\u78BC ${r.status}\uFF0C\u63CF\u8FF0\u672A\u8B8A\u66F4` };
1015
- return { text: fs21.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1240
+ return { text: fs22.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
1016
1241
  } finally {
1017
- fs21.rmSync(dir, { recursive: true, force: true });
1242
+ fs22.rmSync(dir, { recursive: true, force: true });
1018
1243
  }
1019
1244
  }
1020
1245
  function cycle(list, current, step) {
@@ -1085,10 +1310,10 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1085
1310
  if (editing === "title") setS((prev) => ({ ...prev, title: draft }));
1086
1311
  else if (editing === "branch") setS((prev) => ({ ...prev, branch: value2 }));
1087
1312
  else if (editing === "project") {
1088
- if (value2) setProject(path25.resolve(value2.replace(/^~(?=$|\/)/, os7.homedir())));
1313
+ if (value2) setProject(path26.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir())));
1089
1314
  } else if (editing === "attachments" && value2) {
1090
- const file = path25.resolve(value2.replace(/^~(?=$|\/)/, os7.homedir()));
1091
- if (!fs21.existsSync(file) || !fs21.statSync(file).isFile()) {
1315
+ const file = path26.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir()));
1316
+ if (!fs22.existsSync(file) || !fs22.statSync(file).isFile()) {
1092
1317
  setMessage(`\u627E\u4E0D\u5230\u6A94\u6848\uFF1A${file}`);
1093
1318
  return;
1094
1319
  }
@@ -1127,13 +1352,16 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1127
1352
  } else if (field === "baseBranch") {
1128
1353
  setS((prev) => ({ ...prev, baseBranch: cycle(prev.gitInfo?.branches ?? [], prev.baseBranch, step) }));
1129
1354
  } else if (field === "effort") {
1130
- setS((prev) => ({ ...prev, effort: cycle(EFFORTS.map((e) => e.value), prev.effort, step) }));
1355
+ setS((prev) => {
1356
+ const levels = effortOptions(ctx, prev.agentCli, prev.model);
1357
+ return levels.length ? { ...prev, effort: cycle([...levels], prev.effort, step) } : prev;
1358
+ });
1131
1359
  } else if (field === "autoMode") {
1132
1360
  setS((prev) => ({ ...prev, autoMode: !prev.autoMode }));
1133
1361
  } else if (field === "agent" && ctx.agents?.length) {
1134
- setS((prev) => ({ ...prev, agentCli: cycle(ctx.agents.map((a) => a.id), prev.agentCli, step), model: "" }));
1362
+ setS((prev) => fitEffort(ctx, { ...prev, agentCli: cycle(ctx.agents.map((a) => a.id), prev.agentCli, step), model: "" }));
1135
1363
  } else if (field === "model") {
1136
- setS((prev) => ({ ...prev, model: cycle(modelOptions(ctx, prev.agentCli, prev.model), prev.model, step) }));
1364
+ setS((prev) => fitEffort(ctx, { ...prev, model: cycle(modelOptions(ctx, prev.agentCli, prev.model), prev.model, step) }));
1137
1365
  }
1138
1366
  return;
1139
1367
  }
@@ -1168,14 +1396,15 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1168
1396
  case "branch":
1169
1397
  return s.branch || "\uFF08\u7559\u7A7A\u5247\u81EA\u52D5\u547D\u540D\uFF0C\u4F8B\u5982 feature/login-fix\uFF09";
1170
1398
  case "attachments":
1171
- return s.attachments.length ? s.attachments.map((a) => path25.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
1399
+ return s.attachments.length ? s.attachments.map((a) => path26.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
1172
1400
  case "description": {
1173
1401
  if (!s.description) return "\uFF08Enter \u7528\u7DE8\u8F2F\u5668\u64B0\u5BEB\uFF09";
1174
1402
  const lines = s.description.split("\n");
1175
1403
  return lines.length > 1 ? `${lines[0]} \u2026\uFF08\u5171 ${lines.length} \u884C\uFF09` : lines[0];
1176
1404
  }
1177
1405
  case "effort": {
1178
- const e = EFFORTS.find((x) => x.value === s.effort);
1406
+ if (effortOptions(ctx, s.agentCli, s.model).length === 0) return "\u4E0D\u9069\u7528";
1407
+ const e = EFFORTS.find((x) => x.value === s.effort) ?? EFFORTS[0];
1179
1408
  return `\u2039 ${e.label} \xB7 ${e.value} \u203A`;
1180
1409
  }
1181
1410
  case "autoMode":
@@ -1185,8 +1414,11 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1185
1414
  if (ctx.agents.length === 0) return "\u672A\u5075\u6E2C\u5230 Agent CLI\uFF08claude / codex\uFF09";
1186
1415
  return `\u2039 ${ctx.agents.find((a) => a.id === s.agentCli)?.name ?? s.agentCli} \u203A`;
1187
1416
  }
1188
- case "model":
1189
- return modelOptions(ctx, s.agentCli, s.model).length > 1 ? `\u2039 ${s.model || "\u4F7F\u7528\u9810\u8A2D model"} \u203A` : "\u7121\u6CD5\u53D6\u5F97 model list\uFF0C\u5C07\u4F7F\u7528\u9810\u8A2D model";
1417
+ case "model": {
1418
+ if (modelOptions(ctx, s.agentCli, s.model).length <= 1) return "\u7121\u6CD5\u53D6\u5F97 model list\uFF0C\u5C07\u4F7F\u7528\u9810\u8A2D model";
1419
+ const listed = ctx.models[s.agentCli]?.find((m) => m.id === s.model);
1420
+ return `\u2039 ${s.model ? listed?.label ?? s.model : "\u4F7F\u7528\u9810\u8A2D model"} \u203A`;
1421
+ }
1190
1422
  case "submit":
1191
1423
  return busy ?? (s.mode === "new" ? "\uFF3B \u5EFA\u7ACB\u4EFB\u52D9 \uFF3D" : "\uFF3B \u5132\u5B58\u8B8A\u66F4 \uFF3D");
1192
1424
  }
@@ -1199,13 +1431,17 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1199
1431
  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";
1200
1432
  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";
1201
1433
  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`;
1202
- if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path25.basename(s.projectPath)}`;
1434
+ if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path26.basename(s.projectPath)}`;
1203
1435
  }
1204
1436
  if (field === "branch") {
1205
1437
  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";
1206
1438
  return s.mode === "edit" && s.worktreePath ? `${when} \u9019\u5F35\u5361\u5DF2\u6709 worktree\uFF1A\u66F4\u6539\u5206\u652F\u3001\u5C08\u6848\u6216\u57FA\u6E96\u5206\u652F\u6703\u79FB\u9664\u5B83\uFF08\u6709\u672A\u5B8C\u6210\u8B8A\u66F4\u6642\u7121\u6CD5\u5132\u5B58\uFF09\u3002` : when;
1207
1439
  }
1208
- if (field === "effort") return `${EFFORTS.find((x) => x.value === s.effort).description} Claude Code \u8207 Codex \u6703\u5728\u555F\u52D5\u6B64\u4EFB\u52D9\u6642\u5957\u7528\uFF1B\u5BE6\u969B\u53EF\u7528\u7D1A\u8DDD\u4F9D model \u800C\u5B9A\u3002`;
1440
+ if (field === "effort") {
1441
+ if (effortOptions(ctx, s.agentCli, s.model).length === 0) return "\u9019\u500B model \u4E0D\u652F\u63F4\u8ABF\u6574\u63A8\u7406\u5F37\u5EA6\uFF0C\u555F\u52D5\u6642\u4E0D\u6703\u6307\u5B9A\u4EFB\u52D9\u8907\u96DC\u5EA6\u3002";
1442
+ const e = EFFORTS.find((x) => x.value === s.effort) ?? EFFORTS[0];
1443
+ return `${e.description} Claude Code \u8207 Codex \u6703\u5728\u555F\u52D5\u6B64\u4EFB\u52D9\u6642\u5957\u7528\uFF1B\u53EF\u9078\u7684\u7D1A\u8DDD\u4F9D model \u800C\u5B9A\u3002`;
1444
+ }
1209
1445
  if (field === "autoMode") return s.autoMode ? "\u958B\u555F\uFF1AAgent \u6703\u76F4\u63A5\u4FEE\u6539\u6A94\u6848\u3001\u57F7\u884C\u6307\u4EE4\uFF0C\u4E0D\u6703\u9010\u6B21\u5411\u4F60\u78BA\u8A8D\u3002" : "\u95DC\u9589\uFF1AAgent \u6BCF\u6B21\u8981\u52D5\u4F5C\u524D\uFF0C\u90FD\u6703\u5728\u7D42\u7AEF\u6A5F\u88E1\u7B49\u4F60\u5141\u8A31\u3002";
1210
1446
  if (field === "submit") return blockReason(s, ctx, loadingGit);
1211
1447
  return null;
@@ -1260,7 +1496,7 @@ async function loadFormContext(api) {
1260
1496
  const lists = await Promise.all(
1261
1497
  (agents ?? []).map(async (a) => {
1262
1498
  const list = await api.listAgentModels(a.id).catch(() => null);
1263
- return [a.id, list ? list.models.map((m) => m.id) : []];
1499
+ return [a.id, list ? list.models : []];
1264
1500
  })
1265
1501
  );
1266
1502
  return {
@@ -1277,13 +1513,17 @@ var h, EFFORTS, DEFAULT_EFFORT, LABELS, TEXT_FIELDS;
1277
1513
  var init_task_form = __esm({
1278
1514
  "packages/tui/src/task-form.ts"() {
1279
1515
  "use strict";
1516
+ init_effort();
1517
+ init_launch();
1280
1518
  init_attachments();
1281
1519
  h = React.createElement;
1282
1520
  EFFORTS = [
1283
1521
  { value: "low", label: "\u5FEB\u901F", description: "\u9069\u5408\u7BC4\u570D\u660E\u78BA\u3001\u4F4E\u98A8\u96AA\u7684\u5C0F\u4EFB\u52D9\u3002" },
1284
1522
  { value: "medium", label: "\u6A19\u6E96", description: "\u517C\u9867\u901F\u5EA6\u8207\u63A8\u7406\u6DF1\u5EA6\uFF0C\u9069\u5408\u4E00\u822C\u958B\u767C\u4EFB\u52D9\u3002" },
1285
1523
  { value: "high", label: "\u6DF1\u5165", description: "\u9069\u5408\u8DE8\u6A94\u6848\u3001\u9664\u932F\u6216\u9700\u8981\u8F03\u591A\u5224\u65B7\u7684\u4EFB\u52D9\u3002" },
1286
- { value: "xhigh", label: "\u6975\u81F4", description: "\u9069\u5408\u9AD8\u8907\u96DC\u5EA6\u3001\u9577\u93C8\u63A8\u7406\u6216\u9AD8\u98A8\u96AA\u8B8A\u66F4\u3002" }
1524
+ { value: "xhigh", label: "\u6975\u81F4", description: "\u9069\u5408\u9AD8\u8907\u96DC\u5EA6\u3001\u9577\u93C8\u63A8\u7406\u6216\u9AD8\u98A8\u96AA\u8B8A\u66F4\u3002" },
1525
+ { value: "max", label: "\u6700\u5927", description: "\u6700\u6DF1\u7684\u63A8\u7406\uFF0C\u9069\u5408\u6700\u56F0\u96E3\u7684\u554F\u984C\u3002" },
1526
+ { value: "ultra", label: "\u59D4\u6D3E", description: "\u6700\u6DF1\u63A8\u7406\u4E26\u81EA\u52D5\u59D4\u6D3E\u5B50\u4EFB\u52D9\uFF0C\u8017\u6642\u8207\u7528\u91CF\u53EF\u80FD\u660E\u986F\u589E\u52A0\u3002" }
1287
1527
  ];
1288
1528
  DEFAULT_EFFORT = "medium";
1289
1529
  LABELS = {
@@ -1319,7 +1559,7 @@ __export(src_exports, {
1319
1559
  showDiff: () => showDiff
1320
1560
  });
1321
1561
  import { spawnSync as spawnSync2 } from "child_process";
1322
- import path26 from "path";
1562
+ import path27 from "path";
1323
1563
  import React2, { useEffect as useEffect2, useState as useState2 } from "react";
1324
1564
  import { Box as Box2, Text as Text2, render as render2, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
1325
1565
  function findDetachKey(text) {
@@ -1333,8 +1573,8 @@ function quitHelp(backend, isHost) {
1333
1573
  if (isHost) return "q \u96E2\u958B\uFF08\u9019\u500B TUI \u5C31\u662F host\uFF0C\u57F7\u884C\u4E2D\u7684 agent \u6703\u4E00\u8D77\u7D50\u675F\uFF09";
1334
1574
  return "q \u96E2\u958B\uFF08agent \u7531\u57F7\u884C\u4E2D\u7684 host \u6301\u6709\uFF0C\u6703\u7E7C\u7E8C\u57F7\u884C\uFF09";
1335
1575
  }
1336
- function diffEnv(base, hasDelta) {
1337
- const env = { ...base };
1576
+ function diffEnv(base2, hasDelta) {
1577
+ const env = { ...base2 };
1338
1578
  if (hasDelta) env.GIT_PAGER = "delta --side-by-side --paging=always";
1339
1579
  else if (env.LESS === void 0) env.LESS = "R";
1340
1580
  return env;
@@ -1607,15 +1847,15 @@ function hasCommand(cmd, args) {
1607
1847
  return spawnSync2(cmd, args, { stdio: "ignore" }).status === 0;
1608
1848
  }
1609
1849
  function showDiff(task) {
1610
- const base = task.baseBranch ?? "HEAD";
1850
+ const base2 = task.baseBranch ?? "HEAD";
1611
1851
  const env = diffEnv(process.env, hasCommand("delta", ["--version"]));
1612
- spawnSync2("git", ["-C", task.worktreePath, "-c", "color.ui=always", "diff", base], {
1852
+ spawnSync2("git", ["-C", task.worktreePath, "-c", "color.ui=always", "diff", base2], {
1613
1853
  stdio: "inherit",
1614
1854
  env
1615
1855
  });
1616
1856
  }
1617
1857
  function tmuxAttach(opts, sessionName) {
1618
- const conf = path26.join(opts.userDataDir, "tmux.conf");
1858
+ const conf = path27.join(opts.userDataDir, "tmux.conf");
1619
1859
  const inOurServer = (process.env.TMUX ?? "").split(",")[0].endsWith(`/${TMUX_SOCKET}`);
1620
1860
  const args = inOurServer ? ["-L", TMUX_SOCKET, "-f", conf, "switch-client", "-t", `=${sessionName}`] : ["-L", TMUX_SOCKET, "-f", conf, "attach-session", "-t", `=${sessionName}`];
1621
1861
  spawnSync2("tmux", args, { stdio: "inherit", env: { ...process.env, TMUX: inOurServer ? process.env.TMUX : "" } });
@@ -1746,8 +1986,8 @@ var init_src = __esm({
1746
1986
  });
1747
1987
 
1748
1988
  // packages/cli/src/main.ts
1749
- import fs22 from "fs";
1750
- import path27 from "path";
1989
+ import fs23 from "fs";
1990
+ import path28 from "path";
1751
1991
  import { fileURLToPath } from "url";
1752
1992
  import { parseArgs as parseArgs2 } from "node:util";
1753
1993
 
@@ -2223,6 +2463,10 @@ function createBridge(t) {
2223
2463
  startGithubAuthLogin: () => t.invoke("settings:startGithubAuthLogin"),
2224
2464
  cancelGithubAuthLogin: () => t.invoke("settings:cancelGithubAuthLogin"),
2225
2465
  logoutGithubAuth: () => t.invoke("settings:logoutGithubAuth"),
2466
+ /** Open Issues/PRs of the board's GitHub repos that involve me; cached unless `force`. */
2467
+ getGithubInbox: (opts) => t.invoke("github:inbox", opts ?? {}),
2468
+ /** The Issue/PR each card belongs to, keyed by task id. */
2469
+ getGithubTaskLinks: (opts) => t.invoke("github:taskLinks", opts ?? {}),
2226
2470
  onGithubAuthEvent: (callback) => {
2227
2471
  return t.on("github-auth:event", (payload) => callback(payload));
2228
2472
  },
@@ -2533,8 +2777,8 @@ async function generateBranchName(title, description) {
2533
2777
  }
2534
2778
 
2535
2779
  // packages/core/src/git.ts
2536
- import { execFile as execFile4 } from "child_process";
2537
- import { promisify } from "util";
2780
+ import { execFile as execFile5 } from "child_process";
2781
+ import { promisify as promisify2 } from "util";
2538
2782
  import path12 from "path";
2539
2783
  import fs8 from "fs/promises";
2540
2784
 
@@ -2695,192 +2939,303 @@ function unwatchAllSubAgents() {
2695
2939
  }
2696
2940
  }
2697
2941
 
2698
- // packages/core/src/launch.ts
2699
- var ARTIFACTS_DIR_SUFFIX = ".artifacts";
2700
- var SCRATCH_DIR_NAME = "scratch";
2701
- var ARTIFACTS_FALLBACK_DIR = ".vibeflow-artifacts";
2702
- function pathBasename(p) {
2703
- const norm = p.replace(/\\/g, "/").replace(/\/+$/, "");
2704
- const i = norm.lastIndexOf("/");
2705
- return i >= 0 ? norm.slice(i + 1) : norm;
2706
- }
2707
- function agentArtifactsDir(worktreePath, workspacePath) {
2708
- if (!worktreePath || !workspacePath) return null;
2709
- const dir = toShellPath(workspacePath);
2710
- const ws = pathBasename(worktreePath);
2711
- return `${dir}/${ws}${ARTIFACTS_DIR_SUFFIX}`;
2712
- }
2713
- function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR) {
2714
- return [
2715
- "Artifact \u8A2D\u5B9A\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
2716
- `1. \u672C\u4EFB\u52D9\u7684 Artifact \u8CC7\u6599\u593E\u662F ${artifactsDir}/\uFF1B\u9700\u8981\u4FDD\u5B58\u7D66\u4F7F\u7528\u8005\u7684\u9A57\u8B49\u8B49\u64DA\u6216\u5831\u544A\u6642\uFF0C\u8ACB\u5BEB\u5165\u6B64\u8CC7\u6599\u593E\uFF08\u4E0D\u5B58\u5728\u8ACB\u5EFA\u7ACB\uFF09\u3002`,
2717
- `2. \u4F7F\u7528\u8005\u6703\u5728 VibeFlow \u7684 Artifacts \u5206\u9801\u6AA2\u8996\u6839\u76EE\u9304\u5167\u5BB9\uFF1B\u53EA\u653E\u4F60\u6703\u4E3B\u52D5\u8ACB\u4F7F\u7528\u8005\u904E\u76EE\u7684\u6A94\u6848\u3002`,
2718
- `3. \u4F60\u81EA\u5DF1\u7684\u5DE5\u4F5C\u66AB\u5B58\uFF08\u4E00\u6B21\u6027 script\u3001log\u3001\u4E2D\u9593\u8F38\u51FA\u3001debug \u6A94\uFF09\u653E\u5728 ${artifactsDir}/${SCRATCH_DIR_NAME}/\u3002`,
2719
- `4. \u82E5\u5DE5\u5177\u5148\u8F38\u51FA\u5230 /tmp\u3001/private/tmp\u3001$TMPDIR \u6216 ~/Downloads\uFF0C\u8ACB\u628A\u6700\u7D42\u8981\u5448\u73FE\u7684\u6A94\u6848\u8907\u88FD\u5230 ${artifactsDir}/ \u4E26\u78BA\u8A8D\u5B58\u5728\u3002`,
2720
- "5. Artifact \u8CC7\u6599\u593E\u4F4D\u65BC worktree \u4E4B\u5916\uFF1B\u4E0D\u8981\u628A\u5176\u4E2D\u5167\u5BB9\u52A0\u5165 git commit\uFF0C\u4E5F\u4E0D\u8981\u628A\u6700\u7D42\u4EA4\u4ED8\u7269\u53EA\u7559\u5728\u9019\u88E1\u3002"
2721
- ].join("\n");
2722
- }
2723
- var AUTO_PERMISSION_MODE = "auto";
2724
- function claudePermissionFlag(autoMode) {
2725
- return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
2726
- }
2727
- function shellQuote(s) {
2728
- return `'${s.replace(/'/g, `'\\''`)}'`;
2942
+ // packages/core/src/git.ts
2943
+ init_launch();
2944
+ init_env();
2945
+
2946
+ // packages/core/src/github.ts
2947
+ init_env();
2948
+ import { execFile as execFile4 } from "child_process";
2949
+ import { promisify } from "util";
2950
+ var pexec = promisify(execFile4);
2951
+ var defaultGhRunner = async (args) => {
2952
+ const { stdout } = await pexec("gh", args, {
2953
+ env: execEnv(),
2954
+ maxBuffer: 16 * 1024 * 1024,
2955
+ timeout: 3e4,
2956
+ windowsHide: true
2957
+ });
2958
+ return stdout.toString();
2959
+ };
2960
+ function parseGithubRepo(remoteUrl) {
2961
+ if (!remoteUrl) return null;
2962
+ const url = remoteUrl.trim();
2963
+ const match = url.match(/^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/) ?? url.match(/^ssh:\/\/git@github\.com(?::\d+)?\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/) ?? url.match(/^https?:\/\/(?:[^@/\s]+@)?github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/);
2964
+ return match ? `${match[1]}/${match[2]}` : null;
2965
+ }
2966
+ var REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
2967
+ function isGithubRef(value) {
2968
+ if (!value || typeof value !== "object") return false;
2969
+ const v = value;
2970
+ return (v.kind === "issue" || v.kind === "pr") && typeof v.repo === "string" && REPO_RE.test(v.repo) && typeof v.number === "number" && Number.isInteger(v.number) && v.number > 0 && typeof v.url === "string" && v.url.startsWith(`https://github.com/${v.repo}/`);
2971
+ }
2972
+ var ITEM_FIELDS = "number,title,url,body,author,assignees,labels,milestone,createdAt";
2973
+ var ISSUE_FIELDS = `${ITEM_FIELDS},issueType`;
2974
+ var PR_FIELDS = `${ITEM_FIELDS},state,isDraft,reviewDecision,headRefName,baseRefName`;
2975
+ var PR_INDEX_FIELDS = "number,url,state,isDraft,headRefName,closingIssuesReferences";
2976
+ var LIST_LIMIT = "50";
2977
+ var PR_INDEX_LIMIT = "100";
2978
+ async function runJson(run3, args) {
2979
+ const out = await run3(args);
2980
+ const parsed = JSON.parse(out || "[]");
2981
+ return Array.isArray(parsed) ? parsed : [];
2982
+ }
2983
+ function user(raw) {
2984
+ const u = raw;
2985
+ if (!u || typeof u.login !== "string") return null;
2986
+ return typeof u.name === "string" && u.name ? { login: u.login, name: u.name } : { login: u.login };
2987
+ }
2988
+ function base(raw, repo) {
2989
+ const milestone = raw.milestone;
2990
+ return {
2991
+ repo,
2992
+ number: Number(raw.number),
2993
+ title: String(raw.title ?? ""),
2994
+ url: String(raw.url ?? ""),
2995
+ body: typeof raw.body === "string" ? raw.body : "",
2996
+ author: user(raw.author),
2997
+ assignees: Array.isArray(raw.assignees) ? raw.assignees.map(user).filter(Boolean) : [],
2998
+ labels: Array.isArray(raw.labels) ? raw.labels.map((l) => ({ name: String(l.name ?? ""), color: String(l.color ?? "") })) : [],
2999
+ milestone: milestone && typeof milestone.title === "string" ? milestone.title : null,
3000
+ createdAt: String(raw.createdAt ?? "")
3001
+ };
2729
3002
  }
2730
- function toShellPath(p) {
2731
- return p.replace(/\\/g, "/");
3003
+ function prStateOf(raw) {
3004
+ if (raw.state === "MERGED") return "merged";
3005
+ if (raw.state === "CLOSED") return "closed";
3006
+ return raw.isDraft ? "draft" : "open";
2732
3007
  }
2733
- function boardEnvPrefix(task, opts) {
2734
- const board = opts?.boardCli;
2735
- if (!board) return "";
2736
- const entries = [
2737
- ["VIBEFLOW_TASK_ID", task.id],
2738
- ["VIBEFLOW_PROJECT_PATH", task.projectPath],
2739
- ["VIBEFLOW_BRANCH", task.branch],
2740
- ["VIBEFLOW_BASE_BRANCH", task.baseBranch],
2741
- ["VIBEFLOW_STORE_DIR", board?.storeDir],
2742
- ["VIBEFLOW_CLI", board?.cliPath],
2743
- ["VIBEFLOW_CLI_LOADER", board?.loaderPath],
2744
- ["VIBEFLOW_AUTO_MODE", opts?.autoMode ? "1" : "0"]
2745
- ];
2746
- const exports = entries.filter((entry) => Boolean(entry[1])).map(([key, value]) => `${key}=${shellQuote(toShellPath(value))}`);
2747
- return exports.length ? `export ${exports.join(" ")}; ` : "";
3008
+ function toIssue(raw, repo) {
3009
+ const type = raw.issueType;
3010
+ return {
3011
+ ...base(raw, repo),
3012
+ kind: "issue",
3013
+ issueType: type && typeof type.name === "string" ? { name: type.name, ...typeof type.color === "string" ? { color: type.color } : {} } : null
3014
+ };
2748
3015
  }
2749
- function codexAutoFlag(autoMode) {
2750
- return autoMode ? "--dangerously-bypass-approvals-and-sandbox " : "";
3016
+ function toPr(raw, repo) {
3017
+ return {
3018
+ ...base(raw, repo),
3019
+ kind: "pr",
3020
+ state: prStateOf(raw),
3021
+ reviewDecision: typeof raw.reviewDecision === "string" && raw.reviewDecision ? raw.reviewDecision : null,
3022
+ headRefName: String(raw.headRefName ?? ""),
3023
+ baseRefName: String(raw.baseRefName ?? "")
3024
+ };
2751
3025
  }
2752
- function claudeResumeOrFresh(sessionId, worktreePath, tail) {
2753
- if (!worktreePath) return `claude --resume ${sessionId} ${tail}\r`;
2754
- const munged = worktreePath.replace(/[^a-zA-Z0-9]/g, "-");
2755
- const sessionFile = `"$HOME/.claude/projects/${munged}/${sessionId}.jsonl"`;
2756
- return `if [ -f ${sessionFile} ]; then claude --resume ${sessionId} ${tail}; else claude --session-id ${sessionId} ${tail}; fi\r`;
3026
+ function mergeItems(lists) {
3027
+ const byNumber = /* @__PURE__ */ new Map();
3028
+ for (const list of lists) for (const item of list) if (!byNumber.has(item.number)) byNumber.set(item.number, item);
3029
+ return Array.from(byNumber.values()).sort(
3030
+ (a, b) => b.createdAt.localeCompare(a.createdAt) || b.number - a.number
3031
+ );
2757
3032
  }
2758
- var SUBAGENTS_DIR2 = ".vibeflow-subagents";
2759
- var PROGRESS_EVENTS_DIR = ".vibeflow-events";
2760
- var TODO_TOOLS = "TodoWrite|TaskCreate|TaskUpdate";
2761
- function buildClaudeSettings(worktreePath) {
2762
- const settings = { theme: "dark" };
2763
- if (worktreePath) {
2764
- const dir = `${toShellPath(worktreePath)}/${SUBAGENTS_DIR2}`;
2765
- const command = `mkdir -p "${dir}" && cat > "${dir}/$(date +%s)-$$-$RANDOM.json"`;
2766
- const taskHook = {
2767
- matcher: "Task",
2768
- hooks: [{ type: "command", command }]
2769
- };
2770
- const eventsDir = `${toShellPath(worktreePath)}/${PROGRESS_EVENTS_DIR}`;
2771
- const progress = {
2772
- hooks: [{ type: "command", command: `mkdir -p "${eventsDir}" && cat > "${eventsDir}/$(date +%s)-$$-$RANDOM.json"; exit 0` }]
2773
- };
2774
- settings.hooks = {
2775
- PreToolUse: [taskHook],
2776
- PostToolUse: [taskHook, { matcher: TODO_TOOLS, ...progress }],
2777
- Stop: [progress],
2778
- Notification: [progress],
2779
- UserPromptSubmit: [progress]
2780
- };
2781
- }
2782
- return JSON.stringify(settings);
3033
+ async function listIssues(run3, repo, filter) {
3034
+ const args = ["issue", "list", "-R", repo, "--state", "open", "--limit", LIST_LIMIT, ...filter];
3035
+ let rows;
3036
+ try {
3037
+ rows = await runJson(run3, [...args, "--json", ISSUE_FIELDS]);
3038
+ } catch (err) {
3039
+ if (!/issueType/.test(errorText(err))) throw err;
3040
+ rows = await runJson(run3, [...args, "--json", ITEM_FIELDS]);
3041
+ }
3042
+ return rows.map((r) => toIssue(r, repo));
3043
+ }
3044
+ async function listPrs(run3, repo, filter) {
3045
+ const rows = await runJson(run3, [
3046
+ "pr",
3047
+ "list",
3048
+ "-R",
3049
+ repo,
3050
+ "--state",
3051
+ "open",
3052
+ "--limit",
3053
+ LIST_LIMIT,
3054
+ ...filter,
3055
+ "--json",
3056
+ PR_FIELDS
3057
+ ]);
3058
+ return rows.map((r) => toPr(r, repo));
3059
+ }
3060
+ async function fetchRepoItems(run3, repo) {
3061
+ const [assignedIssues, authoredIssues, assignedPrs, authoredPrs, reviewPrs] = await Promise.all([
3062
+ listIssues(run3, repo, ["--assignee", "@me"]),
3063
+ listIssues(run3, repo, ["--author", "@me"]),
3064
+ listPrs(run3, repo, ["--assignee", "@me"]),
3065
+ listPrs(run3, repo, ["--author", "@me"]),
3066
+ listPrs(run3, repo, ["--search", "review-requested:@me"])
3067
+ ]);
3068
+ return {
3069
+ issues: mergeItems([assignedIssues, authoredIssues]),
3070
+ prs: mergeItems([assignedPrs, authoredPrs, reviewPrs])
3071
+ };
2783
3072
  }
2784
- function resolveSystemPrompt(custom) {
2785
- return custom && custom.trim() ? custom : "";
3073
+ async function fetchPrIndex(run3, repo) {
3074
+ const rows = await runJson(run3, [
3075
+ "pr",
3076
+ "list",
3077
+ "-R",
3078
+ repo,
3079
+ "--state",
3080
+ "all",
3081
+ "--limit",
3082
+ PR_INDEX_LIMIT,
3083
+ "--json",
3084
+ PR_INDEX_FIELDS
3085
+ ]);
3086
+ const [owner, name] = repo.split("/");
3087
+ return rows.map((r) => ({
3088
+ number: Number(r.number),
3089
+ url: String(r.url ?? ""),
3090
+ state: prStateOf(r),
3091
+ headRefName: String(r.headRefName ?? ""),
3092
+ closingIssues: (Array.isArray(r.closingIssuesReferences) ? r.closingIssuesReferences : []).filter((ref) => {
3093
+ const repository = ref.repository;
3094
+ const refOwner = repository?.owner?.login;
3095
+ return !repository || repository.name === name && refOwner === owner;
3096
+ }).map((ref) => ({
3097
+ number: Number(ref.number),
3098
+ url: typeof ref.url === "string" ? ref.url : `https://github.com/${repo}/issues/${ref.number}`
3099
+ }))
3100
+ }));
2786
3101
  }
2787
- function buildPrompt(task) {
2788
- const lines = [`\u4EFB\u52D9\u6A19\u984C\uFF1A${task.title}`];
2789
- const description = task.description?.trim();
2790
- if (description) {
2791
- lines.push("", "\u4EFB\u52D9\u63CF\u8FF0\uFF1A", description);
3102
+ function linkFromRef(ref, state) {
3103
+ return { kind: ref.kind, number: ref.number, url: ref.url, state };
3104
+ }
3105
+ function hasRealBranch(task) {
3106
+ return Boolean(task.provisionedAt || task.worktreePath || task.outcome);
3107
+ }
3108
+ function resolveTaskLinks(tasks, repoOfProject, prIndex) {
3109
+ const links = {};
3110
+ for (const task of tasks) {
3111
+ const source = task.github;
3112
+ const repo = source?.repo ?? (task.projectPath ? repoOfProject.get(task.projectPath) : void 0);
3113
+ const index = repo ? prIndex.get(repo) ?? [] : [];
3114
+ let pr;
3115
+ let prEntry;
3116
+ if (source?.kind === "pr") {
3117
+ prEntry = index.find((e) => e.number === source.number);
3118
+ pr = linkFromRef(source, prEntry?.state ?? "open");
3119
+ } else {
3120
+ if (hasRealBranch(task)) {
3121
+ prEntry = index.filter((e) => e.headRefName === task.branch).sort((a, b) => b.number - a.number)[0];
3122
+ }
3123
+ if (prEntry) {
3124
+ pr = { kind: "pr", number: prEntry.number, url: prEntry.url, state: prEntry.state };
3125
+ } else if (task.outcome?.pr) {
3126
+ const o = task.outcome.pr;
3127
+ pr = { kind: "pr", number: o.number, url: o.url, state: prStateOf({ state: o.state }) };
3128
+ }
3129
+ }
3130
+ const issueState = pr?.state === "merged" ? "closed" : "open";
3131
+ let issue;
3132
+ if (source?.kind === "issue") {
3133
+ issue = linkFromRef(source, issueState);
3134
+ } else if (prEntry?.closingIssues[0]) {
3135
+ const ref = prEntry.closingIssues[0];
3136
+ issue = { kind: "issue", number: ref.number, url: ref.url, state: issueState };
3137
+ }
3138
+ if (pr || issue) links[task.id] = { ...issue ? { issue } : {}, ...pr ? { pr } : {} };
2792
3139
  }
2793
- return lines.join("\n");
3140
+ return links;
2794
3141
  }
2795
- function executorSessionId(taskId, runId) {
2796
- const source = runId ? `${taskId}:${runId}` : taskId;
2797
- const raw = source.replace(/[^0-9a-f]/gi, "").toLowerCase();
2798
- const hex = runId ? `${raw.slice(0, 24).padEnd(24, "0")}${namespaceHash(`executor:${source}`)}` : raw.padEnd(32, "0").slice(0, 32);
2799
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
3142
+ function errorText(err) {
3143
+ const e = err;
3144
+ const stderr = e?.stderr ? e.stderr.toString().trim() : "";
3145
+ return stderr || e?.message || String(err);
2800
3146
  }
2801
- function namespaceHash(namespace) {
2802
- let hash = 2166136261;
2803
- for (let i = 0; i < namespace.length; i += 1) {
2804
- hash ^= namespace.charCodeAt(i);
2805
- hash = Math.imul(hash, 16777619);
2806
- }
2807
- return (hash >>> 0).toString(16).padStart(8, "0");
3147
+ var GITHUB_CACHE_TTL_MS = 5 * 60 * 1e3;
3148
+ function boardTasks(board) {
3149
+ return [...board.in_progress, ...board.backlog, ...board.done];
2808
3150
  }
2809
- function assembleCommand(agent, systemPrompt, prompt, model, effort, opts, worktreePath, sessionId, workspacePath) {
2810
- let cmd;
2811
- if (agent === "claude") {
2812
- const settings = ` --settings ${shellQuote(buildClaudeSettings(worktreePath))}`;
2813
- const addDir = workspacePath ? ` --add-dir ${shellQuote(toShellPath(workspacePath))}` : "";
2814
- const modelFlag = model ? ` --model ${model}` : "";
2815
- const effortFlag = effort ? ` --effort ${effort}` : "";
2816
- const library = opts?.library;
2817
- const libraryFlags = library ? ` --plugin-dir ${shellQuote(toShellPath(library.pluginDir))} --add-dir ${shellQuote(toShellPath(library.libraryDir))}` : "";
2818
- const flags = `--chrome${claudePermissionFlag(opts?.autoMode)}${modelFlag}${effortFlag}${settings}${addDir}${libraryFlags}`;
2819
- const sysFlag = systemPrompt ? ` --append-system-prompt ${shellQuote(systemPrompt)}` : "";
2820
- const promptArg = prompt ? ` ${shellQuote(prompt)}` : "";
2821
- const tail = `${flags}${sysFlag}${promptArg}`;
2822
- cmd = sessionId && opts?.resume ? claudeResumeOrFresh(sessionId, worktreePath, tail) : `claude ${sessionId ? `--session-id ${sessionId} ` : opts?.resume ? "--continue " : ""}${tail}\r`;
2823
- } else {
2824
- const combined = [systemPrompt, prompt].filter(Boolean).join("\n\n");
2825
- const codexEffortFlag = effort ? `-c ${shellQuote(`model_reasoning_effort="${effort}"`)} ` : "";
2826
- const codexHome = opts?.library ? `CODEX_HOME=${shellQuote(toShellPath(opts.library.codexHome))} ` : "";
2827
- const promptArg = combined ? ` ${shellQuote(combined)}` : "";
2828
- cmd = `${codexHome}codex ${codexAutoFlag(opts?.autoMode)}${codexEffortFlag}--model ${model}${promptArg}\r`;
3151
+ function boardProjects(board) {
3152
+ const latest = /* @__PURE__ */ new Map();
3153
+ for (const t of boardTasks(board)) {
3154
+ if (!t.projectPath) continue;
3155
+ latest.set(t.projectPath, Math.max(latest.get(t.projectPath) ?? 0, t.createdAt ?? 0));
2829
3156
  }
2830
- if (cmd.length > 2e5) console.warn(`[VibeFlow] launch command is ${cmd.length} bytes \u2014 approaching ARG_MAX`);
2831
- return cmd;
3157
+ return Array.from(latest.entries()).sort((a, b) => b[1] - a[1]).map(([p]) => p);
2832
3158
  }
2833
- var AGENT_NAMES = {
2834
- claude: "Claude Code",
2835
- codex: "Codex CLI"
2836
- };
2837
- var DEFAULT_MODELS = {
2838
- claude: "sonnet",
2839
- codex: "gpt-5.5"
2840
- };
2841
- var LEGACY_MODEL_FALLBACKS = {
2842
- codex: {
2843
- "gpt-5-codex": "gpt-5.5",
2844
- "gpt-5": "gpt-5.5"
3159
+ function basename(p) {
3160
+ return p.split(/[/\\]/).filter(Boolean).pop() ?? p;
3161
+ }
3162
+ function createGithubService(options) {
3163
+ const run3 = options.run ?? defaultGhRunner;
3164
+ const authStatus = options.authStatus;
3165
+ const now = options.now ?? Date.now;
3166
+ const items = /* @__PURE__ */ new Map();
3167
+ const indexes = /* @__PURE__ */ new Map();
3168
+ const repos = /* @__PURE__ */ new Map();
3169
+ function cached2(map, key, force, load) {
3170
+ const hit = map.get(key);
3171
+ if (hit && !force && now() - hit.at < GITHUB_CACHE_TTL_MS) return hit;
3172
+ const entry = { at: now(), value: load() };
3173
+ entry.value.catch(() => {
3174
+ });
3175
+ map.set(key, entry);
3176
+ return entry;
3177
+ }
3178
+ async function projectRepos(board, force) {
3179
+ const out = /* @__PURE__ */ new Map();
3180
+ const projects = boardProjects(board);
3181
+ const resolved = await Promise.all(
3182
+ projects.map((p) => cached2(repos, p, force, () => options.repoOf(p)).value.catch(() => null))
3183
+ );
3184
+ projects.forEach((p, i) => {
3185
+ const repo = resolved[i];
3186
+ if (repo) out.set(p, repo);
3187
+ });
3188
+ return out;
2845
3189
  }
2846
- };
2847
- function normalizeModel(agent, model) {
2848
- return LEGACY_MODEL_FALLBACKS[agent]?.[model] ?? model;
2849
- }
2850
- function taskAgent(task) {
2851
- const agent = task.agentCli;
2852
- return agent && agent in AGENT_NAMES ? agent : "claude";
2853
- }
2854
- function taskModel(task) {
2855
- const agent = taskAgent(task);
2856
- return normalizeModel(agent, task.model || DEFAULT_MODELS[agent]);
2857
- }
2858
- function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
2859
- const agent = taskAgent(task);
2860
- const model = taskModel(task);
2861
- const artifactsDir = agentArtifactsDir(task.worktreePath, workspacePath) ?? ARTIFACTS_FALLBACK_DIR;
2862
- const builtInPrompt = buildArtifactPrompt(artifactsDir);
2863
- const libraryPrompt = opts?.library?.promptText?.trim();
2864
- const customPrompt = resolveSystemPrompt(systemPrompt);
2865
- const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
2866
- const includeTaskPrompt = opts?.includeTaskPrompt !== false;
2867
- const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
2868
- const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
2869
- return boardEnvPrefix(task, opts) + assembleCommand(
2870
- agent,
2871
- sys,
2872
- prompt,
2873
- model,
2874
- task.effort,
2875
- opts,
2876
- task.worktreePath,
2877
- sessionId,
2878
- workspacePath
2879
- );
3190
+ return {
3191
+ async inbox(board, opts = {}) {
3192
+ const force = opts.force === true;
3193
+ const auth = await authStatus();
3194
+ if (!auth.installed) return { status: "gh-missing", fetchedAt: 0, repos: [] };
3195
+ if (!auth.authenticated) return { status: "unauthenticated", fetchedAt: 0, repos: [] };
3196
+ const byProject = await projectRepos(board, force);
3197
+ const repoProject = /* @__PURE__ */ new Map();
3198
+ for (const [project, repo] of byProject) if (!repoProject.has(repo)) repoProject.set(repo, project);
3199
+ const entries = Array.from(repoProject.entries());
3200
+ const results = await Promise.all(
3201
+ entries.map(async ([repo, projectPath]) => {
3202
+ const entry = cached2(items, repo, force, () => fetchRepoItems(run3, repo));
3203
+ const head = { repo, projectPath, projectName: basename(projectPath) };
3204
+ try {
3205
+ return { at: entry.at, inbox: { ...head, ...await entry.value } };
3206
+ } catch (err) {
3207
+ return { at: entry.at, inbox: { ...head, issues: [], prs: [], error: errorText(err) } };
3208
+ }
3209
+ })
3210
+ );
3211
+ return {
3212
+ status: "ok",
3213
+ ...auth.login ? { login: auth.login } : {},
3214
+ fetchedAt: results.length ? Math.min(...results.map((r) => r.at)) : 0,
3215
+ repos: results.map((r) => r.inbox).sort((a, b) => a.repo.localeCompare(b.repo))
3216
+ };
3217
+ },
3218
+ async taskLinks(board, opts = {}) {
3219
+ const force = opts.force === true;
3220
+ const tasks = boardTasks(board);
3221
+ const byProject = await projectRepos(board, force);
3222
+ const wanted = new Set(byProject.values());
3223
+ for (const t of tasks) if (t.github) wanted.add(t.github.repo);
3224
+ const prIndex = /* @__PURE__ */ new Map();
3225
+ await Promise.all(
3226
+ Array.from(wanted).map(async (repo) => {
3227
+ try {
3228
+ prIndex.set(repo, await cached2(indexes, repo, force, () => fetchPrIndex(run3, repo)).value);
3229
+ } catch {
3230
+ }
3231
+ })
3232
+ );
3233
+ return resolveTaskLinks(tasks, byProject, prIndex);
3234
+ }
3235
+ };
2880
3236
  }
2881
3237
 
2882
3238
  // packages/core/src/git.ts
2883
- init_env();
2884
3239
  init_attachments();
2885
3240
 
2886
3241
  // packages/core/src/artifacts.ts
@@ -3038,9 +3393,9 @@ function deleteArtifacts(baseDir, worktreePath) {
3038
3393
  }
3039
3394
 
3040
3395
  // packages/core/src/git.ts
3041
- var pexec = promisify(execFile4);
3396
+ var pexec2 = promisify2(execFile5);
3042
3397
  async function git(cwd, args) {
3043
- const { stdout } = await pexec("git", args, {
3398
+ const { stdout } = await pexec2("git", args, {
3044
3399
  cwd,
3045
3400
  maxBuffer: 32 * 1024 * 1024,
3046
3401
  env: execEnv()
@@ -3409,7 +3764,7 @@ function copyIgnoredDependenciesInBackground(projectPath, worktreePath, entries)
3409
3764
  }
3410
3765
  })();
3411
3766
  }
3412
- async function adoptRemoteBranch(projectPath, worktreePath, branch, base) {
3767
+ async function adoptRemoteBranch(projectPath, worktreePath, branch, base2) {
3413
3768
  try {
3414
3769
  await git(projectPath, ["fetch", "origin", branch]);
3415
3770
  } catch {
@@ -3431,32 +3786,32 @@ async function adoptRemoteBranch(projectPath, worktreePath, branch, base) {
3431
3786
  }
3432
3787
  const ignoredEntries = await copySmallIgnoredFiles(projectPath, worktreePath);
3433
3788
  copyIgnoredDependenciesInBackground(projectPath, worktreePath, ignoredEntries);
3434
- return { branch, worktreePath, pushed: true, baseBranch: base };
3789
+ return { branch, worktreePath, pushed: true, baseBranch: base2 };
3435
3790
  }
3436
3791
  async function provisionWorktree(projectPath, workspacePath, taskId, baseBranch, preferredBranch, opts = {}) {
3437
3792
  await ensureLocalExclude(projectPath);
3438
3793
  const branch = opts.explicitBranch && preferredBranch ? preferredBranch : await resolveBranchName(projectPath, workspacePath, taskId, preferredBranch);
3439
3794
  const worktreePath = path12.join(workspacePath, worktreeDirName(branch));
3440
3795
  const info = await getGitInfo(projectPath);
3441
- const base = baseBranch || info.defaultBase || info.currentBranch || "HEAD";
3796
+ const base2 = baseBranch || info.defaultBase || info.currentBranch || "HEAD";
3442
3797
  if (opts.explicitBranch && info.hasRemote && await remoteBranchExists(projectPath, branch)) {
3443
- return adoptRemoteBranch(projectPath, worktreePath, branch, base);
3798
+ return adoptRemoteBranch(projectPath, worktreePath, branch, base2);
3444
3799
  }
3445
- let startPoint = base;
3800
+ let startPoint = base2;
3446
3801
  if (info.hasRemote) {
3447
3802
  try {
3448
- await git(projectPath, ["fetch", "origin", `${base}:${base}`]);
3803
+ await git(projectPath, ["fetch", "origin", `${base2}:${base2}`]);
3449
3804
  } catch {
3450
3805
  try {
3451
- await git(projectPath, ["fetch", "origin", base]);
3806
+ await git(projectPath, ["fetch", "origin", base2]);
3452
3807
  } catch {
3453
3808
  }
3454
3809
  }
3455
3810
  try {
3456
- await git(projectPath, ["rev-parse", "--verify", `origin/${base}`]);
3457
- startPoint = `origin/${base}`;
3811
+ await git(projectPath, ["rev-parse", "--verify", `origin/${base2}`]);
3812
+ startPoint = `origin/${base2}`;
3458
3813
  } catch {
3459
- startPoint = base;
3814
+ startPoint = base2;
3460
3815
  }
3461
3816
  }
3462
3817
  try {
@@ -3486,7 +3841,7 @@ async function provisionWorktree(projectPath, workspacePath, taskId, baseBranch,
3486
3841
  copySmallIgnoredFiles(projectPath, worktreePath)
3487
3842
  ]);
3488
3843
  copyIgnoredDependenciesInBackground(projectPath, worktreePath, ignoredEntries);
3489
- return { branch, worktreePath, pushed, baseBranch: base };
3844
+ return { branch, worktreePath, pushed, baseBranch: base2 };
3490
3845
  }
3491
3846
  async function removeWorktree(projectPath, worktreePath) {
3492
3847
  try {
@@ -3698,7 +4053,7 @@ var MAX_OUTCOME_COMMITS = 50;
3698
4053
  var OUTCOME_LOG_FORMAT = "%h%x00%s";
3699
4054
  async function getOutcomePr(worktreePath) {
3700
4055
  try {
3701
- const { stdout } = await pexec(
4056
+ const { stdout } = await pexec2(
3702
4057
  "gh",
3703
4058
  ["pr", "view", "--json", "url,number,state,title,body"],
3704
4059
  { cwd: worktreePath, env: execEnv() }
@@ -3759,7 +4114,7 @@ async function captureTaskOutcome(worktreePath, baseBranch) {
3759
4114
  }
3760
4115
  async function getPrStatus(worktreePath) {
3761
4116
  try {
3762
- const { stdout } = await pexec(
4117
+ const { stdout } = await pexec2(
3763
4118
  "gh",
3764
4119
  ["pr", "view", "--json", "url,number,state"],
3765
4120
  { cwd: worktreePath, env: execEnv() }
@@ -3775,17 +4130,9 @@ async function getGithubCompareUrl(worktreePath, baseBranch) {
3775
4130
  try {
3776
4131
  const remoteUrl = await git(worktreePath, ["remote", "get-url", "origin"]);
3777
4132
  const branch = await git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
3778
- let webUrl;
3779
- const sshMatch = remoteUrl.match(/git@github\.com:(.+?)(?:\.git)?$/);
3780
- const httpsMatch = remoteUrl.match(/https?:\/\/github\.com\/(.+?)(?:\.git)?$/);
3781
- if (sshMatch) {
3782
- webUrl = `https://github.com/${sshMatch[1]}`;
3783
- } else if (httpsMatch) {
3784
- webUrl = `https://github.com/${httpsMatch[1]}`;
3785
- } else {
3786
- return null;
3787
- }
3788
- return `${webUrl}/compare/${baseBranch}...${branch}?expand=1`;
4133
+ const repo = parseGithubRepo(remoteUrl);
4134
+ if (!repo) return null;
4135
+ return `https://github.com/${repo}/compare/${baseBranch}...${branch}?expand=1`;
3789
4136
  } catch {
3790
4137
  return null;
3791
4138
  }
@@ -3852,10 +4199,10 @@ function taskAttachmentStagingPath(workspacePath, taskId) {
3852
4199
 
3853
4200
  // packages/core/src/agents.ts
3854
4201
  init_env();
3855
- import { execFile as execFile5 } from "child_process";
3856
- import { promisify as promisify2 } from "util";
3857
- var pexec2 = promisify2(execFile5);
3858
- var AGENT_EFFORTS = ["low", "medium", "high", "xhigh"];
4202
+ init_effort();
4203
+ import { execFile as execFile6 } from "child_process";
4204
+ import { promisify as promisify3 } from "util";
4205
+ var pexec3 = promisify3(execFile6);
3859
4206
  var DEFAULT_TASK_EFFORT = "medium";
3860
4207
  var AGENT_CLIS = [
3861
4208
  {
@@ -3882,7 +4229,7 @@ var AGENT_CLIS = [
3882
4229
  async function commandExists(bin) {
3883
4230
  try {
3884
4231
  const command = process.platform === "win32" ? "where.exe" : "which";
3885
- await pexec2(command, [bin], {
4232
+ await pexec3(command, [bin], {
3886
4233
  env: execEnv(),
3887
4234
  timeout: 2500,
3888
4235
  windowsHide: true
@@ -3957,7 +4304,8 @@ async function createTaskFromInput(input) {
3957
4304
  agentCli: input.agentCli ?? "claude",
3958
4305
  model: input.model || void 0,
3959
4306
  effort: input.effort ?? DEFAULT_TASK_EFFORT,
3960
- autoMode: input.autoMode
4307
+ autoMode: input.autoMode,
4308
+ ...input.github ? { github: input.github } : {}
3961
4309
  };
3962
4310
  try {
3963
4311
  const board = store.get("board");
@@ -4244,8 +4592,8 @@ async function taskCommand(argv) {
4244
4592
  }
4245
4593
 
4246
4594
  // packages/cli/src/host.ts
4247
- import fs19 from "fs";
4248
- import path23 from "path";
4595
+ import fs20 from "fs";
4596
+ import path24 from "path";
4249
4597
  import { randomBytes as randomBytes2 } from "crypto";
4250
4598
 
4251
4599
  // packages/core/src/events.ts
@@ -4311,9 +4659,9 @@ function truncateUtf82(value, maxBytes) {
4311
4659
  return result;
4312
4660
  }
4313
4661
  function safeLibraryName(name) {
4314
- const basename2 = name.replace(/\\/g, "/").split("/").pop() ?? "";
4662
+ const basename3 = name.replace(/\\/g, "/").split("/").pop() ?? "";
4315
4663
  const cleaned = truncateUtf82(
4316
- basename2.replace(/[\u0000-\u001f\u007f]/g, "").trim(),
4664
+ basename3.replace(/[\u0000-\u001f\u007f]/g, "").trim(),
4317
4665
  MAX_NAME_BYTES
4318
4666
  );
4319
4667
  return cleaned && cleaned !== "." && cleaned !== ".." ? cleaned : "untitled";
@@ -4783,35 +5131,266 @@ async function syncBuiltinsAtStartup() {
4783
5131
  }
4784
5132
 
4785
5133
  // packages/core/src/service.ts
4786
- import fs17 from "fs";
4787
- import os6 from "os";
4788
- import path21 from "path";
5134
+ import fs18 from "fs";
5135
+ import os8 from "os";
5136
+ import path22 from "path";
4789
5137
  import { randomUUID as randomUUID4 } from "crypto";
4790
5138
 
4791
5139
  // packages/core/src/agent-models.ts
4792
- import { execFile as execFile6 } from "child_process";
5140
+ import fs13 from "fs";
5141
+ import os6 from "os";
5142
+ import path17 from "path";
5143
+
5144
+ // packages/core/src/claude-models.ts
5145
+ init_effort();
4793
5146
  import fs12 from "fs";
4794
5147
  import os4 from "os";
4795
5148
  import path16 from "path";
4796
- import { promisify as promisify3 } from "util";
5149
+
5150
+ // packages/core/src/json-lines-process.ts
4797
5151
  init_env();
4798
- var pexec3 = promisify3(execFile6);
5152
+ import { spawn as spawn4 } from "child_process";
5153
+ function spawnJsonLines(bin, args, opts) {
5154
+ const spawnOpts = { cwd: opts.cwd, env: opts.env ?? execEnv(), windowsHide: true };
5155
+ const proc = process.platform === "win32" ? spawn4([bin, ...args].join(" "), { ...spawnOpts, shell: true }) : spawn4(bin, args, spawnOpts);
5156
+ const inbox = [];
5157
+ const waiters = [];
5158
+ let failure = null;
5159
+ let stderr = "";
5160
+ let exited = false;
5161
+ const finished = () => {
5162
+ if (exited) return;
5163
+ exited = true;
5164
+ opts.onExit?.();
5165
+ };
5166
+ const fail2 = (err) => {
5167
+ if (failure) return;
5168
+ failure = err;
5169
+ for (const w of waiters.splice(0)) w.reject(err);
5170
+ };
5171
+ const deliver = (message) => {
5172
+ const i = waiters.findIndex((w) => w.match(message));
5173
+ if (i === -1) inbox.push(message);
5174
+ else waiters.splice(i, 1)[0].resolve(message);
5175
+ };
5176
+ let buffered = "";
5177
+ proc.stdout.setEncoding("utf8");
5178
+ proc.stdout.on("data", (chunk) => {
5179
+ buffered += chunk;
5180
+ let nl;
5181
+ while ((nl = buffered.indexOf("\n")) >= 0) {
5182
+ const line = buffered.slice(0, nl).trim();
5183
+ buffered = buffered.slice(nl + 1);
5184
+ if (!line) continue;
5185
+ let message;
5186
+ try {
5187
+ message = JSON.parse(line);
5188
+ } catch {
5189
+ continue;
5190
+ }
5191
+ deliver(message);
5192
+ }
5193
+ });
5194
+ proc.stderr.setEncoding("utf8");
5195
+ proc.stderr.on("data", (chunk) => {
5196
+ stderr = (stderr + chunk).slice(-2048);
5197
+ });
5198
+ proc.on("error", (err) => {
5199
+ fail2(err);
5200
+ if (proc.pid === void 0) {
5201
+ clearTimeout(timer);
5202
+ finished();
5203
+ }
5204
+ });
5205
+ proc.on("exit", (code2, signal) => {
5206
+ finished();
5207
+ clearTimeout(timer);
5208
+ clearTimeout(graceTimer);
5209
+ const reason = signal ? `was killed (${signal})` : `exited with code ${code2}`;
5210
+ fail2(new Error(`${bin} ${reason}${stderr.trim() ? `: ${stderr.trim()}` : ""}`));
5211
+ });
5212
+ proc.stdin.on("error", () => {
5213
+ });
5214
+ const kill = () => {
5215
+ if (exited) return;
5216
+ if (process.platform === "win32" && proc.pid !== void 0) {
5217
+ spawn4("taskkill", ["/pid", String(proc.pid), "/T", "/F"], { windowsHide: true }).on("error", () => proc.kill());
5218
+ return;
5219
+ }
5220
+ proc.kill();
5221
+ };
5222
+ const timer = setTimeout(() => {
5223
+ fail2(new Error(`${bin} did not answer within ${opts.timeoutMs}ms`));
5224
+ kill();
5225
+ }, opts.timeoutMs);
5226
+ let graceTimer;
5227
+ return {
5228
+ send(message) {
5229
+ if (!failure) proc.stdin.write(`${JSON.stringify(message)}
5230
+ `);
5231
+ },
5232
+ next(match) {
5233
+ const i = inbox.findIndex(match);
5234
+ if (i >= 0) return Promise.resolve(inbox.splice(i, 1)[0]);
5235
+ if (failure) return Promise.reject(failure);
5236
+ return new Promise((resolve, reject) => waiters.push({ match, resolve, reject }));
5237
+ },
5238
+ close() {
5239
+ clearTimeout(timer);
5240
+ fail2(new Error(`${bin} conversation closed`));
5241
+ proc.stdin.end();
5242
+ if (!exited && !graceTimer) graceTimer = setTimeout(kill, opts.exitGraceMs ?? 3e3);
5243
+ }
5244
+ };
5245
+ }
5246
+
5247
+ // packages/core/src/claude-models.ts
5248
+ var CLAUDE_MODELS_TIMEOUT_MS = 15e3;
5249
+ var REQUEST_ID = "vibeflow-models";
5250
+ var CLAUDE_ARGS = ["-p", "--input-format", "stream-json", "--output-format", "stream-json", "--verbose"];
5251
+ function openClaude() {
5252
+ const cwd = fs12.mkdtempSync(path16.join(os4.tmpdir(), "vf-claude-models-"));
5253
+ return spawnJsonLines("claude", CLAUDE_ARGS, {
5254
+ cwd,
5255
+ timeoutMs: CLAUDE_MODELS_TIMEOUT_MS,
5256
+ onExit: () => fs12.rmSync(cwd, { recursive: true, force: true })
5257
+ });
5258
+ }
5259
+ function isRecord(value) {
5260
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5261
+ }
5262
+ function isOurResponse(message) {
5263
+ return isRecord(message) && message.type === "control_response" && isRecord(message.response) && message.response.request_id === REQUEST_ID;
5264
+ }
5265
+ function parseClaudeInitialize(message) {
5266
+ const outer = isRecord(message) && isRecord(message.response) ? message.response : null;
5267
+ if (outer?.subtype === "error") throw new Error(`claude refused the handshake: ${String(outer.error ?? "unknown error")}`);
5268
+ const body = outer && isRecord(outer.response) ? outer.response : null;
5269
+ if (!body || !Array.isArray(body.models)) throw new Error("claude handshake has no models array");
5270
+ const seen = /* @__PURE__ */ new Set();
5271
+ const models = [];
5272
+ for (const entry of body.models) {
5273
+ if (!isRecord(entry) || typeof entry.value !== "string") continue;
5274
+ const id = entry.value.trim();
5275
+ if (!id || id === "default" || seen.has(id)) continue;
5276
+ seen.add(id);
5277
+ const label = typeof entry.displayName === "string" && entry.displayName.trim() ? entry.displayName.trim() : id;
5278
+ const description = typeof entry.description === "string" && entry.description.trim() ? entry.description.trim() : void 0;
5279
+ const levels = entry.supportsEffort === true && Array.isArray(entry.supportedEffortLevels) ? entry.supportedEffortLevels.filter(isAgentEffort) : [];
5280
+ models.push({ id, label, ...description ? { description } : {}, efforts: levels });
5281
+ }
5282
+ if (models.length === 0) throw new Error("claude handshake lists no selectable models");
5283
+ return models;
5284
+ }
5285
+ async function fetchClaudeModels(open = openClaude) {
5286
+ const channel = open();
5287
+ try {
5288
+ channel.send({ type: "control_request", request_id: REQUEST_ID, request: { subtype: "initialize" } });
5289
+ return parseClaudeInitialize(await channel.next(isOurResponse));
5290
+ } finally {
5291
+ channel.close();
5292
+ }
5293
+ }
5294
+
5295
+ // packages/core/src/codex-models.ts
5296
+ init_effort();
5297
+ init_env();
5298
+ import os5 from "os";
4799
5299
  var CODEX_MODELS_TIMEOUT_MS = 2e4;
5300
+ var MAX_PAGES = 20;
5301
+ var CodexLoginRequiredError = class extends Error {
5302
+ constructor() {
5303
+ super("Codex CLI is not signed in; run `codex login`");
5304
+ this.name = "CodexLoginRequiredError";
5305
+ }
5306
+ };
5307
+ function codexAppServerCommand() {
5308
+ return {
5309
+ bin: "codex",
5310
+ args: ["app-server", "--listen", "stdio://"],
5311
+ cwd: os5.homedir(),
5312
+ env: execEnv()
5313
+ };
5314
+ }
5315
+ function openCodex() {
5316
+ const { bin, args, cwd, env } = codexAppServerCommand();
5317
+ return spawnJsonLines(bin, args, { cwd, env, timeoutMs: CODEX_MODELS_TIMEOUT_MS });
5318
+ }
5319
+ function isRecord2(value) {
5320
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5321
+ }
5322
+ var responseTo = (id) => (message) => isRecord2(message) && message.id === id && ("result" in message || "error" in message);
5323
+ function resultOf(message, method) {
5324
+ if (!isRecord2(message)) throw new Error(`codex ${method}: malformed response`);
5325
+ if (isRecord2(message.error)) throw new Error(`codex ${method}: ${String(message.error.message ?? "request failed")}`);
5326
+ if (!isRecord2(message.result)) throw new Error(`codex ${method}: response has no result`);
5327
+ return message.result;
5328
+ }
5329
+ function isCodexSignedIn(result) {
5330
+ return result.account != null || result.requiresOpenaiAuth === false;
5331
+ }
5332
+ function parseCodexModelPage(result) {
5333
+ if (!Array.isArray(result.data)) throw new Error("codex model/list has no data array");
5334
+ const models = [];
5335
+ for (const entry of result.data) {
5336
+ if (!isRecord2(entry) || typeof entry.id !== "string" || !entry.id.trim() || entry.hidden === true) continue;
5337
+ const id = entry.id.trim();
5338
+ const label = typeof entry.displayName === "string" && entry.displayName.trim() ? entry.displayName.trim() : id;
5339
+ const description = typeof entry.description === "string" && entry.description.trim() ? entry.description.trim() : void 0;
5340
+ const efforts = Array.isArray(entry.supportedReasoningEfforts) ? entry.supportedReasoningEfforts.map((e) => isRecord2(e) ? e.reasoningEffort : void 0).filter(isAgentEffort) : void 0;
5341
+ models.push({ id, label, ...description ? { description } : {}, ...efforts ? { efforts } : {} });
5342
+ }
5343
+ const nextCursor = typeof result.nextCursor === "string" && result.nextCursor ? result.nextCursor : null;
5344
+ return { models, nextCursor };
5345
+ }
5346
+ async function fetchCodexModels(clientVersion, open = openCodex) {
5347
+ const channel = open();
5348
+ try {
5349
+ channel.send({
5350
+ method: "initialize",
5351
+ id: 1,
5352
+ params: { clientInfo: { name: "vibeflow", title: "VibeFlow", version: clientVersion } }
5353
+ });
5354
+ resultOf(await channel.next(responseTo(1)), "initialize");
5355
+ channel.send({ method: "initialized", params: {} });
5356
+ channel.send({ method: "account/read", id: 2, params: { refreshToken: false } });
5357
+ if (!isCodexSignedIn(resultOf(await channel.next(responseTo(2)), "account/read"))) {
5358
+ throw new CodexLoginRequiredError();
5359
+ }
5360
+ const seen = /* @__PURE__ */ new Set();
5361
+ const models = [];
5362
+ let cursor = null;
5363
+ for (let page = 0; page < MAX_PAGES; page++) {
5364
+ const id = 3 + page;
5365
+ channel.send({
5366
+ method: "model/list",
5367
+ id,
5368
+ params: { limit: 100, includeHidden: false, ...cursor ? { cursor } : {} }
5369
+ });
5370
+ const parsed = parseCodexModelPage(resultOf(await channel.next(responseTo(id)), "model/list"));
5371
+ for (const model of parsed.models) {
5372
+ if (seen.has(model.id)) continue;
5373
+ seen.add(model.id);
5374
+ models.push(model);
5375
+ }
5376
+ cursor = parsed.nextCursor;
5377
+ if (!cursor) break;
5378
+ }
5379
+ if (cursor) throw new Error(`codex model/list did not finish within ${MAX_PAGES} pages`);
5380
+ if (models.length === 0) throw new Error("codex model/list lists no selectable models");
5381
+ return models;
5382
+ } finally {
5383
+ channel.close();
5384
+ }
5385
+ }
5386
+
5387
+ // packages/core/src/agent-models.ts
4800
5388
  function builtinModels(id) {
4801
5389
  return (AGENT_CLIS.find((a) => a.id === id) ?? AGENT_CLIS[0]).models;
4802
5390
  }
4803
5391
  function defaultCodexHome() {
4804
5392
  const configured = process.env.CODEX_HOME?.trim();
4805
- return configured ? configured : path16.join(os4.homedir(), ".codex");
4806
- }
4807
- async function defaultRunCodexModels() {
4808
- const { stdout } = await pexec3("codex", ["debug", "models"], {
4809
- env: execEnv(),
4810
- timeout: CODEX_MODELS_TIMEOUT_MS,
4811
- maxBuffer: 16 * 1024 * 1024,
4812
- windowsHide: true
4813
- });
4814
- return stdout;
5393
+ return configured ? configured : path17.join(os6.homedir(), ".codex");
4815
5394
  }
4816
5395
  function parseCodexCatalog(raw) {
4817
5396
  const body = JSON.parse(raw);
@@ -4836,29 +5415,68 @@ function parseCodexCatalog(raw) {
4836
5415
  function errorMessage(err) {
4837
5416
  return err instanceof Error ? err.message : String(err);
4838
5417
  }
4839
- async function listAgentModels(id, opts = {}) {
4840
- if (id !== "codex") return { models: builtinModels(id), source: "builtin" };
4841
- if (opts.refresh) {
4842
- try {
4843
- const stdout = await (opts.runCodexModels ?? defaultRunCodexModels)();
4844
- return { models: parseCodexCatalog(stdout), source: "codex-cli" };
4845
- } catch (err) {
4846
- return { models: builtinModels(id), source: "builtin", error: errorMessage(err) };
4847
- }
4848
- }
4849
- const cachePath = path16.join(opts.codexHome ?? defaultCodexHome(), "models_cache.json");
5418
+ async function readCodexCache(codexHome) {
5419
+ const cachePath = path17.join(codexHome, "models_cache.json");
4850
5420
  try {
4851
- const raw = await fs12.promises.readFile(cachePath, "utf8");
5421
+ const raw = await fs13.promises.readFile(cachePath, "utf8");
4852
5422
  return { models: parseCodexCatalog(raw), source: "codex-cache" };
4853
5423
  } catch (err) {
4854
5424
  const missing = err.code === "ENOENT";
4855
5425
  return {
4856
- models: builtinModels(id),
5426
+ models: builtinModels("codex"),
4857
5427
  source: "builtin",
4858
5428
  ...missing ? {} : { error: errorMessage(err) }
4859
5429
  };
4860
5430
  }
4861
5431
  }
5432
+ function createModelCatalog(opts) {
5433
+ const fetchClaude = opts.fetchClaude ?? (() => fetchClaudeModels());
5434
+ const fetchCodex = opts.fetchCodex ?? (() => fetchCodexModels(opts.version));
5435
+ const inFlight = /* @__PURE__ */ new Map();
5436
+ const settled = /* @__PURE__ */ new Map();
5437
+ const fallback = (id) => id === "codex" ? readCodexCache(opts.codexHome ?? defaultCodexHome()) : Promise.resolve({ models: builtinModels(id), source: "builtin" });
5438
+ const ask = async (id) => {
5439
+ try {
5440
+ const models = await (id === "codex" ? fetchCodex() : fetchClaude());
5441
+ return { models, source: id === "codex" ? "codex-app-server" : "claude-cli" };
5442
+ } catch (err) {
5443
+ const list = await fallback(id);
5444
+ return {
5445
+ ...list,
5446
+ error: errorMessage(err),
5447
+ ...err instanceof CodexLoginRequiredError ? { loginRequired: true } : {}
5448
+ };
5449
+ }
5450
+ };
5451
+ const refresh = (id) => {
5452
+ const running = inFlight.get(id);
5453
+ if (running) return running;
5454
+ const pending = ask(id).then((list) => {
5455
+ settled.set(id, list);
5456
+ return list;
5457
+ }).finally(() => inFlight.delete(id));
5458
+ inFlight.set(id, pending);
5459
+ return pending;
5460
+ };
5461
+ return {
5462
+ async get(id) {
5463
+ const answer = settled.get(id);
5464
+ if (answer) return answer;
5465
+ const list = await fallback(id);
5466
+ return inFlight.has(id) ? { ...list, pending: true } : list;
5467
+ },
5468
+ refresh,
5469
+ warm() {
5470
+ for (const agent of AGENT_CLIS) void refresh(agent.id);
5471
+ },
5472
+ findModel(id, modelId) {
5473
+ return settled.get(id)?.models.find((model) => model.id === modelId);
5474
+ }
5475
+ };
5476
+ }
5477
+
5478
+ // packages/core/src/service.ts
5479
+ init_effort();
4862
5480
 
4863
5481
  // packages/core/src/github-auth.ts
4864
5482
  init_env();
@@ -5015,45 +5633,45 @@ function startGitHubCliLogin(onEvent) {
5015
5633
  }
5016
5634
 
5017
5635
  // packages/core/src/board-cli.ts
5018
- import fs13 from "fs";
5019
- import path17 from "path";
5636
+ import fs14 from "fs";
5637
+ import path18 from "path";
5020
5638
  import { pathToFileURL } from "url";
5021
5639
  async function boardCliLaunchInfo() {
5022
- const storeDir = path17.dirname(getStorePath());
5640
+ const storeDir = path18.dirname(getStorePath());
5023
5641
  const entry = getPlatform().cliEntry?.();
5024
- if (entry && fs13.existsSync(entry)) return { storeDir, cliPath: entry };
5642
+ if (entry && fs14.existsSync(entry)) return { storeDir, cliPath: entry };
5025
5643
  const root = getPlatform().sourceRoot();
5026
5644
  if (!root) return { storeDir };
5027
- const cliPath = path17.join(root, "scripts", "vibeflow.mjs");
5028
- const loaderPath = path17.join(root, "test", "support", "register.mjs");
5029
- if (!fs13.existsSync(cliPath) || !fs13.existsSync(loaderPath)) return { storeDir };
5645
+ const cliPath = path18.join(root, "scripts", "vibeflow.mjs");
5646
+ const loaderPath = path18.join(root, "test", "support", "register.mjs");
5647
+ if (!fs14.existsSync(cliPath) || !fs14.existsSync(loaderPath)) return { storeDir };
5030
5648
  return { storeDir, cliPath, loaderPath: pathToFileURL(loaderPath).href };
5031
5649
  }
5032
5650
 
5033
5651
  // packages/core/src/decisions.ts
5034
- import fs14 from "fs";
5035
- import path18 from "path";
5652
+ import fs15 from "fs";
5653
+ import path19 from "path";
5036
5654
  var DECISIONS_FILE_SUFFIX = ".DECISIONS.md";
5037
5655
  function decisionsKey(worktreePath, branch) {
5038
- return worktreePath ? path18.basename(worktreePath) : branch.replace(/\//g, "-");
5656
+ return worktreePath ? path19.basename(worktreePath) : branch.replace(/\//g, "-");
5039
5657
  }
5040
5658
  function decisionsPath(workspacePath, key) {
5041
- return path18.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
5659
+ return path19.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
5042
5660
  }
5043
5661
  function deleteDecisions(workspacePath, key) {
5044
5662
  try {
5045
- fs14.rmSync(decisionsPath(workspacePath, key), { force: true });
5663
+ fs15.rmSync(decisionsPath(workspacePath, key), { force: true });
5046
5664
  } catch {
5047
5665
  }
5048
5666
  }
5049
5667
 
5050
5668
  // packages/core/src/specs.ts
5051
- import fs15 from "fs";
5052
- import path19 from "path";
5669
+ import fs16 from "fs";
5670
+ import path20 from "path";
5053
5671
  var SPEC_FILE_NAME = "spec.md";
5054
5672
  var MAX_SPEC_BYTES = 256 * 1024;
5055
5673
  function isSpecPath(p) {
5056
- return path19.posix.basename(p) === SPEC_FILE_NAME;
5674
+ return path20.posix.basename(p) === SPEC_FILE_NAME;
5057
5675
  }
5058
5676
  function clip2(buf) {
5059
5677
  return {
@@ -5068,10 +5686,10 @@ async function readWorktreeSpecs(worktreePath, baseBranch) {
5068
5686
  for (const entry of entries) {
5069
5687
  if (entry.status === "D" || !isSpecPath(entry.path)) continue;
5070
5688
  try {
5071
- const full = path19.join(worktreePath, entry.path);
5072
- const stat = fs15.statSync(full);
5689
+ const full = path20.join(worktreePath, entry.path);
5690
+ const stat = fs16.statSync(full);
5073
5691
  if (!stat.isFile()) continue;
5074
- specs.push({ path: entry.path, ...clip2(fs15.readFileSync(full)), updatedAt: stat.mtimeMs });
5692
+ specs.push({ path: entry.path, ...clip2(fs16.readFileSync(full)), updatedAt: stat.mtimeMs });
5075
5693
  } catch {
5076
5694
  }
5077
5695
  }
@@ -5136,7 +5754,7 @@ async function readSpecsAt(cwd, ref, paths) {
5136
5754
  // packages/core/src/chat-session.ts
5137
5755
  init_env();
5138
5756
  init_git_bash();
5139
- import { spawn as spawn5 } from "child_process";
5757
+ import { spawn as spawn6 } from "child_process";
5140
5758
  import { randomUUID as randomUUID3 } from "crypto";
5141
5759
 
5142
5760
  // packages/core/src/chat-store.ts
@@ -5184,7 +5802,7 @@ function clearMessages(taskId, newSessionId) {
5184
5802
 
5185
5803
  // packages/core/src/chat-session.ts
5186
5804
  init_attachments();
5187
- function basename(p) {
5805
+ function basename2(p) {
5188
5806
  return p.split("/").pop() ?? p;
5189
5807
  }
5190
5808
  function truncate(s, n) {
@@ -5200,14 +5818,14 @@ function summarizeToolUse(name, input) {
5200
5818
  break;
5201
5819
  }
5202
5820
  case "Read":
5203
- summary = `Reading \`${basename(String(input.file_path ?? ""))}\``;
5821
+ summary = `Reading \`${basename2(String(input.file_path ?? ""))}\``;
5204
5822
  break;
5205
5823
  case "Write":
5206
- summary = `Writing \`${basename(String(input.file_path ?? ""))}\``;
5824
+ summary = `Writing \`${basename2(String(input.file_path ?? ""))}\``;
5207
5825
  break;
5208
5826
  case "Edit":
5209
5827
  case "MultiEdit":
5210
- summary = `Editing \`${basename(String(input.file_path ?? ""))}\``;
5828
+ summary = `Editing \`${basename2(String(input.file_path ?? ""))}\``;
5211
5829
  break;
5212
5830
  case "Glob":
5213
5831
  case "Grep":
@@ -5292,7 +5910,7 @@ ${attachmentLines.join("\n")}` : text;
5292
5910
  `--append-system-prompt ${quote(systemPrompt)}`,
5293
5911
  quote(fullText)
5294
5912
  ].filter(Boolean).join(" ") : buildRawAgentCommand(agentCli, model, fullText, systemPrompt, worktreePath, quote, workspacePath);
5295
- const proc = spawn5(shell.command, shell.args(cmd), {
5913
+ const proc = spawn6(shell.command, shell.args(cmd), {
5296
5914
  cwd: worktreePath,
5297
5915
  env: execEnv()
5298
5916
  });
@@ -5418,20 +6036,22 @@ function startChatSend(opts, sender) {
5418
6036
 
5419
6037
  // packages/core/src/service.ts
5420
6038
  init_attachments();
6039
+ init_launch();
5421
6040
 
5422
6041
  // packages/core/src/progress-tracker.ts
5423
6042
  init_progress();
5424
- import fs16 from "fs";
5425
- import os5 from "os";
5426
- import path20 from "path";
6043
+ init_launch();
6044
+ import fs17 from "fs";
6045
+ import os7 from "os";
6046
+ import path21 from "path";
5427
6047
  var SINCE_SLACK_MS = 5e3;
5428
6048
  function claudeProjectDir(home, cwd) {
5429
- return path20.join(home, ".claude", "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
6049
+ return path21.join(home, ".claude", "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
5430
6050
  }
5431
6051
  function pathAliases(p) {
5432
- const resolved = path20.resolve(p);
6052
+ const resolved = path21.resolve(p);
5433
6053
  try {
5434
- const real = fs16.realpathSync(resolved);
6054
+ const real = fs17.realpathSync(resolved);
5435
6055
  return real === resolved ? [resolved] : [resolved, real];
5436
6056
  } catch {
5437
6057
  return [resolved];
@@ -5447,7 +6067,7 @@ function samePath(a, b) {
5447
6067
  }
5448
6068
  function listFiles(dir, test) {
5449
6069
  try {
5450
- return fs16.readdirSync(dir).filter(test).map((name) => path20.join(dir, name));
6070
+ return fs17.readdirSync(dir).filter(test).map((name) => path21.join(dir, name));
5451
6071
  } catch {
5452
6072
  return [];
5453
6073
  }
@@ -5455,22 +6075,22 @@ function listFiles(dir, test) {
5455
6075
  function firstLine(file) {
5456
6076
  let fd;
5457
6077
  try {
5458
- fd = fs16.openSync(file, "r");
6078
+ fd = fs17.openSync(file, "r");
5459
6079
  const buf = Buffer.alloc(64 * 1024);
5460
- const n = fs16.readSync(fd, buf, 0, buf.length, 0);
6080
+ const n = fs17.readSync(fd, buf, 0, buf.length, 0);
5461
6081
  const text = buf.subarray(0, n).toString("utf8");
5462
6082
  const nl = text.indexOf("\n");
5463
6083
  return nl === -1 ? null : text.slice(0, nl);
5464
6084
  } catch {
5465
6085
  return null;
5466
6086
  } finally {
5467
- if (fd !== void 0) fs16.closeSync(fd);
6087
+ if (fd !== void 0) fs17.closeSync(fd);
5468
6088
  }
5469
6089
  }
5470
6090
  function advance(file, cursor, reduce, initial) {
5471
6091
  let size;
5472
6092
  try {
5473
- size = fs16.statSync(file).size;
6093
+ size = fs17.statSync(file).size;
5474
6094
  } catch {
5475
6095
  return;
5476
6096
  }
@@ -5482,9 +6102,9 @@ function advance(file, cursor, reduce, initial) {
5482
6102
  if (size === cursor.offset) return;
5483
6103
  let fd;
5484
6104
  try {
5485
- fd = fs16.openSync(file, "r");
6105
+ fd = fs17.openSync(file, "r");
5486
6106
  const chunk = Buffer.alloc(size - cursor.offset);
5487
- const n = fs16.readSync(fd, chunk, 0, chunk.length, cursor.offset);
6107
+ const n = fs17.readSync(fd, chunk, 0, chunk.length, cursor.offset);
5488
6108
  cursor.offset += n;
5489
6109
  const data = Buffer.concat([cursor.rest, chunk.subarray(0, n)]);
5490
6110
  const nl = data.lastIndexOf(10);
@@ -5498,7 +6118,7 @@ function advance(file, cursor, reduce, initial) {
5498
6118
  }
5499
6119
  } catch {
5500
6120
  } finally {
5501
- if (fd !== void 0) fs16.closeSync(fd);
6121
+ if (fd !== void 0) fs17.closeSync(fd);
5502
6122
  }
5503
6123
  }
5504
6124
  function newCursor(state) {
@@ -5517,14 +6137,14 @@ function codexDayDirs(sessionsRoot, since) {
5517
6137
  const cur = new Date(start.getFullYear(), start.getMonth(), start.getDate());
5518
6138
  while (cur <= end) {
5519
6139
  const pad = (n) => String(n).padStart(2, "0");
5520
- days.push(path20.join(sessionsRoot, String(cur.getFullYear()), pad(cur.getMonth() + 1), pad(cur.getDate())));
6140
+ days.push(path21.join(sessionsRoot, String(cur.getFullYear()), pad(cur.getMonth() + 1), pad(cur.getDate())));
5521
6141
  cur.setDate(cur.getDate() + 1);
5522
6142
  }
5523
6143
  return days;
5524
6144
  }
5525
6145
  function createProgressTracker(opts) {
5526
- const home = opts.homeDir ?? os5.homedir();
5527
- const codexHomes = opts.codexHomes ?? (() => [path20.join(home, ".codex")]);
6146
+ const home = opts.homeDir ?? os7.homedir();
6147
+ const codexHomes = opts.codexHomes ?? (() => [path21.join(home, ".codex")]);
5528
6148
  const pollMs = opts.pollMs ?? 2e3;
5529
6149
  const entries = /* @__PURE__ */ new Map();
5530
6150
  const belongsToRun = (target, startedAt) => target.since == null || startedAt != null && startedAt >= target.since - SINCE_SLACK_MS;
@@ -5533,7 +6153,7 @@ function createProgressTracker(opts) {
5533
6153
  const dirs = pathAliases(target.worktreePath).map((p) => claudeProjectDir(home, p));
5534
6154
  for (const file of dirs.flatMap((dir) => listFiles(dir, (n) => n.endsWith(".jsonl")))) {
5535
6155
  if (entry.claude.has(file) || entry.rejected.has(file)) continue;
5536
- const session = path20.basename(file, ".jsonl");
6156
+ const session = path21.basename(file, ".jsonl");
5537
6157
  if (session !== target.sessionId) {
5538
6158
  const head = firstLine(file);
5539
6159
  if (head == null) continue;
@@ -5546,8 +6166,8 @@ function createProgressTracker(opts) {
5546
6166
  entry.claude.set(file, newCursor(initialClaudeState()));
5547
6167
  }
5548
6168
  for (const file of Array.from(entry.claude.keys())) {
5549
- if (file.includes(`${path20.sep}subagents${path20.sep}`)) continue;
5550
- const sub = path20.join(file.slice(0, -".jsonl".length), "subagents");
6169
+ if (file.includes(`${path21.sep}subagents${path21.sep}`)) continue;
6170
+ const sub = path21.join(file.slice(0, -".jsonl".length), "subagents");
5551
6171
  for (const f of listFiles(sub, (n) => n.endsWith(".jsonl"))) {
5552
6172
  if (!entry.claude.has(f)) entry.claude.set(f, newCursor(initialClaudeState()));
5553
6173
  }
@@ -5556,7 +6176,7 @@ function createProgressTracker(opts) {
5556
6176
  function discoverCodex(entry) {
5557
6177
  const { target } = entry;
5558
6178
  for (const codexHome of codexHomes()) {
5559
- for (const day of codexDayDirs(path20.join(codexHome, "sessions"), target.since)) {
6179
+ for (const day of codexDayDirs(path21.join(codexHome, "sessions"), target.since)) {
5560
6180
  for (const file of listFiles(day, (n) => n.startsWith("rollout-") && n.endsWith(".jsonl"))) {
5561
6181
  if (entry.codex.has(file) || entry.rejected.has(file)) continue;
5562
6182
  const head = firstLine(file);
@@ -5573,11 +6193,11 @@ function createProgressTracker(opts) {
5573
6193
  }
5574
6194
  }
5575
6195
  function drainHooks(entry) {
5576
- const dir = path20.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
6196
+ const dir = path21.join(entry.target.worktreePath, PROGRESS_EVENTS_DIR);
5577
6197
  for (const file of listFiles(dir, (n) => n.endsWith(".json")).sort()) {
5578
6198
  try {
5579
- const at = fs16.statSync(file).mtimeMs;
5580
- const event = JSON.parse(fs16.readFileSync(file, "utf8"));
6199
+ const at = fs17.statSync(file).mtimeMs;
6200
+ const event = JSON.parse(fs17.readFileSync(file, "utf8"));
5581
6201
  const foreign = entry.target.sessionId && typeof event.session_id === "string" && event.session_id !== entry.target.sessionId;
5582
6202
  const activity = foreign ? null : activityFromHook(event, at);
5583
6203
  if (activity) entry.hookActivity = latestActivity(entry.hookActivity ?? activity, activity);
@@ -5585,7 +6205,7 @@ function createProgressTracker(opts) {
5585
6205
  continue;
5586
6206
  }
5587
6207
  try {
5588
- fs16.unlinkSync(file);
6208
+ fs17.unlinkSync(file);
5589
6209
  } catch {
5590
6210
  }
5591
6211
  }
@@ -5597,8 +6217,8 @@ function createProgressTracker(opts) {
5597
6217
  if (target.agent === "claude") {
5598
6218
  for (const [file, cursor] of entry.claude) {
5599
6219
  runUsage = addUsage(runUsage, claudeUsageTotal(cursor.state));
5600
- if (file.includes(`${path20.sep}subagents${path20.sep}`)) continue;
5601
- const isExecutor = path20.basename(file, ".jsonl") === target.sessionId;
6220
+ if (file.includes(`${path21.sep}subagents${path21.sep}`)) continue;
6221
+ const isExecutor = path21.basename(file, ".jsonl") === target.sessionId;
5602
6222
  const s = cursor.state;
5603
6223
  if (isExecutor || !target.sessionId && (!primary || (s.startedAt ?? 0) > (primary.startedAt ?? 0))) {
5604
6224
  primary = { todos: s.todos, contextTokens: s.contextTokens, activity: s.activity, startedAt: s.startedAt };
@@ -5677,10 +6297,10 @@ function createProgressTracker(opts) {
5677
6297
  };
5678
6298
  entry.timer.unref?.();
5679
6299
  entries.set(target.taskId, entry);
5680
- const eventsDir = path20.join(target.worktreePath, PROGRESS_EVENTS_DIR);
6300
+ const eventsDir = path21.join(target.worktreePath, PROGRESS_EVENTS_DIR);
5681
6301
  try {
5682
- fs16.mkdirSync(eventsDir, { recursive: true });
5683
- entry.watcher = fs16.watch(eventsDir, () => {
6302
+ fs17.mkdirSync(eventsDir, { recursive: true });
6303
+ entry.watcher = fs17.watch(eventsDir, () => {
5684
6304
  if (entry.pending) return;
5685
6305
  entry.pending = setTimeout(() => {
5686
6306
  entry.pending = null;
@@ -5765,17 +6385,18 @@ function validBoard(value) {
5765
6385
  }
5766
6386
  return next;
5767
6387
  }
5768
- function createCore({ sessions, bus, version, homeDir }) {
6388
+ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatus, modelCatalog }) {
5769
6389
  const sink = bus.sink();
6390
+ const models = modelCatalog ?? createModelCatalog({ version });
5770
6391
  let libraryCodexHome = null;
5771
6392
  void libraryRoot().then((root) => {
5772
6393
  libraryCodexHome = libraryPaths(root).codexHome;
5773
6394
  }).catch(() => {
5774
6395
  });
5775
- const home = homeDir ?? os6.homedir();
6396
+ const home = homeDir ?? os8.homedir();
5776
6397
  const progress = createProgressTracker({
5777
6398
  homeDir: home,
5778
- codexHomes: () => [path21.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
6399
+ codexHomes: () => [path22.join(home, ".codex"), ...libraryCodexHome ? [libraryCodexHome] : []],
5779
6400
  onUpdate: (taskId, value) => bus.emit("progress:update", { taskId, progress: value }),
5780
6401
  onNotify: (taskId, list) => {
5781
6402
  const prefs = resolveNotificationSettings(getSettings());
@@ -5839,12 +6460,16 @@ function createCore({ sessions, bus, version, homeDir }) {
5839
6460
  requireTask(key.split(":")[0]);
5840
6461
  return key;
5841
6462
  }
6463
+ function launchEffort(task) {
6464
+ const model = models.findModel(taskAgent(task), taskModel(task));
6465
+ return model?.efforts ? clampEffort(task.effort, model.efforts) : task.effort;
6466
+ }
5842
6467
  async function launchCommand(task, intent) {
5843
6468
  const settings = getSettings();
5844
6469
  const library = await libraryLaunchInfo(task.worktreePath);
5845
6470
  const boardCli = await boardCliLaunchInfo();
5846
6471
  return buildAgentCommand(
5847
- task,
6472
+ { ...task, effort: launchEffort(task) },
5848
6473
  settings.systemPrompt ?? "",
5849
6474
  {
5850
6475
  resume: intent.resume === true,
@@ -5858,13 +6483,13 @@ function createCore({ sessions, bus, version, homeDir }) {
5858
6483
  }
5859
6484
  function claudeSessionFile(cwd, sessionId) {
5860
6485
  const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
5861
- return path21.join(os6.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
6486
+ return path22.join(os8.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
5862
6487
  }
5863
6488
  async function removeTaskWorktree(task) {
5864
6489
  if (!task.projectPath || !task.worktreePath) return;
5865
6490
  const branch = task.branch || fallbackBranchName(task.id);
5866
6491
  await removeWorktree(task.projectPath, task.worktreePath);
5867
- deleteArtifacts(task.workspacePath ?? path21.dirname(task.worktreePath), task.worktreePath);
6492
+ deleteArtifacts(task.workspacePath ?? path22.dirname(task.worktreePath), task.worktreePath);
5868
6493
  await deleteBranch(task.projectPath, branch);
5869
6494
  }
5870
6495
  function columnOf(taskId) {
@@ -5907,6 +6532,11 @@ function createCore({ sessions, bus, version, homeDir }) {
5907
6532
  bus.emit("state:changed", getState());
5908
6533
  }
5909
6534
  let folderDialog = null;
6535
+ const github = createGithubService({
6536
+ run: ghRunner,
6537
+ authStatus: githubAuthStatus ?? getGitHubCliAuthStatus,
6538
+ repoOf: async (projectPath) => parseGithubRepo((await getGitInfo(projectPath)).remoteUrl)
6539
+ });
5910
6540
  const handlers = {
5911
6541
  "vibeflow:getState": () => getState(),
5912
6542
  // A card's progress right now (todo list, usage, activity), for a frontend
@@ -5937,10 +6567,21 @@ function createCore({ sessions, bus, version, homeDir }) {
5937
6567
  cancelGitHubCliLogin();
5938
6568
  },
5939
6569
  "settings:logoutGithubAuth": () => logoutGitHubCli(),
6570
+ // Open Issues/PRs of the board's GitHub repos that involve the signed-in
6571
+ // `gh` user; cached per repo, `force` refetches.
6572
+ "github:inbox": async (payload) => {
6573
+ const p = payload === void 0 ? {} : obj(payload, "payload");
6574
+ return github.inbox(getState().board, { force: p.force === true });
6575
+ },
6576
+ "github:taskLinks": async (payload) => {
6577
+ const p = payload === void 0 ? {} : obj(payload, "payload");
6578
+ return github.taskLinks(getState().board, { force: p.force === true });
6579
+ },
5940
6580
  "env:detectAgents": () => detectAgents(),
5941
6581
  "agents:listModels": (payload) => {
5942
6582
  const { agentId: rawId, refresh } = obj(payload, "payload");
5943
- return listAgentModels(agentId(rawId), { refresh: refresh === true });
6583
+ const id = agentId(rawId);
6584
+ return refresh === true ? models.refresh(id) : models.get(id);
5944
6585
  },
5945
6586
  // A task does not exist yet when a project is picked, so these two are the
5946
6587
  // ones that must take a path. They only inspect or `git init` it.
@@ -5954,8 +6595,8 @@ function createCore({ sessions, bus, version, homeDir }) {
5954
6595
  "projects:listRecent": () => listRecentProjects(getStore()),
5955
6596
  // `vibeflow open <path>` while a host runs: only the host writes the store.
5956
6597
  "projects:record": (projectPath) => {
5957
- const p = path21.resolve(str(projectPath, "projectPath"));
5958
- if (!fs17.existsSync(p) || !fs17.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
6598
+ const p = path22.resolve(str(projectPath, "projectPath"));
6599
+ if (!fs18.existsSync(p) || !fs18.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
5959
6600
  recordRecentProject(getStore(), p);
5960
6601
  return listRecentProjects(getStore());
5961
6602
  },
@@ -5966,6 +6607,7 @@ function createCore({ sessions, bus, version, homeDir }) {
5966
6607
  str(p.title ?? "", "title");
5967
6608
  if (p.effort !== void 0 && !AGENT_EFFORTS.includes(p.effort)) invalid("unknown effort");
5968
6609
  if (p.agentCli !== void 0 && !AGENT_IDS2.includes(p.agentCli)) invalid("unknown agent");
6610
+ if (p.github !== void 0 && !isGithubRef(p.github)) invalid("github must be an issue or pr ref on github.com");
5969
6611
  const { task } = await createTaskFromInput({
5970
6612
  projectPath: p.projectPath,
5971
6613
  title: p.title,
@@ -5977,7 +6619,8 @@ function createCore({ sessions, bus, version, homeDir }) {
5977
6619
  model: p.model,
5978
6620
  effort: p.effort,
5979
6621
  autoMode: p.autoMode,
5980
- attachments: p.attachments
6622
+ attachments: p.attachments,
6623
+ github: p.github
5981
6624
  });
5982
6625
  return { state: getState(), task };
5983
6626
  },
@@ -6004,7 +6647,7 @@ function createCore({ sessions, bus, version, homeDir }) {
6004
6647
  if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
6005
6648
  await teardownTask(task.id);
6006
6649
  await resetWorktreeToBase(task.worktreePath, task.baseBranch);
6007
- const workspacePath = task.workspacePath ?? path21.dirname(task.worktreePath);
6650
+ const workspacePath = task.workspacePath ?? path22.dirname(task.worktreePath);
6008
6651
  deleteArtifacts(workspacePath, task.worktreePath);
6009
6652
  deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
6010
6653
  resetSubAgents(task.worktreePath);
@@ -6028,6 +6671,7 @@ function createCore({ sessions, bus, version, homeDir }) {
6028
6671
  if (columnOf(existing.id) !== "backlog") {
6029
6672
  throw new Error("\u53EA\u6709 Backlog \u7684\u4EFB\u52D9\u53EF\u4EE5\u7DE8\u8F2F");
6030
6673
  }
6674
+ if (p.effort !== void 0 && !AGENT_EFFORTS.includes(p.effort)) invalid("unknown effort");
6031
6675
  let gitPatch = {};
6032
6676
  const nextProject = p.projectPath || existing.projectPath;
6033
6677
  const nextBranch = p.branch === void 0 ? "" : str(p.branch, "branch").trim();
@@ -6047,7 +6691,7 @@ function createCore({ sessions, bus, version, homeDir }) {
6047
6691
  }
6048
6692
  const info = await getGitInfo(nextProject);
6049
6693
  if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
6050
- const projectName = path21.basename(nextProject);
6694
+ const projectName = path22.basename(nextProject);
6051
6695
  const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
6052
6696
  await teardownTask(existing.id);
6053
6697
  await removeTaskWorktree(existing);
@@ -6141,7 +6785,7 @@ function createCore({ sessions, bus, version, homeDir }) {
6141
6785
  "claude:sessionExists": (taskId) => {
6142
6786
  const task = requireTask(taskId);
6143
6787
  if (!task.worktreePath) return false;
6144
- return fs17.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
6788
+ return fs18.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
6145
6789
  },
6146
6790
  "sessions:list": async () => {
6147
6791
  const keys = await sessions.list();
@@ -6188,17 +6832,17 @@ function createCore({ sessions, bus, version, homeDir }) {
6188
6832
  const task = requireTask(taskId);
6189
6833
  if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
6190
6834
  const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
6191
- if (!fs17.existsSync(dir)) return "artifacts directory does not exist yet";
6835
+ if (!fs18.existsSync(dir)) return "artifacts directory does not exist yet";
6192
6836
  return getPlatform().openPath(dir);
6193
6837
  },
6194
6838
  "task:getSpecs": async (taskId) => {
6195
6839
  const task = requireTask(taskId);
6196
- const base = task.baseBranch ?? "main";
6197
- if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base);
6840
+ const base2 = task.baseBranch ?? "main";
6841
+ if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base2);
6198
6842
  const since = task.provisionedAt ?? task.launchedAt ?? (task.pushed !== void 0 ? task.createdAt : void 0);
6199
6843
  if (!task.projectPath || !since) return [];
6200
6844
  const changed = task.outcome?.files.filter((f) => f.status !== "D").map((f) => f.path);
6201
- return readBranchSpecs(task.projectPath, task.branch, base, changed, since);
6845
+ return readBranchSpecs(task.projectPath, task.branch, base2, changed, since);
6202
6846
  },
6203
6847
  "board:getCliLaunchInfo": () => boardCliLaunchInfo(),
6204
6848
  "library:list": async () => listLibrary(await libraryRoot(), builtinSkillsDir()),
@@ -6322,7 +6966,7 @@ function createCore({ sessions, bus, version, homeDir }) {
6322
6966
  }
6323
6967
  await removeTaskWorktree(task);
6324
6968
  if (task.workspacePath) {
6325
- fs17.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
6969
+ fs18.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
6326
6970
  }
6327
6971
  clearConversation(task.id);
6328
6972
  removeTask(task.id);
@@ -6375,9 +7019,9 @@ function createCore({ sessions, bus, version, homeDir }) {
6375
7019
  // Watch the containing directory, not the file: JsonStore renames a temp
6376
7020
  // file over the target and so replaces the inode on every save.
6377
7021
  watchStore() {
6378
- const file = path21.basename(getStorePath());
7022
+ const file = path22.basename(getStorePath());
6379
7023
  let debounce = null;
6380
- storeWatcher = fs17.watch(path21.dirname(getStorePath()), (_event, filename) => {
7024
+ storeWatcher = fs18.watch(path22.dirname(getStorePath()), (_event, filename) => {
6381
7025
  if (filename !== file) return;
6382
7026
  if (debounce) clearTimeout(debounce);
6383
7027
  debounce = setTimeout(() => {
@@ -6389,6 +7033,9 @@ function createCore({ sessions, bus, version, homeDir }) {
6389
7033
  });
6390
7034
  return () => storeWatcher?.close();
6391
7035
  },
7036
+ warmModelCatalog() {
7037
+ models.warm();
7038
+ },
6392
7039
  shutdown() {
6393
7040
  sessions.shutdown();
6394
7041
  cancelAllChatSends();
@@ -6410,9 +7057,9 @@ function createSessionBackend(sink, choice) {
6410
7057
  }
6411
7058
 
6412
7059
  // packages/core/src/web-server.ts
6413
- import fs18 from "fs";
7060
+ import fs19 from "fs";
6414
7061
  import http from "http";
6415
- import path22 from "path";
7062
+ import path23 from "path";
6416
7063
  import { timingSafeEqual } from "crypto";
6417
7064
  import { WebSocketServer } from "ws";
6418
7065
  var TOKEN_COOKIE = "vf_token";
@@ -6465,14 +7112,14 @@ function resolveStatic(root, urlPath) {
6465
7112
  return null;
6466
7113
  }
6467
7114
  if (decoded.includes("\0")) return null;
6468
- const rel = path22.posix.normalize(decoded).replace(/^\/+/, "");
6469
- const base = path22.resolve(root);
6470
- const candidates = [rel, path22.posix.join(rel, "index.html"), `${rel}.html`];
7115
+ const rel = path23.posix.normalize(decoded).replace(/^\/+/, "");
7116
+ const base2 = path23.resolve(root);
7117
+ const candidates = [rel, path23.posix.join(rel, "index.html"), `${rel}.html`];
6471
7118
  for (const c of candidates) {
6472
- const full = path22.resolve(base, c);
6473
- if (full !== base && !full.startsWith(base + path22.sep)) return null;
7119
+ const full = path23.resolve(base2, c);
7120
+ if (full !== base2 && !full.startsWith(base2 + path23.sep)) return null;
6474
7121
  try {
6475
- if (fs18.statSync(full).isFile()) return full;
7122
+ if (fs19.statSync(full).isFile()) return full;
6476
7123
  } catch {
6477
7124
  }
6478
7125
  }
@@ -6533,7 +7180,7 @@ async function startWebServer(options) {
6533
7180
  send(res, 404, "Not found");
6534
7181
  return;
6535
7182
  }
6536
- const ext = path22.extname(file).toLowerCase();
7183
+ const ext = path23.extname(file).toLowerCase();
6537
7184
  const immutable = url.pathname.startsWith("/_next/static/");
6538
7185
  res.writeHead(200, {
6539
7186
  "Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
@@ -6546,7 +7193,7 @@ async function startWebServer(options) {
6546
7193
  res.end();
6547
7194
  return;
6548
7195
  }
6549
- fs18.createReadStream(file).pipe(res);
7196
+ fs19.createReadStream(file).pipe(res);
6550
7197
  }
6551
7198
  const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
6552
7199
  function reject(socket, status, reason) {
@@ -6710,6 +7357,7 @@ async function startHost(options) {
6710
7357
  lock.port = server.port;
6711
7358
  updateLock(options.userDataDir, lock);
6712
7359
  const stopWatch = core.watchStore();
7360
+ core.warmModelCatalog();
6713
7361
  const stop = () => stopping ??= (async () => {
6714
7362
  stopWatch();
6715
7363
  core.shutdown();
@@ -6726,7 +7374,7 @@ function loginUrl(lock, pathname = "/home/") {
6726
7374
  return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
6727
7375
  }
6728
7376
  function isWebDir(dir) {
6729
- return fs19.existsSync(path23.join(dir, "home", "index.html"));
7377
+ return fs20.existsSync(path24.join(dir, "home", "index.html"));
6730
7378
  }
6731
7379
 
6732
7380
  // packages/cli/src/remote.ts
@@ -6843,8 +7491,8 @@ function connectToHost(lock) {
6843
7491
  }
6844
7492
 
6845
7493
  // packages/cli/src/doctor.ts
6846
- import fs20 from "fs";
6847
- import path24 from "path";
7494
+ import fs21 from "fs";
7495
+ import path25 from "path";
6848
7496
  import { execFile as execFile8 } from "child_process";
6849
7497
  import { promisify as promisify5 } from "util";
6850
7498
  init_env();
@@ -6984,10 +7632,10 @@ async function runDoctor(options) {
6984
7632
  });
6985
7633
  }
6986
7634
  try {
6987
- fs20.mkdirSync(options.userDataDir, { recursive: true });
6988
- const probe = path24.join(options.userDataDir, `.doctor-${process.pid}`);
6989
- fs20.writeFileSync(probe, "");
6990
- fs20.rmSync(probe);
7635
+ fs21.mkdirSync(options.userDataDir, { recursive: true });
7636
+ const probe = path25.join(options.userDataDir, `.doctor-${process.pid}`);
7637
+ fs21.writeFileSync(probe, "");
7638
+ fs21.rmSync(probe);
6991
7639
  checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
6992
7640
  } catch (err) {
6993
7641
  checks.push({
@@ -7041,24 +7689,24 @@ Options:
7041
7689
  `.trim();
7042
7690
  function detectLayout() {
7043
7691
  const file = fileURLToPath(import.meta.url);
7044
- const here = path27.dirname(file);
7045
- const bundledWeb = path27.join(here, "web");
7046
- if (isWebDir(bundledWeb) || path27.basename(here) === "dist") {
7692
+ const here = path28.dirname(file);
7693
+ const bundledWeb = path28.join(here, "web");
7694
+ if (isWebDir(bundledWeb) || path28.basename(here) === "dist") {
7047
7695
  return {
7048
- version: readVersion(path27.join(here, "..", "package.json")),
7696
+ version: readVersion(path28.join(here, "..", "package.json")),
7049
7697
  webDir: isWebDir(bundledWeb) ? bundledWeb : null,
7050
7698
  sourceRoot: null,
7051
7699
  cliEntry: file
7052
7700
  };
7053
7701
  }
7054
- const root = path27.resolve(here, "..", "..", "..");
7702
+ const root = path28.resolve(here, "..", "..", "..");
7055
7703
  const override = process.env.VIBEFLOW_WEB_DIR;
7056
- const webDir = override && isWebDir(override) ? override : isWebDir(path27.join(root, "app")) ? path27.join(root, "app") : null;
7057
- return { version: readVersion(path27.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
7704
+ const webDir = override && isWebDir(override) ? override : isWebDir(path28.join(root, "app")) ? path28.join(root, "app") : null;
7705
+ return { version: readVersion(path28.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
7058
7706
  }
7059
7707
  function readVersion(pkgPath) {
7060
7708
  try {
7061
- return JSON.parse(fs22.readFileSync(pkgPath, "utf8")).version;
7709
+ return JSON.parse(fs23.readFileSync(pkgPath, "utf8")).version;
7062
7710
  } catch {
7063
7711
  return "0.0.0";
7064
7712
  }
@@ -7264,8 +7912,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
7264
7912
  console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
7265
7913
  return 1;
7266
7914
  }
7267
- const dir = path27.resolve(target);
7268
- if (!fs22.existsSync(dir) || !fs22.statSync(dir).isDirectory()) {
7915
+ const dir = path28.resolve(target);
7916
+ if (!fs23.existsSync(dir) || !fs23.statSync(dir).isDirectory()) {
7269
7917
  console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
7270
7918
  return 1;
7271
7919
  }