@wrongstack/webui 0.281.3 → 0.282.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.
Files changed (46) hide show
  1. package/dist/assets/{AnalyticsDashboard-Cb08RPms.js → AnalyticsDashboard-TCNC-sVb.js} +1 -1
  2. package/dist/assets/AutoPhaseView-BMUiT6aI.js +1 -0
  3. package/dist/assets/{CodeEditor-CSTcX3Za.js → CodeEditor-C0si742k.js} +1 -1
  4. package/dist/assets/{DebugDashboard-D6-znhBO.js → DebugDashboard-BHpiEOcf.js} +1 -1
  5. package/dist/assets/{DesignGalleryView-yKa55dpM.js → DesignGalleryView-BfZTw48N.js} +1 -1
  6. package/dist/assets/KanbanView-C4T4y0b7.js +1 -0
  7. package/dist/assets/{MailboxDetailView-DbX_cNcu.js → MailboxDetailView-DJtjP0NF.js} +1 -1
  8. package/dist/assets/OfficeMapPanel-CgvHvGQm.js +4 -0
  9. package/dist/assets/{ProcessMonitor-DvPiHdzj.js → ProcessMonitor-D53zn0h4.js} +1 -1
  10. package/dist/assets/{QueuePanel-BVvnJXHh.js → QueuePanel-DTO68vaB.js} +1 -1
  11. package/dist/assets/{RefreshDebugView-pmMPOier.js → RefreshDebugView-BXq2P3wo.js} +1 -1
  12. package/dist/assets/{SddBoardView-CGJbbRrh.js → SddBoardView-C8X4MiTy.js} +2 -2
  13. package/dist/assets/{SddFlowGraph-WW0qCx0t.js → SddFlowGraph-CtMpzU4S.js} +1 -1
  14. package/dist/assets/SddWizard-t5Cly3CG.js +1 -0
  15. package/dist/assets/{SessionsDashboard-VwXvmSxS.js → SessionsDashboard-Dqsrp5CK.js} +1 -1
  16. package/dist/assets/SkillDetailView-Dq-fTqSp.js +2 -0
  17. package/dist/assets/{SpecsView-BRdxB6rh.js → SpecsView-Cwd6PiYr.js} +1 -1
  18. package/dist/assets/{TerminalPanel-CAcbPNO_.js → TerminalPanel-Bzyhr2aC.js} +1 -1
  19. package/dist/assets/{chat-store-YPRVJGbn.js → chat-store-CIMOL6-x.js} +1 -1
  20. package/dist/assets/{i18n-BJCOtLro.js → i18n-CNo6A2vu.js} +1 -1
  21. package/dist/assets/index-CWj2bI87.js +145 -0
  22. package/dist/assets/index-D7_FeyCA.css +1 -0
  23. package/dist/assets/{session-store-Do0MGnXu.js → session-store-DN1UYDL9.js} +1 -1
  24. package/dist/assets/ui-store-BdOsq6T-.js +1 -0
  25. package/dist/assets/utils-DBns3uMk.js +1 -0
  26. package/dist/assets/{vendor-Bhf0nZ3B.js → vendor-Ch4m_qLI.js} +25 -25
  27. package/dist/assets/{ws-client-D9IQ9580.js → ws-client-BAV7SLzK.js} +1 -1
  28. package/dist/assets/{xyflow-Bp8EMwkC.js → xyflow-D5_yey7w.js} +3 -3
  29. package/dist/index.html +9 -9
  30. package/dist/index.js +6253 -5282
  31. package/dist/index.js.map +1 -1
  32. package/dist/providers.json +2 -2
  33. package/dist/server/entry.js +642 -21
  34. package/dist/server/entry.js.map +1 -1
  35. package/dist/server/index.js +642 -21
  36. package/dist/server/index.js.map +1 -1
  37. package/dist/types.d.ts +18 -3
  38. package/package.json +6 -6
  39. package/dist/assets/AutoPhaseView--Ec4Mom8.js +0 -1
  40. package/dist/assets/OfficeMapPanel-BMxzEv_S.js +0 -4
  41. package/dist/assets/SddWizard-Cen90k6M.js +0 -1
  42. package/dist/assets/SkillDetailView-CjUeZv6V.js +0 -2
  43. package/dist/assets/index-BSuiEQIC.js +0 -145
  44. package/dist/assets/index-CMiinLaB.css +0 -1
  45. package/dist/assets/ui-store-D3gGpJWY.js +0 -1
  46. package/dist/assets/utils-sFCTikqz.js +0 -1
@@ -4864,7 +4864,7 @@ ${trigger}
4864
4864
  ` : "",
4865
4865
  "## Skills in scope",
4866
4866
  "- `bug-hunter` \u2014 for systematic bug detection patterns",
4867
- "- `output-standards` \u2014 for standardized `<next_steps>` formatting"
4867
+ "- `output-standards` \u2014 for standardized `<nextsteps>` formatting"
4868
4868
  ].join("\n");
4869
4869
  await atomicWrite5(path11.join(targetDir, "SKILL.md"), skillContent);
4870
4870
  send(ws, {
@@ -5720,7 +5720,16 @@ function setupEvents(deps2) {
5720
5720
  });
5721
5721
  });
5722
5722
  events.on("provider.response", (e) => {
5723
- broadcast2(clients, { type: "provider.response", payload: sessionPayload2({ sessionId: e.sessionId, usage: e.usage, stopReason: e.stopReason, messageId: "current" }) });
5723
+ broadcast2(clients, {
5724
+ type: "provider.response",
5725
+ payload: sessionPayload2({
5726
+ sessionId: e.sessionId,
5727
+ content: e.content,
5728
+ usage: e.usage,
5729
+ stopReason: e.stopReason,
5730
+ messageId: "current"
5731
+ })
5732
+ });
5724
5733
  });
5725
5734
  events.on("ctx.pct", (e) => {
5726
5735
  broadcast2(clients, {
@@ -6060,7 +6069,14 @@ function setupEvents(deps2) {
6060
6069
  await fs10.mkdir(dir, { recursive: true });
6061
6070
  await fs10.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
6062
6071
  } catch (err) {
6063
- console.error("[setup-events] Failed to write status.json:", err);
6072
+ console.error(
6073
+ JSON.stringify({
6074
+ level: "error",
6075
+ event: "setup_events.status_write_failed",
6076
+ message: err instanceof Error ? err.message : String(err),
6077
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6078
+ })
6079
+ );
6064
6080
  }
6065
6081
  }
6066
6082
  });
@@ -6150,7 +6166,14 @@ function setupEvents(deps2) {
6150
6166
  console.log(`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`);
6151
6167
  }
6152
6168
  } catch (err) {
6153
- console.error("[setup-events] Failed to start status file watcher:", err);
6169
+ console.error(
6170
+ JSON.stringify({
6171
+ level: "error",
6172
+ event: "setup_events.status_watcher_start_failed",
6173
+ message: err instanceof Error ? err.message : String(err),
6174
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6175
+ })
6176
+ );
6154
6177
  }
6155
6178
  };
6156
6179
  events.on("client.status", (e) => {
@@ -6610,6 +6633,39 @@ import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
6610
6633
  import { expectDefined as expectDefined2 } from "@wrongstack/core";
6611
6634
  import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
6612
6635
  import { makeProviderFromConfig } from "@wrongstack/providers";
6636
+ var UNCONFIGURED_CAPABILITIES = {
6637
+ tools: false,
6638
+ parallelTools: false,
6639
+ vision: false,
6640
+ streaming: false,
6641
+ promptCache: false,
6642
+ systemPrompt: false,
6643
+ jsonMode: false,
6644
+ reasoning: false,
6645
+ maxContext: 0,
6646
+ cacheControl: "none",
6647
+ topK: false,
6648
+ frequencyPenalty: false,
6649
+ presencePenalty: false,
6650
+ seed: false,
6651
+ structuredOutput: false,
6652
+ logprobs: false,
6653
+ audio: false,
6654
+ multipleCompletions: false
6655
+ };
6656
+ function makeUnconfiguredProvider() {
6657
+ const message = "No provider configured. Complete setup before starting an agent run.";
6658
+ return {
6659
+ id: "unconfigured",
6660
+ capabilities: UNCONFIGURED_CAPABILITIES,
6661
+ async *stream() {
6662
+ throw new Error(message);
6663
+ },
6664
+ async complete() {
6665
+ throw new Error(message);
6666
+ }
6667
+ };
6668
+ }
6613
6669
  function logCreateFailure(event, err) {
6614
6670
  console.error(
6615
6671
  JSON.stringify({
@@ -6656,11 +6712,7 @@ function resolveSetupProvider(opts) {
6656
6712
  }
6657
6713
  }
6658
6714
  console.log("[WebUI] No providers configured \u2014 showing setup screen");
6659
- const provider = makeProviderFromConfig("anthropic", {
6660
- type: "anthropic",
6661
- family: "anthropic",
6662
- apiKey: "stub-key-replaced-on-setup"
6663
- });
6715
+ const provider = makeUnconfiguredProvider();
6664
6716
  return { provider, needsSetup: true };
6665
6717
  }
6666
6718
 
@@ -8702,12 +8754,13 @@ function createConnectionHandler(opts) {
8702
8754
  void opts.sessionStartPayload().then(async (payload) => {
8703
8755
  const enriched = { ...payload };
8704
8756
  try {
8705
- const live = opts.context.messages ?? [];
8757
+ const replay = await opts.loadReplay?.();
8758
+ const live = replay?.messages ?? opts.context.messages ?? [];
8706
8759
  const slice = live.length > REPLAY_MESSAGE_CAP ? live.slice(-REPLAY_MESSAGE_CAP) : live;
8707
8760
  if (slice.length > 0) {
8708
8761
  enriched.replayMessages = slice;
8709
8762
  }
8710
- const total = opts.tokenCounter.total();
8763
+ const total = replay?.usage ?? opts.tokenCounter.total();
8711
8764
  if (total.input + total.output + (total.cacheRead ?? 0) + (total.cacheWrite ?? 0) > 0) {
8712
8765
  enriched.replayUsage = {
8713
8766
  input: total.input,
@@ -8834,8 +8887,8 @@ async function handleGoalGet(projectRoot, broadcast2) {
8834
8887
  }
8835
8888
 
8836
8889
  // src/server/handlers/worklist-handlers.ts
8837
- function sendResult2(ws, ctx, ok, message) {
8838
- ctx.send(ws, { type: ok ? "ok" : "error", message });
8890
+ function sendResult2(ws, ctx, ok2, message) {
8891
+ ctx.send(ws, { type: ok2 ? "ok" : "error", message });
8839
8892
  }
8840
8893
  function sessionPayload(ctx, payload) {
8841
8894
  const provided = payload["sessionId"];
@@ -9056,6 +9109,541 @@ async function handleWorklistMessage(ctx, ws, msg) {
9056
9109
  }
9057
9110
  }
9058
9111
 
9112
+ // src/server/kanban-routes.ts
9113
+ import {
9114
+ addColumn,
9115
+ addGoalMetricToTask,
9116
+ addTask,
9117
+ assignTask,
9118
+ claimReadyTask,
9119
+ copyTaskToBoard,
9120
+ createBoard,
9121
+ deserializeTaskGraph,
9122
+ duplicateBoard,
9123
+ exportBoardToTaskGraph,
9124
+ generateBoardFromDescription,
9125
+ getBoard,
9126
+ getKanbanOrchestrationSnapshot,
9127
+ getTask,
9128
+ getTaskChain,
9129
+ listReadyTasks,
9130
+ listBoards,
9131
+ mergeTasks,
9132
+ moveTask,
9133
+ parseLinesIntoTasks,
9134
+ removeBoard,
9135
+ removeColumn,
9136
+ removeTask,
9137
+ releaseTaskClaim,
9138
+ serializeTaskGraph,
9139
+ setTaskChain,
9140
+ splitTask,
9141
+ syncBoardFromTaskGraph,
9142
+ transferTaskToBoard,
9143
+ updateBoard,
9144
+ updateGoalMetricOnTask,
9145
+ updateTask
9146
+ } from "@wrongstack/core";
9147
+ function ok(ws, type, data) {
9148
+ send(ws, { type, payload: { success: true, data: data ?? null } });
9149
+ }
9150
+ function fail(ws, type, message) {
9151
+ send(ws, { type, payload: { success: false, error: message } });
9152
+ }
9153
+ async function handleKanbanRoute(ws, msg, ctx) {
9154
+ if (!msg.type.startsWith("kanban.")) return false;
9155
+ const payload = msg.payload;
9156
+ const type = msg.type;
9157
+ try {
9158
+ switch (type) {
9159
+ case "kanban.list":
9160
+ ok(ws, type, await listBoards(ctx.projectRoot));
9161
+ return true;
9162
+ case "kanban.get": {
9163
+ const boardId = payload?.boardId;
9164
+ if (!boardId) {
9165
+ fail(ws, type, "boardId required");
9166
+ return true;
9167
+ }
9168
+ const board = await getBoard(ctx.projectRoot, boardId);
9169
+ board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
9170
+ return true;
9171
+ }
9172
+ case "kanban.create": {
9173
+ const title = payload?.title;
9174
+ if (!title) {
9175
+ fail(ws, type, "title required");
9176
+ return true;
9177
+ }
9178
+ ok(
9179
+ ws,
9180
+ type,
9181
+ await createBoard(ctx.projectRoot, {
9182
+ title,
9183
+ ...payload?.description ? { description: payload.description } : {},
9184
+ ...payload?.tags ? { tags: payload.tags } : {},
9185
+ ...payload?.columns ? { columns: payload.columns } : {}
9186
+ })
9187
+ );
9188
+ return true;
9189
+ }
9190
+ case "kanban.update": {
9191
+ const boardId = payload?.boardId;
9192
+ if (!boardId) {
9193
+ fail(ws, type, "boardId required");
9194
+ return true;
9195
+ }
9196
+ const board = await updateBoard(ctx.projectRoot, boardId, {
9197
+ ...payload?.title ? { title: payload.title } : {},
9198
+ ...payload?.description ? { description: payload.description } : {},
9199
+ ...payload?.tags ? { tags: payload.tags } : {},
9200
+ ...payload?.columns ? { columns: payload.columns } : {}
9201
+ });
9202
+ board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
9203
+ return true;
9204
+ }
9205
+ case "kanban.duplicate": {
9206
+ const boardId = payload?.boardId;
9207
+ if (!boardId) {
9208
+ fail(ws, type, "boardId required");
9209
+ return true;
9210
+ }
9211
+ const board = await duplicateBoard(ctx.projectRoot, boardId, {
9212
+ ...payload?.title ? { title: payload.title } : {},
9213
+ ...typeof payload?.includeTasks === "boolean" ? { includeTasks: payload.includeTasks } : {},
9214
+ ...typeof payload?.includeCompletedTasks === "boolean" ? { includeCompletedTasks: payload.includeCompletedTasks } : {},
9215
+ ...typeof payload?.preserveAssignment === "boolean" ? { preserveAssignment: payload.preserveAssignment } : {}
9216
+ });
9217
+ board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
9218
+ return true;
9219
+ }
9220
+ case "kanban.delete": {
9221
+ const boardId = payload?.boardId;
9222
+ if (!boardId) {
9223
+ fail(ws, type, "boardId required");
9224
+ return true;
9225
+ }
9226
+ const board = await getBoard(ctx.projectRoot, boardId);
9227
+ ok(ws, type, {
9228
+ removed: board ? await removeBoard(ctx.projectRoot, board.id) : false,
9229
+ boardId: board?.id ?? boardId
9230
+ });
9231
+ return true;
9232
+ }
9233
+ case "kanban.generate": {
9234
+ const description = payload?.description;
9235
+ if (!description) {
9236
+ fail(ws, type, "description required");
9237
+ return true;
9238
+ }
9239
+ const board = await createBoard(
9240
+ ctx.projectRoot,
9241
+ generateBoardFromDescription({
9242
+ description,
9243
+ ...payload?.title ? { title: payload.title } : {},
9244
+ ...payload?.context ? { context: payload.context } : {}
9245
+ })
9246
+ );
9247
+ for (const taskInput of parseLinesIntoTasks(
9248
+ description,
9249
+ board.columns[0]?.id ?? "backlog"
9250
+ )) {
9251
+ await addTask(ctx.projectRoot, board.id, taskInput);
9252
+ }
9253
+ ok(ws, type, await getBoard(ctx.projectRoot, board.id) ?? board);
9254
+ return true;
9255
+ }
9256
+ case "kanban.task.ready": {
9257
+ ok(
9258
+ ws,
9259
+ type,
9260
+ await listReadyTasks(ctx.projectRoot, {
9261
+ ...payload?.boardId ? { boardId: payload.boardId } : {},
9262
+ ...payload?.query ? { query: payload.query } : {},
9263
+ ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
9264
+ ...payload?.priority ? { priority: payload.priority } : {},
9265
+ ...payload?.label ? { label: payload.label } : {},
9266
+ ...payload?.chainId ? { chainId: payload.chainId } : {},
9267
+ ...typeof payload?.limit === "number" ? { limit: payload.limit } : {}
9268
+ })
9269
+ );
9270
+ return true;
9271
+ }
9272
+ case "kanban.snapshot": {
9273
+ ok(
9274
+ ws,
9275
+ type,
9276
+ await getKanbanOrchestrationSnapshot(ctx.projectRoot, {
9277
+ ...payload?.boardId ? { boardId: payload.boardId } : {},
9278
+ ...payload?.query ? { query: payload.query } : {},
9279
+ ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
9280
+ ...payload?.priority ? { priority: payload.priority } : {},
9281
+ ...payload?.status ? { status: payload.status } : {},
9282
+ ...payload?.label ? { label: payload.label } : {},
9283
+ ...payload?.chainId ? { chainId: payload.chainId } : {}
9284
+ })
9285
+ );
9286
+ return true;
9287
+ }
9288
+ case "kanban.taskgraph.export": {
9289
+ const boardId = payload?.boardId;
9290
+ if (!boardId) {
9291
+ fail(ws, type, "boardId required");
9292
+ return true;
9293
+ }
9294
+ const exported = await exportBoardToTaskGraph(ctx.projectRoot, boardId, {
9295
+ ...payload?.graphId ? { graphId: payload.graphId } : {},
9296
+ ...payload?.specId ? { specId: payload.specId } : {},
9297
+ ...payload?.title ? { title: payload.title } : {},
9298
+ ...typeof payload?.preserveOriginTaskIds === "boolean" ? { preserveOriginTaskIds: payload.preserveOriginTaskIds } : {},
9299
+ ...typeof payload?.includeArchived === "boolean" ? { includeArchived: payload.includeArchived } : {}
9300
+ });
9301
+ exported ? ok(ws, type, { board: exported.board, taskGraph: serializeTaskGraph(exported.graph) }) : fail(ws, type, `Board not found: ${boardId}`);
9302
+ return true;
9303
+ }
9304
+ case "kanban.taskgraph.sync": {
9305
+ const boardId = payload?.boardId;
9306
+ const taskGraph = payload?.taskGraph;
9307
+ if (!boardId || !taskGraph) {
9308
+ fail(ws, type, "boardId and taskGraph required");
9309
+ return true;
9310
+ }
9311
+ const result = await syncBoardFromTaskGraph(
9312
+ ctx.projectRoot,
9313
+ boardId,
9314
+ deserializeTaskGraph(taskGraph),
9315
+ {
9316
+ ...payload?.title ? { title: payload.title } : {},
9317
+ ...payload?.description ? { description: payload.description } : {},
9318
+ ...payload?.tags ? { tags: payload.tags } : {},
9319
+ ...payload?.generatedBy ? { generatedBy: payload.generatedBy } : {},
9320
+ ...payload?.sourceSystem ? { sourceSystem: payload.sourceSystem } : {},
9321
+ ...payload?.phaseId ? { phaseId: payload.phaseId } : {},
9322
+ ...typeof payload?.includeCompletedTasks === "boolean" ? { includeCompletedTasks: payload.includeCompletedTasks } : {},
9323
+ ...typeof payload?.archiveMissingTasks === "boolean" ? { archiveMissingTasks: payload.archiveMissingTasks } : {},
9324
+ ...typeof payload?.preserveManualDependencies === "boolean" ? { preserveManualDependencies: payload.preserveManualDependencies } : {}
9325
+ }
9326
+ );
9327
+ result ? ok(ws, type, {
9328
+ board: result.board,
9329
+ taskIdMap: Object.fromEntries(result.taskIdMap),
9330
+ createdTaskIds: result.createdTaskIds,
9331
+ updatedTaskIds: result.updatedTaskIds,
9332
+ archivedTaskIds: result.archivedTaskIds
9333
+ }) : fail(ws, type, `Board not found: ${boardId}`);
9334
+ return true;
9335
+ }
9336
+ case "kanban.task.add": {
9337
+ const boardId = payload?.boardId;
9338
+ const title = payload?.title;
9339
+ if (!boardId || !title) {
9340
+ fail(ws, type, "boardId and title required");
9341
+ return true;
9342
+ }
9343
+ const result = await addTask(ctx.projectRoot, boardId, {
9344
+ title,
9345
+ columnId: payload?.columnId ?? "backlog",
9346
+ ...payload?.description ? { description: payload.description } : {},
9347
+ ...payload?.priority ? { priority: payload.priority } : {},
9348
+ ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
9349
+ ...payload?.labels ? { labels: payload.labels } : {}
9350
+ });
9351
+ result ? ok(ws, type, result.task) : fail(ws, type, `Board not found: ${boardId}`);
9352
+ return true;
9353
+ }
9354
+ case "kanban.task.split": {
9355
+ const boardId = payload?.boardId;
9356
+ const taskId = payload?.taskId;
9357
+ const childTitles = payload?.childTitles;
9358
+ if (!boardId || !taskId || !childTitles?.length) {
9359
+ fail(ws, type, "boardId, taskId, and childTitles required");
9360
+ return true;
9361
+ }
9362
+ const result = await splitTask(ctx.projectRoot, boardId, taskId, {
9363
+ titles: childTitles,
9364
+ ...payload?.targetColumnId ? { columnId: payload.targetColumnId } : {},
9365
+ ...typeof payload?.inheritAssignment === "boolean" ? { inheritAssignment: payload.inheritAssignment } : {},
9366
+ ...typeof payload?.inheritLabels === "boolean" ? { inheritLabels: payload.inheritLabels } : {},
9367
+ ...typeof payload?.inheritSuccessCriteria === "boolean" ? { inheritSuccessCriteria: payload.inheritSuccessCriteria } : {},
9368
+ ...typeof payload?.inheritGoalMetrics === "boolean" ? { inheritGoalMetrics: payload.inheritGoalMetrics } : {},
9369
+ ...typeof payload?.inheritDependencies === "boolean" ? { inheritDependencies: payload.inheritDependencies } : {},
9370
+ ...typeof payload?.chainChildren === "boolean" ? { chainChildren: payload.chainChildren } : {},
9371
+ ...typeof payload?.rewireDependents === "boolean" ? { rewireDependents: payload.rewireDependents } : {}
9372
+ });
9373
+ result ? ok(ws, type, { board: result.board, parent: result.parent, children: result.children }) : fail(ws, type, "Board or task not found");
9374
+ return true;
9375
+ }
9376
+ case "kanban.task.merge": {
9377
+ const boardId = payload?.boardId;
9378
+ const taskIds = payload?.taskIds;
9379
+ const title = payload?.title;
9380
+ if (!boardId || !taskIds?.length || !title) {
9381
+ fail(ws, type, "boardId, taskIds, and title required");
9382
+ return true;
9383
+ }
9384
+ const result = await mergeTasks(ctx.projectRoot, boardId, {
9385
+ taskIds,
9386
+ title,
9387
+ ...payload?.description ? { description: payload.description } : {},
9388
+ ...payload?.targetColumnId ? { targetColumnId: payload.targetColumnId } : {},
9389
+ ...typeof payload?.preserveAssignment === "boolean" ? { preserveAssignment: payload.preserveAssignment } : {},
9390
+ ...typeof payload?.closeSourceTasks === "boolean" ? { closeSourceTasks: payload.closeSourceTasks } : {}
9391
+ });
9392
+ result ? ok(ws, type, {
9393
+ board: result.board,
9394
+ task: result.task,
9395
+ sourceTasks: result.sourceTasks
9396
+ }) : fail(ws, type, "Board or task not found");
9397
+ return true;
9398
+ }
9399
+ case "kanban.task.update": {
9400
+ const boardId = payload?.boardId;
9401
+ const taskId = payload?.taskId;
9402
+ if (!boardId || !taskId) {
9403
+ fail(ws, type, "boardId and taskId required");
9404
+ return true;
9405
+ }
9406
+ const board = await updateTask(ctx.projectRoot, boardId, taskId, {
9407
+ ...payload?.title ? { title: payload.title } : {},
9408
+ ...payload?.description ? { description: payload.description } : {},
9409
+ ...payload?.columnId ? { columnId: payload.columnId } : {},
9410
+ ...payload?.priority ? { priority: payload.priority } : {},
9411
+ ...payload?.status ? { status: payload.status } : {},
9412
+ ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
9413
+ ...payload?.labels ? { labels: payload.labels } : {}
9414
+ });
9415
+ board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
9416
+ return true;
9417
+ }
9418
+ case "kanban.task.move": {
9419
+ const boardId = payload?.boardId;
9420
+ const taskId = payload?.taskId;
9421
+ const columnId = payload?.columnId;
9422
+ if (!boardId || !taskId || !columnId) {
9423
+ fail(ws, type, "boardId, taskId, columnId required");
9424
+ return true;
9425
+ }
9426
+ const board = await moveTask(
9427
+ ctx.projectRoot,
9428
+ boardId,
9429
+ taskId,
9430
+ columnId,
9431
+ payload?.order
9432
+ );
9433
+ board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Move failed");
9434
+ return true;
9435
+ }
9436
+ case "kanban.task.copy":
9437
+ case "kanban.task.transfer": {
9438
+ const boardId = payload?.boardId;
9439
+ const taskId = payload?.taskId;
9440
+ const targetBoardId = payload?.targetBoardId;
9441
+ if (!boardId || !taskId || !targetBoardId) {
9442
+ fail(ws, type, "boardId, taskId, targetBoardId required");
9443
+ return true;
9444
+ }
9445
+ const result = type === "kanban.task.copy" ? await copyTaskToBoard(ctx.projectRoot, boardId, taskId, targetBoardId, {
9446
+ ...payload?.targetColumnId ? { targetColumnId: payload.targetColumnId } : {},
9447
+ ...typeof payload?.preserveAssignment === "boolean" ? { preserveAssignment: payload.preserveAssignment } : {},
9448
+ ...typeof payload?.preserveDependencies === "boolean" ? { preserveDependencies: payload.preserveDependencies } : {}
9449
+ }) : await transferTaskToBoard(ctx.projectRoot, boardId, taskId, targetBoardId, {
9450
+ ...payload?.targetColumnId ? { targetColumnId: payload.targetColumnId } : {},
9451
+ ...typeof payload?.preserveAssignment === "boolean" ? { preserveAssignment: payload.preserveAssignment } : {},
9452
+ ...typeof payload?.preserveDependencies === "boolean" ? { preserveDependencies: payload.preserveDependencies } : {}
9453
+ });
9454
+ result ? ok(ws, type, result.targetBoard) : fail(ws, type, "Board or task not found");
9455
+ return true;
9456
+ }
9457
+ case "kanban.task.chain": {
9458
+ const boardId = payload?.boardId;
9459
+ const taskIds = payload?.taskIds;
9460
+ if (!boardId || !taskIds?.length) {
9461
+ fail(ws, type, "boardId and taskIds required");
9462
+ return true;
9463
+ }
9464
+ const result = await setTaskChain(ctx.projectRoot, boardId, {
9465
+ taskIds,
9466
+ ...payload?.chainId ? { chainId: payload.chainId } : {},
9467
+ ...typeof payload?.enforceDependencies === "boolean" ? { enforceDependencies: payload.enforceDependencies } : {}
9468
+ });
9469
+ result ? ok(ws, type, { board: result.board, chainId: result.chainId, tasks: result.tasks }) : fail(ws, type, "Board or task not found");
9470
+ return true;
9471
+ }
9472
+ case "kanban.task.chain.get": {
9473
+ const boardId = payload?.boardId;
9474
+ const taskOrChainId = payload?.taskId ?? payload?.chainId;
9475
+ if (!boardId || !taskOrChainId) {
9476
+ fail(ws, type, "boardId and taskId or chainId required");
9477
+ return true;
9478
+ }
9479
+ const result = await getTaskChain(ctx.projectRoot, boardId, taskOrChainId);
9480
+ result ? ok(ws, type, { board: result.board, chainId: result.chainId, tasks: result.tasks }) : fail(ws, type, "Chain not found");
9481
+ return true;
9482
+ }
9483
+ case "kanban.task.claim": {
9484
+ const result = await claimReadyTask(ctx.projectRoot, {
9485
+ ...payload?.boardId ? { boardId: payload.boardId } : {},
9486
+ ...payload?.taskId ? { taskId: payload.taskId } : {},
9487
+ ...payload?.agentId ? { agentId: payload.agentId } : {},
9488
+ ...payload?.name ? { name: payload.name } : {},
9489
+ ...payload?.role ? { role: payload.role } : {},
9490
+ ...payload?.provider ? { provider: payload.provider } : {},
9491
+ ...payload?.model ? { model: payload.model } : {},
9492
+ ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
9493
+ ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
9494
+ ...payload?.tools ? { tools: payload.tools } : {},
9495
+ ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
9496
+ ...payload?.assignee ? { assignee: payload.assignee } : {},
9497
+ status: payload?.assignmentStatus ?? "queued"
9498
+ });
9499
+ result ? ok(ws, type, { board: result.board, task: result.task }) : fail(ws, type, "No ready kanban task matched the claim");
9500
+ return true;
9501
+ }
9502
+ case "kanban.task.release": {
9503
+ const boardId = payload?.boardId;
9504
+ const taskId = payload?.taskId;
9505
+ if (!boardId || !taskId) {
9506
+ fail(ws, type, "boardId and taskId required");
9507
+ return true;
9508
+ }
9509
+ const board = await releaseTaskClaim(ctx.projectRoot, boardId, taskId, {
9510
+ ...payload?.status ? { status: payload.status } : {},
9511
+ ...payload?.reason ? { reason: payload.reason } : {},
9512
+ ...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {}
9513
+ });
9514
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
9515
+ return true;
9516
+ }
9517
+ case "kanban.task.metric.add": {
9518
+ const boardId = payload?.boardId;
9519
+ const taskId = payload?.taskId;
9520
+ const name2 = payload?.name;
9521
+ if (!boardId || !taskId || !name2) {
9522
+ fail(ws, type, "boardId, taskId, and name required");
9523
+ return true;
9524
+ }
9525
+ const board = await addGoalMetricToTask(ctx.projectRoot, boardId, taskId, {
9526
+ name: name2,
9527
+ ...payload?.status ? { status: payload.status } : {},
9528
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
9529
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
9530
+ ...payload?.unit ? { unit: payload.unit } : {},
9531
+ ...payload?.notes ? { notes: payload.notes } : {}
9532
+ });
9533
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
9534
+ return true;
9535
+ }
9536
+ case "kanban.task.metric.update": {
9537
+ const boardId = payload?.boardId;
9538
+ const taskId = payload?.taskId;
9539
+ const metricId = payload?.metricId;
9540
+ if (!boardId || !taskId || !metricId) {
9541
+ fail(ws, type, "boardId, taskId, and metricId required");
9542
+ return true;
9543
+ }
9544
+ const board = await updateGoalMetricOnTask(ctx.projectRoot, boardId, taskId, metricId, {
9545
+ ...payload?.name ? { name: payload.name } : {},
9546
+ ...payload?.status ? { status: payload.status } : {},
9547
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
9548
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
9549
+ ...payload?.unit ? { unit: payload.unit } : {},
9550
+ ...payload?.notes ? { notes: payload.notes } : {}
9551
+ });
9552
+ board ? ok(ws, type, board) : fail(ws, type, "Metric not found");
9553
+ return true;
9554
+ }
9555
+ case "kanban.task.assign": {
9556
+ const boardId = payload?.boardId;
9557
+ const taskId = payload?.taskId;
9558
+ if (!boardId || !taskId) {
9559
+ fail(ws, type, "boardId and taskId required");
9560
+ return true;
9561
+ }
9562
+ const board = await assignTask(ctx.projectRoot, boardId, taskId, {
9563
+ ...payload?.agentId ? { agentId: payload.agentId } : {},
9564
+ ...payload?.name ? { name: payload.name } : {},
9565
+ ...payload?.role ? { role: payload.role } : {},
9566
+ ...payload?.provider ? { provider: payload.provider } : {},
9567
+ ...payload?.model ? { model: payload.model } : {},
9568
+ ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
9569
+ ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
9570
+ ...payload?.tools ? { tools: payload.tools } : {},
9571
+ ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {}
9572
+ });
9573
+ board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
9574
+ return true;
9575
+ }
9576
+ case "kanban.task.dispatch":
9577
+ fail(
9578
+ ws,
9579
+ type,
9580
+ "Kanban agent dispatch is only available from the CLI-hosted WebUI runtime."
9581
+ );
9582
+ return true;
9583
+ case "kanban.task.remove": {
9584
+ const boardId = payload?.boardId;
9585
+ const taskId = payload?.taskId;
9586
+ if (!boardId || !taskId) {
9587
+ fail(ws, type, "boardId and taskId required");
9588
+ return true;
9589
+ }
9590
+ const task = await getTask(ctx.projectRoot, boardId, taskId);
9591
+ if (!task) {
9592
+ fail(ws, type, "Board or task not found");
9593
+ return true;
9594
+ }
9595
+ const board = await removeTask(ctx.projectRoot, boardId, taskId);
9596
+ board ? ok(ws, type, { removed: true, boardId: board.id, taskId: task.id, board }) : fail(ws, type, "Board or task not found");
9597
+ return true;
9598
+ }
9599
+ case "kanban.task.get": {
9600
+ const boardId = payload?.boardId;
9601
+ const taskId = payload?.taskId;
9602
+ if (!boardId || !taskId) {
9603
+ fail(ws, type, "boardId and taskId required");
9604
+ return true;
9605
+ }
9606
+ const task = await getTask(ctx.projectRoot, boardId, taskId);
9607
+ task ? ok(ws, type, task) : fail(ws, type, "Task not found");
9608
+ return true;
9609
+ }
9610
+ case "kanban.column.add": {
9611
+ const boardId = payload?.boardId;
9612
+ const title = payload?.title;
9613
+ if (!boardId || !title) {
9614
+ fail(ws, type, "boardId and title required");
9615
+ return true;
9616
+ }
9617
+ const result = await addColumn(ctx.projectRoot, boardId, { title });
9618
+ result ? ok(ws, type, result.board.columns) : fail(ws, type, `Board not found: ${boardId}`);
9619
+ return true;
9620
+ }
9621
+ case "kanban.column.remove": {
9622
+ const boardId = payload?.boardId;
9623
+ const columnId = payload?.columnId;
9624
+ if (!boardId || !columnId) {
9625
+ fail(ws, type, "boardId and columnId required");
9626
+ return true;
9627
+ }
9628
+ const board = await removeColumn(ctx.projectRoot, boardId, columnId, {
9629
+ moveTasksToColumnId: payload?.moveTasksToColumnId
9630
+ });
9631
+ board ? ok(ws, type, { removed: true, boardId: board.id, columnId, board }) : fail(ws, type, `Column not found: ${columnId}`);
9632
+ return true;
9633
+ }
9634
+ default:
9635
+ fail(ws, type, `Unknown kanban message type: ${type}`);
9636
+ return true;
9637
+ }
9638
+ } catch (err) {
9639
+ fail(ws, type, err instanceof Error ? err.message : String(err));
9640
+ return true;
9641
+ }
9642
+ }
9643
+ function findTask(tasks, taskId) {
9644
+ return tasks.find((task) => task.id === taskId || task.id.startsWith(taskId));
9645
+ }
9646
+
9059
9647
  // src/server/mailbox-routes.ts
9060
9648
  async function handleMailboxRoute(ws, msg, handlers) {
9061
9649
  switch (msg.type) {
@@ -9540,6 +10128,7 @@ function createMessageDispatcher(opts) {
9540
10128
  if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
9541
10129
  if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
9542
10130
  if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
10131
+ if (await handleKanbanRoute(ws, msg, { projectRoot: state.getProjectRoot() })) return;
9543
10132
  if (msg.type.startsWith("worktree.") && await deps2.worktreeHandler.handleMessage(
9544
10133
  msg
9545
10134
  ))
@@ -9554,7 +10143,10 @@ function createMessageDispatcher(opts) {
9554
10143
  case "collab.resume":
9555
10144
  case "collab.grant_control":
9556
10145
  case "collab.inject_tool": {
9557
- deps2.collabHandler.handleMessage(ws, msg);
10146
+ deps2.collabHandler.handleMessage(
10147
+ ws,
10148
+ msg
10149
+ );
9558
10150
  return;
9559
10151
  }
9560
10152
  // Integrated terminal — interactive pty transport, bypasses the agent loop.
@@ -9582,7 +10174,10 @@ function createMessageDispatcher(opts) {
9582
10174
  runLock.set(thisRun);
9583
10175
  try {
9584
10176
  const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
9585
- const result = await deps2.agent.run(content, { signal: thisRun.signal, maxIterations: maxIt });
10177
+ const result = await deps2.agent.run(content, {
10178
+ signal: thisRun.signal,
10179
+ maxIterations: maxIt
10180
+ });
9586
10181
  send(ws, {
9587
10182
  type: "run.result",
9588
10183
  payload: sessionPayload2({
@@ -9722,22 +10317,43 @@ function createMessageDispatcher(opts) {
9722
10317
  break;
9723
10318
  // Design Studio — shared handlers (design-handlers.ts).
9724
10319
  case "design.list":
9725
- await handleDesignList(ws, { projectRoot: state.getProjectRoot(), agentMeta: deps2.context });
10320
+ await handleDesignList(ws, {
10321
+ projectRoot: state.getProjectRoot(),
10322
+ agentMeta: deps2.context
10323
+ });
9726
10324
  break;
9727
10325
  case "design.use":
9728
- await handleDesignUse(ws, { projectRoot: state.getProjectRoot(), agentMeta: deps2.context }, msg);
10326
+ await handleDesignUse(
10327
+ ws,
10328
+ { projectRoot: state.getProjectRoot(), agentMeta: deps2.context },
10329
+ msg
10330
+ );
9729
10331
  break;
9730
10332
  case "design.state":
9731
- await handleDesignState(ws, { projectRoot: state.getProjectRoot(), agentMeta: deps2.context });
10333
+ await handleDesignState(ws, {
10334
+ projectRoot: state.getProjectRoot(),
10335
+ agentMeta: deps2.context
10336
+ });
9732
10337
  break;
9733
10338
  case "design.set":
9734
- await handleDesignSet(ws, { projectRoot: state.getProjectRoot(), agentMeta: deps2.context }, msg);
10339
+ await handleDesignSet(
10340
+ ws,
10341
+ { projectRoot: state.getProjectRoot(), agentMeta: deps2.context },
10342
+ msg
10343
+ );
9735
10344
  break;
9736
10345
  case "design.materialize":
9737
- await handleDesignMaterialize(ws, { projectRoot: state.getProjectRoot(), agentMeta: deps2.context }, msg);
10346
+ await handleDesignMaterialize(
10347
+ ws,
10348
+ { projectRoot: state.getProjectRoot(), agentMeta: deps2.context },
10349
+ msg
10350
+ );
9738
10351
  break;
9739
10352
  case "design.verify":
9740
- await handleDesignVerify(ws, { projectRoot: state.getProjectRoot(), agentMeta: deps2.context });
10353
+ await handleDesignVerify(ws, {
10354
+ projectRoot: state.getProjectRoot(),
10355
+ agentMeta: deps2.context
10356
+ });
9741
10357
  break;
9742
10358
  case "diag.get": {
9743
10359
  if (!ensureCurrentSession(ws, msg, "diag.get")) return;
@@ -11930,6 +12546,11 @@ async function startWebUI(opts = {}) {
11930
12546
  sessionStartPayload,
11931
12547
  tokenCounter,
11932
12548
  context,
12549
+ loadReplay: async () => {
12550
+ await session.flush().catch(() => void 0);
12551
+ const data = await sessionStore.load(session.id);
12552
+ return { messages: data.messages, usage: data.usage };
12553
+ },
11933
12554
  clients,
11934
12555
  pendingConfirms,
11935
12556
  autoPhaseHandler,