@kw-yeh/vibeflow 4.1.0 → 4.3.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 +298 -175
- package/dist/web/404/index.html +1 -1
- package/dist/web/404.html +1 -1
- package/dist/web/_next/static/{E9h3ANqgTHA4it6NLyDv5 → _pjqayPqD9pDyPeTbc6L2}/_buildManifest.js +1 -1
- package/dist/web/_next/static/chunks/{8833-76c31db372a29bb8.js → 2524-abaf3a55023f6066.js} +2 -2
- package/dist/web/_next/static/chunks/pages/{_app-66261c34aef6a4cb.js → _app-014e95fe86cd1d39.js} +1 -1
- package/dist/web/_next/static/chunks/pages/home-1fb1d1a49d28f6dd.js +12 -0
- package/dist/web/_next/static/css/{c380333c2873a2b8.css → 24b804d01aa3af3c.css} +1 -1
- package/dist/web/home/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/_next/static/chunks/pages/home-e16a7b49ece28424.js +0 -11
- /package/dist/web/_next/static/{E9h3ANqgTHA4it6NLyDv5 → _pjqayPqD9pDyPeTbc6L2}/_ssgManifest.js +0 -0
package/dist/vibeflow.mjs
CHANGED
|
@@ -555,14 +555,17 @@ ${entry}
|
|
|
555
555
|
}
|
|
556
556
|
}
|
|
557
557
|
function writeAttachments(worktreePath, inputs) {
|
|
558
|
+
if (inputs.length === 0) return [];
|
|
559
|
+
ensureAttachmentExclude(worktreePath);
|
|
560
|
+
return writeAttachmentsTo(path10.join(worktreePath, ATTACHMENTS_DIR), inputs);
|
|
561
|
+
}
|
|
562
|
+
function writeAttachmentsTo(attachmentDir, inputs) {
|
|
558
563
|
if (inputs.length === 0) return [];
|
|
559
564
|
const decodedInputs = inputs.map((input) => ({
|
|
560
565
|
input,
|
|
561
566
|
name: safeAttachmentName(input.name),
|
|
562
567
|
bytes: decodeAttachment(input)
|
|
563
568
|
}));
|
|
564
|
-
ensureAttachmentExclude(worktreePath);
|
|
565
|
-
const attachmentDir = path10.join(worktreePath, ATTACHMENTS_DIR);
|
|
566
569
|
fs6.mkdirSync(attachmentDir, { recursive: true });
|
|
567
570
|
return decodedInputs.map(({ input, name, bytes }) => {
|
|
568
571
|
const id = randomUUID().slice(0, 8);
|
|
@@ -598,16 +601,15 @@ var init_attachments = __esm({
|
|
|
598
601
|
|
|
599
602
|
// packages/tui/src/task-form.ts
|
|
600
603
|
import { spawnSync } from "child_process";
|
|
601
|
-
import
|
|
604
|
+
import fs20 from "fs";
|
|
602
605
|
import os6 from "os";
|
|
603
|
-
import
|
|
606
|
+
import path24 from "path";
|
|
604
607
|
import React, { useEffect, useRef, useState } from "react";
|
|
605
608
|
import { Box, Text, render, useApp, useInput } from "ink";
|
|
606
609
|
function newFormState(defaultAutoMode, agents) {
|
|
607
610
|
const agentCli = agents && agents.length > 0 && !agents.some((a) => a.id === "claude") ? agents[0].id : "claude";
|
|
608
611
|
return {
|
|
609
612
|
mode: "new",
|
|
610
|
-
launched: false,
|
|
611
613
|
title: "",
|
|
612
614
|
description: "",
|
|
613
615
|
projectPath: null,
|
|
@@ -627,11 +629,11 @@ function editFormState(task, defaultAutoMode) {
|
|
|
627
629
|
return {
|
|
628
630
|
mode: "edit",
|
|
629
631
|
taskId: task.id,
|
|
630
|
-
launched: Boolean(task.launchedAt),
|
|
631
632
|
title: task.title,
|
|
632
633
|
description: task.description ?? "",
|
|
633
634
|
projectPath: task.projectPath ?? null,
|
|
634
635
|
originalProjectPath: task.projectPath ?? null,
|
|
636
|
+
worktreePath: task.worktreePath,
|
|
635
637
|
gitInfo: null,
|
|
636
638
|
gitInfoPath: null,
|
|
637
639
|
baseBranch: task.baseBranch ?? "",
|
|
@@ -662,8 +664,8 @@ function visibleFields(s, ctx) {
|
|
|
662
664
|
return fields2;
|
|
663
665
|
}
|
|
664
666
|
const fields = ["title", "description", "effort", "autoMode", "project"];
|
|
665
|
-
if (
|
|
666
|
-
fields.push("agent", "model", "submit");
|
|
667
|
+
if (isRepo && hasRemote) fields.push("baseBranch");
|
|
668
|
+
fields.push("branch", "agent", "model", "submit");
|
|
667
669
|
return fields;
|
|
668
670
|
}
|
|
669
671
|
function modelOptions(ctx, agentCli, current) {
|
|
@@ -712,24 +714,25 @@ function updatePayload(s) {
|
|
|
712
714
|
model: s.model || void 0,
|
|
713
715
|
effort: s.effort,
|
|
714
716
|
autoMode: s.autoMode,
|
|
715
|
-
|
|
717
|
+
...s.projectPath ? { projectPath: s.projectPath, baseBranch: s.baseBranch || null } : {},
|
|
718
|
+
branch: s.branch.trim()
|
|
716
719
|
};
|
|
717
720
|
}
|
|
718
721
|
function editorCommand(env, platform) {
|
|
719
722
|
return env.VISUAL?.trim() || env.EDITOR?.trim() || (platform === "win32" ? "notepad" : "vi");
|
|
720
723
|
}
|
|
721
724
|
function editInEditor(text) {
|
|
722
|
-
const dir =
|
|
723
|
-
const file =
|
|
725
|
+
const dir = fs20.mkdtempSync(path24.join(os6.tmpdir(), "vibeflow-"));
|
|
726
|
+
const file = path24.join(dir, "description.md");
|
|
724
727
|
try {
|
|
725
|
-
|
|
728
|
+
fs20.writeFileSync(file, text);
|
|
726
729
|
const cmd = editorCommand(process.env, process.platform);
|
|
727
730
|
const r = spawnSync(`${cmd} "${file}"`, { stdio: "inherit", shell: true });
|
|
728
731
|
if (r.error) return { error: `\u7121\u6CD5\u555F\u52D5\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\uFF1A${r.error.message}` };
|
|
729
732
|
if (r.status !== 0) return { error: `\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\u7D50\u675F\u78BC ${r.status}\uFF0C\u63CF\u8FF0\u672A\u8B8A\u66F4` };
|
|
730
|
-
return { text:
|
|
733
|
+
return { text: fs20.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
|
|
731
734
|
} finally {
|
|
732
|
-
|
|
735
|
+
fs20.rmSync(dir, { recursive: true, force: true });
|
|
733
736
|
}
|
|
734
737
|
}
|
|
735
738
|
function cycle(list, current, step) {
|
|
@@ -754,16 +757,20 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
754
757
|
const lookup = useRef(0);
|
|
755
758
|
const fields = visibleFields(s, ctx);
|
|
756
759
|
const field = fields[Math.min(focus, fields.length - 1)];
|
|
757
|
-
const projectLocked = s.mode === "edit" && s.launched;
|
|
758
760
|
const missing = isProjectMissing(ctx.recent, s.projectPath);
|
|
759
761
|
useEffect(() => {
|
|
760
|
-
if (!s.projectPath || missing || s.gitInfoPath === s.projectPath
|
|
762
|
+
if (!s.projectPath || missing || s.gitInfoPath === s.projectPath) return;
|
|
761
763
|
const target = s.projectPath;
|
|
762
764
|
const id = ++lookup.current;
|
|
763
765
|
setLoadingGit(true);
|
|
764
766
|
void api.getGitInfo(target).then((info) => {
|
|
765
767
|
if (id !== lookup.current) return;
|
|
766
|
-
setS((prev) => ({
|
|
768
|
+
setS((prev) => ({
|
|
769
|
+
...prev,
|
|
770
|
+
gitInfo: info,
|
|
771
|
+
gitInfoPath: target,
|
|
772
|
+
baseBranch: prev.mode === "edit" && target === prev.originalProjectPath && prev.baseBranch ? prev.baseBranch : info.defaultBase ?? ""
|
|
773
|
+
}));
|
|
767
774
|
}).catch((err) => id === lookup.current && setMessage(`\u5075\u6E2C Git \u5931\u6557\uFF1A${err.message}`)).finally(() => id === lookup.current && setLoadingGit(false));
|
|
768
775
|
}, [s.projectPath]);
|
|
769
776
|
const leave = (result) => {
|
|
@@ -771,7 +778,6 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
771
778
|
exit();
|
|
772
779
|
};
|
|
773
780
|
const setProject = (projectPath) => {
|
|
774
|
-
if (projectLocked) return;
|
|
775
781
|
setS((prev) => ({ ...prev, projectPath, gitInfo: null, gitInfoPath: null, baseBranch: "" }));
|
|
776
782
|
};
|
|
777
783
|
const submit = async () => {
|
|
@@ -797,10 +803,10 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
797
803
|
if (editing === "title") setS((prev) => ({ ...prev, title: draft }));
|
|
798
804
|
else if (editing === "branch") setS((prev) => ({ ...prev, branch: value2 }));
|
|
799
805
|
else if (editing === "project") {
|
|
800
|
-
if (value2) setProject(
|
|
806
|
+
if (value2) setProject(path24.resolve(value2.replace(/^~(?=$|\/)/, os6.homedir())));
|
|
801
807
|
} else if (editing === "attachments" && value2) {
|
|
802
|
-
const file =
|
|
803
|
-
if (!
|
|
808
|
+
const file = path24.resolve(value2.replace(/^~(?=$|\/)/, os6.homedir()));
|
|
809
|
+
if (!fs20.existsSync(file) || !fs20.statSync(file).isFile()) {
|
|
804
810
|
setMessage(`\u627E\u4E0D\u5230\u6A94\u6848\uFF1A${file}`);
|
|
805
811
|
return;
|
|
806
812
|
}
|
|
@@ -833,7 +839,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
833
839
|
if (key.downArrow || key.tab) return setFocus((f) => Math.min(fields.length - 1, f + 1));
|
|
834
840
|
const step = key.leftArrow ? -1 : key.rightArrow ? 1 : 0;
|
|
835
841
|
if (step) {
|
|
836
|
-
if (field === "project"
|
|
842
|
+
if (field === "project") {
|
|
837
843
|
const paths = ctx.recent.map((p) => p.path);
|
|
838
844
|
if (paths.length) setProject(cycle(paths, s.projectPath ?? paths[paths.length - 1], step));
|
|
839
845
|
} else if (field === "baseBranch") {
|
|
@@ -859,7 +865,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
859
865
|
void api.initRepository(s.projectPath).then((info) => setS((prev) => ({ ...prev, gitInfo: info, gitInfoPath: prev.projectPath, baseBranch: info.defaultBase ?? "" }))).catch((err) => setMessage(err.message)).finally(() => setBusy(null));
|
|
860
866
|
return;
|
|
861
867
|
}
|
|
862
|
-
if (TEXT_FIELDS.includes(field)
|
|
868
|
+
if (TEXT_FIELDS.includes(field)) {
|
|
863
869
|
setEditing(field);
|
|
864
870
|
setDraft(field === "title" ? s.title : field === "branch" ? s.branch : field === "project" ? s.projectPath ?? "" : "");
|
|
865
871
|
}
|
|
@@ -870,7 +876,6 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
870
876
|
case "title":
|
|
871
877
|
return s.title || "\uFF08\u4F8B\u5982\uFF1A\u5BE6\u4F5C\u767B\u5165\u9801\u9762\uFF09";
|
|
872
878
|
case "project": {
|
|
873
|
-
if (projectLocked) return `${s.projectPath ? path23.basename(s.projectPath) : "\u2014"}\uFF08\u4EFB\u52D9\u5DF2\u958B\u59CB\uFF0C\u9396\u5B9A\uFF09`;
|
|
874
879
|
if (!s.projectPath) return ctx.recent.length ? "\u2190/\u2192 \u9078\u64C7\u4F7F\u7528\u904E\u7684\u5C08\u6848\uFF0C\u6216 Enter \u8F38\u5165\u8DEF\u5F91" : "Enter \u8F38\u5165\u5C08\u6848\u8CC7\u6599\u593E\u7684\u7D55\u5C0D\u8DEF\u5F91";
|
|
875
880
|
return `${s.projectPath}${missing ? "\uFF08\u8DEF\u5F91\u907A\u5931\uFF09" : ""}`;
|
|
876
881
|
}
|
|
@@ -881,7 +886,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
881
886
|
case "branch":
|
|
882
887
|
return s.branch || "\uFF08\u7559\u7A7A\u5247\u81EA\u52D5\u547D\u540D\uFF0C\u4F8B\u5982 feature/login-fix\uFF09";
|
|
883
888
|
case "attachments":
|
|
884
|
-
return s.attachments.length ? s.attachments.map((a) =>
|
|
889
|
+
return s.attachments.length ? s.attachments.map((a) => path24.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
|
|
885
890
|
case "description": {
|
|
886
891
|
if (!s.description) return "\uFF08Enter \u7528\u7DE8\u8F2F\u5668\u64B0\u5BEB\uFF09";
|
|
887
892
|
const lines = s.description.split("\n");
|
|
@@ -906,15 +911,18 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
906
911
|
};
|
|
907
912
|
const note = () => {
|
|
908
913
|
const info = s.gitInfo;
|
|
909
|
-
if (field === "project"
|
|
914
|
+
if (field === "project") {
|
|
910
915
|
if (loadingGit) return "\u5075\u6E2C Git \u72C0\u614B\u4E2D\u2026";
|
|
911
916
|
if (missing) return `\u627E\u4E0D\u5230 ${s.projectPath}\uFF0C\u5C08\u6848\u53EF\u80FD\u5DF2\u88AB\u79FB\u52D5\u6216\u522A\u9664\u3002`;
|
|
912
917
|
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";
|
|
913
|
-
if (s.mode === "edit" && projectChanged(s)) return "\
|
|
918
|
+
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";
|
|
914
919
|
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`;
|
|
915
|
-
if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${
|
|
920
|
+
if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path24.basename(s.projectPath)}`;
|
|
921
|
+
}
|
|
922
|
+
if (field === "branch") {
|
|
923
|
+
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";
|
|
924
|
+
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;
|
|
916
925
|
}
|
|
917
|
-
if (field === "branch") return info?.hasRemote ? "\u586B\u5BEB\u5F8C\u6703\u5EFA\u7ACB\u540C\u540D\u5206\u652F\uFF1B\u82E5 remote \u5DF2\u6709\u9019\u500B\u5206\u652F\uFF0C\u5247\u76F4\u63A5\u53D6\u56DE\u4E26\u63A5\u7E8C\u4E0A\u9762\u7684\u5DE5\u4F5C\u3002" : "\u586B\u5BEB\u5F8C\u6703\u5EFA\u7ACB\u540C\u540D\u5206\u652F\u3002";
|
|
918
926
|
if (field === "effort") return `${EFFORTS.find((x) => x.value === s.effort).description} Claude Code \u8207 Codex \u6703\u5728\u555F\u52D5\u6B64\u4EFB\u52D9\u6642\u5957\u7528\uFF1B\u5BE6\u969B\u53EF\u7528\u7D1A\u8DDD\u4F9D model \u800C\u5B9A\u3002`;
|
|
919
927
|
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";
|
|
920
928
|
if (field === "submit") return blockReason(s, ctx, loadingGit);
|
|
@@ -1029,7 +1037,7 @@ __export(src_exports, {
|
|
|
1029
1037
|
showDiff: () => showDiff
|
|
1030
1038
|
});
|
|
1031
1039
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1032
|
-
import
|
|
1040
|
+
import path25 from "path";
|
|
1033
1041
|
import React2, { useEffect as useEffect2, useState as useState2 } from "react";
|
|
1034
1042
|
import { Box as Box2, Text as Text2, render as render2, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
|
|
1035
1043
|
function findDetachKey(text) {
|
|
@@ -1130,6 +1138,9 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
|
|
|
1130
1138
|
}
|
|
1131
1139
|
setSel({ column: sel.column + direction, index: 0 });
|
|
1132
1140
|
if (move.effect === "launch") {
|
|
1141
|
+
if (!move.task.worktreePath) {
|
|
1142
|
+
setMessage(`\u6B63\u5728\u70BA\u300C${move.task.title}\u300D\u5F9E\u6700\u65B0\u7684 ${move.task.baseBranch ?? "\u57FA\u6E96\u5206\u652F"} \u5EFA\u7ACB\u5206\u652F ${move.task.branch} \u8207 worktree\uFF0C\u9700\u8981\u5E7E\u79D2\u9418\u2026`);
|
|
1143
|
+
}
|
|
1133
1144
|
await api.term.start({ taskId: move.task.id, launch: {} });
|
|
1134
1145
|
setMessage(`\u5DF2\u555F\u52D5\u300C${move.task.title}\u300D\u7684 agent\u3002Enter \u53EF\u9032\u5165\u7D42\u7AEF\u3002`);
|
|
1135
1146
|
} else if (move.effect === "cleanup") {
|
|
@@ -1189,8 +1200,14 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
|
|
|
1189
1200
|
return;
|
|
1190
1201
|
}
|
|
1191
1202
|
if (!selected) return;
|
|
1192
|
-
if (input === "e")
|
|
1203
|
+
if (input === "e") {
|
|
1204
|
+
if (COLUMNS2[sel.column] !== "backlog") return setMessage("\u57F7\u884C\u4E2D\u8207\u5DF2\u5B8C\u6210\u7684\u5361\u7247\u53EA\u80FD\u67E5\u770B\uFF0C\u7121\u6CD5\u7DE8\u8F2F");
|
|
1205
|
+
return leave({ type: "edit", task: selected });
|
|
1206
|
+
}
|
|
1193
1207
|
if (key.return) {
|
|
1208
|
+
if (!selected.worktreePath && COLUMNS2[sel.column] === "backlog") {
|
|
1209
|
+
return setMessage("\u9019\u5F35\u5361\u5C1A\u672A\u958B\u59CB\u57F7\u884C\uFF0C\u5206\u652F\u8207 worktree \u6703\u5728\u958B\u59CB\u6642\u5EFA\u7ACB");
|
|
1210
|
+
}
|
|
1194
1211
|
if (!selected.worktreePath && !selected.projectPath) return setMessage("\u9019\u5F35\u5361\u6C92\u6709\u5DE5\u4F5C\u76EE\u9304");
|
|
1195
1212
|
if (COLUMNS2[sel.column] === "done") return setMessage("\u5DF2\u5B8C\u6210\u7684\u5361\u7247\u6C92\u6709\u7D42\u7AEF");
|
|
1196
1213
|
return leave({ type: "attach", task: selected });
|
|
@@ -1291,7 +1308,7 @@ function showDiff(task) {
|
|
|
1291
1308
|
});
|
|
1292
1309
|
}
|
|
1293
1310
|
function tmuxAttach(opts, sessionName) {
|
|
1294
|
-
const conf =
|
|
1311
|
+
const conf = path25.join(opts.userDataDir, "tmux.conf");
|
|
1295
1312
|
const inOurServer = (process.env.TMUX ?? "").split(",")[0].endsWith(`/${TMUX_SOCKET}`);
|
|
1296
1313
|
const args = inOurServer ? ["-L", TMUX_SOCKET, "-f", conf, "switch-client", "-t", `=${sessionName}`] : ["-L", TMUX_SOCKET, "-f", conf, "attach-session", "-t", `=${sessionName}`];
|
|
1297
1314
|
spawnSync2("tmux", args, { stdio: "inherit", env: { ...process.env, TMUX: inOurServer ? process.env.TMUX : "" } });
|
|
@@ -1421,8 +1438,8 @@ var init_src = __esm({
|
|
|
1421
1438
|
});
|
|
1422
1439
|
|
|
1423
1440
|
// packages/cli/src/main.ts
|
|
1424
|
-
import
|
|
1425
|
-
import
|
|
1441
|
+
import fs21 from "fs";
|
|
1442
|
+
import path26 from "path";
|
|
1426
1443
|
import { fileURLToPath } from "url";
|
|
1427
1444
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
1428
1445
|
|
|
@@ -1814,8 +1831,8 @@ function createBridge(t) {
|
|
|
1814
1831
|
* in-app preview will not inline. Resolves to '' on success, else a reason.
|
|
1815
1832
|
*/
|
|
1816
1833
|
openArtifactsDir: (taskId) => t.invoke("task:openArtifactsDir", taskId),
|
|
1817
|
-
/**
|
|
1818
|
-
|
|
1834
|
+
/** Every spec.md the task's branch added or changed; empty when there is none. */
|
|
1835
|
+
getSpecs: (taskId) => t.invoke("task:getSpecs", taskId),
|
|
1819
1836
|
/** Store dir + CLI paths for launch injection, so an agent can write cards. */
|
|
1820
1837
|
getBoardCliLaunchInfo: () => t.invoke("board:getCliLaunchInfo"),
|
|
1821
1838
|
/** VibeFlow's own skill / prompt / script store. */
|
|
@@ -3200,6 +3217,9 @@ import { join as join2 } from "path";
|
|
|
3200
3217
|
function projectWorkstationPath(workstationRoot, projectName) {
|
|
3201
3218
|
return join2(workstationRoot, projectName);
|
|
3202
3219
|
}
|
|
3220
|
+
function taskAttachmentStagingPath(workspacePath, taskId) {
|
|
3221
|
+
return join2(workspacePath, ".attachments", taskId);
|
|
3222
|
+
}
|
|
3203
3223
|
|
|
3204
3224
|
// packages/core/src/agents.ts
|
|
3205
3225
|
init_env();
|
|
@@ -3278,25 +3298,21 @@ async function createTaskFromInput(input) {
|
|
|
3278
3298
|
if (explicitBranch) {
|
|
3279
3299
|
await assertBranchAvailable(projectPath, workspacePath, explicitBranch);
|
|
3280
3300
|
}
|
|
3281
|
-
const
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
);
|
|
3297
|
-
}
|
|
3298
|
-
const attachments = writeAttachments(
|
|
3299
|
-
provisionResult.worktreePath,
|
|
3301
|
+
const branch = explicitBranch ?? await generateBranchName(input.title, input.description);
|
|
3302
|
+
const draft = {
|
|
3303
|
+
id: taskId,
|
|
3304
|
+
title: input.title.trim() || `Task ${taskId}`,
|
|
3305
|
+
branch: branch ?? fallbackBranchName(taskId),
|
|
3306
|
+
branchExplicit: explicitBranch ? true : void 0,
|
|
3307
|
+
projectPath,
|
|
3308
|
+
projectName,
|
|
3309
|
+
workspacePath,
|
|
3310
|
+
baseBranch: input.baseBranch || info.defaultBase || info.currentBranch || void 0
|
|
3311
|
+
};
|
|
3312
|
+
const col = input.status ?? "backlog";
|
|
3313
|
+
const provisioned = col === "backlog" ? null : await provisionTaskWorktree(draft);
|
|
3314
|
+
const attachments = provisioned?.worktreePath ? writeAttachments(provisioned.worktreePath, input.attachments ?? []) : writeAttachmentsTo(
|
|
3315
|
+
taskAttachmentStagingPath(workspacePath, taskId),
|
|
3300
3316
|
input.attachments ?? []
|
|
3301
3317
|
);
|
|
3302
3318
|
const attachmentLines = attachments.map(
|
|
@@ -3305,16 +3321,9 @@ async function createTaskFromInput(input) {
|
|
|
3305
3321
|
const baseDescription = input.description?.trim();
|
|
3306
3322
|
const description = attachmentLines.length ? [baseDescription, attachmentLines.join("\n")].filter(Boolean).join("\n\n") : baseDescription || void 0;
|
|
3307
3323
|
const task = {
|
|
3308
|
-
|
|
3309
|
-
|
|
3324
|
+
...draft,
|
|
3325
|
+
...provisioned,
|
|
3310
3326
|
description,
|
|
3311
|
-
branch: provisionResult.branch,
|
|
3312
|
-
projectPath,
|
|
3313
|
-
projectName,
|
|
3314
|
-
worktreePath: provisionResult.worktreePath,
|
|
3315
|
-
workspacePath,
|
|
3316
|
-
baseBranch: provisionResult.baseBranch,
|
|
3317
|
-
pushed: provisionResult.pushed,
|
|
3318
3327
|
createdAt: Date.now(),
|
|
3319
3328
|
agentCli: input.agentCli ?? "claude",
|
|
3320
3329
|
model: input.model || void 0,
|
|
@@ -3323,7 +3332,6 @@ async function createTaskFromInput(input) {
|
|
|
3323
3332
|
};
|
|
3324
3333
|
try {
|
|
3325
3334
|
const board = store.get("board");
|
|
3326
|
-
const col = input.status ?? "backlog";
|
|
3327
3335
|
board[col] = [task, ...board[col]];
|
|
3328
3336
|
store.set("board", board);
|
|
3329
3337
|
recordRecentProject(store, projectPath);
|
|
@@ -3335,6 +3343,38 @@ async function createTaskFromInput(input) {
|
|
|
3335
3343
|
}
|
|
3336
3344
|
return { task, storePath: store.path };
|
|
3337
3345
|
}
|
|
3346
|
+
async function provisionTaskWorktree(task) {
|
|
3347
|
+
if (!task.projectPath || !task.workspacePath) {
|
|
3348
|
+
throw Object.assign(new Error("\u4EFB\u52D9\u6C92\u6709\u5C08\u6848\u8CC7\u6599\u593E\uFF0C\u7121\u6CD5\u5EFA\u7ACB worktree"), {
|
|
3349
|
+
code: "WORKTREE_CREATE_FAILED"
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
await fs9.mkdir(task.workspacePath, { recursive: true });
|
|
3353
|
+
if (task.branchExplicit) {
|
|
3354
|
+
await assertBranchAvailable(task.projectPath, task.workspacePath, task.branch);
|
|
3355
|
+
}
|
|
3356
|
+
try {
|
|
3357
|
+
const result = await provisionWorktree(
|
|
3358
|
+
task.projectPath,
|
|
3359
|
+
task.workspacePath,
|
|
3360
|
+
task.id,
|
|
3361
|
+
task.baseBranch ?? null,
|
|
3362
|
+
task.branch,
|
|
3363
|
+
{ explicitBranch: task.branchExplicit === true }
|
|
3364
|
+
);
|
|
3365
|
+
return {
|
|
3366
|
+
branch: result.branch,
|
|
3367
|
+
worktreePath: result.worktreePath,
|
|
3368
|
+
baseBranch: result.baseBranch,
|
|
3369
|
+
pushed: result.pushed
|
|
3370
|
+
};
|
|
3371
|
+
} catch (err) {
|
|
3372
|
+
throw Object.assign(
|
|
3373
|
+
new Error(`Worktree \u5EFA\u7ACB\u5931\u6557\uFF1A${err.message}`),
|
|
3374
|
+
{ code: "WORKTREE_CREATE_FAILED" }
|
|
3375
|
+
);
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3338
3378
|
function updateTaskFromInput(input) {
|
|
3339
3379
|
const store = input.storePath ? getStoreAtPath(input.storePath) : getStore();
|
|
3340
3380
|
const board = store.get("board");
|
|
@@ -3521,7 +3561,7 @@ async function taskCommand(argv) {
|
|
|
3521
3561
|
title: task2.title,
|
|
3522
3562
|
description: task2.description,
|
|
3523
3563
|
branch: task2.branch,
|
|
3524
|
-
worktreePath: task2.worktreePath
|
|
3564
|
+
worktreePath: task2.worktreePath ?? null
|
|
3525
3565
|
}
|
|
3526
3566
|
};
|
|
3527
3567
|
}
|
|
@@ -3563,7 +3603,7 @@ async function taskCommand(argv) {
|
|
|
3563
3603
|
title: task.title,
|
|
3564
3604
|
projectPath: task.projectPath,
|
|
3565
3605
|
branch: task.branch,
|
|
3566
|
-
worktreePath: task.worktreePath,
|
|
3606
|
+
worktreePath: task.worktreePath ?? null,
|
|
3567
3607
|
baseBranch: task.baseBranch,
|
|
3568
3608
|
agentCli: task.agentCli,
|
|
3569
3609
|
model: task.model,
|
|
@@ -3574,8 +3614,8 @@ async function taskCommand(argv) {
|
|
|
3574
3614
|
}
|
|
3575
3615
|
|
|
3576
3616
|
// packages/cli/src/host.ts
|
|
3577
|
-
import
|
|
3578
|
-
import
|
|
3617
|
+
import fs18 from "fs";
|
|
3618
|
+
import path22 from "path";
|
|
3579
3619
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
3580
3620
|
|
|
3581
3621
|
// packages/core/src/events.ts
|
|
@@ -4113,9 +4153,9 @@ async function syncBuiltinsAtStartup() {
|
|
|
4113
4153
|
}
|
|
4114
4154
|
|
|
4115
4155
|
// packages/core/src/service.ts
|
|
4116
|
-
import
|
|
4156
|
+
import fs16 from "fs";
|
|
4117
4157
|
import os5 from "os";
|
|
4118
|
-
import
|
|
4158
|
+
import path20 from "path";
|
|
4119
4159
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4120
4160
|
|
|
4121
4161
|
// packages/core/src/agent-models.ts
|
|
@@ -4364,34 +4404,81 @@ async function boardCliLaunchInfo() {
|
|
|
4364
4404
|
import fs14 from "fs";
|
|
4365
4405
|
import path18 from "path";
|
|
4366
4406
|
var DECISIONS_FILE_SUFFIX = ".DECISIONS.md";
|
|
4367
|
-
var MAX_DECISION_BYTES = 256 * 1024;
|
|
4368
4407
|
function decisionsKey(worktreePath, branch) {
|
|
4369
4408
|
return worktreePath ? path18.basename(worktreePath) : branch.replace(/\//g, "-");
|
|
4370
4409
|
}
|
|
4371
4410
|
function decisionsPath(workspacePath, key) {
|
|
4372
4411
|
return path18.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
|
|
4373
4412
|
}
|
|
4374
|
-
function
|
|
4375
|
-
const target = decisionsPath(workspacePath, key);
|
|
4413
|
+
function deleteDecisions(workspacePath, key) {
|
|
4376
4414
|
try {
|
|
4377
|
-
|
|
4378
|
-
if (!stat.isFile()) return { path: target, markdown: null };
|
|
4379
|
-
const buf = fs14.readFileSync(target);
|
|
4380
|
-
return {
|
|
4381
|
-
path: target,
|
|
4382
|
-
markdown: buf.subarray(0, MAX_DECISION_BYTES).toString("utf8"),
|
|
4383
|
-
updatedAt: stat.mtimeMs,
|
|
4384
|
-
...buf.byteLength > MAX_DECISION_BYTES ? { truncated: true } : {}
|
|
4385
|
-
};
|
|
4415
|
+
fs14.rmSync(decisionsPath(workspacePath, key), { force: true });
|
|
4386
4416
|
} catch {
|
|
4387
|
-
return { path: target, markdown: null };
|
|
4388
4417
|
}
|
|
4389
4418
|
}
|
|
4390
|
-
|
|
4419
|
+
|
|
4420
|
+
// packages/core/src/specs.ts
|
|
4421
|
+
import fs15 from "fs";
|
|
4422
|
+
import path19 from "path";
|
|
4423
|
+
var SPEC_FILE_NAME = "spec.md";
|
|
4424
|
+
var MAX_SPEC_BYTES = 256 * 1024;
|
|
4425
|
+
function isSpecPath(p) {
|
|
4426
|
+
return path19.posix.basename(p) === SPEC_FILE_NAME;
|
|
4427
|
+
}
|
|
4428
|
+
function clip2(buf) {
|
|
4429
|
+
return {
|
|
4430
|
+
markdown: buf.subarray(0, MAX_SPEC_BYTES).toString("utf8"),
|
|
4431
|
+
...buf.byteLength > MAX_SPEC_BYTES ? { truncated: true } : {}
|
|
4432
|
+
};
|
|
4433
|
+
}
|
|
4434
|
+
async function readWorktreeSpecs(worktreePath, baseBranch) {
|
|
4435
|
+
const baseRef = await resolveBaseRef(worktreePath, baseBranch);
|
|
4436
|
+
const entries = await collectDiffEntries(worktreePath, baseRef);
|
|
4437
|
+
const specs = [];
|
|
4438
|
+
for (const entry of entries) {
|
|
4439
|
+
if (entry.status === "D" || !isSpecPath(entry.path)) continue;
|
|
4440
|
+
try {
|
|
4441
|
+
const full = path19.join(worktreePath, entry.path);
|
|
4442
|
+
const stat = fs15.statSync(full);
|
|
4443
|
+
if (!stat.isFile()) continue;
|
|
4444
|
+
specs.push({ path: entry.path, ...clip2(fs15.readFileSync(full)), updatedAt: stat.mtimeMs });
|
|
4445
|
+
} catch {
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
return specs.sort((a, b) => a.path.localeCompare(b.path));
|
|
4449
|
+
}
|
|
4450
|
+
async function readBranchSpecs(projectPath, branch, baseBranch, changedPaths) {
|
|
4451
|
+
const ref = `refs/heads/${branch}`;
|
|
4391
4452
|
try {
|
|
4392
|
-
|
|
4453
|
+
await git(projectPath, ["rev-parse", "--verify", ref]);
|
|
4393
4454
|
} catch {
|
|
4455
|
+
return [];
|
|
4456
|
+
}
|
|
4457
|
+
let paths = changedPaths;
|
|
4458
|
+
if (!paths) {
|
|
4459
|
+
const baseRef = await resolveBaseRef(projectPath, baseBranch);
|
|
4460
|
+
const names = await git(projectPath, [
|
|
4461
|
+
"diff",
|
|
4462
|
+
"--name-only",
|
|
4463
|
+
"--diff-filter=d",
|
|
4464
|
+
`${baseRef}...${ref}`
|
|
4465
|
+
]).catch(() => "");
|
|
4466
|
+
paths = names.split("\n").map((l) => l.trim());
|
|
4467
|
+
}
|
|
4468
|
+
const specs = [];
|
|
4469
|
+
for (const p of paths.filter(isSpecPath)) {
|
|
4470
|
+
try {
|
|
4471
|
+
const content = await git(projectPath, ["show", `${ref}:${p}`]);
|
|
4472
|
+
const ct = await git(projectPath, ["log", "-1", "--format=%ct", ref, "--", p]);
|
|
4473
|
+
specs.push({
|
|
4474
|
+
path: p,
|
|
4475
|
+
...clip2(Buffer.from(content, "utf8")),
|
|
4476
|
+
...ct ? { updatedAt: Number(ct) * 1e3 } : {}
|
|
4477
|
+
});
|
|
4478
|
+
} catch {
|
|
4479
|
+
}
|
|
4394
4480
|
}
|
|
4481
|
+
return specs.sort((a, b) => a.path.localeCompare(b.path));
|
|
4395
4482
|
}
|
|
4396
4483
|
|
|
4397
4484
|
// packages/core/src/chat-session.ts
|
|
@@ -4705,22 +4792,6 @@ function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR2) {
|
|
|
4705
4792
|
"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"
|
|
4706
4793
|
].join("\n");
|
|
4707
4794
|
}
|
|
4708
|
-
var DECISIONS_FILE_SUFFIX2 = ".DECISIONS.md";
|
|
4709
|
-
function taskDecisionsPath(worktreePath, workspacePath) {
|
|
4710
|
-
if (!worktreePath || !workspacePath) return null;
|
|
4711
|
-
return `${toShellPath(workspacePath)}/${pathBasename(worktreePath)}${DECISIONS_FILE_SUFFIX2}`;
|
|
4712
|
-
}
|
|
4713
|
-
function buildDecisionPrompt(decisionsPath2) {
|
|
4714
|
-
return [
|
|
4715
|
-
"\u6C7A\u7B56\u66F8\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
|
|
4716
|
-
`1. \u672C\u4EFB\u52D9\u7684\u6C7A\u7B56\u66F8\u662F ${decisionsPath2}\uFF1B\u505A\u51FA\u65B0\u6C7A\u7B56\u3001\u6216\u8ABF\u6574\u65E2\u6709\u6C7A\u7B56\u6642\uFF0C\u7576\u4E0B\u5C31\u66F4\u65B0\u5B83\uFF08\u4E0D\u5B58\u5728\u8ACB\u5EFA\u7ACB\uFF09\u3002`,
|
|
4717
|
-
"2. \u53EA\u5BEB\u300C\u9019\u6B21\u65B0\u589E\u4E86\u4EC0\u9EBC\u6C7A\u7B56\u3001\u8ABF\u6574\u4E86\u4EC0\u9EBC\u65E2\u6709\u6C7A\u7B56\u300D\u8207\u7406\u7531\u3002\u9700\u6C42\u8986\u8FF0\u3001\u9032\u5EA6\u56DE\u5831\u3001\u5BE6\u4F5C\u6D41\u6C34\u5E33\u3001\u672C\u4F86\u5C31\u6210\u7ACB\u7684\u6163\u4F8B\u90FD\u4E0D\u8981\u5BEB\u3002",
|
|
4718
|
-
"3. \u6BCF\u5247\u6C7A\u7B56\u4E00\u500B `## <\u4E00\u53E5\u8A71\u8B1B\u5B8C\u7684\u6C7A\u7B56>` \u6BB5\u843D\uFF0C\u5E95\u4E0B 2-4 \u884C\u4EA4\u4EE3\uFF1A\u70BA\u4EC0\u9EBC\u9019\u6A23\u9078\u3001\u5426\u6C7A\u4E86\u4EC0\u9EBC\u66FF\u4EE3\u65B9\u6848\u3001\u8AB0\u4E4B\u5F8C\u6703\u88AB\u9019\u500B\u6C7A\u7B56\u7D81\u4F4F\u3002",
|
|
4719
|
-
"4. \u8981\u8AAA\u660E\u6D41\u7A0B\u3001\u72C0\u614B\u8F49\u63DB\u6216\u6A21\u7D44\u95DC\u4FC2\u6642\uFF0C\u7528 mermaid fence \u756B\u5716\uFF0CVibeFlow \u6703\u76F4\u63A5\u6E32\u67D3\u3002",
|
|
4720
|
-
"5. \u9019\u6B21\u6C92\u6709\u4EFB\u4F55\u6C7A\u7B56\u5C31\u4E0D\u8981\u5EFA\u7ACB\u9019\u500B\u6A94\u6848\uFF1B\u5BE7\u53EF\u7559\u767D\uFF0C\u4E0D\u8981\u6E4A\u5167\u5BB9\u3002",
|
|
4721
|
-
"6. \u7528\u7E41\u9AD4\u4E2D\u6587\u66F8\u5BEB\uFF0C\u7A0B\u5F0F\u8B58\u5225\u5B57\u4FDD\u7559\u82F1\u6587\u3002"
|
|
4722
|
-
].join("\n");
|
|
4723
|
-
}
|
|
4724
4795
|
var AUTO_PERMISSION_MODE = "auto";
|
|
4725
4796
|
function claudePermissionFlag(autoMode) {
|
|
4726
4797
|
return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
|
|
@@ -4849,11 +4920,9 @@ function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
|
|
|
4849
4920
|
const model = taskModel(task);
|
|
4850
4921
|
const artifactsDir = agentArtifactsDir(task.worktreePath, workspacePath) ?? ARTIFACTS_FALLBACK_DIR2;
|
|
4851
4922
|
const builtInPrompt = buildArtifactPrompt(artifactsDir);
|
|
4852
|
-
const decisionsPath2 = taskDecisionsPath(task.worktreePath, workspacePath);
|
|
4853
|
-
const decisionPrompt = decisionsPath2 ? buildDecisionPrompt(decisionsPath2) : "";
|
|
4854
4923
|
const libraryPrompt = opts?.library?.promptText?.trim();
|
|
4855
4924
|
const customPrompt = resolveSystemPrompt(systemPrompt);
|
|
4856
|
-
const sys = [builtInPrompt,
|
|
4925
|
+
const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
|
|
4857
4926
|
const includeTaskPrompt = opts?.includeTaskPrompt !== false;
|
|
4858
4927
|
const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
|
|
4859
4928
|
const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
|
|
@@ -4962,15 +5031,54 @@ function createCore({ sessions, bus, version }) {
|
|
|
4962
5031
|
}
|
|
4963
5032
|
function claudeSessionFile(cwd, sessionId) {
|
|
4964
5033
|
const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
4965
|
-
return
|
|
5034
|
+
return path20.join(os5.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
|
|
4966
5035
|
}
|
|
4967
5036
|
async function removeTaskWorktree(task) {
|
|
4968
5037
|
if (!task.projectPath || !task.worktreePath) return;
|
|
4969
5038
|
const branch = task.branch || fallbackBranchName(task.id);
|
|
4970
5039
|
await removeWorktree(task.projectPath, task.worktreePath);
|
|
4971
|
-
deleteArtifacts(task.workspacePath ??
|
|
5040
|
+
deleteArtifacts(task.workspacePath ?? path20.dirname(task.worktreePath), task.worktreePath);
|
|
4972
5041
|
await deleteBranch(task.projectPath, branch);
|
|
4973
5042
|
}
|
|
5043
|
+
function columnOf(taskId) {
|
|
5044
|
+
const board = getState().board;
|
|
5045
|
+
return COLUMNS.find((col) => board[col].some((t) => t.id === taskId)) ?? null;
|
|
5046
|
+
}
|
|
5047
|
+
const provisioning = /* @__PURE__ */ new Map();
|
|
5048
|
+
function ensureProvisioned(task) {
|
|
5049
|
+
if (task.worktreePath) return Promise.resolve(task);
|
|
5050
|
+
const pending = provisioning.get(task.id);
|
|
5051
|
+
if (pending) return pending;
|
|
5052
|
+
const run2 = (async () => {
|
|
5053
|
+
try {
|
|
5054
|
+
updateTask(task.id, { ...await provisionTaskWorktree(task), launchError: void 0 });
|
|
5055
|
+
bus.emit("state:changed", getState());
|
|
5056
|
+
} catch (err) {
|
|
5057
|
+
returnUnstarted(task.id, err.message);
|
|
5058
|
+
throw err;
|
|
5059
|
+
}
|
|
5060
|
+
const provisioned = findTask(task.id);
|
|
5061
|
+
if (!provisioned) throw new Error("\u627E\u4E0D\u5230\u8981\u57F7\u884C\u7684\u4EFB\u52D9");
|
|
5062
|
+
return provisioned;
|
|
5063
|
+
})().finally(() => provisioning.delete(task.id));
|
|
5064
|
+
provisioning.set(task.id, run2);
|
|
5065
|
+
return run2;
|
|
5066
|
+
}
|
|
5067
|
+
function returnUnstarted(taskId, launchError) {
|
|
5068
|
+
const current = findTask(taskId);
|
|
5069
|
+
if (!current) return;
|
|
5070
|
+
const board = getState().board;
|
|
5071
|
+
const next = {
|
|
5072
|
+
backlog: [
|
|
5073
|
+
{ ...current, launchedAt: void 0, launchError },
|
|
5074
|
+
...board.backlog.filter((t) => t.id !== taskId)
|
|
5075
|
+
],
|
|
5076
|
+
in_progress: board.in_progress.filter((t) => t.id !== taskId),
|
|
5077
|
+
done: board.done.filter((t) => t.id !== taskId)
|
|
5078
|
+
};
|
|
5079
|
+
setBoard(next);
|
|
5080
|
+
bus.emit("state:changed", getState());
|
|
5081
|
+
}
|
|
4974
5082
|
const handlers = {
|
|
4975
5083
|
"vibeflow:getState": () => getState(),
|
|
4976
5084
|
"app:getVersion": () => version,
|
|
@@ -5007,8 +5115,8 @@ function createCore({ sessions, bus, version }) {
|
|
|
5007
5115
|
"projects:listRecent": () => listRecentProjects(getStore()),
|
|
5008
5116
|
// `vibeflow open <path>` while a host runs: only the host writes the store.
|
|
5009
5117
|
"projects:record": (projectPath) => {
|
|
5010
|
-
const p =
|
|
5011
|
-
if (!
|
|
5118
|
+
const p = path20.resolve(str(projectPath, "projectPath"));
|
|
5119
|
+
if (!fs16.existsSync(p) || !fs16.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
|
|
5012
5120
|
recordRecentProject(getStore(), p);
|
|
5013
5121
|
return listRecentProjects(getStore());
|
|
5014
5122
|
},
|
|
@@ -5040,15 +5148,21 @@ function createCore({ sessions, bus, version }) {
|
|
|
5040
5148
|
},
|
|
5041
5149
|
// Discard everything the last run left behind — the PTY session, the
|
|
5042
5150
|
// sub-agent timeline, the conversation id, the code it changed, its artifacts
|
|
5043
|
-
// and its decision record — so the next launch starts over as if the card
|
|
5151
|
+
// and its legacy decision record — so the next launch starts over as if the card
|
|
5044
5152
|
// were new. Launching again is the caller's decision.
|
|
5045
5153
|
"vibeflow:resetTaskRun": async (taskId) => {
|
|
5046
5154
|
const task = requireTask(taskId);
|
|
5047
|
-
if (!task.worktreePath)
|
|
5155
|
+
if (!task.worktreePath) {
|
|
5156
|
+
await teardownTask(task.id);
|
|
5157
|
+
updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID4() });
|
|
5158
|
+
const reset2 = findTask(task.id);
|
|
5159
|
+
if (!reset2) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
|
|
5160
|
+
return { state: getState(), task: reset2 };
|
|
5161
|
+
}
|
|
5048
5162
|
if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
|
|
5049
5163
|
await teardownTask(task.id);
|
|
5050
5164
|
await resetWorktreeToBase(task.worktreePath, task.baseBranch);
|
|
5051
|
-
const workspacePath = task.workspacePath ??
|
|
5165
|
+
const workspacePath = task.workspacePath ?? path20.dirname(task.worktreePath);
|
|
5052
5166
|
deleteArtifacts(workspacePath, task.worktreePath);
|
|
5053
5167
|
deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
|
|
5054
5168
|
resetSubAgents(task.worktreePath);
|
|
@@ -5058,21 +5172,25 @@ function createCore({ sessions, bus, version }) {
|
|
|
5058
5172
|
bus.emit("subagents:update", { taskId: task.id, subAgents: [] });
|
|
5059
5173
|
return { state: getState(), task: reset };
|
|
5060
5174
|
},
|
|
5061
|
-
// Edit an existing card's fields.
|
|
5062
|
-
//
|
|
5063
|
-
//
|
|
5064
|
-
//
|
|
5175
|
+
// Edit an existing card's fields. Only a Backlog card is editable: once it
|
|
5176
|
+
// runs, what it was asked to do and where it does it are history. The
|
|
5177
|
+
// git-bound fields (project, base, branch) are just stored — the worktree is
|
|
5178
|
+
// built when the card starts. A Backlog card that already has one (made
|
|
5179
|
+
// before provisioning moved to launch, or returned from a run) gives it up,
|
|
5180
|
+
// but only while it holds no changes.
|
|
5065
5181
|
"vibeflow:updateTask": async (payload) => {
|
|
5066
5182
|
const p = obj(payload, "payload");
|
|
5067
5183
|
const existing = requireTask(p.taskId);
|
|
5184
|
+
if (columnOf(existing.id) !== "backlog") {
|
|
5185
|
+
throw new Error("\u53EA\u6709 Backlog \u7684\u4EFB\u52D9\u53EF\u4EE5\u7DE8\u8F2F");
|
|
5186
|
+
}
|
|
5068
5187
|
let gitPatch = {};
|
|
5069
5188
|
const nextProject = p.projectPath || existing.projectPath;
|
|
5189
|
+
const nextBranch = p.branch === void 0 ? "" : str(p.branch, "branch").trim();
|
|
5070
5190
|
const projectChanged2 = Boolean(p.projectPath && p.projectPath !== existing.projectPath);
|
|
5071
5191
|
const baseChanged = p.baseBranch != null && p.baseBranch !== (existing.baseBranch ?? null);
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
throw new Error("\u4EFB\u52D9\u5DF2\u958B\u59CB\u57F7\u884C\uFF0C\u7121\u6CD5\u66F4\u63DB\u5C08\u6848\u8CC7\u6599\u593E\u6216\u57FA\u6E96\u5206\u652F");
|
|
5075
|
-
}
|
|
5192
|
+
const branchChanged = Boolean(nextBranch && nextBranch !== existing.branch);
|
|
5193
|
+
if (nextProject && (projectChanged2 || baseChanged || branchChanged)) {
|
|
5076
5194
|
if (existing.worktreePath) {
|
|
5077
5195
|
const diff = await getWorktreeDiffEntries(
|
|
5078
5196
|
existing.worktreePath,
|
|
@@ -5080,35 +5198,30 @@ function createCore({ sessions, bus, version }) {
|
|
|
5080
5198
|
{ fetch: false }
|
|
5081
5199
|
);
|
|
5082
5200
|
if (diff.length > 0) {
|
|
5083
|
-
throw new Error("\u76EE\u524D worktree \u5DF2\u6709\u8B8A\u66F4\uFF0C\u7121\u6CD5\u66F4\u63DB\u5C08\u6848\u8CC7\u6599\u593E\
|
|
5201
|
+
throw new Error("\u76EE\u524D worktree \u5DF2\u6709\u8B8A\u66F4\uFF0C\u7121\u6CD5\u66F4\u63DB\u5C08\u6848\u8CC7\u6599\u593E\u3001\u57FA\u6E96\u5206\u652F\u6216\u5206\u652F\u540D\u7A31");
|
|
5084
5202
|
}
|
|
5085
5203
|
}
|
|
5086
5204
|
const info = await getGitInfo(nextProject);
|
|
5087
5205
|
if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
|
|
5206
|
+
const projectName = path20.basename(nextProject);
|
|
5207
|
+
const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
|
|
5088
5208
|
await teardownTask(existing.id);
|
|
5089
5209
|
await removeTaskWorktree(existing);
|
|
5090
|
-
|
|
5091
|
-
const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
|
|
5092
|
-
await fs15.promises.mkdir(workspacePath, { recursive: true });
|
|
5093
|
-
const result = await provisionWorktree(
|
|
5094
|
-
nextProject,
|
|
5095
|
-
workspacePath,
|
|
5096
|
-
existing.id,
|
|
5097
|
-
p.baseBranch ?? existing.baseBranch ?? null,
|
|
5098
|
-
existing.branch
|
|
5099
|
-
);
|
|
5210
|
+
if (branchChanged) await assertBranchAvailable(nextProject, workspacePath, nextBranch);
|
|
5100
5211
|
recordRecentProject(getStore(), nextProject);
|
|
5101
5212
|
gitPatch = {
|
|
5102
5213
|
projectPath: nextProject,
|
|
5103
5214
|
projectName,
|
|
5104
|
-
branch: result.branch,
|
|
5105
|
-
worktreePath: result.worktreePath,
|
|
5106
5215
|
workspacePath,
|
|
5107
|
-
baseBranch:
|
|
5108
|
-
|
|
5216
|
+
baseBranch: (p.baseBranch ?? (projectChanged2 ? null : existing.baseBranch)) || info.defaultBase || info.currentBranch || void 0,
|
|
5217
|
+
...branchChanged ? { branch: nextBranch, branchExplicit: true } : {},
|
|
5218
|
+
worktreePath: void 0,
|
|
5219
|
+
pushed: void 0,
|
|
5220
|
+
launchedAt: void 0
|
|
5109
5221
|
};
|
|
5110
5222
|
}
|
|
5111
5223
|
updateTask(existing.id, {
|
|
5224
|
+
launchError: void 0,
|
|
5112
5225
|
title: (p.title ?? "").trim() || `Task ${existing.id}`,
|
|
5113
5226
|
description: p.description?.trim() || void 0,
|
|
5114
5227
|
agentCli: p.agentCli,
|
|
@@ -5124,10 +5237,12 @@ function createCore({ sessions, bus, version }) {
|
|
|
5124
5237
|
// string or a cwd, so a connected client cannot run anything else.
|
|
5125
5238
|
"pty:start": async (payload) => {
|
|
5126
5239
|
const p = obj(payload, "payload");
|
|
5127
|
-
const
|
|
5128
|
-
const key = sessionKeyFor(
|
|
5129
|
-
const
|
|
5130
|
-
|
|
5240
|
+
const requested = requireTask(p.taskId);
|
|
5241
|
+
const key = sessionKeyFor(requested.id, p.sessionKey);
|
|
5242
|
+
const pending = provisioning.get(requested.id);
|
|
5243
|
+
const task = p.launch ? await ensureProvisioned(requested) : pending ? await pending : requested;
|
|
5244
|
+
const cwd = task.worktreePath;
|
|
5245
|
+
if (!cwd) throw new Error("\u4EFB\u52D9\u5C1A\u672A\u958B\u59CB\u57F7\u884C\uFF0C\u9084\u6C92\u6709 worktree");
|
|
5131
5246
|
let command;
|
|
5132
5247
|
let fresh = p.fresh === true;
|
|
5133
5248
|
if (p.launch) {
|
|
@@ -5177,7 +5292,7 @@ function createCore({ sessions, bus, version }) {
|
|
|
5177
5292
|
"claude:sessionExists": (taskId) => {
|
|
5178
5293
|
const task = requireTask(taskId);
|
|
5179
5294
|
if (!task.worktreePath) return false;
|
|
5180
|
-
return
|
|
5295
|
+
return fs16.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
|
|
5181
5296
|
},
|
|
5182
5297
|
"sessions:list": async () => {
|
|
5183
5298
|
const keys = await sessions.list();
|
|
@@ -5224,13 +5339,16 @@ function createCore({ sessions, bus, version }) {
|
|
|
5224
5339
|
const task = requireTask(taskId);
|
|
5225
5340
|
if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
|
|
5226
5341
|
const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
|
|
5227
|
-
if (!
|
|
5342
|
+
if (!fs16.existsSync(dir)) return "artifacts directory does not exist yet";
|
|
5228
5343
|
return getPlatform().openPath(dir);
|
|
5229
5344
|
},
|
|
5230
|
-
"task:
|
|
5345
|
+
"task:getSpecs": async (taskId) => {
|
|
5231
5346
|
const task = requireTask(taskId);
|
|
5232
|
-
|
|
5233
|
-
|
|
5347
|
+
const base = task.baseBranch ?? "main";
|
|
5348
|
+
if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base);
|
|
5349
|
+
if (!task.projectPath || !task.launchedAt) return [];
|
|
5350
|
+
const changed = task.outcome?.files.filter((f) => f.status !== "D").map((f) => f.path);
|
|
5351
|
+
return readBranchSpecs(task.projectPath, task.branch, base, changed);
|
|
5234
5352
|
},
|
|
5235
5353
|
"board:getCliLaunchInfo": () => boardCliLaunchInfo(),
|
|
5236
5354
|
"library:list": async () => listLibrary(await libraryRoot(), builtinSkillsDir()),
|
|
@@ -5333,6 +5451,9 @@ function createCore({ sessions, bus, version }) {
|
|
|
5333
5451
|
deleteDecisions(task.workspacePath, decisionsKey(task.worktreePath, task.branch));
|
|
5334
5452
|
}
|
|
5335
5453
|
await removeTaskWorktree(task);
|
|
5454
|
+
if (task.workspacePath) {
|
|
5455
|
+
fs16.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
|
|
5456
|
+
}
|
|
5336
5457
|
clearConversation(task.id);
|
|
5337
5458
|
removeTask(task.id);
|
|
5338
5459
|
return getState();
|
|
@@ -5340,9 +5461,11 @@ function createCore({ sessions, bus, version }) {
|
|
|
5340
5461
|
"attachments:write": (payload) => {
|
|
5341
5462
|
const p = obj(payload, "payload");
|
|
5342
5463
|
const task = requireTask(p.taskId);
|
|
5343
|
-
if (!task.worktreePath) throw new Error("\u627E\u4E0D\u5230\u4EFB\u52D9 worktree");
|
|
5344
5464
|
if (!Array.isArray(p.attachments)) invalid("attachments must be an array");
|
|
5345
|
-
|
|
5465
|
+
const inputs = p.attachments;
|
|
5466
|
+
if (task.worktreePath) return writeAttachments(task.worktreePath, inputs);
|
|
5467
|
+
if (!task.workspacePath) throw new Error("\u627E\u4E0D\u5230\u4EFB\u52D9 worktree");
|
|
5468
|
+
return writeAttachmentsTo(taskAttachmentStagingPath(task.workspacePath, task.id), inputs);
|
|
5346
5469
|
},
|
|
5347
5470
|
"chat:load": (taskId) => loadConversation(requireTask(taskId).id),
|
|
5348
5471
|
"chat:cancel": (taskId) => {
|
|
@@ -5382,9 +5505,9 @@ function createCore({ sessions, bus, version }) {
|
|
|
5382
5505
|
// Watch the containing directory, not the file: JsonStore renames a temp
|
|
5383
5506
|
// file over the target and so replaces the inode on every save.
|
|
5384
5507
|
watchStore() {
|
|
5385
|
-
const file =
|
|
5508
|
+
const file = path20.basename(getStorePath());
|
|
5386
5509
|
let debounce = null;
|
|
5387
|
-
storeWatcher =
|
|
5510
|
+
storeWatcher = fs16.watch(path20.dirname(getStorePath()), (_event, filename) => {
|
|
5388
5511
|
if (filename !== file) return;
|
|
5389
5512
|
if (debounce) clearTimeout(debounce);
|
|
5390
5513
|
debounce = setTimeout(() => {
|
|
@@ -5416,9 +5539,9 @@ function createSessionBackend(sink, choice) {
|
|
|
5416
5539
|
}
|
|
5417
5540
|
|
|
5418
5541
|
// packages/core/src/web-server.ts
|
|
5419
|
-
import
|
|
5542
|
+
import fs17 from "fs";
|
|
5420
5543
|
import http from "http";
|
|
5421
|
-
import
|
|
5544
|
+
import path21 from "path";
|
|
5422
5545
|
import { timingSafeEqual } from "crypto";
|
|
5423
5546
|
import { WebSocketServer } from "ws";
|
|
5424
5547
|
var TOKEN_COOKIE = "vf_token";
|
|
@@ -5471,14 +5594,14 @@ function resolveStatic(root, urlPath) {
|
|
|
5471
5594
|
return null;
|
|
5472
5595
|
}
|
|
5473
5596
|
if (decoded.includes("\0")) return null;
|
|
5474
|
-
const rel =
|
|
5475
|
-
const base =
|
|
5476
|
-
const candidates = [rel,
|
|
5597
|
+
const rel = path21.posix.normalize(decoded).replace(/^\/+/, "");
|
|
5598
|
+
const base = path21.resolve(root);
|
|
5599
|
+
const candidates = [rel, path21.posix.join(rel, "index.html"), `${rel}.html`];
|
|
5477
5600
|
for (const c of candidates) {
|
|
5478
|
-
const full =
|
|
5479
|
-
if (full !== base && !full.startsWith(base +
|
|
5601
|
+
const full = path21.resolve(base, c);
|
|
5602
|
+
if (full !== base && !full.startsWith(base + path21.sep)) return null;
|
|
5480
5603
|
try {
|
|
5481
|
-
if (
|
|
5604
|
+
if (fs17.statSync(full).isFile()) return full;
|
|
5482
5605
|
} catch {
|
|
5483
5606
|
}
|
|
5484
5607
|
}
|
|
@@ -5539,7 +5662,7 @@ async function startWebServer(options) {
|
|
|
5539
5662
|
send(res, 404, "Not found");
|
|
5540
5663
|
return;
|
|
5541
5664
|
}
|
|
5542
|
-
const ext =
|
|
5665
|
+
const ext = path21.extname(file).toLowerCase();
|
|
5543
5666
|
const immutable = url.pathname.startsWith("/_next/static/");
|
|
5544
5667
|
res.writeHead(200, {
|
|
5545
5668
|
"Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
|
|
@@ -5552,7 +5675,7 @@ async function startWebServer(options) {
|
|
|
5552
5675
|
res.end();
|
|
5553
5676
|
return;
|
|
5554
5677
|
}
|
|
5555
|
-
|
|
5678
|
+
fs17.createReadStream(file).pipe(res);
|
|
5556
5679
|
}
|
|
5557
5680
|
const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
|
|
5558
5681
|
function reject(socket, status, reason) {
|
|
@@ -5724,7 +5847,7 @@ function loginUrl(lock, pathname = "/home/") {
|
|
|
5724
5847
|
return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
|
|
5725
5848
|
}
|
|
5726
5849
|
function isWebDir(dir) {
|
|
5727
|
-
return
|
|
5850
|
+
return fs18.existsSync(path22.join(dir, "home", "index.html"));
|
|
5728
5851
|
}
|
|
5729
5852
|
|
|
5730
5853
|
// packages/cli/src/remote.ts
|
|
@@ -5841,8 +5964,8 @@ function connectToHost(lock) {
|
|
|
5841
5964
|
}
|
|
5842
5965
|
|
|
5843
5966
|
// packages/cli/src/doctor.ts
|
|
5844
|
-
import
|
|
5845
|
-
import
|
|
5967
|
+
import fs19 from "fs";
|
|
5968
|
+
import path23 from "path";
|
|
5846
5969
|
import { execFile as execFile7 } from "child_process";
|
|
5847
5970
|
import { promisify as promisify5 } from "util";
|
|
5848
5971
|
init_env();
|
|
@@ -5982,10 +6105,10 @@ async function runDoctor(options) {
|
|
|
5982
6105
|
});
|
|
5983
6106
|
}
|
|
5984
6107
|
try {
|
|
5985
|
-
|
|
5986
|
-
const probe =
|
|
5987
|
-
|
|
5988
|
-
|
|
6108
|
+
fs19.mkdirSync(options.userDataDir, { recursive: true });
|
|
6109
|
+
const probe = path23.join(options.userDataDir, `.doctor-${process.pid}`);
|
|
6110
|
+
fs19.writeFileSync(probe, "");
|
|
6111
|
+
fs19.rmSync(probe);
|
|
5989
6112
|
checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
|
|
5990
6113
|
} catch (err) {
|
|
5991
6114
|
checks.push({
|
|
@@ -6039,24 +6162,24 @@ Options:
|
|
|
6039
6162
|
`.trim();
|
|
6040
6163
|
function detectLayout() {
|
|
6041
6164
|
const file = fileURLToPath(import.meta.url);
|
|
6042
|
-
const here =
|
|
6043
|
-
const bundledWeb =
|
|
6044
|
-
if (isWebDir(bundledWeb) ||
|
|
6165
|
+
const here = path26.dirname(file);
|
|
6166
|
+
const bundledWeb = path26.join(here, "web");
|
|
6167
|
+
if (isWebDir(bundledWeb) || path26.basename(here) === "dist") {
|
|
6045
6168
|
return {
|
|
6046
|
-
version: readVersion(
|
|
6169
|
+
version: readVersion(path26.join(here, "..", "package.json")),
|
|
6047
6170
|
webDir: isWebDir(bundledWeb) ? bundledWeb : null,
|
|
6048
6171
|
sourceRoot: null,
|
|
6049
6172
|
cliEntry: file
|
|
6050
6173
|
};
|
|
6051
6174
|
}
|
|
6052
|
-
const root =
|
|
6175
|
+
const root = path26.resolve(here, "..", "..", "..");
|
|
6053
6176
|
const override = process.env.VIBEFLOW_WEB_DIR;
|
|
6054
|
-
const webDir = override && isWebDir(override) ? override : isWebDir(
|
|
6055
|
-
return { version: readVersion(
|
|
6177
|
+
const webDir = override && isWebDir(override) ? override : isWebDir(path26.join(root, "app")) ? path26.join(root, "app") : null;
|
|
6178
|
+
return { version: readVersion(path26.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
|
|
6056
6179
|
}
|
|
6057
6180
|
function readVersion(pkgPath) {
|
|
6058
6181
|
try {
|
|
6059
|
-
return JSON.parse(
|
|
6182
|
+
return JSON.parse(fs21.readFileSync(pkgPath, "utf8")).version;
|
|
6060
6183
|
} catch {
|
|
6061
6184
|
return "0.0.0";
|
|
6062
6185
|
}
|
|
@@ -6262,8 +6385,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
|
|
|
6262
6385
|
console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
|
|
6263
6386
|
return 1;
|
|
6264
6387
|
}
|
|
6265
|
-
const dir =
|
|
6266
|
-
if (!
|
|
6388
|
+
const dir = path26.resolve(target);
|
|
6389
|
+
if (!fs21.existsSync(dir) || !fs21.statSync(dir).isDirectory()) {
|
|
6267
6390
|
console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
|
|
6268
6391
|
return 1;
|
|
6269
6392
|
}
|