@kw-yeh/vibeflow 4.11.38 → 4.11.39

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
@@ -168,8 +168,8 @@ function shellArgs(shell, command) {
168
168
  const isPosixShell = process.platform !== "win32" || shell.toLowerCase().endsWith("bash.exe");
169
169
  if (isPosixShell) return command ? ["-lic", command] : ["-l"];
170
170
  if (command) {
171
- const message = GIT_BASH_MISSING_MESSAGE.replace(/'/g, "''");
172
- return ["-NoProfile", "-NonInteractive", "-Command", `Write-Host '${message}' -ForegroundColor Yellow; exit 1`];
171
+ const message2 = GIT_BASH_MISSING_MESSAGE.replace(/'/g, "''");
172
+ return ["-NoProfile", "-NonInteractive", "-Command", `Write-Host '${message2}' -ForegroundColor Yellow; exit 1`];
173
173
  }
174
174
  return ["-NoProfile"];
175
175
  }
@@ -638,7 +638,7 @@ function masterSystemPrompt(opts) {
638
638
  '2. \u6D3E\u5DE5\uFF1AVF task create --store-path "$VIBEFLOW_STORE_DIR" --master "$VIBEFLOW_TASK_ID" --project <repo \u7D55\u5C0D\u8DEF\u5F91> --title <\u6A19\u984C> --prompt <\u5167\u5BB9>',
639
639
  " prompt \u5FC5\u9808\u81EA\u5305\u542B\uFF1A\u80CC\u666F\u3001\u7BC4\u570D\u3001\u9A57\u6536\u689D\u4EF6\u3001\u8207\u5176\u4ED6\u5361\u7684\u4ECB\u9762\u7D04\u5B9A\u2014\u2014slave \u770B\u4E0D\u5230\u4F60\u7684\u5C0D\u8A71\u3002",
640
640
  " \u6A19\u984C\u8981\u77ED\u3001\u5F7C\u6B64\u4E00\u773C\u5206\u5F97\u51FA\u4F86\uFF1A\u4EE5\u9019\u5F35\u5361\u8CA0\u8CAC\u7684\u90E8\u5206\u958B\u982D\uFF08\u4F8B\u5982\u300C\u5F8C\u7AEF\uFF1A\u2026\u300D\u300C\u9A57\u6536\uFF1A\u2026\u300D\uFF09\uFF0C\u4E0D\u8981\u8B93\u591A\u5F35\u5361\u5171\u7528\u540C\u4E00\u6BB5\u958B\u982D\u3002",
641
- " \u9700\u8981\u6642\u53EF\u52A0 --agent / --model / --effort / --base-branch\u3002"
641
+ " \u9810\u8A2D\u4E0D\u8981\u52A0 --model / --effort\uFF1A\u6C92\u6307\u5B9A\u6642 Jev \u6703\u4F9D\u4EFB\u52D9\u8907\u96DC\u5EA6\u6311\u6A21\u578B\u8207 effort\uFF1B\u53EA\u6709\u4EFB\u52D9\u660E\u78BA\u9700\u8981\u7279\u5B9A\u6A21\u578B\u6216 effort \u6642\u624D\u6307\u5B9A\u3002\u9700\u8981\u6642\u53EF\u52A0 --agent / --base-branch\u3002"
642
642
  );
643
643
  lines.push(
644
644
  opts.autoMode ? '3. \u555F\u52D5\uFF1A\u6C92\u6709\u524D\u7F6E\u4F9D\u8CF4\u7684\u5361\u76F4\u63A5 VF task start --store-path "$VIBEFLOW_STORE_DIR" --task <id>\uFF1B\u6709\u4F9D\u8CF4\u7684\u5148\u7559\u5728 Backlog\uFF0C\u524D\u7F6E\u5361\u5B8C\u6210\u5F8C\u518D\u555F\u52D5\u3002' : "3. \u555F\u52D5\uFF1A\u4E0D\u8981\u81EA\u5DF1\u555F\u52D5\u5361\u7247\u3002\u5217\u51FA\u4F60\u5EFA\u4E86\u54EA\u4E9B\u5361\u3001\u5EFA\u8B70\u7684\u555F\u52D5\u9806\u5E8F\uFF0C\u7B49\u4F7F\u7528\u8005\u5728 VibeFlow \u6309\u958B\u59CB\u3002"
@@ -655,12 +655,16 @@ function masterSystemPrompt(opts) {
655
655
  );
656
656
  return lines.join("\n");
657
657
  }
658
- function buildPrompt(task) {
658
+ function buildPrompt(task, preface) {
659
659
  const lines = [`\u4EFB\u52D9\u6A19\u984C\uFF1A${task.title}`];
660
660
  const description = task.description?.trim();
661
661
  if (description) {
662
662
  lines.push("", "\u4EFB\u52D9\u63CF\u8FF0\uFF1A", description);
663
663
  }
664
+ const context = preface?.context.trim();
665
+ if (preface && context) {
666
+ lines.push("", `\u524D\u7F6E\u5206\u6790\uFF08${preface.model}\uFF0C\u50C5\u4F9B\u53C3\u8003\uFF09\uFF1A`, context);
667
+ }
664
668
  return lines.join("\n");
665
669
  }
666
670
  function executorSessionId(taskId, runId) {
@@ -718,9 +722,9 @@ function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
718
722
  const custom = resolveSystemPrompt(systemPrompt);
719
723
  const canWrite = Boolean(opts?.boardCli?.cliPath);
720
724
  const role = task.role === "master" ? masterSystemPrompt({ canWrite, autoMode: opts?.autoMode === true }) : task.masterId ? slaveSystemPrompt({ canWrite }) : null;
721
- const sys = role ? [custom, role].filter(Boolean).join("\n\n") : custom;
725
+ const sys = task.masterId && task.role !== "master" ? role ?? "" : role ? [custom, role].filter(Boolean).join("\n\n") : custom;
722
726
  const includeTaskPrompt = opts?.includeTaskPrompt !== false;
723
- const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
727
+ const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task, opts?.preface) : "";
724
728
  const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
725
729
  return boardEnvPrefix(task, opts, workspacePath) + assembleCommand(
726
730
  agent,
@@ -986,13 +990,13 @@ function reduceClaudeLine(state, line) {
986
990
  if (!entry) return state;
987
991
  const at = parseTime(entry.timestamp);
988
992
  const next = { ...state, startedAt: state.startedAt ?? at };
989
- const message = entry.message;
990
- const content = Array.isArray(message?.content) ? message.content : [];
991
- if (entry.type === "assistant" && message) {
992
- const usage = message.usage;
993
- if (usage && typeof message.id === "string") {
993
+ const message2 = entry.message;
994
+ const content = Array.isArray(message2?.content) ? message2.content : [];
995
+ if (entry.type === "assistant" && message2) {
996
+ const usage = message2.usage;
997
+ if (usage && typeof message2.id === "string") {
994
998
  const u = claudeUsage(usage);
995
- next.usageById = { ...state.usageById, [message.id]: u };
999
+ next.usageById = { ...state.usageById, [message2.id]: u };
996
1000
  next.contextTokens = u.input + u.cacheRead + u.cacheWrite;
997
1001
  }
998
1002
  let tool;
@@ -1008,11 +1012,11 @@ function reduceClaudeLine(state, line) {
1008
1012
  }
1009
1013
  }
1010
1014
  if (tool) next.activity = { state: "working", tool, at };
1011
- else if (message.stop_reason === "end_turn") next.activity = { state: "waiting", waitingFor: "input", at };
1015
+ else if (message2.stop_reason === "end_turn") next.activity = { state: "waiting", waitingFor: "input", at };
1012
1016
  else next.activity = { state: "working", at };
1013
1017
  return next;
1014
1018
  }
1015
- if (entry.type === "user" && message) {
1019
+ if (entry.type === "user" && message2) {
1016
1020
  const result = entry.toolUseResult && typeof entry.toolUseResult === "object" ? entry.toolUseResult : void 0;
1017
1021
  let pending = next.pendingTasks;
1018
1022
  for (const block of content) {
@@ -1342,7 +1346,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1342
1346
  const [focus, setFocus] = useState(initialFocus);
1343
1347
  const [editing, setEditing] = useState(null);
1344
1348
  const [draft, setDraft] = useState("");
1345
- const [message, setMessage] = useState(initialMessage ?? "");
1349
+ const [message2, setMessage] = useState(initialMessage ?? "");
1346
1350
  const [busy, setBusy] = useState(null);
1347
1351
  const [loadingGit, setLoadingGit] = useState(false);
1348
1352
  const [confirmLeave, setConfirmLeave] = useState(false);
@@ -1561,7 +1565,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1561
1565
  )
1562
1566
  ),
1563
1567
  extra ? h(Text, { dimColor: true }, ` ${extra}`) : null,
1564
- confirmLeave ? h(Text, { color: "yellow" }, "\u6709\u672A\u5132\u5B58\u7684\u8B8A\u66F4\uFF0C\u78BA\u5B9A\u8981\u96E2\u958B\uFF1F\uFF08y/n\uFF09") : message ? h(Text, { color: "yellow" }, message) : h(Text, { dimColor: true }, hint)
1568
+ confirmLeave ? h(Text, { color: "yellow" }, "\u6709\u672A\u5132\u5B58\u7684\u8B8A\u66F4\uFF0C\u78BA\u5B9A\u8981\u96E2\u958B\uFF1F\uFF08y/n\uFF09") : message2 ? h(Text, { color: "yellow" }, message2) : h(Text, { dimColor: true }, hint)
1565
1569
  );
1566
1570
  }
1567
1571
  function showForm(api, ctx, initial, pristine, initialFocus = 0, initialMessage) {
@@ -1702,7 +1706,7 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
1702
1706
  const [sel, setSel] = useState2(initial);
1703
1707
  const [running, setRunning] = useState2(/* @__PURE__ */ new Set());
1704
1708
  const [backend, setBackend] = useState2("pty");
1705
- const [message, setMessage] = useState2(initialMessage ?? "");
1709
+ const [message2, setMessage] = useState2(initialMessage ?? "");
1706
1710
  const [confirm, setConfirm] = useState2(null);
1707
1711
  const [help, setHelp] = useState2(false);
1708
1712
  const [progress, setProgress] = useState2({});
@@ -1913,7 +1917,7 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
1913
1917
  Box2,
1914
1918
  { flexDirection: "column" },
1915
1919
  h2(Box2, { flexDirection: "row" }, ...columns),
1916
- confirm ? h2(Text2, { color: "yellow" }, confirm.prompt) : message ? h2(Text2, { color: "yellow" }, message) : h2(
1920
+ confirm ? h2(Text2, { color: "yellow" }, confirm.prompt) : message2 ? h2(Text2, { color: "yellow" }, message2) : h2(
1917
1921
  Text2,
1918
1922
  { dimColor: true },
1919
1923
  `h/l \u5207\u6B04 \xB7 j/k \u9078\u5361 \xB7 H/L \u79FB\u52D5 \xB7 Enter \u7D42\u7AEF \xB7 n \u65B0\u589E \xB7 e \u7DE8\u8F2F \xB7 d diff \xB7 w Web \xB7 ? \u8AAA\u660E \xB7 q \u96E2\u958B [${backend}]`
@@ -2037,24 +2041,24 @@ async function runForm(opts, action) {
2037
2041
  }
2038
2042
  async function runTui(opts) {
2039
2043
  let sel = { column: 1, index: 0 };
2040
- let message;
2044
+ let message2;
2041
2045
  for (; ; ) {
2042
- const result = await showBoard(opts, sel, message);
2046
+ const result = await showBoard(opts, sel, message2);
2043
2047
  sel = result.sel;
2044
- message = void 0;
2048
+ message2 = void 0;
2045
2049
  const { action } = result;
2046
2050
  if (action.type === "quit") return;
2047
2051
  try {
2048
- if (action.type === "attach") message = await openTerminal(opts, action.task);
2052
+ if (action.type === "attach") message2 = await openTerminal(opts, action.task);
2049
2053
  else if (action.type === "diff") showDiff(action.task);
2050
2054
  else {
2051
2055
  process.stdout.write(CLEAR_SCREEN);
2052
2056
  const done = await runForm(opts, action);
2053
- message = done.message;
2057
+ message2 = done.message;
2054
2058
  if (done.sel) sel = done.sel;
2055
2059
  }
2056
2060
  } catch (err) {
2057
- message = `\u5931\u6557\uFF1A${err.message}`;
2061
+ message2 = `\u5931\u6557\uFF1A${err.message}`;
2058
2062
  }
2059
2063
  process.stdout.write(CLEAR_SCREEN);
2060
2064
  }
@@ -2652,7 +2656,7 @@ function createBridge(t) {
2652
2656
  restoreBuiltinSkill: (payload) => t.invoke("library:restoreBuiltin", payload),
2653
2657
  /** Shipped skills the user deleted, offered for restore. */
2654
2658
  listRemovedBuiltinSkills: () => t.invoke("library:removedBuiltins"),
2655
- approve: (taskId, message) => t.invoke("git:approve", { taskId, message }),
2659
+ approve: (taskId, message2) => t.invoke("git:approve", { taskId, message: message2 }),
2656
2660
  /** Use the task's agent CLI to generate a commit message for branch changes. */
2657
2661
  generateCommitMessage: (taskId) => t.invoke("git:generateCommitMessage", taskId),
2658
2662
  /** Check whether a PR exists for the task's branch. */
@@ -4233,11 +4237,11 @@ async function getWorktreeDiff(worktreePath, baseBranch) {
4233
4237
  }
4234
4238
  return files;
4235
4239
  }
4236
- async function commitAndPush(worktreePath, message) {
4240
+ async function commitAndPush(worktreePath, message2) {
4237
4241
  await git(worktreePath, ["add", "-A"]);
4238
4242
  let committed = false;
4239
4243
  try {
4240
- await git(worktreePath, ["commit", "-m", message]);
4244
+ await git(worktreePath, ["commit", "-m", message2]);
4241
4245
  committed = true;
4242
4246
  } catch {
4243
4247
  committed = false;
@@ -4249,7 +4253,7 @@ async function commitAndPush(worktreePath, message) {
4249
4253
  } catch {
4250
4254
  pushed = false;
4251
4255
  }
4252
- return { committed, pushed, message };
4256
+ return { committed, pushed, message: message2 };
4253
4257
  }
4254
4258
  var MAX_OUTCOME_COMMITS = 50;
4255
4259
  var OUTCOME_LOG_FORMAT = "%h%x00%s";
@@ -4453,8 +4457,8 @@ init_attachments();
4453
4457
  function mastersRoot(storeFile) {
4454
4458
  return path13.join(path13.dirname(storeFile), "masters");
4455
4459
  }
4456
- function taskError(message, code2) {
4457
- return Object.assign(new Error(message), { code: code2 });
4460
+ function taskError(message2, code2) {
4461
+ return Object.assign(new Error(message2), { code: code2 });
4458
4462
  }
4459
4463
  function assertMasterRef(board, masterId, taskId) {
4460
4464
  const master = Object.values(board).flat().find((t) => t.id === masterId);
@@ -4733,8 +4737,8 @@ function createWsTransport(options) {
4733
4737
  function dispatch(channel, payload) {
4734
4738
  for (const cb of Array.from(listeners.get(channel) ?? [])) cb(payload);
4735
4739
  }
4736
- function post(message) {
4737
- const data = JSON.stringify(message);
4740
+ function post(message2) {
4741
+ const data = JSON.stringify(message2);
4738
4742
  if (socket && socket.readyState === OPEN) socket.send(data);
4739
4743
  else queue.push(data);
4740
4744
  }
@@ -4872,13 +4876,13 @@ Examples:
4872
4876
  `.trim();
4873
4877
  var CliFailure = class extends Error {
4874
4878
  code;
4875
- constructor(code2, message) {
4876
- super(message);
4879
+ constructor(code2, message2) {
4880
+ super(message2);
4877
4881
  this.code = code2;
4878
4882
  }
4879
4883
  };
4880
- function fail(code2, message) {
4881
- throw new CliFailure(code2, message);
4884
+ function fail(code2, message2) {
4885
+ throw new CliFailure(code2, message2);
4882
4886
  }
4883
4887
  function requireOneOf(flag, value, allowed) {
4884
4888
  if (value !== void 0 && !allowed.includes(value)) {
@@ -5617,10 +5621,10 @@ function spawnJsonLines(bin, args, opts) {
5617
5621
  failure = err;
5618
5622
  for (const w of waiters.splice(0)) w.reject(err);
5619
5623
  };
5620
- const deliver = (message) => {
5621
- const i = waiters.findIndex((w) => w.match(message));
5622
- if (i === -1) inbox.push(message);
5623
- else waiters.splice(i, 1)[0].resolve(message);
5624
+ const deliver = (message2) => {
5625
+ const i = waiters.findIndex((w) => w.match(message2));
5626
+ if (i === -1) inbox.push(message2);
5627
+ else waiters.splice(i, 1)[0].resolve(message2);
5624
5628
  };
5625
5629
  let buffered = "";
5626
5630
  proc.stdout.setEncoding("utf8");
@@ -5631,13 +5635,13 @@ function spawnJsonLines(bin, args, opts) {
5631
5635
  const line = buffered.slice(0, nl).trim();
5632
5636
  buffered = buffered.slice(nl + 1);
5633
5637
  if (!line) continue;
5634
- let message;
5638
+ let message2;
5635
5639
  try {
5636
- message = JSON.parse(line);
5640
+ message2 = JSON.parse(line);
5637
5641
  } catch {
5638
5642
  continue;
5639
5643
  }
5640
- deliver(message);
5644
+ deliver(message2);
5641
5645
  }
5642
5646
  });
5643
5647
  proc.stderr.setEncoding("utf8");
@@ -5674,8 +5678,8 @@ function spawnJsonLines(bin, args, opts) {
5674
5678
  }, opts.timeoutMs);
5675
5679
  let graceTimer;
5676
5680
  return {
5677
- send(message) {
5678
- if (!failure) proc.stdin.write(`${JSON.stringify(message)}
5681
+ send(message2) {
5682
+ if (!failure) proc.stdin.write(`${JSON.stringify(message2)}
5679
5683
  `);
5680
5684
  },
5681
5685
  next(match) {
@@ -5708,11 +5712,11 @@ function openClaude() {
5708
5712
  function isRecord(value) {
5709
5713
  return typeof value === "object" && value !== null && !Array.isArray(value);
5710
5714
  }
5711
- function isOurResponse(message) {
5712
- return isRecord(message) && message.type === "control_response" && isRecord(message.response) && message.response.request_id === REQUEST_ID;
5715
+ function isOurResponse(message2) {
5716
+ return isRecord(message2) && message2.type === "control_response" && isRecord(message2.response) && message2.response.request_id === REQUEST_ID;
5713
5717
  }
5714
- function parseClaudeInitialize(message) {
5715
- const outer = isRecord(message) && isRecord(message.response) ? message.response : null;
5718
+ function parseClaudeInitialize(message2) {
5719
+ const outer = isRecord(message2) && isRecord(message2.response) ? message2.response : null;
5716
5720
  if (outer?.subtype === "error") throw new Error(`claude refused the handshake: ${String(outer.error ?? "unknown error")}`);
5717
5721
  const body = outer && isRecord(outer.response) ? outer.response : null;
5718
5722
  if (!body || !Array.isArray(body.models)) throw new Error("claude handshake has no models array");
@@ -5773,12 +5777,12 @@ function openCodex() {
5773
5777
  function isRecord2(value) {
5774
5778
  return typeof value === "object" && value !== null && !Array.isArray(value);
5775
5779
  }
5776
- var responseTo = (id) => (message) => isRecord2(message) && message.id === id && ("result" in message || "error" in message);
5777
- function resultOf(message, method) {
5778
- if (!isRecord2(message)) throw new Error(`codex ${method}: malformed response`);
5779
- if (isRecord2(message.error)) throw new Error(`codex ${method}: ${String(message.error.message ?? "request failed")}`);
5780
- if (!isRecord2(message.result)) throw new Error(`codex ${method}: response has no result`);
5781
- return message.result;
5780
+ var responseTo = (id) => (message2) => isRecord2(message2) && message2.id === id && ("result" in message2 || "error" in message2);
5781
+ function resultOf(message2, method) {
5782
+ if (!isRecord2(message2)) throw new Error(`codex ${method}: malformed response`);
5783
+ if (isRecord2(message2.error)) throw new Error(`codex ${method}: ${String(message2.error.message ?? "request failed")}`);
5784
+ if (!isRecord2(message2.result)) throw new Error(`codex ${method}: response has no result`);
5785
+ return message2.result;
5782
5786
  }
5783
5787
  function isCodexSignedIn(result) {
5784
5788
  return result.account != null || result.requiresOpenaiAuth === false;
@@ -5941,53 +5945,175 @@ init_effort();
5941
5945
  init_launch();
5942
5946
  var API_URL = "https://api.typesafe.ai/v1/systemone";
5943
5947
  var MAX_TASK_CHARS = 12e3;
5948
+ var MAX_CONTEXT_CHARS = 8e3;
5949
+ var SCOUT_TIMEOUT_MS = 5 * 6e4;
5950
+ var SAFE_MODEL_ID = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
5951
+ function tier(model, agent) {
5952
+ const name = `${model.id} ${model.label}`.toLowerCase();
5953
+ if (/haiku|mini|nano|luna|flash/.test(name)) return 0;
5954
+ if (/opus|fable|astra|pro\b/.test(name)) return 2;
5955
+ if (agent === "codex" && /gpt-5\.5|gpt-5\.4(?!-mini)/.test(name)) return 2;
5956
+ return 1;
5957
+ }
5958
+ var TIER_LABELS = ["lowest cost", "balanced", "highest capability"];
5959
+ function routeCandidates(agent, models) {
5960
+ const byTier = /* @__PURE__ */ new Map();
5961
+ for (const model of models) {
5962
+ if (!SAFE_MODEL_ID.test(model.id) || model.id === "default") continue;
5963
+ const rank = tier(model, agent);
5964
+ if (!byTier.has(rank)) byTier.set(rank, model);
5965
+ }
5966
+ return [...byTier.entries()].sort(([a], [b]) => a - b).map(([, model]) => model);
5967
+ }
5968
+ function isChoice(value) {
5969
+ return !!value && typeof value === "object" && value.type === "choice" && typeof value.choice === "string";
5970
+ }
5971
+ var defaultScout = (agent, model, effort, prompt, cwd) => {
5972
+ const args = agent === "claude" ? ["-p", "--model", model, "--permission-mode", "plan", ...effort ? ["--effort", effort] : []] : [
5973
+ "exec",
5974
+ "--model",
5975
+ model,
5976
+ ...effort ? ["-c", `model_reasoning_effort=${effort}`] : [],
5977
+ "--sandbox",
5978
+ "read-only",
5979
+ "--skip-git-repo-check",
5980
+ "-"
5981
+ ];
5982
+ return runAgentPrint(agent, args, prompt, { cwd, timeout: SCOUT_TIMEOUT_MS, maxBuffer: 128 * 1024 });
5983
+ };
5944
5984
  function createJevRouter(opts = {}) {
5945
5985
  const getApiKey = opts.getApiKey ?? (() => opts.apiKey ?? process.env.TYPESAFE_API_KEY?.trim());
5946
5986
  const fetcher = opts.fetcher ?? fetch;
5987
+ const scout = opts.scout ?? defaultScout;
5988
+ async function ask(state, questions) {
5989
+ const apiKey = getApiKey();
5990
+ if (!apiKey) throw new Error("\u672A\u8A2D\u5B9A Jev API key\uFF08\u53EF\u5728\u8A2D\u5B9A\u4E2D\u8F38\u5165\u6216\u63D0\u4F9B TYPESAFE_API_KEY\uFF09");
5991
+ const response = await fetcher(API_URL, {
5992
+ method: "POST",
5993
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
5994
+ body: JSON.stringify({ model: "jev-latest", state, questions }),
5995
+ signal: AbortSignal.timeout(15e3)
5996
+ });
5997
+ if (response.status === 402 || response.status === 429) {
5998
+ throw new Error(`Jev \u984D\u5EA6\u5DF2\u7528\u5B8C\u6216\u8ACB\u6C42\u904E\u591A\uFF08HTTP ${response.status}\uFF09\uFF0C\u6539\u7528 CLI \u76EE\u524D\u7684\u6A21\u578B`);
5999
+ }
6000
+ if (!response.ok) throw new Error(`Jev API \u56DE\u61C9 ${response.status}`);
6001
+ const body = await response.json();
6002
+ if (!body.answers) throw new Error("Jev API \u6C92\u6709\u56DE\u50B3\u5224\u65B7");
6003
+ return body.answers;
6004
+ }
6005
+ async function routeEffort(task, catalog) {
6006
+ const model = modelFor(catalog, taskModel(task));
6007
+ const levels = selectableEfforts(model);
6008
+ if (levels.length === 0) return { status: "fallback", reason: "\u76EE\u524D\u6A21\u578B\u4E0D\u652F\u63F4\u8ABF\u6574 effort" };
6009
+ try {
6010
+ const answers = await ask({
6011
+ task: buildPrompt(task).slice(0, MAX_TASK_CHARS),
6012
+ agent: taskAgent(task),
6013
+ currentModel: model?.id ?? taskModel(task) ?? "CLI default (catalog unavailable)"
6014
+ }, { effort: effortQuestion(levels) });
6015
+ const choice = answers.effort;
6016
+ if (!isChoice(choice) || !levels.includes(choice.choice)) {
6017
+ throw new Error("Jev \u9078\u4E86\u76EE\u524D\u6A21\u578B\u4E0D\u652F\u63F4\u7684 effort");
6018
+ }
6019
+ return { status: "routed", effort: choice.choice };
6020
+ } catch (err) {
6021
+ return { status: "fallback", reason: message(err) };
6022
+ }
6023
+ }
6024
+ async function routeModel(task, catalog, ro) {
6025
+ const agent = taskAgent(task);
6026
+ const candidates = routeCandidates(agent, catalog);
6027
+ if (candidates.length === 0) return { status: "fallback", reason: "\u6A21\u578B\u6E05\u55AE\u6C92\u6709\u53EF\u7528\u7684\u6A21\u578B" };
6028
+ const chooseEffort = task.effort === "auto";
6029
+ const strongest = candidates[candidates.length - 1];
6030
+ const levels = MANUAL_EFFORTS.filter((level) => candidates.some((m) => selectableEfforts(m).includes(level)));
6031
+ const taskText = buildPrompt(task).slice(0, MAX_TASK_CHARS);
6032
+ const modelQuestion = {
6033
+ type: "choice",
6034
+ instructions: "Choose the lowest-cost available model likely to implement this software task correctly. Prefer more capable models as complexity, ambiguity or risk rises.",
6035
+ criteria: Object.fromEntries(candidates.map((m) => [m.id, `${m.label}; ${TIER_LABELS[tier(m, agent)]}`]))
6036
+ };
6037
+ const questions = (withUnclear) => ({
6038
+ complexity: {
6039
+ type: "choice",
6040
+ instructions: "How difficult is this software task to implement correctly?",
6041
+ criteria: {
6042
+ simple: "Small, well scoped and routine",
6043
+ moderate: "Some design choices or multiple files",
6044
+ complex: "Architecture, ambiguous requirements, high risk, or many interacting parts",
6045
+ ...withUnclear ? { unclear: "Cannot be judged from the description alone; the repository must be read first" } : {}
6046
+ }
6047
+ },
6048
+ model: modelQuestion,
6049
+ ...chooseEffort && levels.length > 0 ? { effort: effortQuestion(levels) } : {}
6050
+ });
6051
+ const decide = (answers, extra) => {
6052
+ const model = candidates.find((m) => isChoice(answers.model) && m.id === answers.model.choice);
6053
+ if (!model) throw new Error("Jev \u9078\u4E86\u4E0D\u5728\u6A21\u578B\u6E05\u55AE\u4E2D\u7684\u6A21\u578B");
6054
+ let effort;
6055
+ if (chooseEffort && levels.length > 0) {
6056
+ const choice = answers.effort;
6057
+ if (!isChoice(choice) || !levels.includes(choice.choice)) throw new Error("Jev \u56DE\u50B3\u4E0D\u53D7\u652F\u63F4\u7684 effort");
6058
+ effort = clampEffort(choice.choice, selectableEfforts(model));
6059
+ }
6060
+ return { status: "routed", model: model.id, ...effort ? { effort } : {}, ...extra };
6061
+ };
6062
+ const conservative = (extra) => {
6063
+ const effort = chooseEffort ? clampEffort("high", selectableEfforts(strongest)) : void 0;
6064
+ return { model: strongest.id, ...effort ? { effort } : {}, ...extra };
6065
+ };
6066
+ let first;
6067
+ let complexity;
6068
+ try {
6069
+ ro.onPhase?.("judging");
6070
+ first = await ask({ task: taskText, agent }, questions(true));
6071
+ const c = first.complexity;
6072
+ if (!isChoice(c) || !["simple", "moderate", "complex", "unclear"].includes(c.choice)) throw new Error("Jev \u56DE\u50B3\u4E0D\u53D7\u652F\u63F4\u7684\u8907\u96DC\u5EA6");
6073
+ complexity = c.choice;
6074
+ if (complexity !== "unclear") return decide(first, { complexity });
6075
+ } catch (err) {
6076
+ return { status: "fallback", reason: message(err) };
6077
+ }
6078
+ if (ro.scout === false) return conservative({ status: "routed", complexity });
6079
+ let context;
6080
+ try {
6081
+ if (!ro.cwd) throw new Error("\u88DC\u5145 context \u6642\u627E\u4E0D\u5230 worktree");
6082
+ ro.onPhase?.("scouting");
6083
+ const scoutEffort = clampEffort("high", selectableEfforts(strongest));
6084
+ const instruction = "Read the repository to prepare the following software task. Do not edit any file. Report concisely: the affected areas and files, requirements that are unclear, the main risks, and how to verify the work. Return only the analysis.\n\n" + taskText;
6085
+ context = (await scout(agent, strongest.id, scoutEffort, instruction, ro.cwd)).trim().slice(0, MAX_CONTEXT_CHARS);
6086
+ if (!context) throw new Error("\u88DC\u5145 context \u7684\u6A21\u578B\u6C92\u6709\u56DE\u50B3\u5206\u6790");
6087
+ } catch (err) {
6088
+ return conservative({ status: "fallback", complexity, reason: message(err) });
6089
+ }
6090
+ try {
6091
+ ro.onPhase?.("selecting");
6092
+ const second = await ask({ task: taskText, agent, context }, questions(false));
6093
+ return decide(second, { complexity, scoutModel: strongest.id, context });
6094
+ } catch (err) {
6095
+ return { status: "fallback", complexity, reason: message(err), scoutModel: strongest.id, context };
6096
+ }
6097
+ }
5947
6098
  return {
5948
6099
  get enabled() {
5949
6100
  return Boolean(getApiKey());
5950
6101
  },
5951
- async route(task, catalog) {
5952
- const model = modelFor(catalog, taskModel(task));
5953
- const levels = selectableEfforts(model);
5954
- if (levels.length === 0) return { status: "fallback", reason: "\u76EE\u524D\u6A21\u578B\u4E0D\u652F\u63F4\u8ABF\u6574 effort" };
5955
- try {
5956
- const apiKey = getApiKey();
5957
- if (!apiKey) throw new Error("\u672A\u8A2D\u5B9A Jev API key\uFF08\u53EF\u5728\u8A2D\u5B9A\u4E2D\u8F38\u5165\u6216\u63D0\u4F9B TYPESAFE_API_KEY\uFF09");
5958
- const response = await fetcher(API_URL, {
5959
- method: "POST",
5960
- headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
5961
- body: JSON.stringify({
5962
- model: "jev-latest",
5963
- state: {
5964
- task: buildPrompt(task).slice(0, MAX_TASK_CHARS),
5965
- agent: task.agentCli === "codex" ? "codex" : "claude",
5966
- currentModel: model?.id ?? taskModel(task) ?? "CLI default (catalog unavailable)"
5967
- },
5968
- questions: {
5969
- effort: {
5970
- type: "choice",
5971
- instructions: "Choose the lowest reasoning effort likely to complete this software task correctly on the current model. Increase effort for complexity, ambiguity, or risk.",
5972
- criteria: Object.fromEntries(levels.map((level) => [level, effortDescription(level)]))
5973
- }
5974
- }
5975
- }),
5976
- signal: AbortSignal.timeout(15e3)
5977
- });
5978
- if (!response.ok) throw new Error(`Jev API \u56DE\u61C9 ${response.status}`);
5979
- const body = await response.json();
5980
- const choice = body.answers?.effort;
5981
- if (choice?.type !== "choice" || !levels.includes(choice.choice)) {
5982
- throw new Error("Jev \u9078\u4E86\u76EE\u524D\u6A21\u578B\u4E0D\u652F\u63F4\u7684 effort");
5983
- }
5984
- return { status: "routed", effort: choice.choice };
5985
- } catch (err) {
5986
- return { status: "fallback", reason: err instanceof Error ? err.message : String(err) };
5987
- }
6102
+ route(task, catalog, ro = {}) {
6103
+ return taskModel(task) ? routeEffort(task, catalog) : routeModel(task, catalog, ro);
5988
6104
  }
5989
6105
  };
5990
6106
  }
6107
+ function effortQuestion(levels) {
6108
+ return {
6109
+ type: "choice",
6110
+ instructions: "Choose the lowest reasoning effort likely to complete this software task correctly on the chosen model. Increase effort for complexity, ambiguity, or risk.",
6111
+ criteria: Object.fromEntries(levels.map((level) => [level, effortDescription(level)]))
6112
+ };
6113
+ }
6114
+ function message(err) {
6115
+ return err instanceof Error ? err.message : String(err);
6116
+ }
5991
6117
  function effortDescription(level) {
5992
6118
  const descriptions = {
5993
6119
  low: "Routine, small and well scoped work",
@@ -6116,11 +6242,11 @@ async function getGitHubCliAuthStatus() {
6116
6242
  } catch {
6117
6243
  }
6118
6244
  }
6119
- const message = errorMessage2(err);
6245
+ const message2 = errorMessage2(err);
6120
6246
  return {
6121
- installed: !/ENOENT|not found/i.test(message),
6247
+ installed: !/ENOENT|not found/i.test(message2),
6122
6248
  authenticated: false,
6123
- error: message
6249
+ error: message2
6124
6250
  };
6125
6251
  }
6126
6252
  }
@@ -6706,13 +6832,13 @@ function setConversation(taskId, conversation) {
6706
6832
  function loadConversation(taskId) {
6707
6833
  return getChatStore().get("conversations")[taskId] ?? null;
6708
6834
  }
6709
- function appendMessage(taskId, message) {
6835
+ function appendMessage(taskId, message2) {
6710
6836
  const existing = loadConversation(taskId) ?? {
6711
6837
  taskId,
6712
6838
  messages: [],
6713
6839
  updatedAt: 0
6714
6840
  };
6715
- existing.messages.push(message);
6841
+ existing.messages.push(message2);
6716
6842
  existing.updatedAt = Date.now();
6717
6843
  setConversation(taskId, existing);
6718
6844
  }
@@ -7267,8 +7393,8 @@ var PASTE_END = "\x1B[201~";
7267
7393
  var PASTE_GAP_MS = 200;
7268
7394
  var DEFAULT_QUIET_MS = 3e3;
7269
7395
  var DEFAULT_TIMEOUT_MS = 12e4;
7270
- function coded(code2, message) {
7271
- return Object.assign(new Error(message), { code: code2 });
7396
+ function coded(code2, message2) {
7397
+ return Object.assign(new Error(message2), { code: code2 });
7272
7398
  }
7273
7399
  function isBlocked(activity) {
7274
7400
  return activity?.state === "waiting" && activity.waitingFor === "permission";
@@ -7453,11 +7579,11 @@ var InvalidRequestError = class extends Error {
7453
7579
  };
7454
7580
  var STANDALONE_KEY = /^terminal_[0-9a-f-]{36}$/;
7455
7581
  var MAX_MESSAGE_LENGTH = 2e4;
7456
- function coded2(code2, message) {
7457
- return Object.assign(new Error(message), { code: code2 });
7582
+ function coded2(code2, message2) {
7583
+ return Object.assign(new Error(message2), { code: code2 });
7458
7584
  }
7459
- function invalid(message) {
7460
- throw new InvalidRequestError(message);
7585
+ function invalid(message2) {
7586
+ throw new InvalidRequestError(message2);
7461
7587
  }
7462
7588
  function str(value, name) {
7463
7589
  if (typeof value !== "string") invalid(`${name} must be a string`);
@@ -7638,16 +7764,23 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7638
7764
  const task = requireTask(key.split(":")[0]);
7639
7765
  return str(task.worktreePath ?? task.projectPath, "directory");
7640
7766
  }
7767
+ function launchModel(task, route) {
7768
+ return taskModel(task) ?? route?.model;
7769
+ }
7641
7770
  function launchEffort(task, route) {
7642
7771
  const effort = task.effort === "auto" ? route?.effort : task.effort;
7643
- const model = models.findModel(taskAgent(task), taskModel(task));
7772
+ const model = models.findModel(taskAgent(task), launchModel(task, route));
7644
7773
  return model?.efforts ? clampEffort(effort, model.efforts) : effort;
7645
7774
  }
7775
+ function settledRoute(task) {
7776
+ return task.jevRoute?.status === "routing" ? void 0 : task.jevRoute;
7777
+ }
7646
7778
  async function launchCommand(task, intent) {
7647
7779
  const settings = getSettings();
7648
7780
  const boardCli = await boardCliLaunchInfo();
7649
- let route = task.jevRoute?.status === "routing" ? void 0 : task.jevRoute;
7650
- if (task.effort === "auto" && !intent.resume && intent.includeTaskPrompt !== false) {
7781
+ let route = settledRoute(task);
7782
+ const needsRoute = !taskModel(task) || task.effort === "auto";
7783
+ if (needsRoute && !intent.resume && intent.includeTaskPrompt !== false) {
7651
7784
  let pending = routing.get(task.id);
7652
7785
  if (!pending) {
7653
7786
  const setRoute = (jevRoute) => {
@@ -7657,7 +7790,12 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7657
7790
  pending = (async () => {
7658
7791
  setRoute({ status: "routing" });
7659
7792
  const candidateModels = (await (router.enabled === false ? models.get(taskAgent(task)) : models.refresh(taskAgent(task)))).models;
7660
- const selected = await router.route(task, candidateModels);
7793
+ const selected = await router.route(task, candidateModels, {
7794
+ // A master has no repo to read, only its notes folder.
7795
+ scout: !isMasterTask(task),
7796
+ cwd: task.worktreePath,
7797
+ onPhase: (phase) => setRoute({ status: "routing", phase })
7798
+ });
7661
7799
  setRoute(selected);
7662
7800
  return selected;
7663
7801
  })().catch((err) => {
@@ -7668,14 +7806,16 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7668
7806
  }
7669
7807
  route = await pending;
7670
7808
  }
7809
+ const preface = route?.context && route.scoutModel ? { model: route.scoutModel, context: route.context } : void 0;
7671
7810
  return buildAgentCommand(
7672
- { ...task, model: taskModel(task), effort: launchEffort(task, route) },
7811
+ { ...task, model: launchModel(task, route), effort: launchEffort(task, route) },
7673
7812
  settings.systemPrompt ?? "",
7674
7813
  {
7675
7814
  resume: intent.resume === true,
7676
7815
  includeTaskPrompt: intent.includeTaskPrompt !== false,
7677
7816
  boardCli,
7678
- autoMode: task.autoMode ?? settings.autoMode
7817
+ autoMode: task.autoMode ?? settings.autoMode,
7818
+ preface
7679
7819
  },
7680
7820
  task.workspacePath
7681
7821
  );
@@ -7784,6 +7924,15 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7784
7924
  const task = findTask(taskId);
7785
7925
  if (task && sessions.isAttached?.(taskId) === false) await startTaskSession(task, taskId, { taskId });
7786
7926
  }
7927
+ function launchChoice(task) {
7928
+ const route = settledRoute(task);
7929
+ const model = launchModel(task, route);
7930
+ const effort = launchEffort(task, route);
7931
+ return {
7932
+ ...model ? { model } : {},
7933
+ ...effort && effort !== "auto" ? { effort } : {}
7934
+ };
7935
+ }
7787
7936
  async function summarizeTasks(filter) {
7788
7937
  const board = getState().board;
7789
7938
  const alive = new Set(await sessions.list());
@@ -7805,6 +7954,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7805
7954
  ...task.masterId ? { masterId: task.masterId } : {},
7806
7955
  ...task.projectName ? { projectName: task.projectName } : {},
7807
7956
  ...task.branch ? { branch: task.branch } : {},
7957
+ ...launchChoice(task),
7808
7958
  running: alive.has(task.id),
7809
7959
  ...task.launchedAt ? { launchedAt: task.launchedAt } : {},
7810
7960
  ...task.launchError ? { launchError: task.launchError } : {},
@@ -8194,8 +8344,8 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
8194
8344
  const file = await readTerminalFile(terminalDirectory(p.sessionKey), str(p.path, "path"), true);
8195
8345
  if (!file.ok) return { ok: false, message: file.message };
8196
8346
  try {
8197
- const message = await getPlatform().openPath(file.path);
8198
- return message ? { ok: false, message } : { ok: true };
8347
+ const message2 = await getPlatform().openPath(file.path);
8348
+ return message2 ? { ok: false, message: message2 } : { ok: true };
8199
8349
  } catch (error) {
8200
8350
  return { ok: false, message: error instanceof Error ? error.message : "\u7121\u6CD5\u958B\u555F\u6A94\u6848\u3002" };
8201
8351
  }