@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 +1018 -370
- package/dist/web/404/index.html +1 -1
- package/dist/web/404.html +1 -1
- package/dist/web/_next/static/{1-jD1GlcBxcAPCb5OcjRK → MCpYTSYna3Ll-2lXwbETR}/_buildManifest.js +1 -1
- package/dist/web/_next/static/chunks/{8527-5442098d5121c96b.js → 7451-e9f74fece50504ed.js} +2 -2
- package/dist/web/_next/static/chunks/pages/{_app-d965862fbafe3116.js → _app-496c474d7f14dd07.js} +1 -1
- package/dist/web/_next/static/chunks/pages/home-ead2bccf81dc3d85.js +12 -0
- package/dist/web/_next/static/css/0e63f58e447f85ed.css +2 -0
- package/dist/web/home/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/_next/static/chunks/pages/home-f31c4cf97ed808cf.js +0 -12
- package/dist/web/_next/static/css/7b78ec950bc90c91.css +0 -2
- /package/dist/web/_next/static/{1-jD1GlcBxcAPCb5OcjRK → MCpYTSYna3Ll-2lXwbETR}/_ssgManifest.js +0 -0
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
|
|
715
|
+
const basename3 = name.replace(/\\/g, "/").split("/").pop() ?? "";
|
|
526
716
|
const sanitized = truncateUtf8(
|
|
527
|
-
|
|
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
|
|
887
|
-
import
|
|
888
|
-
import
|
|
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 =
|
|
1008
|
-
const file =
|
|
1232
|
+
const dir = fs22.mkdtempSync(path26.join(os9.tmpdir(), "vibeflow-"));
|
|
1233
|
+
const file = path26.join(dir, "description.md");
|
|
1009
1234
|
try {
|
|
1010
|
-
|
|
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:
|
|
1240
|
+
return { text: fs22.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
|
|
1016
1241
|
} finally {
|
|
1017
|
-
|
|
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(
|
|
1313
|
+
if (value2) setProject(path26.resolve(value2.replace(/^~(?=$|\/)/, os9.homedir())));
|
|
1089
1314
|
} else if (editing === "attachments" && value2) {
|
|
1090
|
-
const file =
|
|
1091
|
-
if (!
|
|
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) =>
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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"}/${
|
|
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")
|
|
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
|
|
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
|
|
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(
|
|
1337
|
-
const env = { ...
|
|
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
|
|
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",
|
|
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 =
|
|
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
|
|
1750
|
-
import
|
|
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
|
|
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/
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
return
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
}
|
|
2727
|
-
|
|
2728
|
-
|
|
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
|
|
2731
|
-
|
|
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
|
|
2734
|
-
const
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
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
|
|
2750
|
-
return
|
|
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
|
|
2753
|
-
|
|
2754
|
-
const
|
|
2755
|
-
|
|
2756
|
-
|
|
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
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
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
|
|
2785
|
-
|
|
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
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
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
|
|
3140
|
+
return links;
|
|
2794
3141
|
}
|
|
2795
|
-
function
|
|
2796
|
-
const
|
|
2797
|
-
const
|
|
2798
|
-
|
|
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
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
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
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
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
|
-
|
|
2831
|
-
return cmd;
|
|
3157
|
+
return Array.from(latest.entries()).sort((a, b) => b[1] - a[1]).map(([p]) => p);
|
|
2832
3158
|
}
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
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
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
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
|
|
3396
|
+
var pexec2 = promisify2(execFile5);
|
|
3042
3397
|
async function git(cwd, args) {
|
|
3043
|
-
const { stdout } = await
|
|
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,
|
|
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:
|
|
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
|
|
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,
|
|
3798
|
+
return adoptRemoteBranch(projectPath, worktreePath, branch, base2);
|
|
3444
3799
|
}
|
|
3445
|
-
let startPoint =
|
|
3800
|
+
let startPoint = base2;
|
|
3446
3801
|
if (info.hasRemote) {
|
|
3447
3802
|
try {
|
|
3448
|
-
await git(projectPath, ["fetch", "origin", `${
|
|
3803
|
+
await git(projectPath, ["fetch", "origin", `${base2}:${base2}`]);
|
|
3449
3804
|
} catch {
|
|
3450
3805
|
try {
|
|
3451
|
-
await git(projectPath, ["fetch", "origin",
|
|
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/${
|
|
3457
|
-
startPoint = `origin/${
|
|
3811
|
+
await git(projectPath, ["rev-parse", "--verify", `origin/${base2}`]);
|
|
3812
|
+
startPoint = `origin/${base2}`;
|
|
3458
3813
|
} catch {
|
|
3459
|
-
startPoint =
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
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
|
-
|
|
3856
|
-
import {
|
|
3857
|
-
|
|
3858
|
-
var
|
|
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
|
|
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
|
|
4248
|
-
import
|
|
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
|
|
4662
|
+
const basename3 = name.replace(/\\/g, "/").split("/").pop() ?? "";
|
|
4315
4663
|
const cleaned = truncateUtf82(
|
|
4316
|
-
|
|
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
|
|
4787
|
-
import
|
|
4788
|
-
import
|
|
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
|
|
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
|
-
|
|
5149
|
+
|
|
5150
|
+
// packages/core/src/json-lines-process.ts
|
|
4797
5151
|
init_env();
|
|
4798
|
-
|
|
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 :
|
|
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
|
|
4840
|
-
|
|
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
|
|
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(
|
|
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
|
|
5019
|
-
import
|
|
5636
|
+
import fs14 from "fs";
|
|
5637
|
+
import path18 from "path";
|
|
5020
5638
|
import { pathToFileURL } from "url";
|
|
5021
5639
|
async function boardCliLaunchInfo() {
|
|
5022
|
-
const storeDir =
|
|
5640
|
+
const storeDir = path18.dirname(getStorePath());
|
|
5023
5641
|
const entry = getPlatform().cliEntry?.();
|
|
5024
|
-
if (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 =
|
|
5028
|
-
const loaderPath =
|
|
5029
|
-
if (!
|
|
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
|
|
5035
|
-
import
|
|
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 ?
|
|
5656
|
+
return worktreePath ? path19.basename(worktreePath) : branch.replace(/\//g, "-");
|
|
5039
5657
|
}
|
|
5040
5658
|
function decisionsPath(workspacePath, key) {
|
|
5041
|
-
return
|
|
5659
|
+
return path19.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
|
|
5042
5660
|
}
|
|
5043
5661
|
function deleteDecisions(workspacePath, key) {
|
|
5044
5662
|
try {
|
|
5045
|
-
|
|
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
|
|
5052
|
-
import
|
|
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
|
|
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 =
|
|
5072
|
-
const stat =
|
|
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(
|
|
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
|
|
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
|
|
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 \`${
|
|
5821
|
+
summary = `Reading \`${basename2(String(input.file_path ?? ""))}\``;
|
|
5204
5822
|
break;
|
|
5205
5823
|
case "Write":
|
|
5206
|
-
summary = `Writing \`${
|
|
5824
|
+
summary = `Writing \`${basename2(String(input.file_path ?? ""))}\``;
|
|
5207
5825
|
break;
|
|
5208
5826
|
case "Edit":
|
|
5209
5827
|
case "MultiEdit":
|
|
5210
|
-
summary = `Editing \`${
|
|
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 =
|
|
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
|
-
|
|
5425
|
-
import
|
|
5426
|
-
import
|
|
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
|
|
6049
|
+
return path21.join(home, ".claude", "projects", cwd.replace(/[^a-zA-Z0-9]/g, "-"));
|
|
5430
6050
|
}
|
|
5431
6051
|
function pathAliases(p) {
|
|
5432
|
-
const resolved =
|
|
6052
|
+
const resolved = path21.resolve(p);
|
|
5433
6053
|
try {
|
|
5434
|
-
const real =
|
|
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
|
|
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 =
|
|
6078
|
+
fd = fs17.openSync(file, "r");
|
|
5459
6079
|
const buf = Buffer.alloc(64 * 1024);
|
|
5460
|
-
const n =
|
|
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)
|
|
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 =
|
|
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 =
|
|
6105
|
+
fd = fs17.openSync(file, "r");
|
|
5486
6106
|
const chunk = Buffer.alloc(size - cursor.offset);
|
|
5487
|
-
const n =
|
|
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)
|
|
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(
|
|
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 ??
|
|
5527
|
-
const codexHomes = opts.codexHomes ?? (() => [
|
|
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 =
|
|
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(`${
|
|
5550
|
-
const sub =
|
|
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(
|
|
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 =
|
|
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 =
|
|
5580
|
-
const event = JSON.parse(
|
|
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
|
-
|
|
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(`${
|
|
5601
|
-
const isExecutor =
|
|
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 =
|
|
6300
|
+
const eventsDir = path21.join(target.worktreePath, PROGRESS_EVENTS_DIR);
|
|
5681
6301
|
try {
|
|
5682
|
-
|
|
5683
|
-
entry.watcher =
|
|
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 ??
|
|
6396
|
+
const home = homeDir ?? os8.homedir();
|
|
5776
6397
|
const progress = createProgressTracker({
|
|
5777
6398
|
homeDir: home,
|
|
5778
|
-
codexHomes: () => [
|
|
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
|
|
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 ??
|
|
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
|
-
|
|
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 =
|
|
5958
|
-
if (!
|
|
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 ??
|
|
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 =
|
|
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
|
|
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 (!
|
|
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
|
|
6197
|
-
if (task.worktreePath) return readWorktreeSpecs(task.worktreePath,
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
7022
|
+
const file = path22.basename(getStorePath());
|
|
6379
7023
|
let debounce = null;
|
|
6380
|
-
storeWatcher =
|
|
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
|
|
7060
|
+
import fs19 from "fs";
|
|
6414
7061
|
import http from "http";
|
|
6415
|
-
import
|
|
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 =
|
|
6469
|
-
const
|
|
6470
|
-
const candidates = [rel,
|
|
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 =
|
|
6473
|
-
if (full !==
|
|
7119
|
+
const full = path23.resolve(base2, c);
|
|
7120
|
+
if (full !== base2 && !full.startsWith(base2 + path23.sep)) return null;
|
|
6474
7121
|
try {
|
|
6475
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
6847
|
-
import
|
|
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
|
-
|
|
6988
|
-
const probe =
|
|
6989
|
-
|
|
6990
|
-
|
|
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 =
|
|
7045
|
-
const bundledWeb =
|
|
7046
|
-
if (isWebDir(bundledWeb) ||
|
|
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(
|
|
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 =
|
|
7702
|
+
const root = path28.resolve(here, "..", "..", "..");
|
|
7055
7703
|
const override = process.env.VIBEFLOW_WEB_DIR;
|
|
7056
|
-
const webDir = override && isWebDir(override) ? override : isWebDir(
|
|
7057
|
-
return { version: readVersion(
|
|
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(
|
|
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 =
|
|
7268
|
-
if (!
|
|
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
|
}
|