@kw-yeh/vibeflow 4.2.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 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);
@@ -607,7 +610,6 @@ 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 (projectChanged(s) && isRepo && hasRemote) fields.push("baseBranch");
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,7 +714,8 @@ function updatePayload(s) {
712
714
  model: s.model || void 0,
713
715
  effort: s.effort,
714
716
  autoMode: s.autoMode,
715
- ...!s.launched && s.projectPath ? { projectPath: s.projectPath, baseBranch: s.baseBranch || null } : {}
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) {
@@ -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 || s.mode === "edit" && !projectChanged(s)) return;
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) => ({ ...prev, gitInfo: info, gitInfoPath: target, baseBranch: info.defaultBase ?? "" }));
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 () => {
@@ -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" && !projectLocked) {
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) && !(field === "project" && projectLocked)) {
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 ? path24.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
  }
@@ -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" && !projectLocked) {
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 "\u66F4\u63DB\u5C08\u6848\u6703\u5728\u65B0\u5C08\u6848\u91CD\u5EFA worktree\uFF08\u6B64\u4EFB\u52D9\u5C1A\u672A\u958B\u59CB\uFF0C\u7121\u8B8A\u66F4\u6703\u907A\u5931\uFF09\u3002";
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
920
  if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path24.basename(s.projectPath)}`;
916
921
  }
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";
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;
925
+ }
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);
@@ -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") return leave({ type: "edit", task: selected });
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 });
@@ -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 preferredBranch = explicitBranch ?? await generateBranchName(input.title, input.description);
3282
- let provisionResult;
3283
- try {
3284
- provisionResult = await provisionWorktree(
3285
- projectPath,
3286
- workspacePath,
3287
- taskId,
3288
- input.baseBranch ?? null,
3289
- preferredBranch,
3290
- { explicitBranch: Boolean(explicitBranch) }
3291
- );
3292
- } catch (err) {
3293
- throw Object.assign(
3294
- new Error(`Worktree \u5EFA\u7ACB\u5931\u6557\uFF1A${err.message}`),
3295
- { code: "WORKTREE_CREATE_FAILED" }
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
- id: taskId,
3309
- title: input.title.trim() || `Task ${taskId}`,
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,
@@ -5000,6 +5040,45 @@ function createCore({ sessions, bus, version }) {
5000
5040
  deleteArtifacts(task.workspacePath ?? path20.dirname(task.worktreePath), task.worktreePath);
5001
5041
  await deleteBranch(task.projectPath, branch);
5002
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
+ }
5003
5082
  const handlers = {
5004
5083
  "vibeflow:getState": () => getState(),
5005
5084
  "app:getVersion": () => version,
@@ -5073,7 +5152,13 @@ function createCore({ sessions, bus, version }) {
5073
5152
  // were new. Launching again is the caller's decision.
5074
5153
  "vibeflow:resetTaskRun": async (taskId) => {
5075
5154
  const task = requireTask(taskId);
5076
- if (!task.worktreePath) throw new Error("\u4EFB\u52D9\u6C92\u6709\u53EF\u7528\u7684 worktree");
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
+ }
5077
5162
  if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
5078
5163
  await teardownTask(task.id);
5079
5164
  await resetWorktreeToBase(task.worktreePath, task.baseBranch);
@@ -5087,21 +5172,25 @@ function createCore({ sessions, bus, version }) {
5087
5172
  bus.emit("subagents:update", { taskId: task.id, subAgents: [] });
5088
5173
  return { state: getState(), task: reset };
5089
5174
  },
5090
- // Edit an existing card's fields. Most are plain metadata read at launch time
5091
- // (title / description / agent / model / workspace). The project folder
5092
- // and base branch are git-bound: re-selecting them rebuilds the worktree, which
5093
- // is only allowed for not-yet-launched tasks (no work to lose).
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.
5094
5181
  "vibeflow:updateTask": async (payload) => {
5095
5182
  const p = obj(payload, "payload");
5096
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
+ }
5097
5187
  let gitPatch = {};
5098
5188
  const nextProject = p.projectPath || existing.projectPath;
5189
+ const nextBranch = p.branch === void 0 ? "" : str(p.branch, "branch").trim();
5099
5190
  const projectChanged2 = Boolean(p.projectPath && p.projectPath !== existing.projectPath);
5100
5191
  const baseChanged = p.baseBranch != null && p.baseBranch !== (existing.baseBranch ?? null);
5101
- if (nextProject && (projectChanged2 || baseChanged)) {
5102
- if (existing.launchedAt) {
5103
- 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");
5104
- }
5192
+ const branchChanged = Boolean(nextBranch && nextBranch !== existing.branch);
5193
+ if (nextProject && (projectChanged2 || baseChanged || branchChanged)) {
5105
5194
  if (existing.worktreePath) {
5106
5195
  const diff = await getWorktreeDiffEntries(
5107
5196
  existing.worktreePath,
@@ -5109,35 +5198,30 @@ function createCore({ sessions, bus, version }) {
5109
5198
  { fetch: false }
5110
5199
  );
5111
5200
  if (diff.length > 0) {
5112
- throw new Error("\u76EE\u524D worktree \u5DF2\u6709\u8B8A\u66F4\uFF0C\u7121\u6CD5\u66F4\u63DB\u5C08\u6848\u8CC7\u6599\u593E\u6216\u57FA\u6E96\u5206\u652F");
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");
5113
5202
  }
5114
5203
  }
5115
5204
  const info = await getGitInfo(nextProject);
5116
5205
  if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
5117
- await teardownTask(existing.id);
5118
- await removeTaskWorktree(existing);
5119
5206
  const projectName = path20.basename(nextProject);
5120
5207
  const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
5121
- await fs16.promises.mkdir(workspacePath, { recursive: true });
5122
- const result = await provisionWorktree(
5123
- nextProject,
5124
- workspacePath,
5125
- existing.id,
5126
- p.baseBranch ?? existing.baseBranch ?? null,
5127
- existing.branch
5128
- );
5208
+ await teardownTask(existing.id);
5209
+ await removeTaskWorktree(existing);
5210
+ if (branchChanged) await assertBranchAvailable(nextProject, workspacePath, nextBranch);
5129
5211
  recordRecentProject(getStore(), nextProject);
5130
5212
  gitPatch = {
5131
5213
  projectPath: nextProject,
5132
5214
  projectName,
5133
- branch: result.branch,
5134
- worktreePath: result.worktreePath,
5135
5215
  workspacePath,
5136
- baseBranch: result.baseBranch,
5137
- pushed: result.pushed
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
5138
5221
  };
5139
5222
  }
5140
5223
  updateTask(existing.id, {
5224
+ launchError: void 0,
5141
5225
  title: (p.title ?? "").trim() || `Task ${existing.id}`,
5142
5226
  description: p.description?.trim() || void 0,
5143
5227
  agentCli: p.agentCli,
@@ -5153,10 +5237,12 @@ function createCore({ sessions, bus, version }) {
5153
5237
  // string or a cwd, so a connected client cannot run anything else.
5154
5238
  "pty:start": async (payload) => {
5155
5239
  const p = obj(payload, "payload");
5156
- const task = requireTask(p.taskId);
5157
- const key = sessionKeyFor(task.id, p.sessionKey);
5158
- const cwd = task.worktreePath ?? task.projectPath;
5159
- if (!cwd) throw new Error("\u4EFB\u52D9\u6C92\u6709\u53EF\u7528\u7684\u5DE5\u4F5C\u76EE\u9304");
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");
5160
5246
  let command;
5161
5247
  let fresh = p.fresh === true;
5162
5248
  if (p.launch) {
@@ -5260,7 +5346,7 @@ function createCore({ sessions, bus, version }) {
5260
5346
  const task = requireTask(taskId);
5261
5347
  const base = task.baseBranch ?? "main";
5262
5348
  if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base);
5263
- if (!task.projectPath) return [];
5349
+ if (!task.projectPath || !task.launchedAt) return [];
5264
5350
  const changed = task.outcome?.files.filter((f) => f.status !== "D").map((f) => f.path);
5265
5351
  return readBranchSpecs(task.projectPath, task.branch, base, changed);
5266
5352
  },
@@ -5365,6 +5451,9 @@ function createCore({ sessions, bus, version }) {
5365
5451
  deleteDecisions(task.workspacePath, decisionsKey(task.worktreePath, task.branch));
5366
5452
  }
5367
5453
  await removeTaskWorktree(task);
5454
+ if (task.workspacePath) {
5455
+ fs16.rmSync(taskAttachmentStagingPath(task.workspacePath, task.id), { recursive: true, force: true });
5456
+ }
5368
5457
  clearConversation(task.id);
5369
5458
  removeTask(task.id);
5370
5459
  return getState();
@@ -5372,9 +5461,11 @@ function createCore({ sessions, bus, version }) {
5372
5461
  "attachments:write": (payload) => {
5373
5462
  const p = obj(payload, "payload");
5374
5463
  const task = requireTask(p.taskId);
5375
- if (!task.worktreePath) throw new Error("\u627E\u4E0D\u5230\u4EFB\u52D9 worktree");
5376
5464
  if (!Array.isArray(p.attachments)) invalid("attachments must be an array");
5377
- return writeAttachments(task.worktreePath, p.attachments);
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);
5378
5469
  },
5379
5470
  "chat:load": (taskId) => loadConversation(requireTask(taskId).id),
5380
5471
  "chat:cancel": (taskId) => {
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/c380333c2873a2b8.css" as="style"/><link rel="stylesheet" href="/_next/static/css/c380333c2873a2b8.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-014e95fe86cd1d39.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/lyOZoF78HzRwmQ3DYf88f/_buildManifest.js" defer=""></script><script src="/_next/static/lyOZoF78HzRwmQ3DYf88f/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"lyOZoF78HzRwmQ3DYf88f","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/24b804d01aa3af3c.css" as="style"/><link rel="stylesheet" href="/_next/static/css/24b804d01aa3af3c.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-014e95fe86cd1d39.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/_pjqayPqD9pDyPeTbc6L2/_buildManifest.js" defer=""></script><script src="/_next/static/_pjqayPqD9pDyPeTbc6L2/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"_pjqayPqD9pDyPeTbc6L2","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
package/dist/web/404.html CHANGED
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/c380333c2873a2b8.css" as="style"/><link rel="stylesheet" href="/_next/static/css/c380333c2873a2b8.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-014e95fe86cd1d39.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/lyOZoF78HzRwmQ3DYf88f/_buildManifest.js" defer=""></script><script src="/_next/static/lyOZoF78HzRwmQ3DYf88f/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"lyOZoF78HzRwmQ3DYf88f","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/24b804d01aa3af3c.css" as="style"/><link rel="stylesheet" href="/_next/static/css/24b804d01aa3af3c.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-014e95fe86cd1d39.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/_pjqayPqD9pDyPeTbc6L2/_buildManifest.js" defer=""></script><script src="/_next/static/_pjqayPqD9pDyPeTbc6L2/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"_pjqayPqD9pDyPeTbc6L2","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
@@ -1 +1 @@
1
- self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:0,errorRate:1e-4,numBits:0,numHashes:null,bitArray:[]},__routerFilterDynamic:{numItems:0,errorRate:1e-4,numBits:0,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-8f65b0f8749f42d2.js"],"/home":["static/chunks/8833-76c31db372a29bb8.js","static/chunks/pages/home-e692e9e0783d2fdf.js"],sortedPages:["/_app","/_error","/home"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
1
+ self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:0,errorRate:1e-4,numBits:0,numHashes:null,bitArray:[]},__routerFilterDynamic:{numItems:0,errorRate:1e-4,numBits:0,numHashes:null,bitArray:[]},"/_error":["static/chunks/pages/_error-8f65b0f8749f42d2.js"],"/home":["static/chunks/2524-abaf3a55023f6066.js","static/chunks/pages/home-1fb1d1a49d28f6dd.js"],sortedPages:["/_app","/_error","/home"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();