whipped 0.4.0 → 0.5.1

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/cli.js CHANGED
@@ -3529,7 +3529,7 @@ function isResumableSessionState(state) {
3529
3529
  function normalizeTag(raw2) {
3530
3530
  return raw2.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3531
3531
  }
3532
- var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
3532
+ var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeBulkCardImportItemSchema, runtimeBulkCardsCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
3533
3533
  var init_api_contract = __esm({
3534
3534
  "src/core/api-contract.ts"() {
3535
3535
  "use strict";
@@ -3962,6 +3962,14 @@ Do NOT include:
3962
3962
  modelConfig: cardModelConfigSchema.optional(),
3963
3963
  activeLevel: tierLevelSchema.optional()
3964
3964
  });
3965
+ runtimeBulkCardImportItemSchema = runtimeCardCreateRequestSchema.extend({
3966
+ tempId: z.string().optional()
3967
+ });
3968
+ runtimeBulkCardsCreateRequestSchema = z.object({
3969
+ // Batch-wide base branch; an item's own baseRef overrides it, else resolved server-side.
3970
+ baseRef: z.string().optional(),
3971
+ cards: z.array(runtimeBulkCardImportItemSchema).min(1)
3972
+ });
3965
3973
  runtimeCardMoveRequestSchema = z.object({
3966
3974
  cardId: z.string(),
3967
3975
  targetColumnId: runtimeBoardColumnIdSchema,
@@ -8001,6 +8009,7 @@ __export(workspace_state_exports, {
8001
8009
  clearCardSession: () => clearCardSession,
8002
8010
  closeAllOpenTerminalSessions: () => closeAllOpenTerminalSessions,
8003
8011
  createCard: () => createCard,
8012
+ createCardsBulk: () => createCardsBulk,
8004
8013
  deleteCard: () => deleteCard,
8005
8014
  downloadGithubImages: () => downloadGithubImages,
8006
8015
  endTerminalSession: () => endTerminalSession,
@@ -8551,6 +8560,79 @@ async function createCard(workspaceId, data, baseRef) {
8551
8560
  tx();
8552
8561
  return card;
8553
8562
  }
8563
+ async function createCardsBulk(workspaceId, items, baseRef) {
8564
+ const db = getDb();
8565
+ const now = Date.now();
8566
+ const projectConfig = loadProjectConfigInternal(workspaceId);
8567
+ const realIds = items.map(() => generateTaskId());
8568
+ const tempIdToRealId = /* @__PURE__ */ new Map();
8569
+ items.forEach((item, i) => {
8570
+ if (item.tempId) tempIdToRealId.set(item.tempId, realIds[i]);
8571
+ });
8572
+ const resolveRef = (ref) => tempIdToRealId.get(ref) ?? ref;
8573
+ const cards = items.map((item, i) => {
8574
+ const type = item.type ?? "task";
8575
+ const columnId = item.columnId ?? "todo";
8576
+ const workflow = resolveWorkflowForCard(projectConfig.workflows, { workflowId: item.workflowId, type });
8577
+ const waitsFor = (item.waitsFor ?? []).map(resolveRef);
8578
+ const dependsOn = item.dependsOn ? resolveRef(item.dependsOn) : void 0;
8579
+ return {
8580
+ id: realIds[i],
8581
+ description: item.description,
8582
+ columnId,
8583
+ type,
8584
+ readyForDev: item.readyForDev ?? type === "story",
8585
+ agentId: item.agentId,
8586
+ priority: item.priority,
8587
+ // dependsOn (stacking) and waitsFor (gate) are mutually exclusive — waitsFor wins.
8588
+ dependsOn: waitsFor.length > 0 ? void 0 : dependsOn,
8589
+ waitsFor,
8590
+ subtaskIds: (item.subtaskIds ?? []).map(resolveRef),
8591
+ autoFixAttempts: 0,
8592
+ activeLevel: item.activeLevel ?? highestWorkflowLevel(workflow),
8593
+ modelConfig: item.modelConfig ?? snapshotModelConfig(workflow),
8594
+ baseRef: item.baseRef ?? baseRef,
8595
+ createdAt: now,
8596
+ updatedAt: now,
8597
+ githubIssueUrl: item.githubIssueUrl,
8598
+ workflowId: item.workflowId ?? workflow?.id,
8599
+ descriptionAttachments: item.descriptionAttachments ?? [],
8600
+ branchName: item.branchName,
8601
+ reviewComments: [],
8602
+ activityLog: [],
8603
+ terminalSessions: [],
8604
+ githubCommentIds: []
8605
+ };
8606
+ });
8607
+ const columnCounts = /* @__PURE__ */ new Map();
8608
+ const nextPosition = (columnId) => {
8609
+ if (!columnCounts.has(columnId)) {
8610
+ const row = db.prepare("SELECT COUNT(*) AS n FROM cards WHERE workspace_id = ? AND column_id = ?").get(workspaceId, columnId);
8611
+ columnCounts.set(columnId, row.n);
8612
+ }
8613
+ const pos = columnCounts.get(columnId);
8614
+ columnCounts.set(columnId, pos + 1);
8615
+ return pos;
8616
+ };
8617
+ const tx = db.transaction(() => {
8618
+ for (const card of cards) {
8619
+ upsertCardRow(db, workspaceId, { ...card, dependsOn: void 0 }, nextPosition(card.columnId));
8620
+ }
8621
+ const setDep = db.prepare("UPDATE cards SET depends_on_id = ? WHERE id = ?");
8622
+ for (const card of cards) {
8623
+ if (card.dependsOn && db.prepare("SELECT 1 FROM cards WHERE id = ?").get(card.dependsOn)) {
8624
+ setDep.run(card.dependsOn, card.id);
8625
+ } else if (card.dependsOn) {
8626
+ card.dependsOn = void 0;
8627
+ }
8628
+ replaceCardWaitsFor(db, card.id, card.waitsFor ?? []);
8629
+ replaceCardSubtasks(db, card.id, card.subtaskIds ?? []);
8630
+ }
8631
+ bumpBoardRevision(db, workspaceId);
8632
+ });
8633
+ tx();
8634
+ return cards;
8635
+ }
8554
8636
  async function appendActivityLog(workspaceId, cardId, message) {
8555
8637
  const db = getDb();
8556
8638
  const tx = db.transaction(() => {
@@ -18810,9 +18892,6 @@ var BoardPoller = class {
18810
18892
  await moveCard(workspaceId, taskId, "reopened");
18811
18893
  await appendActivityLog(workspaceId, taskId, `${reason} \u2192 Reopened`);
18812
18894
  await clearCardSession(workspaceId, taskId);
18813
- const refreshedBoard = await loadBoard(workspaceId);
18814
- const refreshedCard = refreshedBoard.cards[taskId] ?? card;
18815
- void scheduler.triggerParentReopenCascade(refreshedCard, refreshedBoard.cards);
18816
18895
  updated = true;
18817
18896
  }
18818
18897
  if (updated) stateHub.broadcastWorkspaceUpdate(workspaceId);
@@ -21590,123 +21669,6 @@ function tryParseAgentJson(output) {
21590
21669
  return null;
21591
21670
  }
21592
21671
  }
21593
- async function runParentReopenCascade(parentCard, childCards, options) {
21594
- const { workspaceId, repoPath, mcpBinary, serverUrl, stateHub, secrets, registerStopCallback, registerLiveProcess } = options;
21595
- const streamId = `${parentCard.id}-cascade-${Date.now()}`;
21596
- const mcpConfigPath = getMcpConfigPath(streamId);
21597
- await writeClaudeMcpConfig(mcpBinary, serverUrl, workspaceId, "claude", mcpConfigPath).catch(() => {
21598
- });
21599
- const parentBranch = getCardBranch(parentCard);
21600
- const systemPrompt = buildCascadeSystemPrompt(parentCard, parentBranch, childCards);
21601
- logger.info(
21602
- `[cascade] Spawning cascade agent for parent "${parentCard.description?.split("\n")[0]?.slice(0, 60) ?? parentCard.id}" (${childCards.length} children)`
21603
- );
21604
- await appendTerminalSession(workspaceId, parentCard.id, {
21605
- streamId,
21606
- type: "cascade",
21607
- startedAt: Date.now(),
21608
- state: "running"
21609
- });
21610
- stateHub.broadcastWorkspaceUpdate(workspaceId);
21611
- await runAgentOnce(
21612
- "claude",
21613
- "Evaluate each child ticket and take the appropriate action.",
21614
- repoPath,
21615
- workspaceId,
21616
- streamId,
21617
- stateHub,
21618
- registerStopCallback,
21619
- registerLiveProcess,
21620
- mcpConfigPath,
21621
- systemPrompt,
21622
- void 0,
21623
- buildSecretsEnv(secrets),
21624
- "low"
21625
- );
21626
- const cascadeStopped = options.isStreamManuallyStopped(streamId);
21627
- logger.info(`[cascade:${streamId}] Cascade agent done \u2014 manuallyStopped=${cascadeStopped}`);
21628
- await endTerminalSession(workspaceId, parentCard.id, streamId, Date.now(), cascadeStopped ? "stopped" : "completed");
21629
- if (cascadeStopped) return;
21630
- stateHub.broadcastWorkspaceUpdate(workspaceId);
21631
- if (options.onChildReset) {
21632
- const afterBoard = await loadBoard(workspaceId);
21633
- const resetChildren = childCards.filter((child) => afterBoard.cards[child.id]?.columnId === "todo");
21634
- for (const child of resetChildren) {
21635
- logger.info(
21636
- `[cascade] Recursing into reset child "${child.description?.split("\n")[0]?.slice(0, 60) ?? child.id}"`
21637
- );
21638
- await options.onChildReset(child);
21639
- }
21640
- }
21641
- }
21642
- function buildCascadeSystemPrompt(parentCard, parentBranch, childCards) {
21643
- const comments = parentCard.reviewComments ?? [];
21644
- const iterations = groupIntoIterations(parentCard);
21645
- const current = iterations[iterations.length - 1];
21646
- const currentInputSummaries = current?.input.map((c) => c.summary).filter(Boolean) ?? [];
21647
- const reopenReason = currentInputSummaries.length > 0 ? currentInputSummaries.join("\n") : "Parent task was reopened.";
21648
- const allDevSummaries = comments.filter((c) => c.type === "dev").map((c, i) => `Dev iteration ${i + 1}:
21649
- ${c.summary}`).join("\n\n");
21650
- const childLines = childCards.map((child) => {
21651
- const devComment = [...child.reviewComments ?? []].reverse().find((c) => c.type === "dev");
21652
- const desc = child.description ? `
21653
- ${child.description}
21654
- ` : "";
21655
- return [
21656
- `### [${child.id}] (${child.columnId})`,
21657
- desc,
21658
- devComment ? `**Dev summary:** ${devComment.summary}` : "No dev work completed yet."
21659
- ].filter(Boolean).join("\n");
21660
- }).join("\n\n");
21661
- return `You are a Kanban board manager. A parent task was reopened and you must decide what to do with its dependent child tasks.
21662
-
21663
- All data you need is already provided below \u2014 do NOT call \`kanban_get_board\`. Proceed directly to taking action.
21664
-
21665
-
21666
- ## Parent Task (Reopened)
21667
-
21668
- **[${parentCard.id}]**
21669
- ${parentCard.description ? `
21670
- ${parentCard.description}
21671
- ` : ""}
21672
- **Reason for reopening (= the parent's new direction, not yet implemented):** ${reopenReason}
21673
- ${allDevSummaries ? `
21674
- Parent's full dev history (OLD state \u2014 do NOT use this to judge conflicts, use the reopening reason above):
21675
- ${allDevSummaries}
21676
- ` : ""}
21677
-
21678
- ## Child Tasks to Evaluate
21679
-
21680
- ${childLines}
21681
-
21682
- ## Decision Rules
21683
-
21684
- CRITICAL: The cascade runs BEFORE the parent's dev agent implements the feedback. The parent's existing dev summary reflects its OLD state \u2014 do NOT use it to evaluate conflicts. Use ONLY the **reason for reopening** (the human feedback) to determine what the parent is about to change. That is the parent's new direction.
21685
-
21686
- **Reset a child when:**
21687
- - The reopening reason describes a change that directly conflicts with the child's purpose or existing work
21688
- - e.g. reason is "Remove username field", child's purpose is "Add username field" \u2192 direct conflict \u2192 reset
21689
- - e.g. reason is "Change the API response shape", child is building a UI that consumes that API \u2192 reset
21690
-
21691
- **Leave a child alone when:**
21692
- - The reopening reason describes a change to something completely unrelated to the child's purpose
21693
- - e.g. reason is "Remove email field", child's purpose is "Add username field" \u2192 unrelated \u2192 leave alone
21694
- - e.g. reason is "Fix a bug in the payment module", child is working on user profiles \u2192 leave alone
21695
-
21696
- The default is to **leave children alone**. Only reset when the reopening reason directly conflicts with what the child is doing.
21697
-
21698
- ## Steps for EACH child you decide to reset
21699
-
21700
- 1. Call \`kanban_stop_task\` if the child is in_progress.
21701
- 2. Call \`kanban_add_comment\` on the **CHILD** card with:
21702
- - type: "cascade"
21703
- - status: "fail"
21704
- - summary: Explain specifically what the parent changed and why this child's prior work needs to be revisited.
21705
- - issues: include one blocking issue with severity "blocking" and message: "Run \`git merge ${parentBranch}\` \u2014 no fetch needed since all task worktrees in this project share the same local repo. After merging, implement this task's original goal on top of the parent's new state. The parent's changes are the new baseline \u2014 build on them, do not mirror them."
21706
- 3. Call \`kanban_move_card\` to "todo" for that child.
21707
-
21708
- After handling all children, call \`kanban_add_comment\` on the PARENT card (${parentCard.id}) with type "cascade" and a brief summary of each decision.`;
21709
- }
21710
21672
 
21711
21673
  // src/daemon/scheduler.ts
21712
21674
  var FAST_EXIT_THRESHOLD_MS = 8e3;
@@ -21735,10 +21697,8 @@ var TaskScheduler = class {
21735
21697
  manuallyStoppedForHook = /* @__PURE__ */ new Set();
21736
21698
  // Tasks stopped before the dev agent started (e.g. during plan phase).
21737
21699
  planPhaseManuallyStopped = /* @__PURE__ */ new Set();
21738
- // Individual review/cascade stream IDs stopped by a manual stopTask() call.
21700
+ // Individual review stream IDs stopped by a manual stopTask() call.
21739
21701
  manuallyStoppedStreams = /* @__PURE__ */ new Set();
21740
- // Tasks stopped because their parent was reopened — session set to "stopped" in onExit.
21741
- parentReopenedTasks = /* @__PURE__ */ new Set();
21742
21702
  // Shared worktree IDs currently in use by a dev agent — prevents sibling cards from
21743
21703
  // running concurrently in the same worktree directory.
21744
21704
  runningSharedWorktrees = /* @__PURE__ */ new Set();
@@ -22232,12 +22192,6 @@ ${devSystemPromptResult.text}`;
22232
22192
  stateHub.broadcastWorkspaceUpdate(workspaceId);
22233
22193
  return;
22234
22194
  }
22235
- if (this.parentReopenedTasks.has(taskId)) {
22236
- this.parentReopenedTasks.delete(taskId);
22237
- await endTerminalSession(workspaceId, taskId, devStreamId, Date.now(), "stopped");
22238
- stateHub.broadcastWorkspaceUpdate(workspaceId);
22239
- return;
22240
- }
22241
22195
  if (this.hookHandledTasks.has(taskId)) {
22242
22196
  this.hookHandledTasks.delete(taskId);
22243
22197
  const exitedAt2 = Date.now();
@@ -22403,45 +22357,6 @@ ${devSystemPromptResult.text}`;
22403
22357
  isStreamManuallyStopped(streamId) {
22404
22358
  return this.manuallyStoppedStreams.delete(streamId);
22405
22359
  }
22406
- // Stop a task because its parent was reopened — session becomes "stopped" rather than being removed.
22407
- interruptForParentReopen(taskId) {
22408
- const task = this.running.get(taskId);
22409
- if (task) {
22410
- logger.info(`[scheduler] Interrupting task ${taskId} due to parent reopen`);
22411
- this.setRecentBuffer(task.streamId, task.outputBuffer);
22412
- void saveTerminalBuffer(this.options.workspaceId, task.streamId, task.outputBuffer);
22413
- this.parentReopenedTasks.add(taskId);
22414
- task.process.kill();
22415
- this.running.delete(taskId);
22416
- }
22417
- }
22418
- async triggerParentReopenCascade(parentCard, boardCards) {
22419
- if (parentCard.type !== "task") return;
22420
- const { workspaceId, repoPath, serverUrl, stateHub } = this.options;
22421
- const childCards = Object.values(boardCards).filter(
22422
- (card) => card.dependsOn?.includes(parentCard.id) && (card.columnId === "in_progress" || card.columnId === "ready_for_review")
22423
- );
22424
- if (childCards.length === 0) return;
22425
- logger.info(
22426
- `[scheduler] triggerParentReopenCascade: ${childCards.length} children for parent "${parentCard.description?.split("\n")[0]?.slice(0, 60) ?? parentCard.id}"`
22427
- );
22428
- const projectConfig = await loadProjectConfig(workspaceId);
22429
- void runParentReopenCascade(parentCard, childCards, {
22430
- workspaceId,
22431
- repoPath,
22432
- serverUrl,
22433
- mcpBinary: getMcpServerPath(),
22434
- stateHub,
22435
- secrets: projectConfig.secrets ?? [],
22436
- registerStopCallback: this.registerStopCallback.bind(this),
22437
- registerLiveProcess: this.registerLiveProcess.bind(this),
22438
- isStreamManuallyStopped: this.isStreamManuallyStopped.bind(this),
22439
- onChildReset: async (child) => {
22440
- const latestBoard = await loadBoard(workspaceId);
22441
- await this.triggerParentReopenCascade(child, latestBoard.cards);
22442
- }
22443
- });
22444
- }
22445
22360
  getOutputBuffer(streamId) {
22446
22361
  for (const task of this.running.values()) {
22447
22362
  if (task.streamId === streamId) return task.outputBuffer;
@@ -25759,6 +25674,36 @@ Stack: ${err instanceof Error ? err.stack : ""}`
25759
25674
  throw err;
25760
25675
  }
25761
25676
  };
25677
+ var bulkCreateCardsService = async (workspaceId, items, requestedBase) => {
25678
+ const workspaces = await listWorkspaces();
25679
+ const ws = workspaces.find((w2) => w2.workspaceId === workspaceId);
25680
+ if (!ws) throw NotFoundError("Workspace");
25681
+ const config = await loadProjectConfig(workspaceId);
25682
+ if (config.workflows.filter((w2) => !w2.forStory).length === 0) {
25683
+ throw BadRequestError("Create at least one workflow before importing tickets.");
25684
+ }
25685
+ const hasStoryWorkflow = config.workflows.some((w2) => w2.forStory);
25686
+ const board = await loadBoard(workspaceId);
25687
+ const existingCardIds = new Set(Object.keys(board.cards));
25688
+ const tempIds = new Set(items.map((it) => it.tempId).filter((t) => Boolean(t)));
25689
+ const errors = [];
25690
+ items.forEach((item, index) => {
25691
+ if (!item.description?.trim()) errors.push({ index, message: "description is required" });
25692
+ if ((item.type === "story" || item.type === "subtask") && !hasStoryWorkflow) {
25693
+ errors.push({ index, message: "story/subtask tickets require a story workflow \u2014 create one first" });
25694
+ }
25695
+ const refs = [...item.dependsOn ? [item.dependsOn] : [], ...item.waitsFor ?? [], ...item.subtaskIds ?? []];
25696
+ for (const ref of refs) {
25697
+ if (!tempIds.has(ref) && !existingCardIds.has(ref)) {
25698
+ errors.push({ index, message: `unknown reference "${ref}"` });
25699
+ }
25700
+ }
25701
+ });
25702
+ if (errors.length > 0) throw BadRequestError("Import validation failed", errors);
25703
+ const baseRef = requestedBase || config.defaultBaseBranch || getDefaultBranch(ws.repoPath);
25704
+ const cards = await createCardsBulk(workspaceId, items, baseRef);
25705
+ return { cards };
25706
+ };
25762
25707
  var listBranchesService = async (workspaceId, remote = false) => {
25763
25708
  const workspaces = await listWorkspaces();
25764
25709
  const ws = workspaces.find((w2) => w2.workspaceId === workspaceId);
@@ -25963,11 +25908,6 @@ var moveCardService = async (workspaceId, cardId, targetColumnId, targetIndex) =
25963
25908
  }
25964
25909
  if (targetColumnId === "reopened") {
25965
25910
  await updateCard(workspaceId, cardId, { autoFixAttempts: 0 });
25966
- const movedBoard = await loadBoard(workspaceId);
25967
- const movedCard = movedBoard.cards[cardId];
25968
- if (movedCard) {
25969
- return { board, reopenCascade: { movedCard, boardCards: movedBoard.cards } };
25970
- }
25971
25911
  }
25972
25912
  return { board };
25973
25913
  };
@@ -26065,11 +26005,6 @@ var submitHumanFeedbackService = async (workspaceId, cardId, comment, attachment
26065
26005
  await moveCard(workspaceId, cardId, "reopened");
26066
26006
  await clearCardSession(workspaceId, cardId);
26067
26007
  await appendActivityLog(workspaceId, cardId, "Human feedback submitted \u2192 moved to Reopened");
26068
- const feedbackBoard = await loadBoard(workspaceId);
26069
- const feedbackCard = feedbackBoard.cards[cardId];
26070
- if (feedbackCard) {
26071
- return { ok: true, reopenCascade: { feedbackCard, boardCards: feedbackBoard.cards } };
26072
- }
26073
26008
  return { ok: true };
26074
26009
  };
26075
26010
  var prepareStartAgentService = async (workspaceId, cardId) => {
@@ -26212,6 +26147,12 @@ var cardsController = new Hono2().post("/", zv("json", runtimeCardCreateRequestS
26212
26147
  const card = await createCardService(workspaceId, cardData, baseRef);
26213
26148
  ctx.stateHub.broadcastWorkspaceUpdate(workspaceId);
26214
26149
  return c.json(card);
26150
+ }).post("/bulk", zv("json", runtimeBulkCardsCreateRequestSchema.extend({ workspaceId: z5.string() })), async (c) => {
26151
+ const ctx = c.var.ctx;
26152
+ const { workspaceId, baseRef, cards } = c.req.valid("json");
26153
+ const result = await bulkCreateCardsService(workspaceId, cards, baseRef);
26154
+ ctx.stateHub.broadcastWorkspaceUpdate(workspaceId);
26155
+ return c.json(result);
26215
26156
  }).get(
26216
26157
  "/branches",
26217
26158
  zv("query", z5.object({ workspaceId: z5.string(), remote: z5.enum(["true", "false"]).optional() })),
@@ -26304,12 +26245,6 @@ var cardsController = new Hono2().post("/", zv("json", runtimeCardCreateRequestS
26304
26245
  const ctx = c.var.ctx;
26305
26246
  const { workspaceId, cardId, targetColumnId, targetIndex } = c.req.valid("json");
26306
26247
  const result = await moveCardService(workspaceId, cardId, targetColumnId, targetIndex);
26307
- if (result.reopenCascade) {
26308
- const scheduler = ctx.getScheduler(workspaceId);
26309
- if (scheduler) {
26310
- void scheduler.triggerParentReopenCascade(result.reopenCascade.movedCard, result.reopenCascade.boardCards);
26311
- }
26312
- }
26313
26248
  ctx.stateHub.broadcastWorkspaceUpdate(workspaceId);
26314
26249
  return c.json(result.board);
26315
26250
  }).delete(
@@ -26381,12 +26316,6 @@ var cardsController = new Hono2().post("/", zv("json", runtimeCardCreateRequestS
26381
26316
  const { workspaceId, cardId, comment, attachments, type, metadata } = c.req.valid("json");
26382
26317
  const result = await submitHumanFeedbackService(workspaceId, cardId, comment, attachments, type, metadata);
26383
26318
  ctx.stateHub.broadcastWorkspaceUpdate(workspaceId);
26384
- if (result.reopenCascade) {
26385
- const scheduler = ctx.getScheduler(workspaceId);
26386
- if (scheduler) {
26387
- void scheduler.triggerParentReopenCascade(result.reopenCascade.feedbackCard, result.reopenCascade.boardCards);
26388
- }
26389
- }
26390
26319
  return c.json({ ok: result.ok });
26391
26320
  }
26392
26321
  ).post("/start-agent", zv("json", z5.object({ workspaceId: z5.string(), cardId: z5.string() })), async (c) => {
@@ -26403,12 +26332,6 @@ var cardsController = new Hono2().post("/", zv("json", runtimeCardCreateRequestS
26403
26332
  ctx.getScheduler(workspaceId)?.stopTask(cardId);
26404
26333
  ctx.stateHub.broadcastWorkspaceUpdate(workspaceId);
26405
26334
  return c.json({ ok: true });
26406
- }).post("/interrupt-task", zv("json", z5.object({ workspaceId: z5.string(), cardId: z5.string() })), async (c) => {
26407
- const ctx = c.var.ctx;
26408
- const { workspaceId, cardId } = c.req.valid("json");
26409
- ctx.getScheduler(workspaceId)?.interruptForParentReopen(cardId);
26410
- ctx.stateHub.broadcastWorkspaceUpdate(workspaceId);
26411
- return c.json({ ok: true });
26412
26335
  }).post(
26413
26336
  "/set-pr-meta",
26414
26337
  zv(
@@ -28638,7 +28561,7 @@ process.on("uncaughtException", (err) => {
28638
28561
  if (err.code === "EPIPE" || err.code === "ECONNRESET") return;
28639
28562
  throw err;
28640
28563
  });
28641
- var VERSION9 = true ? "0.4.0" : "0.0.0-dev";
28564
+ var VERSION9 = true ? "0.5.1" : "0.0.0-dev";
28642
28565
  async function isPortAvailable(port, host) {
28643
28566
  return new Promise((resolve5) => {
28644
28567
  const probe = createServer2();
@@ -33,7 +33,7 @@ function highestWorkflowLevel(workflow) {
33
33
  }
34
34
  return LEVEL_ORDER[bestIdx] ?? "medium";
35
35
  }
36
- var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
36
+ var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeBulkCardImportItemSchema, runtimeBulkCardsCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
37
37
  var init_api_contract = __esm({
38
38
  "src/core/api-contract.ts"() {
39
39
  "use strict";
@@ -466,6 +466,14 @@ Do NOT include:
466
466
  modelConfig: cardModelConfigSchema.optional(),
467
467
  activeLevel: tierLevelSchema.optional()
468
468
  });
469
+ runtimeBulkCardImportItemSchema = runtimeCardCreateRequestSchema.extend({
470
+ tempId: z.string().optional()
471
+ });
472
+ runtimeBulkCardsCreateRequestSchema = z.object({
473
+ // Batch-wide base branch; an item's own baseRef overrides it, else resolved server-side.
474
+ baseRef: z.string().optional(),
475
+ cards: z.array(runtimeBulkCardImportItemSchema).min(1)
476
+ });
469
477
  runtimeCardMoveRequestSchema = z.object({
470
478
  cardId: z.string(),
471
479
  targetColumnId: runtimeBoardColumnIdSchema,
@@ -794,6 +802,7 @@ __export(workspace_state_exports, {
794
802
  clearCardSession: () => clearCardSession,
795
803
  closeAllOpenTerminalSessions: () => closeAllOpenTerminalSessions,
796
804
  createCard: () => createCard,
805
+ createCardsBulk: () => createCardsBulk,
797
806
  deleteCard: () => deleteCard,
798
807
  downloadGithubImages: () => downloadGithubImages,
799
808
  endTerminalSession: () => endTerminalSession,
@@ -1344,6 +1353,79 @@ async function createCard(workspaceId2, data, baseRef) {
1344
1353
  tx();
1345
1354
  return card;
1346
1355
  }
1356
+ async function createCardsBulk(workspaceId2, items, baseRef) {
1357
+ const db = getDb();
1358
+ const now = Date.now();
1359
+ const projectConfig = loadProjectConfigInternal(workspaceId2);
1360
+ const realIds = items.map(() => generateTaskId());
1361
+ const tempIdToRealId = /* @__PURE__ */ new Map();
1362
+ items.forEach((item, i) => {
1363
+ if (item.tempId) tempIdToRealId.set(item.tempId, realIds[i]);
1364
+ });
1365
+ const resolveRef = (ref) => tempIdToRealId.get(ref) ?? ref;
1366
+ const cards = items.map((item, i) => {
1367
+ const type = item.type ?? "task";
1368
+ const columnId = item.columnId ?? "todo";
1369
+ const workflow = resolveWorkflowForCard(projectConfig.workflows, { workflowId: item.workflowId, type });
1370
+ const waitsFor = (item.waitsFor ?? []).map(resolveRef);
1371
+ const dependsOn = item.dependsOn ? resolveRef(item.dependsOn) : void 0;
1372
+ return {
1373
+ id: realIds[i],
1374
+ description: item.description,
1375
+ columnId,
1376
+ type,
1377
+ readyForDev: item.readyForDev ?? type === "story",
1378
+ agentId: item.agentId,
1379
+ priority: item.priority,
1380
+ // dependsOn (stacking) and waitsFor (gate) are mutually exclusive — waitsFor wins.
1381
+ dependsOn: waitsFor.length > 0 ? void 0 : dependsOn,
1382
+ waitsFor,
1383
+ subtaskIds: (item.subtaskIds ?? []).map(resolveRef),
1384
+ autoFixAttempts: 0,
1385
+ activeLevel: item.activeLevel ?? highestWorkflowLevel(workflow),
1386
+ modelConfig: item.modelConfig ?? snapshotModelConfig(workflow),
1387
+ baseRef: item.baseRef ?? baseRef,
1388
+ createdAt: now,
1389
+ updatedAt: now,
1390
+ githubIssueUrl: item.githubIssueUrl,
1391
+ workflowId: item.workflowId ?? workflow?.id,
1392
+ descriptionAttachments: item.descriptionAttachments ?? [],
1393
+ branchName: item.branchName,
1394
+ reviewComments: [],
1395
+ activityLog: [],
1396
+ terminalSessions: [],
1397
+ githubCommentIds: []
1398
+ };
1399
+ });
1400
+ const columnCounts = /* @__PURE__ */ new Map();
1401
+ const nextPosition = (columnId) => {
1402
+ if (!columnCounts.has(columnId)) {
1403
+ const row = db.prepare("SELECT COUNT(*) AS n FROM cards WHERE workspace_id = ? AND column_id = ?").get(workspaceId2, columnId);
1404
+ columnCounts.set(columnId, row.n);
1405
+ }
1406
+ const pos = columnCounts.get(columnId);
1407
+ columnCounts.set(columnId, pos + 1);
1408
+ return pos;
1409
+ };
1410
+ const tx = db.transaction(() => {
1411
+ for (const card of cards) {
1412
+ upsertCardRow(db, workspaceId2, { ...card, dependsOn: void 0 }, nextPosition(card.columnId));
1413
+ }
1414
+ const setDep = db.prepare("UPDATE cards SET depends_on_id = ? WHERE id = ?");
1415
+ for (const card of cards) {
1416
+ if (card.dependsOn && db.prepare("SELECT 1 FROM cards WHERE id = ?").get(card.dependsOn)) {
1417
+ setDep.run(card.dependsOn, card.id);
1418
+ } else if (card.dependsOn) {
1419
+ card.dependsOn = void 0;
1420
+ }
1421
+ replaceCardWaitsFor(db, card.id, card.waitsFor ?? []);
1422
+ replaceCardSubtasks(db, card.id, card.subtaskIds ?? []);
1423
+ }
1424
+ bumpBoardRevision(db, workspaceId2);
1425
+ });
1426
+ tx();
1427
+ return cards;
1428
+ }
1347
1429
  async function appendActivityLog(workspaceId2, cardId, message) {
1348
1430
  const db = getDb();
1349
1431
  const tx = db.transaction(() => {
@@ -1610,7 +1692,6 @@ var ROUTES = {
1610
1692
  "cards.move": { method: "POST", path: () => "cards/move" },
1611
1693
  "cards.delete": { method: "DELETE", path: (i) => `cards/${i.cardId}` },
1612
1694
  "cards.addReviewComment": { method: "POST", path: () => "cards/add-review-comment" },
1613
- "cards.interruptTask": { method: "POST", path: () => "cards/interrupt-task" },
1614
1695
  "cards.setPrMeta": { method: "POST", path: () => "cards/set-pr-meta" },
1615
1696
  "cards.setPlan": { method: "POST", path: () => "cards/set-plan" },
1616
1697
  "workflows.upsert": { method: "POST", path: () => "workflows" },
@@ -2065,19 +2146,6 @@ registerTool(
2065
2146
  return { content: [{ type: "text", text: `Deleted card ${cardId}.` }] };
2066
2147
  }
2067
2148
  );
2068
- registerTool(
2069
- "kanban_stop_task",
2070
- {
2071
- description: "Stop an in-progress agent task. The session is marked 'stopped' (preserving history) so the card can be restarted later. Use this before moving a child card to todo when its parent was reopened.",
2072
- inputSchema: {
2073
- cardId: z2.string().describe("The card ID of the in-progress task to stop")
2074
- }
2075
- },
2076
- async ({ cardId }) => {
2077
- await apiMutate("cards.interruptTask", { workspaceId, cardId });
2078
- return { content: [{ type: "text", text: `Task ${cardId} interrupted.` }] };
2079
- }
2080
- );
2081
2149
  registerTool(
2082
2150
  "kanban_get_workflows",
2083
2151
  {