@kw-yeh/vibeflow 4.11.30 → 4.11.33
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 +523 -191
- package/dist/web/404/index.html +1 -1
- package/dist/web/404.html +1 -1
- package/dist/web/_next/static/{BTRiVzJ-AA12GlLg1YtO3 → YMl7SPiK7DW5_3Za2vx9B}/_buildManifest.js +1 -1
- package/dist/web/_next/static/chunks/{1068-2abb4920b8ee6ac6.js → 4660-ff69cceca93d46bd.js} +2 -2
- package/dist/web/_next/static/chunks/pages/{_app-ad6be2a298405ba4.js → _app-8718d90c431152c1.js} +1 -1
- package/dist/web/_next/static/chunks/pages/home-feccf4845a143c16.js +13 -0
- package/dist/web/_next/static/css/18a64fb6123882cc.css +2 -0
- package/dist/web/home/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/_next/static/chunks/pages/home-1bb9109f75a2f42d.js +0 -13
- package/dist/web/_next/static/css/517bd79243800caf.css +0 -2
- /package/dist/web/_next/static/{BTRiVzJ-AA12GlLg1YtO3 → YMl7SPiK7DW5_3Za2vx9B}/_ssgManifest.js +0 -0
package/dist/vibeflow.mjs
CHANGED
|
@@ -594,6 +594,38 @@ function buildClaudeSettings(worktreePath) {
|
|
|
594
594
|
function resolveSystemPrompt(custom) {
|
|
595
595
|
return custom && custom.trim() ? custom : "";
|
|
596
596
|
}
|
|
597
|
+
function masterSystemPrompt(opts) {
|
|
598
|
+
const lines = [
|
|
599
|
+
"\u4F60\u662F VibeFlow \u7684 Master agent\uFF0C\u4E0D\u5C6C\u65BC\u4EFB\u4F55 repo\uFF0C\u5DE5\u4F5C\u76EE\u9304\u53EA\u662F\u4F60\u7684\u7B46\u8A18\u5340\u3002",
|
|
600
|
+
"\u4F60\u7684\u5DE5\u4F5C\u662F\u628A\u4F7F\u7528\u8005\u7684\u4EFB\u52D9\u62C6\u89E3\u3001\u6D3E\u7D66 slave \u5361\u7247\u57F7\u884C\u3001\u8FFD\u8E64\u5230\u5B8C\u6210\uFF0C\u800C\u4E0D\u662F\u81EA\u5DF1\u6539\u7A0B\u5F0F\u78BC\u3002",
|
|
601
|
+
""
|
|
602
|
+
];
|
|
603
|
+
if (!opts.canWrite) {
|
|
604
|
+
lines.push(
|
|
605
|
+
"\u9019\u6B21\u555F\u52D5\u7121\u6CD5\u4F7F\u7528 board CLI\uFF08\u627E\u4E0D\u5230 VIBEFLOW_CLI\uFF09\uFF0C\u4F60\u4E0D\u80FD\u5EFA\u7ACB\u6216\u555F\u52D5\u5361\u7247\u3002",
|
|
606
|
+
"\u8ACB\u628A\u62C6\u89E3\u7D50\u679C\u6574\u7406\u6210\u5361\u7247\u6E05\u55AE\uFF08\u6A19\u984C\u3001\u5C08\u6848\u8DEF\u5F91\u3001prompt\u3001\u5148\u5F8C\u9806\u5E8F\uFF09\u4EA4\u7D66\u4F7F\u7528\u8005\u624B\u52D5\u5EFA\u7ACB\u3002"
|
|
607
|
+
);
|
|
608
|
+
return lines.join("\n");
|
|
609
|
+
}
|
|
610
|
+
lines.push(
|
|
611
|
+
'\u5148\u5B9A\u7FA9\u9019\u500B shell \u51FD\u5F0F\uFF0C\u4E4B\u5F8C\u6240\u6709\u6307\u4EE4\u90FD\u7528\u5B83\uFF0C\u4E26\u4E14\u4E00\u5F8B\u5E36\u4E0A --store-path "$VIBEFLOW_STORE_DIR"\uFF1A',
|
|
612
|
+
' VF() { if [ -n "$VIBEFLOW_CLI_LOADER" ]; then node --experimental-strip-types --import "$VIBEFLOW_CLI_LOADER" "$VIBEFLOW_CLI" "$@"; else node "$VIBEFLOW_CLI" "$@"; fi; }',
|
|
613
|
+
"",
|
|
614
|
+
"1. \u5224\u65B7\uFF1A\u4E00\u5F35\u5361\u505A\u5F97\u5B8C\u5C31\u53EA\u5EFA\u4E00\u5F35\uFF1B\u6D89\u53CA\u591A\u500B repo \u6216\u53EF\u5E73\u884C\u7684\u7368\u7ACB\u5DE5\u4F5C\u624D\u62C6\u6210\u591A\u5F35\u3002\u6BCF\u5F35\u53EA\u52D5\u4E00\u500B repo\uFF0C\u5404\u81EA\u80FD\u7368\u7ACB\u9A57\u6536\u3002",
|
|
615
|
+
'2. \u6D3E\u5DE5\uFF1AVF task create --store-path "$VIBEFLOW_STORE_DIR" --master "$VIBEFLOW_TASK_ID" --project <repo \u7D55\u5C0D\u8DEF\u5F91> --title <\u6A19\u984C> --prompt <\u5167\u5BB9>',
|
|
616
|
+
" prompt \u5FC5\u9808\u81EA\u5305\u542B\uFF1A\u80CC\u666F\u3001\u7BC4\u570D\u3001\u9A57\u6536\u689D\u4EF6\u3001\u8207\u5176\u4ED6\u5361\u7684\u4ECB\u9762\u7D04\u5B9A\u2014\u2014slave \u770B\u4E0D\u5230\u4F60\u7684\u5C0D\u8A71\u3002",
|
|
617
|
+
" \u9700\u8981\u6642\u53EF\u52A0 --agent / --model / --effort / --base-branch\u3002"
|
|
618
|
+
);
|
|
619
|
+
lines.push(
|
|
620
|
+
opts.autoMode ? '3. \u555F\u52D5\uFF1A\u6C92\u6709\u524D\u7F6E\u4F9D\u8CF4\u7684\u5361\u76F4\u63A5 VF task start --store-path "$VIBEFLOW_STORE_DIR" --task <id>\uFF1B\u6709\u4F9D\u8CF4\u7684\u5148\u7559\u5728 Backlog\uFF0C\u524D\u7F6E\u5361\u5B8C\u6210\u5F8C\u518D\u555F\u52D5\u3002' : "3. \u555F\u52D5\uFF1A\u4E0D\u8981\u81EA\u5DF1\u555F\u52D5\u5361\u7247\u3002\u5217\u51FA\u4F60\u5EFA\u4E86\u54EA\u4E9B\u5361\u3001\u5EFA\u8B70\u7684\u555F\u52D5\u9806\u5E8F\uFF0C\u7B49\u4F7F\u7528\u8005\u5728 VibeFlow \u6309\u958B\u59CB\u3002"
|
|
621
|
+
);
|
|
622
|
+
lines.push(
|
|
623
|
+
'4. \u76E3\u807D\uFF1AVF task list --store-path "$VIBEFLOW_STORE_DIR" --master "$VIBEFLOW_TASK_ID" \u6703\u56DE\u50B3\u6BCF\u5F35\u5361\u7684\u6B04\u4F4D\u3001\u662F\u5426\u5728\u8DD1\u3001todo \u9032\u5EA6\u3001\u6D3B\u52D5\u72C0\u614B\u8207 PR\u3002',
|
|
624
|
+
" \u5B9A\u671F\u67E5\u770B\uFF1B\u5361\u5728\u7B49\u5F85\u6B0A\u9650\u3001\u6709 launchError\u3001\u6216\u9577\u6642\u9593\u6C92\u6709\u6D3B\u52D5\u6642\uFF0C\u544A\u8A34\u4F7F\u7528\u8005\u4E26\u63D0\u51FA\u8655\u7406\u65B9\u5F0F\u3002",
|
|
625
|
+
"5. \u4F60\u4E0D\u6703\u628A\u5361\u7247\u79FB\u5230 Done\u3001\u4E0D\u6703\u5408\u4F75 PR\u3002\u5168\u90E8 slave \u5B8C\u6210\u5F8C\uFF0C\u6574\u7406\u6BCF\u5F35\u7684\u7D50\u679C\u8207 PR \u9023\u7D50\uFF0C\u4EA4\u7D66\u4F7F\u7528\u8005\u505A\u6700\u5F8C\u78BA\u8A8D\u3002"
|
|
626
|
+
);
|
|
627
|
+
return lines.join("\n");
|
|
628
|
+
}
|
|
597
629
|
function buildPrompt(task) {
|
|
598
630
|
const lines = [`\u4EFB\u52D9\u6A19\u984C\uFF1A${task.title}`];
|
|
599
631
|
const description = task.description?.trim();
|
|
@@ -654,7 +686,8 @@ function taskModel(task) {
|
|
|
654
686
|
function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
|
|
655
687
|
const agent = taskAgent(task);
|
|
656
688
|
const model = taskModel(task);
|
|
657
|
-
const
|
|
689
|
+
const custom = resolveSystemPrompt(systemPrompt);
|
|
690
|
+
const sys = task.role === "master" ? [custom, masterSystemPrompt({ canWrite: Boolean(opts?.boardCli?.cliPath), autoMode: opts?.autoMode === true })].filter(Boolean).join("\n\n") : custom;
|
|
658
691
|
const includeTaskPrompt = opts?.includeTaskPrompt !== false;
|
|
659
692
|
const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
|
|
660
693
|
const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
|
|
@@ -1116,6 +1149,7 @@ function newFormState(defaultAutoMode, agents) {
|
|
|
1116
1149
|
const agentCli = agents && agents.length > 0 && !agents.some((a) => a.id === "claude") ? agents[0].id : "claude";
|
|
1117
1150
|
return {
|
|
1118
1151
|
mode: "new",
|
|
1152
|
+
role: "slave",
|
|
1119
1153
|
title: "",
|
|
1120
1154
|
description: "",
|
|
1121
1155
|
projectPath: null,
|
|
@@ -1135,6 +1169,7 @@ function editFormState(task, defaultAutoMode) {
|
|
|
1135
1169
|
return {
|
|
1136
1170
|
mode: "edit",
|
|
1137
1171
|
taskId: task.id,
|
|
1172
|
+
role: task.role === "master" ? "master" : "slave",
|
|
1138
1173
|
title: task.title,
|
|
1139
1174
|
description: task.description ?? "",
|
|
1140
1175
|
projectPath: task.projectPath ?? null,
|
|
@@ -1161,8 +1196,11 @@ function visibleFields(s, ctx) {
|
|
|
1161
1196
|
const missing = isProjectMissing(ctx.recent, s.projectPath);
|
|
1162
1197
|
const isRepo = s.gitInfo?.isRepo ?? false;
|
|
1163
1198
|
const hasRemote = s.gitInfo?.hasRemote ?? false;
|
|
1199
|
+
if (s.role === "master") {
|
|
1200
|
+
return s.mode === "new" ? ["role", "title", "attachments", "description", "effort", "autoMode", "agent", "model", "submit"] : ["title", "description", "effort", "autoMode", "agent", "model", "submit"];
|
|
1201
|
+
}
|
|
1164
1202
|
if (s.mode === "new") {
|
|
1165
|
-
const fields2 = ["title", "project"];
|
|
1203
|
+
const fields2 = ["role", "title", "project"];
|
|
1166
1204
|
if (s.gitInfo && !missing && !isRepo) fields2.push("gitInit");
|
|
1167
1205
|
if (isRepo && hasRemote) fields2.push("baseBranch");
|
|
1168
1206
|
if (isRepo) fields2.push("branch");
|
|
@@ -1189,6 +1227,7 @@ function fitEffort(ctx, s) {
|
|
|
1189
1227
|
}
|
|
1190
1228
|
function blockReason(s, ctx, loadingGit) {
|
|
1191
1229
|
if (!s.title.trim()) return "\u8ACB\u8F38\u5165\u4EFB\u52D9\u6A19\u984C";
|
|
1230
|
+
if (s.role === "master") return null;
|
|
1192
1231
|
if (loadingGit) return "\u5075\u6E2C Git \u72C0\u614B\u4E2D\u2026";
|
|
1193
1232
|
const missing = isProjectMissing(ctx.recent, s.projectPath);
|
|
1194
1233
|
if (s.mode === "new") {
|
|
@@ -1205,12 +1244,15 @@ function blockReason(s, ctx, loadingGit) {
|
|
|
1205
1244
|
}
|
|
1206
1245
|
function createPayload(s) {
|
|
1207
1246
|
const hasRemote = s.gitInfo?.hasRemote ?? false;
|
|
1247
|
+
const target = s.role === "master" ? { role: "master" } : {
|
|
1248
|
+
projectPath: s.projectPath,
|
|
1249
|
+
baseBranch: hasRemote ? s.baseBranch || null : null,
|
|
1250
|
+
branch: s.branch.trim() || void 0
|
|
1251
|
+
};
|
|
1208
1252
|
return {
|
|
1209
1253
|
title: s.title.trim(),
|
|
1210
1254
|
description: s.description.trim(),
|
|
1211
|
-
|
|
1212
|
-
baseBranch: hasRemote ? s.baseBranch || null : null,
|
|
1213
|
-
branch: s.branch.trim() || void 0,
|
|
1255
|
+
...target,
|
|
1214
1256
|
agentCli: s.agentCli,
|
|
1215
1257
|
model: s.model || void 0,
|
|
1216
1258
|
effort: s.effort,
|
|
@@ -1227,8 +1269,10 @@ function updatePayload(s) {
|
|
|
1227
1269
|
model: s.model || void 0,
|
|
1228
1270
|
effort: s.effort,
|
|
1229
1271
|
autoMode: s.autoMode,
|
|
1230
|
-
...s.
|
|
1231
|
-
|
|
1272
|
+
...s.role === "master" ? {} : {
|
|
1273
|
+
...s.projectPath ? { projectPath: s.projectPath, baseBranch: s.baseBranch || null } : {},
|
|
1274
|
+
branch: s.branch.trim()
|
|
1275
|
+
}
|
|
1232
1276
|
};
|
|
1233
1277
|
}
|
|
1234
1278
|
function editorCommand(env, platform) {
|
|
@@ -1254,7 +1298,7 @@ function cycle(list, current, step) {
|
|
|
1254
1298
|
return list[((i < 0 ? 0 : i + step) % list.length + list.length) % list.length];
|
|
1255
1299
|
}
|
|
1256
1300
|
function sameForm(a, b) {
|
|
1257
|
-
const pick = (s) => [s.title, s.description, s.projectPath, s.baseBranch, s.branch, s.attachments.join("\n"), s.effort, s.autoMode, s.agentCli, s.model];
|
|
1301
|
+
const pick = (s) => [s.role, s.title, s.description, s.projectPath, s.baseBranch, s.branch, s.attachments.join("\n"), s.effort, s.autoMode, s.agentCli, s.model];
|
|
1258
1302
|
return JSON.stringify(pick(a)) === JSON.stringify(pick(b));
|
|
1259
1303
|
}
|
|
1260
1304
|
function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, onResult }) {
|
|
@@ -1352,7 +1396,9 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
1352
1396
|
if (key.downArrow || key.tab) return setFocus((f) => Math.min(fields.length - 1, f + 1));
|
|
1353
1397
|
const step = key.leftArrow ? -1 : key.rightArrow ? 1 : 0;
|
|
1354
1398
|
if (step) {
|
|
1355
|
-
if (field === "
|
|
1399
|
+
if (field === "role") {
|
|
1400
|
+
setS((prev) => ({ ...prev, role: prev.role === "master" ? "slave" : "master" }));
|
|
1401
|
+
} else if (field === "project") {
|
|
1356
1402
|
const paths = ctx.recent.map((p) => p.path);
|
|
1357
1403
|
if (paths.length) setProject(cycle(paths, s.projectPath ?? paths[paths.length - 1], step));
|
|
1358
1404
|
} else if (field === "baseBranch") {
|
|
@@ -1389,6 +1435,8 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
|
|
|
1389
1435
|
const value = (f) => {
|
|
1390
1436
|
if (editing === f) return `${draft}\u258F`;
|
|
1391
1437
|
switch (f) {
|
|
1438
|
+
case "role":
|
|
1439
|
+
return s.role === "master" ? "\u2039 Master\uFF1A\u4E0D\u7D81 repo\uFF0C\u62C6\u89E3\u4E26\u6D3E\u5DE5\u7D66\u5176\u4ED6\u5361\u7247 \u203A" : "\u2039 Slave\uFF1A\u5728\u4E00\u500B Git \u5C08\u6848\u88E1\u57F7\u884C \u203A";
|
|
1392
1440
|
case "title":
|
|
1393
1441
|
return s.title || "\uFF08\u4F8B\u5982\uFF1A\u5BE6\u4F5C\u767B\u5165\u9801\u9762\uFF09";
|
|
1394
1442
|
case "project": {
|
|
@@ -1534,6 +1582,7 @@ var init_task_form = __esm({
|
|
|
1534
1582
|
];
|
|
1535
1583
|
DEFAULT_EFFORT = "auto";
|
|
1536
1584
|
LABELS = {
|
|
1585
|
+
role: "\u89D2\u8272",
|
|
1537
1586
|
title: "\u4EFB\u52D9\u6A19\u984C",
|
|
1538
1587
|
project: "\u5C08\u6848\u8CC7\u6599\u593E",
|
|
1539
1588
|
gitInit: "\u521D\u59CB\u5316 Git",
|
|
@@ -1811,13 +1860,16 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
|
|
|
1811
1860
|
...tasks.length === 0 ? [h2(Text2, { key: "empty", dimColor: true }, "\u2014")] : tasks.map((t, ti) => {
|
|
1812
1861
|
const isSel = active && ti === sel.index;
|
|
1813
1862
|
const mark = running.has(t.id) ? "\u25CF " : " ";
|
|
1863
|
+
const role = t.role === "master" ? "\u25C6 " : t.masterId ? "\u21B3 " : "";
|
|
1864
|
+
const masterTitle = t.masterId ? COLUMNS2.flatMap((c) => board[c]).find((m) => m.id === t.masterId)?.title : void 0;
|
|
1865
|
+
const detail = t.role === "master" ? `Master \xB7 ${COLUMNS2.flatMap((c) => board[c]).filter((x) => x.masterId === t.id).length} \u500B slave` : `${t.projectName ?? ""} \xB7 ${t.branch}${masterTitle ? ` \xB7 \u25C6 ${masterTitle}` : ""}`;
|
|
1814
1866
|
const summary = col === "backlog" ? "" : progressSummary(col === "in_progress" ? progress[t.id] : null, t.usage);
|
|
1815
1867
|
return h2(
|
|
1816
1868
|
Box2,
|
|
1817
1869
|
{ key: t.id, flexDirection: "column" },
|
|
1818
|
-
h2(Text2, { inverse: isSel, color: running.has(t.id) ? "green" : void 0 }, truncate2(`${mark}${t.title}`, colWidth - 4)),
|
|
1870
|
+
h2(Text2, { inverse: isSel, color: running.has(t.id) ? "green" : t.role === "master" ? "magenta" : void 0 }, truncate2(`${mark}${role}${t.title}`, colWidth - 4)),
|
|
1819
1871
|
summary ? h2(Text2, { dimColor: !isSel, color: isSel ? "cyan" : void 0 }, truncate2(` ${summary}`, colWidth - 4)) : null,
|
|
1820
|
-
isSel ? h2(Text2, { dimColor: true }, truncate2(` ${
|
|
1872
|
+
isSel ? h2(Text2, { dimColor: true }, truncate2(` ${detail}`, colWidth - 4)) : null
|
|
1821
1873
|
);
|
|
1822
1874
|
})
|
|
1823
1875
|
);
|
|
@@ -2331,6 +2383,9 @@ function recordRecentProject(store, projectPath) {
|
|
|
2331
2383
|
}
|
|
2332
2384
|
|
|
2333
2385
|
// packages/core/src/store.ts
|
|
2386
|
+
function isMasterTask(task) {
|
|
2387
|
+
return task.role === "master";
|
|
2388
|
+
}
|
|
2334
2389
|
var DEFAULT_NOTIFICATION_SETTINGS = {
|
|
2335
2390
|
enabled: true,
|
|
2336
2391
|
stepCompleted: false,
|
|
@@ -2454,7 +2509,7 @@ function updateTask(taskId, patch) {
|
|
|
2454
2509
|
function removeTask(taskId) {
|
|
2455
2510
|
const board = getStore().get("board");
|
|
2456
2511
|
Object.keys(board).forEach((col) => {
|
|
2457
|
-
board[col] = board[col].filter((t) => t.id !== taskId);
|
|
2512
|
+
board[col] = board[col].filter((t) => t.id !== taskId).map((t) => t.masterId === taskId ? { ...t, masterId: void 0 } : t);
|
|
2458
2513
|
});
|
|
2459
2514
|
getStore().set("board", board);
|
|
2460
2515
|
}
|
|
@@ -2520,6 +2575,10 @@ function createBridge(t) {
|
|
|
2520
2575
|
listAgentModels: (agentId2, refresh = false) => t.invoke("agents:listModels", { agentId: agentId2, refresh }),
|
|
2521
2576
|
createTask: (payload) => t.invoke("vibeflow:createTask", payload),
|
|
2522
2577
|
updateTask: (payload) => t.invoke("vibeflow:updateTask", payload),
|
|
2578
|
+
/** Move a Backlog card to In Progress and start its agent on the host. */
|
|
2579
|
+
startTask: (taskId) => t.invoke("vibeflow:startTask", taskId),
|
|
2580
|
+
/** Cards as a master sees them; filter by master or a single card. */
|
|
2581
|
+
taskSummaries: (filter) => t.invoke("task:summaries", filter),
|
|
2523
2582
|
/** Clear prior execution state and prepare a fresh run of this task. */
|
|
2524
2583
|
resetTaskRun: (taskId) => t.invoke("vibeflow:resetTaskRun", taskId),
|
|
2525
2584
|
removeTask: (taskId) => t.invoke("vibeflow:removeTask", taskId),
|
|
@@ -2602,6 +2661,7 @@ function createBridge(t) {
|
|
|
2602
2661
|
// Interactive terminal bridge (Phase 3).
|
|
2603
2662
|
term: {
|
|
2604
2663
|
readFile: (sessionKey, path33) => t.invoke("terminal:readFile", { sessionKey, path: path33 }),
|
|
2664
|
+
existingFiles: (sessionKey, paths) => t.invoke("terminal:existingFiles", { sessionKey, paths }),
|
|
2605
2665
|
openFile: (sessionKey, path33) => t.invoke("terminal:openFile", { sessionKey, path: path33 }),
|
|
2606
2666
|
/**
|
|
2607
2667
|
* Start a terminal for a task: its agent when `launch` is given, else an
|
|
@@ -3181,6 +3241,7 @@ function linkFromRef(ref, state) {
|
|
|
3181
3241
|
return { kind: ref.kind, number: ref.number, url: ref.url, state };
|
|
3182
3242
|
}
|
|
3183
3243
|
function hasRealBranch(task) {
|
|
3244
|
+
if (task.role === "master") return false;
|
|
3184
3245
|
return Boolean(task.provisionedAt || task.worktreePath || task.outcome);
|
|
3185
3246
|
}
|
|
3186
3247
|
function resolveTaskLinks(tasks, repoOfProject, prIndex) {
|
|
@@ -4352,8 +4413,28 @@ async function detectAgents() {
|
|
|
4352
4413
|
|
|
4353
4414
|
// packages/core/src/tasks.ts
|
|
4354
4415
|
init_attachments();
|
|
4416
|
+
function mastersRoot(storeFile) {
|
|
4417
|
+
return path13.join(path13.dirname(storeFile), "masters");
|
|
4418
|
+
}
|
|
4419
|
+
function taskError(message, code2) {
|
|
4420
|
+
return Object.assign(new Error(message), { code: code2 });
|
|
4421
|
+
}
|
|
4422
|
+
function assertMasterRef(board, masterId, taskId) {
|
|
4423
|
+
const master = Object.values(board).flat().find((t) => t.id === masterId);
|
|
4424
|
+
if (!master) throw taskError(`\u627E\u4E0D\u5230 master \u5361\u7247\uFF1A${masterId}`, "MASTER_NOT_FOUND");
|
|
4425
|
+
if (!isMasterTask(master)) throw taskError(`\u5361\u7247 ${masterId} \u4E0D\u662F master`, "NOT_A_MASTER");
|
|
4426
|
+
if (taskId && masterId === taskId) throw taskError("\u5361\u7247\u4E0D\u80FD\u6307\u5B9A\u81EA\u5DF1\u70BA master", "NOT_A_MASTER");
|
|
4427
|
+
}
|
|
4428
|
+
async function assertDirectory(dir) {
|
|
4429
|
+
const stat = await fs10.stat(dir).catch(() => null);
|
|
4430
|
+
if (!path13.isAbsolute(dir) || !stat?.isDirectory()) {
|
|
4431
|
+
throw taskError(`\u627E\u4E0D\u5230\u8CC7\u6599\u593E\uFF1A${dir}`, "WORK_DIR_NOT_FOUND");
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4355
4434
|
async function createTaskFromInput(input) {
|
|
4356
|
-
|
|
4435
|
+
if (input.role === "master") return createMasterTask(input);
|
|
4436
|
+
const projectPath = input.projectPath;
|
|
4437
|
+
if (!projectPath) throw taskError("\u5C1A\u672A\u9078\u64C7\u5C08\u6848\u8CC7\u6599\u593E", "PROJECT_NOT_FOUND");
|
|
4357
4438
|
try {
|
|
4358
4439
|
await fs10.access(projectPath);
|
|
4359
4440
|
} catch {
|
|
@@ -4369,6 +4450,7 @@ async function createTaskFromInput(input) {
|
|
|
4369
4450
|
const taskId = randomUUID2().slice(0, 8);
|
|
4370
4451
|
const explicitBranch = input.branch?.trim() || null;
|
|
4371
4452
|
const store = input.storePath ? getStoreAtPath(input.storePath) : getStore();
|
|
4453
|
+
if (input.masterId) assertMasterRef(store.get("board"), input.masterId);
|
|
4372
4454
|
const projectName = path13.basename(projectPath);
|
|
4373
4455
|
const workspacePath = projectWorkstationPath(
|
|
4374
4456
|
resolveWorkstationPath(store.get("settings")),
|
|
@@ -4409,6 +4491,7 @@ async function createTaskFromInput(input) {
|
|
|
4409
4491
|
model: input.model || void 0,
|
|
4410
4492
|
effort: input.effort ?? DEFAULT_TASK_EFFORT,
|
|
4411
4493
|
autoMode: input.autoMode,
|
|
4494
|
+
...input.masterId ? { masterId: input.masterId } : {},
|
|
4412
4495
|
...input.github ? { github: input.github } : {},
|
|
4413
4496
|
...input.jira ? { jira: input.jira } : {}
|
|
4414
4497
|
};
|
|
@@ -4425,7 +4508,54 @@ async function createTaskFromInput(input) {
|
|
|
4425
4508
|
}
|
|
4426
4509
|
return { task, storePath: store.path };
|
|
4427
4510
|
}
|
|
4511
|
+
async function createMasterTask(input) {
|
|
4512
|
+
if (input.masterId) throw taskError("master \u5361\u7247\u4E0D\u80FD\u518D\u96B8\u5C6C\u5176\u4ED6 master", "NOT_A_MASTER");
|
|
4513
|
+
const workDir = input.workDir?.trim() || void 0;
|
|
4514
|
+
if (workDir) await assertDirectory(workDir);
|
|
4515
|
+
const store = input.storePath ? getStoreAtPath(input.storePath) : getStore();
|
|
4516
|
+
const taskId = randomUUID2().slice(0, 8);
|
|
4517
|
+
const workspacePath = path13.join(mastersRoot(store.path), taskId);
|
|
4518
|
+
const draft = {
|
|
4519
|
+
id: taskId,
|
|
4520
|
+
title: input.title.trim() || `Master ${taskId}`,
|
|
4521
|
+
branch: "",
|
|
4522
|
+
role: "master",
|
|
4523
|
+
workspacePath,
|
|
4524
|
+
...workDir ? { workDir } : {}
|
|
4525
|
+
};
|
|
4526
|
+
const col = input.status ?? "backlog";
|
|
4527
|
+
const provisioned = col === "backlog" ? null : await provisionTaskWorktree(draft);
|
|
4528
|
+
const attachments = provisioned?.worktreePath ? writeAttachments(provisioned.worktreePath, input.attachments ?? []) : writeAttachmentsTo(taskAttachmentStagingPath(workspacePath, taskId), input.attachments ?? []);
|
|
4529
|
+
const attachmentLines = attachments.map((attachment) => `[\u9644\u4EF6: ${attachment.path}]`);
|
|
4530
|
+
const baseDescription = input.description?.trim();
|
|
4531
|
+
const description = attachmentLines.length ? [baseDescription, attachmentLines.join("\n")].filter(Boolean).join("\n\n") : baseDescription || void 0;
|
|
4532
|
+
const task = {
|
|
4533
|
+
...draft,
|
|
4534
|
+
...provisioned,
|
|
4535
|
+
description,
|
|
4536
|
+
createdAt: Date.now(),
|
|
4537
|
+
agentCli: input.agentCli ?? "claude",
|
|
4538
|
+
model: input.model || void 0,
|
|
4539
|
+
effort: input.effort ?? DEFAULT_TASK_EFFORT,
|
|
4540
|
+
autoMode: input.autoMode
|
|
4541
|
+
};
|
|
4542
|
+
try {
|
|
4543
|
+
const board = store.get("board");
|
|
4544
|
+
board[col] = [task, ...board[col]];
|
|
4545
|
+
store.set("board", board);
|
|
4546
|
+
} catch (err) {
|
|
4547
|
+
throw taskError(`Store \u5BEB\u5165\u5931\u6557\uFF1A${err.message}`, "STORE_WRITE_FAILED");
|
|
4548
|
+
}
|
|
4549
|
+
return { task, storePath: store.path };
|
|
4550
|
+
}
|
|
4428
4551
|
async function provisionTaskWorktree(task) {
|
|
4552
|
+
if (isMasterTask(task)) {
|
|
4553
|
+
if (!task.workspacePath) throw taskError("master \u5361\u7247\u6C92\u6709\u5DE5\u4F5C\u8CC7\u6599\u593E", "WORKTREE_CREATE_FAILED");
|
|
4554
|
+
const worktreePath = task.workDir ?? path13.join(task.workspacePath, "work");
|
|
4555
|
+
await fs10.mkdir(worktreePath, { recursive: true });
|
|
4556
|
+
await fs10.mkdir(task.workspacePath, { recursive: true });
|
|
4557
|
+
return { branch: "", worktreePath, provisionedAt: Date.now() };
|
|
4558
|
+
}
|
|
4429
4559
|
if (!task.projectPath || !task.workspacePath) {
|
|
4430
4560
|
throw Object.assign(new Error("\u4EFB\u52D9\u6C92\u6709\u5C08\u6848\u8CC7\u6599\u593E\uFF0C\u7121\u6CD5\u5EFA\u7ACB worktree"), {
|
|
4431
4561
|
code: "WORKTREE_CREATE_FAILED"
|
|
@@ -4469,12 +4599,17 @@ function updateTaskFromInput(input) {
|
|
|
4469
4599
|
});
|
|
4470
4600
|
}
|
|
4471
4601
|
const current = board[from].find((t) => t.id === input.taskId);
|
|
4602
|
+
if (input.masterId) {
|
|
4603
|
+
if (isMasterTask(current)) throw taskError("master \u5361\u7247\u4E0D\u80FD\u518D\u96B8\u5C6C\u5176\u4ED6 master", "NOT_A_MASTER");
|
|
4604
|
+
assertMasterRef(board, input.masterId, current.id);
|
|
4605
|
+
}
|
|
4472
4606
|
const title = input.title?.trim();
|
|
4473
4607
|
const description = input.description?.trim();
|
|
4474
4608
|
const task = {
|
|
4475
4609
|
...current,
|
|
4476
4610
|
...title ? { title } : {},
|
|
4477
|
-
...input.description !== void 0 ? { description: description || void 0 } : {}
|
|
4611
|
+
...input.description !== void 0 ? { description: description || void 0 } : {},
|
|
4612
|
+
...input.masterId !== void 0 ? { masterId: input.masterId || void 0 } : {}
|
|
4478
4613
|
};
|
|
4479
4614
|
const to = input.status ?? from;
|
|
4480
4615
|
if (to === from) {
|
|
@@ -4496,9 +4631,125 @@ function updateTaskFromInput(input) {
|
|
|
4496
4631
|
|
|
4497
4632
|
// packages/cli/src/task-command.ts
|
|
4498
4633
|
init_attachments();
|
|
4634
|
+
|
|
4635
|
+
// packages/cli/src/remote.ts
|
|
4636
|
+
import WebSocket from "ws";
|
|
4637
|
+
|
|
4638
|
+
// packages/core/src/ws-transport.ts
|
|
4639
|
+
var OPEN = 1;
|
|
4640
|
+
var MAX_BACKOFF_MS = 5e3;
|
|
4641
|
+
function createWsTransport(options) {
|
|
4642
|
+
let socket = null;
|
|
4643
|
+
let nextId = 1;
|
|
4644
|
+
let connectedOnce = false;
|
|
4645
|
+
let closed = false;
|
|
4646
|
+
let backoff = 250;
|
|
4647
|
+
const queue = [];
|
|
4648
|
+
const pending = /* @__PURE__ */ new Map();
|
|
4649
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
4650
|
+
function flush() {
|
|
4651
|
+
while (socket && socket.readyState === OPEN && queue.length > 0) socket.send(queue.shift());
|
|
4652
|
+
}
|
|
4653
|
+
function open() {
|
|
4654
|
+
if (closed) return;
|
|
4655
|
+
const ws = options.connect();
|
|
4656
|
+
socket = ws;
|
|
4657
|
+
ws.onopen = () => {
|
|
4658
|
+
backoff = 250;
|
|
4659
|
+
options.onStatus?.(true);
|
|
4660
|
+
flush();
|
|
4661
|
+
if (connectedOnce) options.onReconnect?.();
|
|
4662
|
+
connectedOnce = true;
|
|
4663
|
+
};
|
|
4664
|
+
ws.onmessage = (ev) => {
|
|
4665
|
+
let msg;
|
|
4666
|
+
try {
|
|
4667
|
+
msg = JSON.parse(String(ev.data));
|
|
4668
|
+
} catch {
|
|
4669
|
+
return;
|
|
4670
|
+
}
|
|
4671
|
+
if (typeof msg.event === "string") {
|
|
4672
|
+
dispatch(msg.event, msg.payload);
|
|
4673
|
+
return;
|
|
4674
|
+
}
|
|
4675
|
+
const entry = typeof msg.id === "number" ? pending.get(msg.id) : void 0;
|
|
4676
|
+
if (!entry) return;
|
|
4677
|
+
pending.delete(msg.id);
|
|
4678
|
+
if (msg.error) entry.reject(Object.assign(new Error(msg.error.message), { code: msg.error.code }));
|
|
4679
|
+
else entry.resolve(msg.result === null ? void 0 : msg.result);
|
|
4680
|
+
};
|
|
4681
|
+
ws.onerror = () => {
|
|
4682
|
+
};
|
|
4683
|
+
ws.onclose = () => {
|
|
4684
|
+
if (socket !== ws) return;
|
|
4685
|
+
socket = null;
|
|
4686
|
+
options.onStatus?.(false);
|
|
4687
|
+
for (const [id, entry] of Array.from(pending)) {
|
|
4688
|
+
pending.delete(id);
|
|
4689
|
+
entry.reject(new Error("\u8207 VibeFlow core \u7684\u9023\u7DDA\u4E2D\u65B7"));
|
|
4690
|
+
}
|
|
4691
|
+
if (closed) return;
|
|
4692
|
+
setTimeout(open, backoff);
|
|
4693
|
+
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
4694
|
+
};
|
|
4695
|
+
}
|
|
4696
|
+
function dispatch(channel, payload) {
|
|
4697
|
+
for (const cb of Array.from(listeners.get(channel) ?? [])) cb(payload);
|
|
4698
|
+
}
|
|
4699
|
+
function post(message) {
|
|
4700
|
+
const data = JSON.stringify(message);
|
|
4701
|
+
if (socket && socket.readyState === OPEN) socket.send(data);
|
|
4702
|
+
else queue.push(data);
|
|
4703
|
+
}
|
|
4704
|
+
open();
|
|
4705
|
+
return {
|
|
4706
|
+
kind: "ws",
|
|
4707
|
+
invoke(method, ...params) {
|
|
4708
|
+
const id = nextId++;
|
|
4709
|
+
return new Promise((resolve, reject) => {
|
|
4710
|
+
pending.set(id, { resolve, reject });
|
|
4711
|
+
post({ id, method, params });
|
|
4712
|
+
});
|
|
4713
|
+
},
|
|
4714
|
+
send(method, ...params) {
|
|
4715
|
+
post({ method, params });
|
|
4716
|
+
},
|
|
4717
|
+
on(channel, callback) {
|
|
4718
|
+
let set = listeners.get(channel);
|
|
4719
|
+
if (!set) listeners.set(channel, set = /* @__PURE__ */ new Set());
|
|
4720
|
+
set.add(callback);
|
|
4721
|
+
return () => {
|
|
4722
|
+
set.delete(callback);
|
|
4723
|
+
};
|
|
4724
|
+
},
|
|
4725
|
+
close() {
|
|
4726
|
+
closed = true;
|
|
4727
|
+
socket?.close();
|
|
4728
|
+
},
|
|
4729
|
+
dispatch
|
|
4730
|
+
};
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4733
|
+
// packages/cli/src/remote.ts
|
|
4734
|
+
function connectToHost(lock) {
|
|
4735
|
+
const url = `ws://127.0.0.1:${lock.port}/ws`;
|
|
4736
|
+
const transport = createWsTransport({
|
|
4737
|
+
connect: () => new WebSocket(url, {
|
|
4738
|
+
headers: { Authorization: `Bearer ${lock.token}`, Host: `127.0.0.1:${lock.port}` }
|
|
4739
|
+
})
|
|
4740
|
+
});
|
|
4741
|
+
return {
|
|
4742
|
+
api: createBridge(transport),
|
|
4743
|
+
invoke: (method, ...params) => transport.invoke(method, ...params),
|
|
4744
|
+
close: () => transport.close()
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
// packages/cli/src/task-command.ts
|
|
4499
4749
|
var AGENT_IDS = AGENT_CLIS.map((agent) => agent.id);
|
|
4500
4750
|
var STATUSES = ["backlog", "in_progress", "done"];
|
|
4501
|
-
var SUBCOMMANDS = ["create", "update", "artifact"];
|
|
4751
|
+
var SUBCOMMANDS = ["create", "update", "start", "list", "show", "artifact"];
|
|
4752
|
+
var ROLES = ["slave", "master"];
|
|
4502
4753
|
var MODES = ["existing", "new"];
|
|
4503
4754
|
var AUTO_MODES = ["on", "off"];
|
|
4504
4755
|
var TASK_USAGE = `
|
|
@@ -4507,10 +4758,18 @@ VibeFlow task commands
|
|
|
4507
4758
|
Usage:
|
|
4508
4759
|
vibeflow task create [options]
|
|
4509
4760
|
vibeflow task update [options]
|
|
4761
|
+
vibeflow task start --task <id> [options]
|
|
4762
|
+
vibeflow task list [--master <id>] [options]
|
|
4763
|
+
vibeflow task show --task <id> [options]
|
|
4510
4764
|
vibeflow task artifact add --file <path> [options]
|
|
4511
4765
|
|
|
4512
4766
|
task create options:
|
|
4513
|
-
--
|
|
4767
|
+
--role <role> ${ROLES.join(" | ")} (default: slave). A master has no project:
|
|
4768
|
+
it runs in its own folder and dispatches other cards.
|
|
4769
|
+
--project <path> Target project directory (required for a slave)
|
|
4770
|
+
--work-dir <path> Master only: run in this existing folder (git or not)
|
|
4771
|
+
instead of one VibeFlow creates
|
|
4772
|
+
--master <id> Dispatch the new card under this master card
|
|
4514
4773
|
--title <text> Task title (required)
|
|
4515
4774
|
--prompt <text> Task prompt / description (required)
|
|
4516
4775
|
--status <column> ${STATUSES.join(" | ")} (default: backlog)
|
|
@@ -4534,11 +4793,19 @@ task update options:
|
|
|
4534
4793
|
--title <text> New card title
|
|
4535
4794
|
--prompt <text> New card description (empty string clears it)
|
|
4536
4795
|
--status <column> ${STATUSES.join(" | ")} \u2014 moves the card between columns
|
|
4796
|
+
--master <id|none> Put the card under a master, or "none" to take it out
|
|
4537
4797
|
--store-path <dir> Explicit store directory
|
|
4538
4798
|
--profile <name> dev | prod \u2014 shorthand for common store paths
|
|
4539
4799
|
|
|
4540
|
-
At least one of --title / --prompt / --status is required. Branch,
|
|
4541
|
-
and project path are provisioned on disk and cannot be changed here.
|
|
4800
|
+
At least one of --title / --prompt / --status / --master is required. Branch,
|
|
4801
|
+
worktree and project path are provisioned on disk and cannot be changed here.
|
|
4802
|
+
|
|
4803
|
+
task start / list / show (need the running VibeFlow host):
|
|
4804
|
+
start --task <id> Move a Backlog card to In Progress and start its agent,
|
|
4805
|
+
the same as dragging it in the app
|
|
4806
|
+
list [--master <id>] Every card, or one master's cards, with column, whether
|
|
4807
|
+
its agent is running, todo progress, activity and PR
|
|
4808
|
+
show --task <id> The same summary for one card
|
|
4542
4809
|
|
|
4543
4810
|
task artifact add options:
|
|
4544
4811
|
--file <path> Copy a file into the card's user-facing Artifacts folder
|
|
@@ -4548,7 +4815,7 @@ task artifact add options:
|
|
|
4548
4815
|
|
|
4549
4816
|
Notes:
|
|
4550
4817
|
A card created with --status in_progress does NOT start running by itself.
|
|
4551
|
-
|
|
4818
|
+
Use "task start" on a Backlog card, or start it from the app.
|
|
4552
4819
|
|
|
4553
4820
|
Examples:
|
|
4554
4821
|
vibeflow task create \\
|
|
@@ -4601,6 +4868,9 @@ async function taskCommand(argv) {
|
|
|
4601
4868
|
args: argv,
|
|
4602
4869
|
options: {
|
|
4603
4870
|
project: { type: "string" },
|
|
4871
|
+
role: { type: "string" },
|
|
4872
|
+
"work-dir": { type: "string" },
|
|
4873
|
+
master: { type: "string" },
|
|
4604
4874
|
title: { type: "string" },
|
|
4605
4875
|
prompt: { type: "string" },
|
|
4606
4876
|
task: { type: "string" },
|
|
@@ -4628,7 +4898,7 @@ async function taskCommand(argv) {
|
|
|
4628
4898
|
const [sub, action] = positionals;
|
|
4629
4899
|
if (values.help || !sub) return null;
|
|
4630
4900
|
const storePath = resolveStorePath(
|
|
4631
|
-
values["store-path"] ?? (sub === "artifact" ? process.env.VIBEFLOW_STORE_DIR : void 0),
|
|
4901
|
+
values["store-path"] ?? (sub === "artifact" || HOST_COMMANDS.includes(sub) ? process.env.VIBEFLOW_STORE_DIR : void 0),
|
|
4632
4902
|
values.profile
|
|
4633
4903
|
);
|
|
4634
4904
|
setPlatform(createNodePlatform({ userDataDir: storePath }));
|
|
@@ -4643,17 +4913,19 @@ async function taskCommand(argv) {
|
|
|
4643
4913
|
if (!task2) fail("TASK_NOT_FOUND", `Task not found: ${taskId}`);
|
|
4644
4914
|
return { taskId, path: addTaskArtifact(task2, values.file) };
|
|
4645
4915
|
}
|
|
4916
|
+
if (HOST_COMMANDS.includes(sub)) return hostCommand(sub, values, storePath);
|
|
4646
4917
|
if (sub === "update") {
|
|
4647
4918
|
if (!values.task) fail("MISSING_ARGUMENT", "Missing required arguments: --task");
|
|
4648
4919
|
requireOneOf("--status", values.status, STATUSES);
|
|
4649
|
-
if (values.title === void 0 && values.prompt === void 0 && values.status === void 0) {
|
|
4650
|
-
fail("MISSING_ARGUMENT", "task update needs at least one of: --title, --prompt, --status");
|
|
4920
|
+
if (values.title === void 0 && values.prompt === void 0 && values.status === void 0 && values.master === void 0) {
|
|
4921
|
+
fail("MISSING_ARGUMENT", "task update needs at least one of: --title, --prompt, --status, --master");
|
|
4651
4922
|
}
|
|
4652
4923
|
const { task: task2, storePath: resolvedPath2 } = updateTaskFromInput({
|
|
4653
4924
|
taskId: values.task,
|
|
4654
4925
|
title: values.title,
|
|
4655
4926
|
description: values.prompt,
|
|
4656
4927
|
status: values.status,
|
|
4928
|
+
masterId: values.master === void 0 ? void 0 : values.master === "none" ? null : values.master,
|
|
4657
4929
|
storePath
|
|
4658
4930
|
});
|
|
4659
4931
|
return {
|
|
@@ -4663,12 +4935,19 @@ async function taskCommand(argv) {
|
|
|
4663
4935
|
title: task2.title,
|
|
4664
4936
|
description: task2.description,
|
|
4665
4937
|
branch: task2.branch,
|
|
4666
|
-
worktreePath: task2.worktreePath ?? null
|
|
4938
|
+
worktreePath: task2.worktreePath ?? null,
|
|
4939
|
+
masterId: task2.masterId ?? null
|
|
4667
4940
|
}
|
|
4668
4941
|
};
|
|
4669
4942
|
}
|
|
4943
|
+
requireOneOf("--role", values.role, ROLES);
|
|
4944
|
+
const master = values.role === "master";
|
|
4945
|
+
if (!master && values["work-dir"] !== void 0) fail("INVALID_ARGUMENT", "--work-dir is only for --role master");
|
|
4946
|
+
if (master && (values.project || values["base-branch"] || values.branch || values.mode)) {
|
|
4947
|
+
fail("INVALID_ARGUMENT", "A master card has no project: drop --project / --base-branch / --branch / --mode");
|
|
4948
|
+
}
|
|
4670
4949
|
const missing = [];
|
|
4671
|
-
if (!values.project) missing.push("--project");
|
|
4950
|
+
if (!master && !values.project) missing.push("--project");
|
|
4672
4951
|
if (!values.title) missing.push("--title");
|
|
4673
4952
|
if (!values.prompt) missing.push("--prompt");
|
|
4674
4953
|
if (missing.length) fail("MISSING_ARGUMENT", `Missing required arguments: ${missing.join(", ")}`);
|
|
@@ -4685,6 +4964,9 @@ async function taskCommand(argv) {
|
|
|
4685
4964
|
}
|
|
4686
4965
|
const { task, storePath: resolvedPath } = await createTaskFromInput({
|
|
4687
4966
|
projectPath: values.project,
|
|
4967
|
+
role: master ? "master" : void 0,
|
|
4968
|
+
workDir: values["work-dir"],
|
|
4969
|
+
masterId: values.master,
|
|
4688
4970
|
title: values.title,
|
|
4689
4971
|
description: values.prompt,
|
|
4690
4972
|
status: values.status ?? "backlog",
|
|
@@ -4703,7 +4985,9 @@ async function taskCommand(argv) {
|
|
|
4703
4985
|
task: {
|
|
4704
4986
|
id: task.id,
|
|
4705
4987
|
title: task.title,
|
|
4706
|
-
|
|
4988
|
+
role: task.role ?? "slave",
|
|
4989
|
+
masterId: task.masterId ?? null,
|
|
4990
|
+
projectPath: task.projectPath ?? null,
|
|
4707
4991
|
branch: task.branch,
|
|
4708
4992
|
worktreePath: task.worktreePath ?? null,
|
|
4709
4993
|
baseBranch: task.baseBranch,
|
|
@@ -4714,6 +4998,28 @@ async function taskCommand(argv) {
|
|
|
4714
4998
|
}
|
|
4715
4999
|
};
|
|
4716
5000
|
}
|
|
5001
|
+
var HOST_COMMANDS = ["start", "list", "show"];
|
|
5002
|
+
async function hostCommand(sub, values, storePath) {
|
|
5003
|
+
if (sub !== "list" && !values.task) fail("MISSING_ARGUMENT", "Missing required arguments: --task");
|
|
5004
|
+
const lock = liveHost(storePath);
|
|
5005
|
+
if (!lock) fail("HOST_NOT_RUNNING", `No VibeFlow host is running for ${storePath}. Start it with \`vibeflow\` first.`);
|
|
5006
|
+
const remote = connectToHost(lock);
|
|
5007
|
+
try {
|
|
5008
|
+
if (sub === "start") {
|
|
5009
|
+
const { task } = await remote.api.startTask(values.task);
|
|
5010
|
+
return { task: { id: task.id, title: task.title, worktreePath: task.worktreePath ?? null } };
|
|
5011
|
+
}
|
|
5012
|
+
if (sub === "show") {
|
|
5013
|
+
const [task] = await remote.api.taskSummaries({ taskId: values.task });
|
|
5014
|
+
return { task };
|
|
5015
|
+
}
|
|
5016
|
+
return { tasks: await remote.api.taskSummaries(values.master ? { masterId: values.master } : void 0) };
|
|
5017
|
+
} catch (err) {
|
|
5018
|
+
fail("HOST_ERROR", err.message);
|
|
5019
|
+
} finally {
|
|
5020
|
+
remote.close();
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
4717
5023
|
|
|
4718
5024
|
// packages/cli/src/host.ts
|
|
4719
5025
|
import fs25 from "fs";
|
|
@@ -5158,6 +5464,17 @@ function resolveTerminalFile(cwd, input) {
|
|
|
5158
5464
|
if (target.includes("\0")) throw new Error("\u6A94\u6848\u8DEF\u5F91\u7121\u6548\u3002");
|
|
5159
5465
|
return path16.resolve(cwd, target);
|
|
5160
5466
|
}
|
|
5467
|
+
var MAX_FILE_CANDIDATES = 128;
|
|
5468
|
+
async function existingTerminalFiles(cwd, inputs) {
|
|
5469
|
+
const found2 = await Promise.all(inputs.slice(0, MAX_FILE_CANDIDATES).map(async (input) => {
|
|
5470
|
+
try {
|
|
5471
|
+
return (await fs13.stat(resolveTerminalFile(cwd, input))).isFile();
|
|
5472
|
+
} catch {
|
|
5473
|
+
return false;
|
|
5474
|
+
}
|
|
5475
|
+
}));
|
|
5476
|
+
return inputs.filter((_, i) => found2[i]);
|
|
5477
|
+
}
|
|
5161
5478
|
async function readTerminalFile(cwd, input, metadataOnly = false) {
|
|
5162
5479
|
let target;
|
|
5163
5480
|
try {
|
|
@@ -6966,6 +7283,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
6966
7283
|
bus.emit("progress:notify", { taskId, title: task.title, notifications });
|
|
6967
7284
|
}
|
|
6968
7285
|
});
|
|
7286
|
+
function gitWorktree(task) {
|
|
7287
|
+
return isMasterTask(task) ? void 0 : task.worktreePath;
|
|
7288
|
+
}
|
|
6969
7289
|
function progressTarget(task) {
|
|
6970
7290
|
if (!task.worktreePath) return null;
|
|
6971
7291
|
const agent = taskAgent(task);
|
|
@@ -7143,6 +7463,87 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7143
7463
|
authStatus: jiraAuthStatus ?? (() => getJiraAuthStatus(acliRunner)),
|
|
7144
7464
|
pointFields: () => getSettings().jira?.storyPointsFields?.length ? getSettings().jira.storyPointsFields : DEFAULT_POINT_FIELDS
|
|
7145
7465
|
});
|
|
7466
|
+
async function startTaskSession(requested, key, p) {
|
|
7467
|
+
const pending = provisioning.get(requested.id);
|
|
7468
|
+
const task = p.launch ? await ensureProvisioned(requested) : pending ? await pending : requested;
|
|
7469
|
+
const cwd = task.worktreePath;
|
|
7470
|
+
if (!cwd) throw new Error("\u4EFB\u52D9\u5C1A\u672A\u958B\u59CB\u57F7\u884C\uFF0C\u9084\u6C92\u6709 worktree");
|
|
7471
|
+
let command;
|
|
7472
|
+
let fresh = p.fresh === true;
|
|
7473
|
+
if (p.launch) {
|
|
7474
|
+
const intent = obj(p.launch, "launch");
|
|
7475
|
+
if (intent.resume && await sessions.isAlive(key)) {
|
|
7476
|
+
fresh = false;
|
|
7477
|
+
} else {
|
|
7478
|
+
command = (await launchCommand(task, intent)).replace(/\r$/, "");
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7481
|
+
if (p.launch) ensureTaskArtifactDirs(task);
|
|
7482
|
+
const result = await sessions.start(key, {
|
|
7483
|
+
cwd,
|
|
7484
|
+
command,
|
|
7485
|
+
fresh,
|
|
7486
|
+
cols: typeof p.cols === "number" ? p.cols : void 0,
|
|
7487
|
+
rows: typeof p.rows === "number" ? p.rows : void 0,
|
|
7488
|
+
// Session ended (natural exit included), so stop its watcher.
|
|
7489
|
+
onExit: () => unwatchSubAgents(key)
|
|
7490
|
+
});
|
|
7491
|
+
terminalDirectories.set(key, cwd);
|
|
7492
|
+
watchSubAgents(key, cwd, (subAgents) => {
|
|
7493
|
+
bus.emit("subagents:update", { taskId: task.id, subAgents });
|
|
7494
|
+
});
|
|
7495
|
+
if (p.launch) {
|
|
7496
|
+
const current = findTask(task.id);
|
|
7497
|
+
if (current) trackProgress(current);
|
|
7498
|
+
}
|
|
7499
|
+
return result;
|
|
7500
|
+
}
|
|
7501
|
+
async function summarizeTasks(filter) {
|
|
7502
|
+
const board = getState().board;
|
|
7503
|
+
const alive = new Set(await sessions.list());
|
|
7504
|
+
const summaries = [];
|
|
7505
|
+
for (const column of COLUMNS) {
|
|
7506
|
+
for (const task of board[column]) {
|
|
7507
|
+
if (filter.taskId && task.id !== filter.taskId) continue;
|
|
7508
|
+
if (filter.masterId && task.masterId !== filter.masterId) continue;
|
|
7509
|
+
if (column === "in_progress" && !progress.isTracking(task.id)) trackProgress(task);
|
|
7510
|
+
const snapshot = progress.isTracking(task.id) ? progress.snapshot(task.id) : null;
|
|
7511
|
+
const todos = snapshot?.todos;
|
|
7512
|
+
const current = todos?.find((t) => t.status === "in_progress");
|
|
7513
|
+
const pr = task.outcome?.pr;
|
|
7514
|
+
summaries.push({
|
|
7515
|
+
id: task.id,
|
|
7516
|
+
title: task.title,
|
|
7517
|
+
column,
|
|
7518
|
+
...task.role ? { role: task.role } : {},
|
|
7519
|
+
...task.masterId ? { masterId: task.masterId } : {},
|
|
7520
|
+
...task.projectName ? { projectName: task.projectName } : {},
|
|
7521
|
+
...task.branch ? { branch: task.branch } : {},
|
|
7522
|
+
running: alive.has(task.id),
|
|
7523
|
+
...task.launchedAt ? { launchedAt: task.launchedAt } : {},
|
|
7524
|
+
...task.launchError ? { launchError: task.launchError } : {},
|
|
7525
|
+
...todos ? {
|
|
7526
|
+
todos: {
|
|
7527
|
+
done: todos.filter((t) => t.status === "completed").length,
|
|
7528
|
+
total: todos.length,
|
|
7529
|
+
...current ? { current: current.activeForm ?? current.content } : {}
|
|
7530
|
+
}
|
|
7531
|
+
} : {},
|
|
7532
|
+
...snapshot ? { activity: snapshot.activity } : {},
|
|
7533
|
+
...pr ? { pr: { number: pr.number, url: pr.url, state: pr.state } } : {},
|
|
7534
|
+
...task.outcome ? {
|
|
7535
|
+
outcome: {
|
|
7536
|
+
commits: task.outcome.commits.length,
|
|
7537
|
+
files: task.outcome.files.length,
|
|
7538
|
+
additions: task.outcome.additions,
|
|
7539
|
+
deletions: task.outcome.deletions
|
|
7540
|
+
}
|
|
7541
|
+
} : {}
|
|
7542
|
+
});
|
|
7543
|
+
}
|
|
7544
|
+
}
|
|
7545
|
+
return summaries;
|
|
7546
|
+
}
|
|
7146
7547
|
const handlers = {
|
|
7147
7548
|
"vibeflow:getState": () => getState(),
|
|
7148
7549
|
// A card's progress right now (todo list, usage, activity), for a frontend
|
|
@@ -7242,8 +7643,13 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7242
7643
|
},
|
|
7243
7644
|
"vibeflow:createTask": async (payload) => {
|
|
7244
7645
|
const p = obj(payload, "payload");
|
|
7245
|
-
if (
|
|
7246
|
-
|
|
7646
|
+
if (p.role !== void 0 && p.role !== "master") invalid("unknown role");
|
|
7647
|
+
if (p.role !== "master") {
|
|
7648
|
+
if (!p.projectPath) throw new Error("\u5C1A\u672A\u9078\u64C7\u5C08\u6848\u8CC7\u6599\u593E");
|
|
7649
|
+
str(p.projectPath, "projectPath");
|
|
7650
|
+
}
|
|
7651
|
+
if (p.workDir !== void 0) str(p.workDir, "workDir");
|
|
7652
|
+
if (p.masterId !== void 0) str(p.masterId, "masterId");
|
|
7247
7653
|
str(p.title ?? "", "title");
|
|
7248
7654
|
if (p.effort !== void 0 && !AGENT_EFFORTS.includes(p.effort)) invalid("unknown effort");
|
|
7249
7655
|
if (p.agentCli !== void 0 && !AGENT_IDS2.includes(p.agentCli)) invalid("unknown agent");
|
|
@@ -7251,6 +7657,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7251
7657
|
if (p.jira !== void 0 && !isJiraRef(p.jira)) invalid("jira must be a Jira ticket ref");
|
|
7252
7658
|
const { task } = await createTaskFromInput({
|
|
7253
7659
|
projectPath: p.projectPath,
|
|
7660
|
+
role: p.role,
|
|
7661
|
+
workDir: p.workDir,
|
|
7662
|
+
masterId: p.masterId,
|
|
7254
7663
|
title: p.title,
|
|
7255
7664
|
description: p.description,
|
|
7256
7665
|
baseBranch: p.baseBranch,
|
|
@@ -7278,6 +7687,18 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7278
7687
|
// were new. Launching again is the caller's decision.
|
|
7279
7688
|
"vibeflow:resetTaskRun": async (taskId) => {
|
|
7280
7689
|
const task = requireTask(taskId);
|
|
7690
|
+
if (isMasterTask(task) && task.worktreePath) {
|
|
7691
|
+
await teardownTask(task.id);
|
|
7692
|
+
if (task.workspacePath) deleteArtifacts(task.workspacePath, task.worktreePath);
|
|
7693
|
+
resetSubAgents(task.worktreePath);
|
|
7694
|
+
const usage2 = usageThroughCurrentRun(task);
|
|
7695
|
+
progress.untrack(task.id);
|
|
7696
|
+
updateTask(task.id, { launchedAt: Date.now(), runId: randomUUID5(), jevRoute: void 0, ...usage2 ? { usage: usage2 } : {} });
|
|
7697
|
+
const reset2 = findTask(task.id);
|
|
7698
|
+
if (!reset2) throw new Error("\u627E\u4E0D\u5230\u8981\u91CD\u7F6E\u7684\u4EFB\u52D9");
|
|
7699
|
+
bus.emit("subagents:update", { taskId: task.id, subAgents: [] });
|
|
7700
|
+
return { state: getState(), task: reset2 };
|
|
7701
|
+
}
|
|
7281
7702
|
if (!task.worktreePath) {
|
|
7282
7703
|
await teardownTask(task.id);
|
|
7283
7704
|
progress.untrack(task.id);
|
|
@@ -7314,8 +7735,16 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7314
7735
|
throw new Error("\u53EA\u6709 Backlog \u7684\u4EFB\u52D9\u53EF\u4EE5\u7DE8\u8F2F");
|
|
7315
7736
|
}
|
|
7316
7737
|
if (p.effort !== void 0 && !AGENT_EFFORTS.includes(p.effort)) invalid("unknown effort");
|
|
7738
|
+
let masterPatch = {};
|
|
7739
|
+
if (p.masterId !== void 0) {
|
|
7740
|
+
if (p.masterId !== null) {
|
|
7741
|
+
if (isMasterTask(existing)) invalid("a master cannot belong to another master");
|
|
7742
|
+
assertMasterRef(getState().board, str(p.masterId, "masterId"), existing.id);
|
|
7743
|
+
}
|
|
7744
|
+
masterPatch = { masterId: p.masterId || void 0 };
|
|
7745
|
+
}
|
|
7317
7746
|
let gitPatch = {};
|
|
7318
|
-
const nextProject = p.projectPath || existing.projectPath;
|
|
7747
|
+
const nextProject = isMasterTask(existing) ? void 0 : p.projectPath || existing.projectPath;
|
|
7319
7748
|
const nextBranch = p.branch === void 0 ? "" : str(p.branch, "branch").trim();
|
|
7320
7749
|
const projectChanged2 = Boolean(p.projectPath && p.projectPath !== existing.projectPath);
|
|
7321
7750
|
const baseChanged = p.baseBranch != null && p.baseBranch !== (existing.baseBranch ?? null);
|
|
@@ -7360,6 +7789,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7360
7789
|
jevRoute: void 0,
|
|
7361
7790
|
effort: p.effort,
|
|
7362
7791
|
autoMode: p.autoMode,
|
|
7792
|
+
...masterPatch,
|
|
7363
7793
|
...gitPatch
|
|
7364
7794
|
});
|
|
7365
7795
|
return getState();
|
|
@@ -7370,40 +7800,35 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7370
7800
|
"pty:start": async (payload) => {
|
|
7371
7801
|
const p = obj(payload, "payload");
|
|
7372
7802
|
const requested = requireTask(p.taskId);
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
const result = await sessions.start(key, {
|
|
7390
|
-
cwd,
|
|
7391
|
-
command,
|
|
7392
|
-
fresh,
|
|
7393
|
-
cols: typeof p.cols === "number" ? p.cols : void 0,
|
|
7394
|
-
rows: typeof p.rows === "number" ? p.rows : void 0,
|
|
7395
|
-
// Session ended (natural exit included), so stop its watcher.
|
|
7396
|
-
onExit: () => unwatchSubAgents(key)
|
|
7803
|
+
return startTaskSession(requested, sessionKeyFor(requested.id, p.sessionKey), p);
|
|
7804
|
+
},
|
|
7805
|
+
// What dragging a card into In Progress does, without a frontend: the card
|
|
7806
|
+
// moves, provisions and starts its agent here, so whoever opens it later
|
|
7807
|
+
// attaches to a run that is already going. This is how a master starts the
|
|
7808
|
+
// cards it dispatched.
|
|
7809
|
+
"vibeflow:startTask": async (taskId) => {
|
|
7810
|
+
const task = requireTask(taskId);
|
|
7811
|
+
const column = columnOf(task.id);
|
|
7812
|
+
if (column !== "backlog") throw new Error(`\u53EA\u6709 Backlog \u7684\u5361\u7247\u53EF\u4EE5\u555F\u52D5\uFF08\u76EE\u524D\u5728 ${column}\uFF09`);
|
|
7813
|
+
const board = getState().board;
|
|
7814
|
+
const started = { ...task, launchedAt: Date.now(), launchError: void 0 };
|
|
7815
|
+
setBoard({
|
|
7816
|
+
backlog: board.backlog.filter((t) => t.id !== task.id),
|
|
7817
|
+
in_progress: [started, ...board.in_progress],
|
|
7818
|
+
done: board.done
|
|
7397
7819
|
});
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7820
|
+
bus.emit("state:changed", getState());
|
|
7821
|
+
await startTaskSession(started, task.id, { taskId: task.id, launch: {} });
|
|
7822
|
+
return { state: getState(), task: findTask(task.id) ?? started };
|
|
7823
|
+
},
|
|
7824
|
+
// Every card on the board, or one master's cards, as the CLI and a master's
|
|
7825
|
+
// agent read them.
|
|
7826
|
+
"task:summaries": (payload) => {
|
|
7827
|
+
const p = payload === void 0 ? {} : obj(payload, "payload");
|
|
7828
|
+
return summarizeTasks({
|
|
7829
|
+
masterId: p.masterId === void 0 ? void 0 : requireTask(p.masterId).id,
|
|
7830
|
+
taskId: p.taskId === void 0 ? void 0 : requireTask(p.taskId).id
|
|
7401
7831
|
});
|
|
7402
|
-
if (p.launch) {
|
|
7403
|
-
const current = findTask(task.id);
|
|
7404
|
-
if (current) trackProgress(current);
|
|
7405
|
-
}
|
|
7406
|
-
return result;
|
|
7407
7832
|
},
|
|
7408
7833
|
"terminal:start": async (payload) => {
|
|
7409
7834
|
const p = obj(payload, "payload");
|
|
@@ -7419,8 +7844,11 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7419
7844
|
if (p.taskId) {
|
|
7420
7845
|
const requested = requireTask(p.taskId);
|
|
7421
7846
|
const column = columnOf(requested.id);
|
|
7422
|
-
const task = column === "in_progress"
|
|
7423
|
-
cwd = str(
|
|
7847
|
+
const task = !requested.worktreePath && (column === "in_progress" || isMasterTask(requested)) ? await ensureProvisioned(requested) : requested;
|
|
7848
|
+
cwd = str(
|
|
7849
|
+
isMasterTask(task) ? task.worktreePath : column === "backlog" ? task.projectPath : task.worktreePath ?? task.projectPath,
|
|
7850
|
+
"directory"
|
|
7851
|
+
);
|
|
7424
7852
|
projectPath = task.projectPath ?? cwd;
|
|
7425
7853
|
} else if (p.projectPath) {
|
|
7426
7854
|
cwd = str(p.projectPath, "projectPath");
|
|
@@ -7452,6 +7880,11 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7452
7880
|
const p = obj(payload, "payload");
|
|
7453
7881
|
return readTerminalFile(terminalDirectory(p.sessionKey), str(p.path, "path"));
|
|
7454
7882
|
},
|
|
7883
|
+
"terminal:existingFiles": async (payload) => {
|
|
7884
|
+
const p = obj(payload, "payload");
|
|
7885
|
+
if (!Array.isArray(p.paths)) invalid("paths must be an array");
|
|
7886
|
+
return existingTerminalFiles(terminalDirectory(p.sessionKey), p.paths.map((value) => str(value, "path")));
|
|
7887
|
+
},
|
|
7455
7888
|
"terminal:openFile": async (payload) => {
|
|
7456
7889
|
const p = obj(payload, "payload");
|
|
7457
7890
|
const file = await readTerminalFile(terminalDirectory(p.sessionKey), str(p.path, "path"), true);
|
|
@@ -7509,8 +7942,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7509
7942
|
},
|
|
7510
7943
|
"git:getDiff": (taskId) => {
|
|
7511
7944
|
const task = requireTask(taskId);
|
|
7512
|
-
|
|
7513
|
-
|
|
7945
|
+
const worktree = gitWorktree(task);
|
|
7946
|
+
if (!worktree) return [];
|
|
7947
|
+
return getWorktreeDiff(worktree, task.baseBranch ?? "HEAD");
|
|
7514
7948
|
},
|
|
7515
7949
|
/**
|
|
7516
7950
|
* Changed-file list without content — cheap enough for the sidebar to poll.
|
|
@@ -7519,16 +7953,18 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7519
7953
|
*/
|
|
7520
7954
|
"git:getDiffEntries": (taskId, opts) => {
|
|
7521
7955
|
const task = requireTask(taskId);
|
|
7522
|
-
|
|
7956
|
+
const worktree = gitWorktree(task);
|
|
7957
|
+
if (!worktree) return [];
|
|
7523
7958
|
const fetch2 = opts && typeof opts === "object" ? opts.fetch : void 0;
|
|
7524
|
-
return getWorktreeDiffEntries(
|
|
7959
|
+
return getWorktreeDiffEntries(worktree, task.baseBranch ?? "HEAD", {
|
|
7525
7960
|
fetch: typeof fetch2 === "boolean" ? fetch2 : void 0
|
|
7526
7961
|
});
|
|
7527
7962
|
},
|
|
7528
7963
|
"git:getDiffFile": (taskId, filePath) => {
|
|
7529
7964
|
const task = requireTask(taskId);
|
|
7530
|
-
|
|
7531
|
-
|
|
7965
|
+
const worktree = gitWorktree(task);
|
|
7966
|
+
if (!worktree) return null;
|
|
7967
|
+
return getWorktreeDiffFile(worktree, task.baseBranch ?? "HEAD", str(filePath, "filePath"));
|
|
7532
7968
|
},
|
|
7533
7969
|
"task:listArtifacts": (taskId) => {
|
|
7534
7970
|
const task = requireTask(taskId);
|
|
@@ -7554,6 +7990,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7554
7990
|
"task:getSpecs": async (taskId) => {
|
|
7555
7991
|
const task = requireTask(taskId);
|
|
7556
7992
|
const base2 = task.baseBranch ?? "main";
|
|
7993
|
+
if (isMasterTask(task)) return [];
|
|
7557
7994
|
if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base2);
|
|
7558
7995
|
const since = task.provisionedAt ?? task.launchedAt ?? (task.pushed !== void 0 ? task.createdAt : void 0);
|
|
7559
7996
|
if (!task.projectPath || !since) return [];
|
|
@@ -7609,25 +8046,29 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7609
8046
|
"git:approve": async (payload) => {
|
|
7610
8047
|
const p = obj(payload, "payload");
|
|
7611
8048
|
const task = requireTask(p.taskId);
|
|
7612
|
-
|
|
7613
|
-
|
|
8049
|
+
const worktree = gitWorktree(task);
|
|
8050
|
+
if (!worktree) throw new Error("\u627E\u4E0D\u5230\u6B64\u4EFB\u52D9\u7684 worktree");
|
|
8051
|
+
const result = await commitAndPush(worktree, str(p.message, "message"));
|
|
7614
8052
|
if (result.pushed) updateTask(task.id, { pushed: true });
|
|
7615
8053
|
return { result, state: getState() };
|
|
7616
8054
|
},
|
|
7617
8055
|
"git:generateCommitMessage": (taskId) => {
|
|
7618
8056
|
const task = requireTask(taskId);
|
|
7619
|
-
|
|
7620
|
-
|
|
8057
|
+
const worktree = gitWorktree(task);
|
|
8058
|
+
if (!worktree) throw new Error("\u627E\u4E0D\u5230\u6B64\u4EFB\u52D9\u7684 worktree");
|
|
8059
|
+
return generateCommitMessage(worktree, task.baseBranch ?? "main", task.agentCli ?? "claude");
|
|
7621
8060
|
},
|
|
7622
8061
|
"git:getPrStatus": (taskId) => {
|
|
7623
8062
|
const task = requireTask(taskId);
|
|
7624
|
-
|
|
7625
|
-
|
|
8063
|
+
const worktree = gitWorktree(task);
|
|
8064
|
+
if (!worktree) return null;
|
|
8065
|
+
return getPrStatus(worktree);
|
|
7626
8066
|
},
|
|
7627
8067
|
"git:getGithubCompareUrl": (taskId) => {
|
|
7628
8068
|
const task = requireTask(taskId);
|
|
7629
|
-
|
|
7630
|
-
|
|
8069
|
+
const worktree = gitWorktree(task);
|
|
8070
|
+
if (!worktree) return null;
|
|
8071
|
+
return getGithubCompareUrl(worktree, task.baseBranch ?? "main");
|
|
7631
8072
|
},
|
|
7632
8073
|
// One dialog at a time: a second click while it is open joins it instead
|
|
7633
8074
|
// of stacking another window on the host.
|
|
@@ -7655,8 +8096,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7655
8096
|
await teardownTask(task.id);
|
|
7656
8097
|
cancelChatSend(task.id);
|
|
7657
8098
|
let outcome;
|
|
7658
|
-
|
|
7659
|
-
|
|
8099
|
+
const worktree = gitWorktree(task);
|
|
8100
|
+
if (worktree) {
|
|
8101
|
+
outcome = await captureTaskOutcome(worktree, task.baseBranch ?? "main") ?? void 0;
|
|
7660
8102
|
}
|
|
7661
8103
|
const usage = usageThroughCurrentRun(task);
|
|
7662
8104
|
progress.untrack(task.id);
|
|
@@ -7685,6 +8127,9 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
|
|
|
7685
8127
|
if (task.workspacePath) {
|
|
7686
8128
|
fs21.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
|
|
7687
8129
|
}
|
|
8130
|
+
if (isMasterTask(task) && task.workspacePath) {
|
|
8131
|
+
fs21.rmSync(task.workspacePath, { recursive: true, force: true });
|
|
8132
|
+
}
|
|
7688
8133
|
clearConversation(task.id);
|
|
7689
8134
|
removeTask(task.id);
|
|
7690
8135
|
return getState();
|
|
@@ -8395,119 +8840,6 @@ function isWebDir(dir) {
|
|
|
8395
8840
|
return fs25.existsSync(path28.join(dir, "home", "index.html"));
|
|
8396
8841
|
}
|
|
8397
8842
|
|
|
8398
|
-
// packages/cli/src/remote.ts
|
|
8399
|
-
import WebSocket from "ws";
|
|
8400
|
-
|
|
8401
|
-
// packages/core/src/ws-transport.ts
|
|
8402
|
-
var OPEN = 1;
|
|
8403
|
-
var MAX_BACKOFF_MS = 5e3;
|
|
8404
|
-
function createWsTransport(options) {
|
|
8405
|
-
let socket = null;
|
|
8406
|
-
let nextId = 1;
|
|
8407
|
-
let connectedOnce = false;
|
|
8408
|
-
let closed = false;
|
|
8409
|
-
let backoff = 250;
|
|
8410
|
-
const queue = [];
|
|
8411
|
-
const pending = /* @__PURE__ */ new Map();
|
|
8412
|
-
const listeners = /* @__PURE__ */ new Map();
|
|
8413
|
-
function flush() {
|
|
8414
|
-
while (socket && socket.readyState === OPEN && queue.length > 0) socket.send(queue.shift());
|
|
8415
|
-
}
|
|
8416
|
-
function open() {
|
|
8417
|
-
if (closed) return;
|
|
8418
|
-
const ws = options.connect();
|
|
8419
|
-
socket = ws;
|
|
8420
|
-
ws.onopen = () => {
|
|
8421
|
-
backoff = 250;
|
|
8422
|
-
options.onStatus?.(true);
|
|
8423
|
-
flush();
|
|
8424
|
-
if (connectedOnce) options.onReconnect?.();
|
|
8425
|
-
connectedOnce = true;
|
|
8426
|
-
};
|
|
8427
|
-
ws.onmessage = (ev) => {
|
|
8428
|
-
let msg;
|
|
8429
|
-
try {
|
|
8430
|
-
msg = JSON.parse(String(ev.data));
|
|
8431
|
-
} catch {
|
|
8432
|
-
return;
|
|
8433
|
-
}
|
|
8434
|
-
if (typeof msg.event === "string") {
|
|
8435
|
-
dispatch(msg.event, msg.payload);
|
|
8436
|
-
return;
|
|
8437
|
-
}
|
|
8438
|
-
const entry = typeof msg.id === "number" ? pending.get(msg.id) : void 0;
|
|
8439
|
-
if (!entry) return;
|
|
8440
|
-
pending.delete(msg.id);
|
|
8441
|
-
if (msg.error) entry.reject(Object.assign(new Error(msg.error.message), { code: msg.error.code }));
|
|
8442
|
-
else entry.resolve(msg.result === null ? void 0 : msg.result);
|
|
8443
|
-
};
|
|
8444
|
-
ws.onerror = () => {
|
|
8445
|
-
};
|
|
8446
|
-
ws.onclose = () => {
|
|
8447
|
-
if (socket !== ws) return;
|
|
8448
|
-
socket = null;
|
|
8449
|
-
options.onStatus?.(false);
|
|
8450
|
-
for (const [id, entry] of Array.from(pending)) {
|
|
8451
|
-
pending.delete(id);
|
|
8452
|
-
entry.reject(new Error("\u8207 VibeFlow core \u7684\u9023\u7DDA\u4E2D\u65B7"));
|
|
8453
|
-
}
|
|
8454
|
-
if (closed) return;
|
|
8455
|
-
setTimeout(open, backoff);
|
|
8456
|
-
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
8457
|
-
};
|
|
8458
|
-
}
|
|
8459
|
-
function dispatch(channel, payload) {
|
|
8460
|
-
for (const cb of Array.from(listeners.get(channel) ?? [])) cb(payload);
|
|
8461
|
-
}
|
|
8462
|
-
function post(message) {
|
|
8463
|
-
const data = JSON.stringify(message);
|
|
8464
|
-
if (socket && socket.readyState === OPEN) socket.send(data);
|
|
8465
|
-
else queue.push(data);
|
|
8466
|
-
}
|
|
8467
|
-
open();
|
|
8468
|
-
return {
|
|
8469
|
-
kind: "ws",
|
|
8470
|
-
invoke(method, ...params) {
|
|
8471
|
-
const id = nextId++;
|
|
8472
|
-
return new Promise((resolve, reject) => {
|
|
8473
|
-
pending.set(id, { resolve, reject });
|
|
8474
|
-
post({ id, method, params });
|
|
8475
|
-
});
|
|
8476
|
-
},
|
|
8477
|
-
send(method, ...params) {
|
|
8478
|
-
post({ method, params });
|
|
8479
|
-
},
|
|
8480
|
-
on(channel, callback) {
|
|
8481
|
-
let set = listeners.get(channel);
|
|
8482
|
-
if (!set) listeners.set(channel, set = /* @__PURE__ */ new Set());
|
|
8483
|
-
set.add(callback);
|
|
8484
|
-
return () => {
|
|
8485
|
-
set.delete(callback);
|
|
8486
|
-
};
|
|
8487
|
-
},
|
|
8488
|
-
close() {
|
|
8489
|
-
closed = true;
|
|
8490
|
-
socket?.close();
|
|
8491
|
-
},
|
|
8492
|
-
dispatch
|
|
8493
|
-
};
|
|
8494
|
-
}
|
|
8495
|
-
|
|
8496
|
-
// packages/cli/src/remote.ts
|
|
8497
|
-
function connectToHost(lock) {
|
|
8498
|
-
const url = `ws://127.0.0.1:${lock.port}/ws`;
|
|
8499
|
-
const transport = createWsTransport({
|
|
8500
|
-
connect: () => new WebSocket(url, {
|
|
8501
|
-
headers: { Authorization: `Bearer ${lock.token}`, Host: `127.0.0.1:${lock.port}` }
|
|
8502
|
-
})
|
|
8503
|
-
});
|
|
8504
|
-
return {
|
|
8505
|
-
api: createBridge(transport),
|
|
8506
|
-
invoke: (method, ...params) => transport.invoke(method, ...params),
|
|
8507
|
-
close: () => transport.close()
|
|
8508
|
-
};
|
|
8509
|
-
}
|
|
8510
|
-
|
|
8511
8843
|
// packages/cli/src/doctor.ts
|
|
8512
8844
|
import fs26 from "fs";
|
|
8513
8845
|
import path29 from "path";
|