@wrongstack/webui-server 0.286.0 → 0.287.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.
Files changed (42) hide show
  1. package/dist/index.js +693 -189
  2. package/dist/index.js.map +4 -4
  3. package/dist/server/autophase-ws-handler.d.ts +17 -1
  4. package/dist/server/autophase-ws-handler.d.ts.map +1 -1
  5. package/dist/server/context-meta.d.ts.map +1 -1
  6. package/dist/server/entry.js +672 -187
  7. package/dist/server/entry.js.map +4 -4
  8. package/dist/server/handlers.js.map +1 -1
  9. package/dist/server/http-server.d.ts.map +1 -1
  10. package/dist/server/index.d.ts +4 -3
  11. package/dist/server/index.d.ts.map +1 -1
  12. package/dist/server/instance-registry.d.ts +3 -1
  13. package/dist/server/instance-registry.d.ts.map +1 -1
  14. package/dist/server/kanban-routes.d.ts +3 -0
  15. package/dist/server/kanban-routes.d.ts.map +1 -1
  16. package/dist/server/memory-handlers.d.ts +41 -9
  17. package/dist/server/memory-handlers.d.ts.map +1 -1
  18. package/dist/server/message-dispatcher.d.ts.map +1 -1
  19. package/dist/server/mode-handlers.d.ts.map +1 -1
  20. package/dist/server/port-utils.d.ts +48 -10
  21. package/dist/server/port-utils.d.ts.map +1 -1
  22. package/dist/server/pre-context-services.d.ts.map +1 -1
  23. package/dist/server/pref-helpers.d.ts +1 -1
  24. package/dist/server/pref-helpers.d.ts.map +1 -1
  25. package/dist/server/provider-handlers.d.ts +13 -0
  26. package/dist/server/provider-handlers.d.ts.map +1 -1
  27. package/dist/server/provider-routes.d.ts +2 -0
  28. package/dist/server/provider-routes.d.ts.map +1 -1
  29. package/dist/server/routes.d.ts +18 -20
  30. package/dist/server/routes.d.ts.map +1 -1
  31. package/dist/server/sdd-wizard-wiring.d.ts +31 -1
  32. package/dist/server/sdd-wizard-wiring.d.ts.map +1 -1
  33. package/dist/server/server-runtime.d.ts +1 -0
  34. package/dist/server/server-runtime.d.ts.map +1 -1
  35. package/dist/server/session-handlers.d.ts +4 -4
  36. package/dist/server/session-handlers.d.ts.map +1 -1
  37. package/dist/server/session-history.d.ts +29 -0
  38. package/dist/server/session-history.d.ts.map +1 -0
  39. package/dist/server/setup-events.d.ts.map +1 -1
  40. package/dist/server/start-webui.d.ts.map +1 -1
  41. package/dist/server/ws-payload-validation.d.ts.map +1 -1
  42. package/package.json +9 -9
@@ -42,12 +42,13 @@ function commitsSince(cwd, baseSha, branch) {
42
42
  }
43
43
  }
44
44
  var AutoPhaseWebSocketHandler = class {
45
- constructor(agent, context, logger, storeDir, events, projectRoot) {
45
+ constructor(agent, context, logger, storeDir, events, projectRoot, onBoardState) {
46
46
  this.agent = agent;
47
47
  this.context = context;
48
48
  this.logger = logger;
49
49
  this.events = events;
50
50
  this.projectRoot = projectRoot;
51
+ this.onBoardState = onBoardState;
51
52
  this.store = new PhaseStore({ baseDir: storeDir });
52
53
  }
53
54
  agent;
@@ -55,6 +56,7 @@ var AutoPhaseWebSocketHandler = class {
55
56
  logger;
56
57
  events;
57
58
  projectRoot;
59
+ onBoardState;
58
60
  orchestrator = null;
59
61
  graph = null;
60
62
  store;
@@ -399,6 +401,15 @@ Type: ${task.type}`;
399
401
  if (!this.graph) return;
400
402
  const state = this.buildState(activePhaseId);
401
403
  this.broadcast({ type: "autophase.state", payload: state });
404
+ if (this.onBoardState) {
405
+ try {
406
+ this.onBoardState(this.graph.id, state);
407
+ } catch (err) {
408
+ this.logger.error(
409
+ `[AutoPhase] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
410
+ );
411
+ }
412
+ }
402
413
  }
403
414
  buildState(activePhaseId) {
404
415
  if (!this.graph) {
@@ -2614,10 +2625,7 @@ function cspSourceFromUrl(rawUrl) {
2614
2625
  return void 0;
2615
2626
  }
2616
2627
  }
2617
- var ALLOWED_INLINE_SCRIPT_HASHES = [
2618
- "'sha256-6PXDy0zrpXa6mvYOl11bZ8nubNUL7ushPUhGDZtaexg='",
2619
- "'sha256-6sIdwbEBx7jj0drqSHHm7MqvmoYD3CQ4lp8Zp8blcb0='"
2620
- ];
2628
+ var EXTRA_SCRIPT_SOURCES = ["'wasm-unsafe-eval'"];
2621
2629
  function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2622
2630
  const connect = /* @__PURE__ */ new Set([
2623
2631
  "'self'",
@@ -2631,7 +2639,7 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2631
2639
  }
2632
2640
  const publicWsSource = publicWsUrl ? cspSourceFromUrl(publicWsUrl) : void 0;
2633
2641
  if (publicWsSource) connect.add(publicWsSource);
2634
- const scriptSrc = ["'self'", ...ALLOWED_INLINE_SCRIPT_HASHES].join(" ");
2642
+ const scriptSrc = ["'self'", ...EXTRA_SCRIPT_SOURCES].join(" ");
2635
2643
  return `default-src 'self'; script-src ${scriptSrc}; style-src 'self' 'unsafe-inline'; connect-src ${Array.from(connect).join(" ")}; img-src 'self' data:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`;
2636
2644
  }
2637
2645
  function isInsideDist(candidate, distDir) {
@@ -3281,8 +3289,24 @@ function errorMessage(err) {
3281
3289
  }
3282
3290
 
3283
3291
  // src/server/memory-handlers.ts
3292
+ function isSuperMemoryStore(store) {
3293
+ const s = store;
3294
+ return typeof s.stats === "function" && typeof s.listSuper === "function" && typeof s.getSuperMemory === "function" && typeof s.updateSuperMemory === "function" && typeof s.deleteSuperMemory === "function";
3295
+ }
3296
+ function requiresSuperMemory(command) {
3297
+ return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
3298
+ }
3284
3299
  async function handleMemoryList(ws, memoryStore) {
3285
3300
  try {
3301
+ if (isSuperMemoryStore(memoryStore)) {
3302
+ const [stats, memories] = await Promise.all([
3303
+ memoryStore.stats(),
3304
+ memoryStore.listSuper()
3305
+ ]);
3306
+ const text2 = memories.length === 0 ? "\u{1F9E0} Super Memory is empty." : formatSuperMemoryText(stats, memories);
3307
+ send(ws, { type: "memory.list", payload: { text: text2 } });
3308
+ return;
3309
+ }
3286
3310
  const text = await memoryStore.readAll();
3287
3311
  send(ws, { type: "memory.list", payload: { text } });
3288
3312
  } catch (err) {
@@ -3292,26 +3316,128 @@ async function handleMemoryList(ws, memoryStore) {
3292
3316
  });
3293
3317
  }
3294
3318
  }
3295
- async function handleMemoryRemember(ws, msg, memoryStore) {
3296
- const { text, scope } = msg.payload;
3319
+ function formatSuperMemoryText(stats, memories) {
3320
+ const active = stats.byStatus["active"] ?? 0;
3321
+ const stale = stats.byStatus["stale"] ?? 0;
3322
+ const archived = stats.byStatus["archived"] ?? 0;
3323
+ const lines = [
3324
+ "## \u{1F9E0} Super Memory",
3325
+ "",
3326
+ `**Total:** ${stats.total} \xB7 \u{1F7E2} ${active} active \xB7 \u{1F7E1} ${stale} stale \xB7 \u{1F535} ${archived} archived \xB7 **edges:** ${stats.edges}`,
3327
+ ""
3328
+ ];
3329
+ for (const m of memories) {
3330
+ const tags = m.tags.length > 0 ? ` \`${m.tags.slice(0, 3).join("` `")}\`` : "";
3331
+ const icon = m.status === "active" ? "\u{1F7E2}" : m.status === "stale" ? "\u{1F7E1}" : m.status === "archived" ? "\u{1F535}" : "\u26AA";
3332
+ const preview = m.text.replace(/\s+/g, " ").trim().slice(0, 140);
3333
+ lines.push(`- ${icon} \`${m.id.slice(0, 12)}\u2026\` [${m.kind}] ${preview}${tags}`);
3334
+ }
3335
+ return lines.join("\n");
3336
+ }
3337
+ async function handleSuperMemoryList(ws, memoryStore) {
3338
+ if (!isSuperMemoryStore(memoryStore)) {
3339
+ send(ws, { type: "memory.super.list", payload: { error: requiresSuperMemory("memory.super.list") } });
3340
+ return;
3341
+ }
3297
3342
  try {
3298
- await memoryStore.remember(text, scope ?? "project-memory");
3299
- sendResult(ws, true, "Saved to memory");
3343
+ const [stats, memories] = await Promise.all([
3344
+ memoryStore.stats(),
3345
+ memoryStore.listSuper()
3346
+ ]);
3347
+ send(ws, { type: "memory.super.list", payload: { memories, stats } });
3300
3348
  } catch (err) {
3301
- sendResult(ws, false, errMessage(err));
3349
+ send(ws, { type: "memory.super.list", payload: { error: errMessage(err) } });
3302
3350
  }
3303
3351
  }
3304
- async function handleMemoryForget(ws, msg, memoryStore) {
3305
- const { text, scope } = msg.payload;
3352
+ async function handleSuperMemoryGet(ws, msg, memoryStore) {
3353
+ if (!isSuperMemoryStore(memoryStore)) {
3354
+ send(ws, { type: "memory.super.get", payload: { error: requiresSuperMemory("memory.super.get") } });
3355
+ return;
3356
+ }
3357
+ const { id } = msg.payload;
3358
+ if (!id) {
3359
+ send(ws, { type: "memory.super.get", payload: { error: "id is required" } });
3360
+ return;
3361
+ }
3306
3362
  try {
3307
- const removed = await memoryStore.forget(text, scope ?? "project-memory");
3308
- sendResult(
3309
- ws,
3310
- removed > 0,
3311
- removed > 0 ? `Removed ${removed} entr${removed === 1 ? "y" : "ies"}` : "No matching entries"
3312
- );
3363
+ const memory = await memoryStore.getSuperMemory(id);
3364
+ if (!memory) {
3365
+ send(ws, { type: "memory.super.get", payload: { error: `Memory "${id}" not found.` } });
3366
+ return;
3367
+ }
3368
+ send(ws, { type: "memory.super.get", payload: { memory } });
3313
3369
  } catch (err) {
3314
- sendResult(ws, false, errMessage(err));
3370
+ send(ws, { type: "memory.super.get", payload: { error: errMessage(err) } });
3371
+ }
3372
+ }
3373
+ async function handleSuperMemoryUpdate(ws, msg, memoryStore) {
3374
+ if (!isSuperMemoryStore(memoryStore)) {
3375
+ send(ws, { type: "memory.super.update", payload: { error: requiresSuperMemory("memory.super.update") } });
3376
+ return;
3377
+ }
3378
+ const payload = msg.payload;
3379
+ const id = payload["id"];
3380
+ if (!id) {
3381
+ send(ws, { type: "memory.super.update", payload: { error: "id is required" } });
3382
+ return;
3383
+ }
3384
+ const patch = { ...payload };
3385
+ delete patch["id"];
3386
+ if (Object.keys(patch).length === 0) {
3387
+ send(ws, { type: "memory.super.update", payload: { error: "No fields to update." } });
3388
+ return;
3389
+ }
3390
+ try {
3391
+ const memory = await memoryStore.updateSuperMemory(id, patch);
3392
+ send(ws, { type: "memory.super.update", payload: { memory } });
3393
+ } catch (err) {
3394
+ send(ws, { type: "memory.super.update", payload: { error: errMessage(err) } });
3395
+ }
3396
+ }
3397
+ async function handleSuperMemoryRemember(ws, msg, memoryStore) {
3398
+ if (!isSuperMemoryStore(memoryStore)) {
3399
+ send(ws, { type: "memory.super.remember", payload: { error: requiresSuperMemory("memory.super.remember") } });
3400
+ return;
3401
+ }
3402
+ const payload = msg.payload;
3403
+ const text = payload["text"];
3404
+ if (!text || !text.trim()) {
3405
+ send(ws, { type: "memory.super.remember", payload: { error: "text is required" } });
3406
+ return;
3407
+ }
3408
+ try {
3409
+ const memory = await memoryStore.rememberSuper({
3410
+ text: text.trim(),
3411
+ kind: payload["kind"],
3412
+ scope: payload["scope"],
3413
+ tags: payload["tags"],
3414
+ anchors: payload["anchors"],
3415
+ importance: payload["importance"],
3416
+ confidence: payload["confidence"],
3417
+ freshness: payload["freshness"],
3418
+ supersedes: payload["supersedes"],
3419
+ contradicts: payload["contradicts"]
3420
+ });
3421
+ send(ws, { type: "memory.super.remember", payload: { memory } });
3422
+ } catch (err) {
3423
+ send(ws, { type: "memory.super.remember", payload: { error: errMessage(err) } });
3424
+ }
3425
+ }
3426
+ async function handleSuperMemoryDelete(ws, msg, memoryStore) {
3427
+ if (!isSuperMemoryStore(memoryStore)) {
3428
+ send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
3429
+ return;
3430
+ }
3431
+ const { id, reason } = msg.payload;
3432
+ if (!id) {
3433
+ send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
3434
+ return;
3435
+ }
3436
+ try {
3437
+ await memoryStore.deleteSuperMemory(id, reason);
3438
+ send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
3439
+ } catch (err) {
3440
+ send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
3315
3441
  }
3316
3442
  }
3317
3443
 
@@ -3903,6 +4029,8 @@ var SddBoardWebSocketHandler = class {
3903
4029
  import * as path8 from "node:path";
3904
4030
  import { spawnSync as spawnSync2 } from "node:child_process";
3905
4031
  import {
4032
+ DefaultTaskStore,
4033
+ TaskTracker,
3906
4034
  WorktreeManager as WorktreeManager2,
3907
4035
  ToolValidationError as ToolValidationError4
3908
4036
  } from "@wrongstack/core";
@@ -3918,6 +4046,63 @@ import {
3918
4046
  startSddRun,
3919
4047
  TaskGraphStore
3920
4048
  } from "@wrongstack/sdd";
4049
+ function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
4050
+ const runTracker = tracker ?? (() => {
4051
+ const t = new TaskTracker({ store: new DefaultTaskStore() });
4052
+ t.setGraph(graph);
4053
+ return t;
4054
+ })();
4055
+ const worktreesEnabled = config.worktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
4056
+ let worktrees;
4057
+ if (worktreesEnabled) {
4058
+ const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
4059
+ cwd: deps2.projectRoot,
4060
+ encoding: "utf8",
4061
+ windowsHide: true
4062
+ }).stdout?.trim() === "true";
4063
+ if (inGit) {
4064
+ void cleanupStaleSddWorktrees({
4065
+ projectRoot: deps2.projectRoot,
4066
+ boardsDir: deps2.projectSddBoards
4067
+ }).catch(() => void 0);
4068
+ worktrees = new WorktreeManager2({
4069
+ projectRoot: deps2.projectRoot,
4070
+ events: deps2.events,
4071
+ sessionId: () => deps2.agent.ctx.session?.id
4072
+ });
4073
+ }
4074
+ }
4075
+ const superviseFailure = deps2.brain && deps2.runIsolatedTurn ? new SddSupervisor({
4076
+ brain: deps2.brain,
4077
+ reassignModels: config.fallbackModels,
4078
+ generateSubtasks: makeLlmSubtaskGenerator({
4079
+ run: (prompt) => deps2.runIsolatedTurn(prompt, "Task Splitter")
4080
+ }),
4081
+ requestLlmVerdict: true
4082
+ }).superviseFailure : deps2.brain ? new SddSupervisor({
4083
+ brain: deps2.brain,
4084
+ reassignModels: config.fallbackModels,
4085
+ requestLlmVerdict: true
4086
+ }).superviseFailure : void 0;
4087
+ return startSddRun({
4088
+ tracker: runTracker,
4089
+ graph,
4090
+ agent: deps2.agent,
4091
+ projectRoot: deps2.projectRoot,
4092
+ events: deps2.events,
4093
+ sessionId: () => deps2.agent.ctx.session?.id,
4094
+ subagentFactory: deps2.subagentFactory,
4095
+ boardStore: new SddBoardStore2({ baseDir: deps2.projectSddBoards }),
4096
+ registry: deps2.registry ?? new SddRunRegistry(),
4097
+ verifyTask: makeCommandVerifier(),
4098
+ ...superviseFailure ? { superviseFailure } : {},
4099
+ ...config.parallelSlots !== void 0 ? { parallelSlots: config.parallelSlots } : {},
4100
+ ...config.defaultModel !== void 0 ? { defaultModel: config.defaultModel } : {},
4101
+ ...config.defaultProvider !== void 0 ? { defaultProvider: config.defaultProvider } : {},
4102
+ ...config.fallbackModels !== void 0 ? { fallbackModels: config.fallbackModels } : {},
4103
+ ...worktrees ? { worktrees } : {}
4104
+ });
4105
+ }
3921
4106
  var PLANNING_ONLY_GUARD = "SYSTEM: You are running a PLANNING-ONLY specification interview. Do NOT write, create, or edit any files, and do NOT run shell/terminal commands or use any code-editing tools \u2014 they are disabled here and any attempt will fail and waste the turn. Respond with TEXT ONLY: ask your question, or emit the requested spec / plan / task JSON. All code is written later, automatically, once the plan is approved and the multi-agent run starts.\n\n---\n\n";
3922
4107
  function buildSddWizardDeps(opts) {
3923
4108
  const registry = new SddRunRegistry();
@@ -3953,64 +4138,27 @@ function buildSddWizardDeps(opts) {
3953
4138
  field: "taskGraph"
3954
4139
  });
3955
4140
  }
3956
- const worktreesEnabled = useWorktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
3957
- let worktrees;
3958
- if (worktreesEnabled) {
3959
- const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
3960
- cwd: opts.projectRoot,
3961
- encoding: "utf8",
3962
- windowsHide: true
3963
- }).stdout?.trim() === "true";
3964
- if (inGit) {
3965
- await cleanupStaleSddWorktrees({
3966
- projectRoot: opts.projectRoot,
3967
- boardsDir: opts.paths.projectSddBoards
3968
- }).catch(() => void 0);
3969
- worktrees = new WorktreeManager2({
3970
- projectRoot: opts.projectRoot,
3971
- events: opts.events,
3972
- sessionId: () => opts.agent.ctx.session?.id
3973
- });
3974
- }
3975
- }
3976
- const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
3977
- const verifyTask = makeCommandVerifier();
3978
- const superviseFailure = opts.brain ? new SddSupervisor({
3979
- brain: opts.brain,
3980
- // The run-level fallback chain (chosen in the wizard) doubles as the
3981
- // supervisor's reassign options — a `reassign` verdict rotates the
3982
- // worker model on retry. Empty/undefined → reassign option dropped.
3983
- reassignModels: fallbackModels,
3984
- // LLM auto-split: decompose a retry-exhausted task into smaller
3985
- // sub-tasks on an isolated read-only turn. Heavily validated +
3986
- // bounded; an empty result degrades the split into a retry.
3987
- generateSubtasks: makeLlmSubtaskGenerator({
3988
- run: (prompt) => runIsolatedTurn(prompt, "Task Splitter")
3989
- }),
3990
- // The standalone brain is a tiered policy→LLM arbiter with NO
3991
- // human-escalation wrapper (see index.ts), so it never blocks on a
3992
- // human prompt — an unresolved verdict degrades to a bounded retry.
3993
- // Safe to let the LLM layer actually pick reassign/split.
3994
- requestLlmVerdict: true
3995
- }).superviseFailure : void 0;
3996
- const handle = startSddRun({
3997
- tracker,
4141
+ const handle = startSddRunFromGraph(
3998
4142
  graph,
3999
- agent: opts.agent,
4000
- projectRoot: opts.projectRoot,
4001
- events: opts.events,
4002
- sessionId: () => opts.agent.ctx.session?.id,
4003
- subagentFactory: opts.subagentFactory,
4004
- worktrees,
4005
- boardStore,
4006
- registry,
4007
- parallelSlots,
4008
- defaultModel,
4009
- defaultProvider,
4010
- fallbackModels,
4011
- verifyTask,
4012
- superviseFailure
4013
- });
4143
+ {
4144
+ agent: opts.agent,
4145
+ events: opts.events,
4146
+ projectRoot: opts.projectRoot,
4147
+ subagentFactory: opts.subagentFactory,
4148
+ projectSddBoards: opts.paths.projectSddBoards,
4149
+ registry,
4150
+ runIsolatedTurn,
4151
+ ...opts.brain ? { brain: opts.brain } : {}
4152
+ },
4153
+ {
4154
+ ...parallelSlots !== void 0 ? { parallelSlots } : {},
4155
+ ...defaultModel !== void 0 ? { defaultModel } : {},
4156
+ ...defaultProvider !== void 0 ? { defaultProvider } : {},
4157
+ ...fallbackModels !== void 0 ? { fallbackModels } : {},
4158
+ ...useWorktrees !== void 0 ? { worktrees: useWorktrees } : {}
4159
+ },
4160
+ tracker
4161
+ );
4014
4162
  void handle.completion.catch(() => {
4015
4163
  });
4016
4164
  return { runId: handle.runId };
@@ -4154,6 +4302,31 @@ var SddWizardWebSocketHandler = class {
4154
4302
  }
4155
4303
  };
4156
4304
 
4305
+ // src/server/session-history.ts
4306
+ function toSessionHistoryEntry(summary, currentSessionId) {
4307
+ return {
4308
+ id: summary.id,
4309
+ title: summary.title,
4310
+ ...summary.name !== void 0 ? { name: summary.name } : {},
4311
+ startedAt: summary.startedAt,
4312
+ ...summary.endedAt !== void 0 ? { endedAt: summary.endedAt } : {},
4313
+ model: summary.model,
4314
+ provider: summary.provider,
4315
+ tokenTotal: summary.tokenTotal,
4316
+ ...summary.iterationCount !== void 0 ? { iterationCount: summary.iterationCount } : {},
4317
+ ...summary.toolCallCount !== void 0 ? { toolCallCount: summary.toolCallCount } : {},
4318
+ ...summary.toolErrorCount !== void 0 ? { toolErrorCount: summary.toolErrorCount } : {},
4319
+ ...summary.fileChangeCount !== void 0 ? { fileChangeCount: summary.fileChangeCount } : {},
4320
+ ...summary.toolBreakdown !== void 0 ? { toolBreakdown: summary.toolBreakdown } : {},
4321
+ ...summary.compactionCount !== void 0 ? { compactionCount: summary.compactionCount } : {},
4322
+ ...summary.outcome !== void 0 ? { outcome: summary.outcome } : {},
4323
+ isCurrent: summary.id === currentSessionId
4324
+ };
4325
+ }
4326
+ function toSessionHistoryEntries(summaries, currentSessionId) {
4327
+ return summaries.map((summary) => toSessionHistoryEntry(summary, currentSessionId));
4328
+ }
4329
+
4157
4330
  // src/server/shell-open.ts
4158
4331
  import * as fs8 from "node:fs/promises";
4159
4332
  import * as path9 from "node:path";
@@ -4447,7 +4620,8 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4447
4620
  "uiLocale",
4448
4621
  "thinkingWord",
4449
4622
  "refinerProvider",
4450
- "refinerModel"
4623
+ "refinerModel",
4624
+ "refinerFallbackProfile"
4451
4625
  ]);
4452
4626
  var ENUM_PREF_KEYS = {
4453
4627
  autonomy: AUTONOMY_VALUES,
@@ -6323,6 +6497,18 @@ function setupEvents(deps2) {
6323
6497
  payload: sessionPayload2({ event: eventName, ...payload })
6324
6498
  });
6325
6499
  });
6500
+ events.onPattern("cron:state_snapshot", (_eventName, payload) => {
6501
+ broadcast2(clients, {
6502
+ type: "cron.snapshot",
6503
+ payload
6504
+ });
6505
+ });
6506
+ events.onPattern("cron:job_fired", (_eventName, payload) => {
6507
+ broadcast2(clients, {
6508
+ type: "cron.job_fired",
6509
+ payload
6510
+ });
6511
+ });
6326
6512
  on("client.status", async (e) => {
6327
6513
  broadcast2(clients, { type: "client.status_update", payload: e });
6328
6514
  if (wpaths?.projectStatus) {
@@ -6672,9 +6858,11 @@ function positiveNumber(value) {
6672
6858
  // src/server/server-runtime.ts
6673
6859
  import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
6674
6860
  async function resolvePorts(opts) {
6675
- const requestedWsPort = opts.wsPort ?? 3457;
6861
+ const surface = opts.surface ?? "webui";
6862
+ const surfaceDefaults = surface === "simpleui" ? { http: 3466, ws: 3467 } : { http: 3456, ws: 3457 };
6863
+ const requestedWsPort = opts.wsPort ?? surfaceDefaults.ws;
6676
6864
  const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
6677
- const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? "3456", 10);
6865
+ const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? String(surfaceDefaults.http), 10);
6678
6866
  const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
6679
6867
  const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
6680
6868
  const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
@@ -6832,8 +7020,8 @@ function resolveWebuiDistDir(fromUrl, explicitDistDir) {
6832
7020
  if (explicitDistDir) return path12.resolve(explicitDistDir);
6833
7021
  try {
6834
7022
  const requireFromHere2 = createRequire(fromUrl);
6835
- const serverEntry = requireFromHere2.resolve("@wrongstack/webui/server");
6836
- return path12.resolve(path12.dirname(serverEntry), "..");
7023
+ const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
7024
+ return path12.dirname(serverEntry);
6837
7025
  } catch {
6838
7026
  return path12.resolve(path12.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
6839
7027
  }
@@ -6856,7 +7044,7 @@ function startHttpServer(opts) {
6856
7044
  console.log(`[WebUI] HTTP server running on ${openUrl}`);
6857
7045
  if (opts.openBrowser) openBrowser(openUrl);
6858
7046
  void registerInstance(
6859
- { pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path12.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
7047
+ { pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path12.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
6860
7048
  registryBaseDir
6861
7049
  ).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
6862
7050
  });
@@ -6923,6 +7111,10 @@ import {
6923
7111
  rememberTool,
6924
7112
  searchMemoryTool
6925
7113
  } from "@wrongstack/tools";
7114
+ import {
7115
+ attachSessionKanbanMirror,
7116
+ hydrateSessionKanban
7117
+ } from "@wrongstack/tools/session-kanban";
6926
7118
  import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
6927
7119
 
6928
7120
  // src/server/setup-screen.ts
@@ -7063,6 +7255,7 @@ function seedContextMeta(config, context) {
7063
7255
  }
7064
7256
  meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
7065
7257
  meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
7258
+ meta["refinerFallbackProfile"] = autonomyCfg["refinerFallbackProfile"] ?? "";
7066
7259
  meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
7067
7260
  meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
7068
7261
  meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
@@ -7396,12 +7589,13 @@ async function createPreContextServices(input) {
7396
7589
  })();
7397
7590
  const memoryStore = container.resolve(TOKENS.MemoryStore);
7398
7591
  if (config.features.memory) {
7399
- toolRegistry.register(rememberTool(memoryStore));
7400
- toolRegistry.register(forgetTool(memoryStore));
7401
- toolRegistry.register(searchMemoryTool(memoryStore));
7402
- toolRegistry.register(relatedMemoryTool(memoryStore));
7403
7592
  if (isSuperMemoryService(memoryStore)) {
7404
7593
  for (const tool of createSuperMemoryTools(memoryStore)) toolRegistry.register(tool);
7594
+ } else {
7595
+ toolRegistry.register(rememberTool(memoryStore));
7596
+ toolRegistry.register(forgetTool(memoryStore));
7597
+ toolRegistry.register(searchMemoryTool(memoryStore));
7598
+ toolRegistry.register(relatedMemoryTool(memoryStore));
7405
7599
  }
7406
7600
  }
7407
7601
  toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
@@ -7530,6 +7724,9 @@ async function createPreContextServices(input) {
7530
7724
  const promptsCtx = { promptLoader, promptUsage };
7531
7725
  const systemPromptBuilder = new DefaultSystemPromptBuilder({
7532
7726
  memoryStore,
7727
+ // Super Memory's turn middleware is the single memory-injection channel;
7728
+ // don't also inject a static memory section here (avoids double injection).
7729
+ injectMemory: false,
7533
7730
  skillLoader,
7534
7731
  modeStore,
7535
7732
  modeId,
@@ -7580,6 +7777,8 @@ async function createPreContextServices(input) {
7580
7777
  "task.path",
7581
7778
  sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
7582
7779
  );
7780
+ await hydrateSessionKanban(context);
7781
+ attachSessionKanbanMirror(context);
7583
7782
  seedContextMeta(config, context);
7584
7783
  return {
7585
7784
  modelsRegistry,
@@ -9713,8 +9912,10 @@ import {
9713
9912
  serializeTaskGraph
9714
9913
  } from "@wrongstack/core";
9715
9914
  import {
9915
+ addCheckToTask,
9716
9916
  addColumn,
9717
9917
  addGoalMetricToTask,
9918
+ addNoteToTask,
9718
9919
  addTask,
9719
9920
  assignTask,
9720
9921
  claimReadyTask,
@@ -9728,29 +9929,47 @@ import {
9728
9929
  getKanbanQueueHealth,
9729
9930
  getTask,
9730
9931
  getTaskChain,
9731
- listReadyTasks,
9732
9932
  listBoards,
9933
+ listReadyTasks,
9733
9934
  mergeTasks,
9734
9935
  moveTask,
9735
9936
  parseLinesIntoTasks,
9937
+ reconcileKanbanBoard,
9938
+ recoverStaleTaskAssignments,
9939
+ releaseTaskClaim,
9736
9940
  removeBoard,
9737
9941
  removeColumn,
9738
9942
  removeTask,
9739
- releaseTaskClaim,
9740
9943
  setTaskChain,
9741
9944
  splitTask,
9742
9945
  syncBoardFromTaskGraph,
9743
9946
  transferTaskToBoard,
9744
9947
  updateBoard,
9948
+ updateCheckOnTask,
9745
9949
  updateGoalMetricOnTask,
9746
9950
  updateTask
9747
9951
  } from "@wrongstack/kanban";
9952
+ import { applySessionKanbanTaskToSource } from "@wrongstack/tools/session-kanban";
9953
+ async function syncSessionSource(ctx, task, remove = false) {
9954
+ if (!ctx.context) return;
9955
+ const update = await applySessionKanbanTaskToSource(ctx.context, task, { remove });
9956
+ const sessionId = ctx.context.session?.id ?? "";
9957
+ if (update.todos)
9958
+ ctx.broadcast?.({ type: "todos.updated", payload: { sessionId, todos: update.todos } });
9959
+ if (update.tasks)
9960
+ ctx.broadcast?.({ type: "tasks.updated", payload: { sessionId, tasks: update.tasks.tasks } });
9961
+ if (update.plan)
9962
+ ctx.broadcast?.({ type: "plan.updated", payload: { sessionId, plan: update.plan } });
9963
+ }
9748
9964
  function ok(ws, type, data) {
9749
9965
  send(ws, { type, payload: { success: true, data: data ?? null } });
9750
9966
  }
9751
9967
  function fail(ws, type, message) {
9752
9968
  send(ws, { type, payload: { success: false, error: message } });
9753
9969
  }
9970
+ function has(payload, key) {
9971
+ return payload !== void 0 && Object.hasOwn(payload, key);
9972
+ }
9754
9973
  async function handleKanbanRoute(ws, msg, ctx) {
9755
9974
  if (!msg.type.startsWith("kanban.")) return false;
9756
9975
  const payload = msg.payload;
@@ -9779,6 +9998,38 @@ async function handleKanbanRoute(ws, msg, ctx) {
9779
9998
  ok(ws, type, await getKanbanQueueHealth(ctx.projectRoot, { boardId: hBoardId }));
9780
9999
  return true;
9781
10000
  }
10001
+ case "kanban.supervisor.status":
10002
+ case "kanban.supervisor.audit": {
10003
+ const boardId = payload?.boardId;
10004
+ if (!boardId) {
10005
+ fail(ws, type, "boardId required");
10006
+ return true;
10007
+ }
10008
+ const board = await getBoard(ctx.projectRoot, boardId);
10009
+ if (!board) {
10010
+ fail(ws, type, `Board not found: ${boardId}`);
10011
+ return true;
10012
+ }
10013
+ const reconciled = await reconcileKanbanBoard(ctx.projectRoot, boardId);
10014
+ let health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
10015
+ const recovered = health.staleAssignments.count ? await recoverStaleTaskAssignments(ctx.projectRoot, boardId, {
10016
+ mode: board.supervisor?.recoveryMode ?? "auto",
10017
+ reason: "On-demand standalone Kanban supervisor audit."
10018
+ }) : null;
10019
+ if (recovered) health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
10020
+ const anomalyCount = health.staleAssignments.count + health.dependencyBlocked.count + health.failedRetryable.count + health.counts.failed + health.counts.blocked;
10021
+ ok(ws, type, {
10022
+ boardId,
10023
+ status: board.supervisor?.enabled === false ? "disabled" : anomalyCount ? "attention" : "healthy",
10024
+ mode: board.supervisor?.mode ?? "deterministic",
10025
+ lastAuditAt: (/* @__PURE__ */ new Date()).toISOString(),
10026
+ reconciledTaskIds: reconciled?.tasks.map((task) => task.id) ?? [],
10027
+ staleRecoveredTaskIds: recovered?.tasks.map((task) => task.id) ?? [],
10028
+ anomalyCount,
10029
+ summary: `${health.counts.running} running \xB7 ${health.counts.ready} ready \xB7 ${health.counts.review} review \xB7 ${health.counts.blocked} blocked \xB7 ${health.counts.failed} failed`
10030
+ });
10031
+ return true;
10032
+ }
9782
10033
  case "kanban.create": {
9783
10034
  const title = payload?.title;
9784
10035
  if (!title) {
@@ -9807,7 +10058,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
9807
10058
  ...payload?.title ? { title: payload.title } : {},
9808
10059
  ...payload?.description ? { description: payload.description } : {},
9809
10060
  ...payload?.tags ? { tags: payload.tags } : {},
9810
- ...payload?.columns ? { columns: payload.columns } : {}
10061
+ ...payload?.columns ? { columns: payload.columns } : {},
10062
+ ...has(payload, "supervisor") ? {
10063
+ supervisor: payload?.supervisor ?? null
10064
+ } : {}
9811
10065
  });
9812
10066
  board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
9813
10067
  return true;
@@ -9834,6 +10088,11 @@ async function handleKanbanRoute(ws, msg, ctx) {
9834
10088
  return true;
9835
10089
  }
9836
10090
  const board = await getBoard(ctx.projectRoot, boardId);
10091
+ const activeSessionId = ctx.context?.session?.id;
10092
+ if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
10093
+ fail(ws, type, "The active session Kanban board cannot be deleted.");
10094
+ return true;
10095
+ }
9837
10096
  ok(ws, type, {
9838
10097
  removed: board ? await removeBoard(ctx.projectRoot, board.id) : false,
9839
10098
  boardId: board?.id ?? boardId
@@ -10014,15 +10273,31 @@ async function handleKanbanRoute(ws, msg, ctx) {
10014
10273
  return true;
10015
10274
  }
10016
10275
  const board = await updateTask(ctx.projectRoot, boardId, taskId, {
10017
- ...payload?.title ? { title: payload.title } : {},
10018
- ...payload?.description ? { description: payload.description } : {},
10019
- ...payload?.columnId ? { columnId: payload.columnId } : {},
10020
- ...payload?.priority ? { priority: payload.priority } : {},
10021
- ...payload?.status ? { status: payload.status } : {},
10022
- ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
10023
- ...payload?.labels ? { labels: payload.labels } : {}
10276
+ ...has(payload, "title") ? { title: payload?.title } : {},
10277
+ ...has(payload, "description") ? { description: payload?.description ?? "" } : {},
10278
+ ...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
10279
+ ...has(payload, "priority") ? { priority: payload?.priority } : {},
10280
+ ...has(payload, "type") ? { type: payload?.type } : {},
10281
+ ...has(payload, "status") ? { status: payload?.status } : {},
10282
+ ...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
10283
+ ...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
10284
+ ...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
10285
+ ...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
10286
+ ...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
10287
+ ...has(payload, "retryPolicy") ? {
10288
+ retryPolicy: payload?.retryPolicy ?? null
10289
+ } : {},
10290
+ ...has(payload, "costCeilingUsd") ? {
10291
+ costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
10292
+ } : {}
10024
10293
  });
10025
- board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
10294
+ if (!board) {
10295
+ fail(ws, type, "Board or task not found");
10296
+ return true;
10297
+ }
10298
+ const task = findTask(board.tasks, taskId);
10299
+ if (task) await syncSessionSource(ctx, task);
10300
+ ok(ws, type, task);
10026
10301
  return true;
10027
10302
  }
10028
10303
  case "kanban.task.move": {
@@ -10040,7 +10315,13 @@ async function handleKanbanRoute(ws, msg, ctx) {
10040
10315
  columnId,
10041
10316
  payload?.order
10042
10317
  );
10043
- board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Move failed");
10318
+ if (!board) {
10319
+ fail(ws, type, "Move failed");
10320
+ return true;
10321
+ }
10322
+ const task = findTask(board.tasks, taskId);
10323
+ if (task) await syncSessionSource(ctx, task);
10324
+ ok(ws, type, task);
10044
10325
  return true;
10045
10326
  }
10046
10327
  case "kanban.task.copy":
@@ -10099,8 +10380,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
10099
10380
  ...payload?.role ? { role: payload.role } : {},
10100
10381
  ...payload?.provider ? { provider: payload.provider } : {},
10101
10382
  ...payload?.model ? { model: payload.model } : {},
10383
+ ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
10102
10384
  ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
10103
10385
  ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
10386
+ ...payload?.skills ? { skills: payload.skills } : {},
10104
10387
  ...payload?.tools ? { tools: payload.tools } : {},
10105
10388
  ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
10106
10389
  ...payload?.assignee ? { assignee: payload.assignee } : {},
@@ -10119,7 +10402,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
10119
10402
  const board = await releaseTaskClaim(ctx.projectRoot, boardId, taskId, {
10120
10403
  ...payload?.status ? { status: payload.status } : {},
10121
10404
  ...payload?.reason ? { reason: payload.reason } : {},
10122
- ...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {}
10405
+ ...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {},
10406
+ ...typeof payload?.maxAttempts === "number" ? { maxAttempts: payload.maxAttempts } : {},
10407
+ ...typeof payload?.costCeilingUsd === "number" ? { costCeilingUsd: payload.costCeilingUsd } : {},
10408
+ ...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
10123
10409
  });
10124
10410
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10125
10411
  return true;
@@ -10175,14 +10461,66 @@ async function handleKanbanRoute(ws, msg, ctx) {
10175
10461
  ...payload?.role ? { role: payload.role } : {},
10176
10462
  ...payload?.provider ? { provider: payload.provider } : {},
10177
10463
  ...payload?.model ? { model: payload.model } : {},
10464
+ ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
10178
10465
  ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
10179
10466
  ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
10467
+ ...payload?.skills ? { skills: payload.skills } : {},
10180
10468
  ...payload?.tools ? { tools: payload.tools } : {},
10181
- ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {}
10469
+ ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
10470
+ ...payload?.assignee ? { assignee: payload.assignee } : {},
10471
+ ...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
10472
+ ...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
10473
+ ...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
10182
10474
  });
10183
10475
  board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
10184
10476
  return true;
10185
10477
  }
10478
+ case "kanban.task.check.add": {
10479
+ const boardId = payload?.boardId;
10480
+ const taskId = payload?.taskId;
10481
+ const description = payload?.description;
10482
+ if (!boardId || !taskId || !description) {
10483
+ fail(ws, type, "boardId, taskId, and description required");
10484
+ return true;
10485
+ }
10486
+ const board = await addCheckToTask(ctx.projectRoot, boardId, taskId, {
10487
+ description,
10488
+ type: payload?.checkType ?? "manual",
10489
+ status: payload?.status ?? "pending"
10490
+ });
10491
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10492
+ return true;
10493
+ }
10494
+ case "kanban.task.check.update": {
10495
+ const boardId = payload?.boardId;
10496
+ const taskId = payload?.taskId;
10497
+ const checkId = payload?.checkId;
10498
+ if (!boardId || !taskId || !checkId) {
10499
+ fail(ws, type, "boardId, taskId, and checkId required");
10500
+ return true;
10501
+ }
10502
+ const board = await updateCheckOnTask(ctx.projectRoot, boardId, taskId, checkId, {
10503
+ ...has(payload, "status") ? { status: payload?.status } : {}
10504
+ });
10505
+ if (!board) fail(ws, type, "Check not found");
10506
+ else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
10507
+ return true;
10508
+ }
10509
+ case "kanban.task.note.add": {
10510
+ const boardId = payload?.boardId;
10511
+ const taskId = payload?.taskId;
10512
+ const content = payload?.content;
10513
+ if (!boardId || !taskId || !content) {
10514
+ fail(ws, type, "boardId, taskId, and content required");
10515
+ return true;
10516
+ }
10517
+ const board = await addNoteToTask(ctx.projectRoot, boardId, taskId, {
10518
+ author: payload?.author ?? "webui",
10519
+ content
10520
+ });
10521
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10522
+ return true;
10523
+ }
10186
10524
  case "kanban.task.dispatch":
10187
10525
  fail(
10188
10526
  ws,
@@ -10206,7 +10544,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
10206
10544
  return true;
10207
10545
  }
10208
10546
  const board = await removeTask(ctx.projectRoot, boardId, taskId);
10209
- board ? ok(ws, type, { removed: true, boardId: board.id, taskId: task.id, board }) : fail(ws, type, "Board or task not found");
10547
+ if (!board) {
10548
+ fail(ws, type, "Board or task not found");
10549
+ return true;
10550
+ }
10551
+ await syncSessionSource(ctx, task, true);
10552
+ ok(ws, type, { removed: true, boardId: board.id, taskId: task.id, board });
10210
10553
  return true;
10211
10554
  }
10212
10555
  case "kanban.task.get": {
@@ -10518,6 +10861,7 @@ async function handleProviderRoute(ws, msg, routes) {
10518
10861
  baseUrl,
10519
10862
  apiKey
10520
10863
  });
10864
+ await routes.adoptDefaultProviderIfUnset(id);
10521
10865
  return true;
10522
10866
  }
10523
10867
  case "provider.remove": {
@@ -10756,7 +11100,12 @@ function createMessageDispatcher(opts) {
10756
11100
  if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
10757
11101
  if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
10758
11102
  if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
10759
- if (await handleKanbanRoute(ws, msg, { projectRoot: state.getProjectRoot() })) return;
11103
+ if (await handleKanbanRoute(ws, msg, {
11104
+ projectRoot: state.getProjectRoot(),
11105
+ context: deps2.context,
11106
+ broadcast: (message) => broadcast(state.getClients(), message)
11107
+ }))
11108
+ return;
10760
11109
  if (msg.type.startsWith("worktree.") && await deps2.worktreeHandler.handleMessage(
10761
11110
  msg
10762
11111
  ))
@@ -10805,17 +11154,14 @@ function createMessageDispatcher(opts) {
10805
11154
  let input = content;
10806
11155
  const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
10807
11156
  if (imageBlocks.length > 0) {
10808
- const routed = await routeImagesForModel(
10809
- buildUserContentBlocks(content, imageBlocks),
10810
- {
10811
- supportsVision: deps2.agent.ctx.provider.capabilities.vision,
10812
- adapters: () => createToolVisionAdapters(deps2.agent.tools),
10813
- ctx: deps2.agent.ctx,
10814
- signal: thisRun.signal,
10815
- providerId: deps2.agent.ctx.provider.id,
10816
- model: deps2.agent.ctx.model
10817
- }
10818
- );
11157
+ const routed = await routeImagesForModel(buildUserContentBlocks(content, imageBlocks), {
11158
+ supportsVision: deps2.agent.ctx.provider.capabilities.vision,
11159
+ adapters: () => createToolVisionAdapters(deps2.agent.tools),
11160
+ ctx: deps2.agent.ctx,
11161
+ signal: thisRun.signal,
11162
+ providerId: deps2.agent.ctx.provider.id,
11163
+ model: deps2.agent.ctx.model
11164
+ });
10819
11165
  input = routed.blocks;
10820
11166
  }
10821
11167
  const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
@@ -10911,7 +11257,9 @@ function createMessageDispatcher(opts) {
10911
11257
  const currentCfg = deps2.configStore.get().tools ?? {};
10912
11258
  const currentDisabled = currentCfg.disabledTools ?? [];
10913
11259
  if (ok2 && !currentDisabled.includes(name2)) {
10914
- deps2.configStore.update({ tools: { ...currentCfg, disabledTools: [...currentDisabled, name2] } });
11260
+ deps2.configStore.update({
11261
+ tools: { ...currentCfg, disabledTools: [...currentDisabled, name2] }
11262
+ });
10915
11263
  }
10916
11264
  send(ws, { type: "tool.disabled", payload: { name: name2, ok: ok2 } });
10917
11265
  break;
@@ -10927,19 +11275,31 @@ function createMessageDispatcher(opts) {
10927
11275
  const currentCfg = deps2.configStore.get().tools ?? {};
10928
11276
  const currentDisabled = currentCfg.disabledTools ?? [];
10929
11277
  deps2.configStore.update({
10930
- tools: { ...currentCfg, disabledTools: currentDisabled.filter((n) => n !== name2) }
11278
+ tools: {
11279
+ ...currentCfg,
11280
+ disabledTools: currentDisabled.filter((n) => n !== name2)
11281
+ }
10931
11282
  });
10932
11283
  }
10933
11284
  send(ws, { type: "tool.enabled", payload: { name: name2, ok: ok2 } });
10934
11285
  break;
10935
11286
  }
10936
11287
  // ── Memory operations — delegated to shared handlers (memory-handlers.ts) ──
11288
+ // `memory.list` renders the single Super Memory store; all writes go
11289
+ // through the structured `memory.super.*` operations below.
10937
11290
  case "memory.list":
10938
11291
  return handleMemoryList(ws, deps2.memoryStore);
10939
- case "memory.remember":
10940
- return handleMemoryRemember(ws, msg, deps2.memoryStore);
10941
- case "memory.forget":
10942
- return handleMemoryForget(ws, msg, deps2.memoryStore);
11292
+ // ── SuperMemory operations ──
11293
+ case "memory.super.list":
11294
+ return handleSuperMemoryList(ws, deps2.memoryStore);
11295
+ case "memory.super.get":
11296
+ return handleSuperMemoryGet(ws, msg, deps2.memoryStore);
11297
+ case "memory.super.update":
11298
+ return handleSuperMemoryUpdate(ws, msg, deps2.memoryStore);
11299
+ case "memory.super.delete":
11300
+ return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
11301
+ case "memory.super.remember":
11302
+ return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
10943
11303
  // ── MCP tripwires — handleMcpRoute claims these upstream. ──
10944
11304
  case "mcp.list":
10945
11305
  throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
@@ -11274,6 +11634,7 @@ var PREF_KEYS = [
11274
11634
  // these were browser-editable there but rejected as unknown keys here).
11275
11635
  "refinerProvider",
11276
11636
  "refinerModel",
11637
+ "refinerFallbackProfile",
11277
11638
  "thinkingWord",
11278
11639
  "statuslineMode",
11279
11640
  "animationStyle",
@@ -11356,6 +11717,8 @@ async function persistPrefsToConfig(deps2, holder, payload) {
11356
11717
  setAutonomy("refinerProvider", payload["refinerProvider"]);
11357
11718
  if (typeof payload["refinerModel"] === "string")
11358
11719
  setAutonomy("refinerModel", payload["refinerModel"]);
11720
+ if (typeof payload["refinerFallbackProfile"] === "string")
11721
+ setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
11359
11722
  if (typeof payload["thinkingWord"] === "string")
11360
11723
  setAutonomy("thinkingWord", payload["thinkingWord"]);
11361
11724
  if (typeof payload["statuslineMode"] === "string")
@@ -11561,6 +11924,23 @@ function projectSavedProviders(providers) {
11561
11924
  });
11562
11925
  }
11563
11926
  var probeScrubber = new DefaultSecretScrubber();
11927
+ async function probeModelDescriptors(cfg) {
11928
+ if (!cfg.baseUrl) return [];
11929
+ try {
11930
+ const keys = normalizeKeys(cfg);
11931
+ const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
11932
+ const result = await probeLocalLlm({
11933
+ baseUrl: cfg.baseUrl,
11934
+ apiKey: active?.apiKey,
11935
+ noAuth: false,
11936
+ scrubber: probeScrubber
11937
+ });
11938
+ if (!result.ok || !result.modelIds) return [];
11939
+ return result.modelIds.map((id) => ({ id, name: id, capabilities: [] }));
11940
+ } catch {
11941
+ return [];
11942
+ }
11943
+ }
11564
11944
  function createProviderHandlers(deps2) {
11565
11945
  const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
11566
11946
  let configWriteLock = deps2.getConfigWriteLock();
@@ -11847,7 +12227,9 @@ import path20 from "node:path";
11847
12227
  import {
11848
12228
  enhanceUserPrompt,
11849
12229
  gatedEnhancerReasoning,
12230
+ nextEnhanceTimeout,
11850
12231
  recentTextTurns,
12232
+ resolveEnhanceFallbackRef,
11851
12233
  resolveProviderModelList
11852
12234
  } from "@wrongstack/core";
11853
12235
  import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
@@ -12012,6 +12394,8 @@ function createModeHandlers(ctx) {
12012
12394
  const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
12013
12395
  const freshBuilder = new DefaultSystemPromptBuilder2({
12014
12396
  memoryStore: ctx.memoryStore,
12397
+ // Single injection channel: Super Memory turn middleware, not a static section.
12398
+ injectMemory: false,
12015
12399
  skillLoader: ctx.skillLoader,
12016
12400
  modeStore: ctx.modeStore,
12017
12401
  modeId: id,
@@ -12254,7 +12638,9 @@ function createSessionHandlers(ctx) {
12254
12638
  },
12255
12639
  listContextModes: async (ws, msg) => {
12256
12640
  if (!ensureCurrentSession(ws, msg, "context.modes.list")) return;
12257
- const active = String(ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID);
12641
+ const active = String(
12642
+ ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID
12643
+ );
12258
12644
  const allModes = ctx.customModeStore.list().map((m) => ({
12259
12645
  id: m.id,
12260
12646
  name: m.name,
@@ -12265,7 +12651,10 @@ function createSessionHandlers(ctx) {
12265
12651
  eliseThreshold: m.eliseThreshold,
12266
12652
  custom: m.custom === true
12267
12653
  }));
12268
- send(ws, { type: "context.modes.list", payload: sessionPayload2({ activeId: active, modes: allModes }) });
12654
+ send(ws, {
12655
+ type: "context.modes.list",
12656
+ payload: sessionPayload2({ activeId: active, modes: allModes })
12657
+ });
12269
12658
  },
12270
12659
  switchContextMode: async (ws, msg) => {
12271
12660
  if (!ensureCurrentSession(ws, msg, "context.mode.switch")) return;
@@ -12347,7 +12736,10 @@ function createSessionHandlers(ctx) {
12347
12736
  const { id } = parsed.value;
12348
12737
  if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
12349
12738
  ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
12350
- ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({}, DEFAULT_CONTEXT_WINDOW_MODE_ID);
12739
+ ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
12740
+ {},
12741
+ DEFAULT_CONTEXT_WINDOW_MODE_ID
12742
+ );
12351
12743
  }
12352
12744
  const result = ctx.customModeStore.remove(id);
12353
12745
  sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
@@ -12360,15 +12752,7 @@ function createSessionHandlers(ctx) {
12360
12752
  send(ws, {
12361
12753
  type: "sessions.list",
12362
12754
  payload: {
12363
- sessions: list.map((s) => ({
12364
- id: s.id,
12365
- title: s.title,
12366
- startedAt: s.startedAt,
12367
- model: s.model,
12368
- provider: s.provider,
12369
- tokenTotal: s.tokenTotal,
12370
- isCurrent: s.id === currentId
12371
- }))
12755
+ sessions: toSessionHistoryEntries(list, currentId)
12372
12756
  }
12373
12757
  });
12374
12758
  } catch (err) {
@@ -12382,8 +12766,17 @@ function createSessionHandlers(ctx) {
12382
12766
  sendResult(ws, false, "Cannot delete the active session");
12383
12767
  return;
12384
12768
  }
12385
- await ctx.getSessionStore().delete(id);
12769
+ const store = ctx.getSessionStore();
12770
+ await store.delete(id);
12386
12771
  sendResult(ws, true, `Session ${id} deleted`);
12772
+ try {
12773
+ const list = await store.list(200);
12774
+ broadcast(ctx.clients, {
12775
+ type: "sessions.list",
12776
+ payload: { sessions: toSessionHistoryEntries(list, ctx.getSession().id) }
12777
+ });
12778
+ } catch {
12779
+ }
12387
12780
  } catch (err) {
12388
12781
  sendResult(ws, false, errMessage(err));
12389
12782
  }
@@ -12399,23 +12792,17 @@ function createSessionHandlers(ctx) {
12399
12792
  try {
12400
12793
  await ctx.getSessionStore().rename(id, name2);
12401
12794
  sendResult(ws, true, name2 ? `Renamed session to "${name2}"` : `Cleared session name`);
12402
- const list = await ctx.getSessionStore().list(50);
12403
- const currentId = ctx.getSession().id;
12404
- broadcast(ctx.clients, {
12405
- type: "sessions.list",
12406
- payload: {
12407
- sessions: list.map((s) => ({
12408
- id: s.id,
12409
- name: s.name,
12410
- title: s.title,
12411
- startedAt: s.startedAt,
12412
- model: s.model,
12413
- provider: s.provider,
12414
- tokenTotal: s.tokenTotal,
12415
- isCurrent: s.id === currentId
12416
- }))
12417
- }
12418
- });
12795
+ try {
12796
+ const list = await ctx.getSessionStore().list(200);
12797
+ const currentId = ctx.getSession().id;
12798
+ broadcast(ctx.clients, {
12799
+ type: "sessions.list",
12800
+ payload: {
12801
+ sessions: toSessionHistoryEntries(list, currentId)
12802
+ }
12803
+ });
12804
+ } catch {
12805
+ }
12419
12806
  } catch (err) {
12420
12807
  sendResult(ws, false, errMessage(err));
12421
12808
  }
@@ -12515,6 +12902,25 @@ function buildRoutes(state, deps2, cb) {
12515
12902
  clients: state.getClients(),
12516
12903
  modelsRegistry: deps2.modelsRegistry
12517
12904
  });
12905
+ async function applyModelSwitchCore(newProvider, newModel) {
12906
+ const cur = state.getConfig();
12907
+ state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
12908
+ deps2.configStore.update({ provider: newProvider, model: newModel });
12909
+ deps2.context.model = newModel;
12910
+ const newCfg = state.getConfig();
12911
+ const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
12912
+ const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
12913
+ deps2.context.provider = newProv;
12914
+ await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
12915
+ await cb.updateGlobalConfig((config) => {
12916
+ config.provider = newProvider;
12917
+ config.model = newModel;
12918
+ }, "model.switch");
12919
+ broadcast(state.getClients(), {
12920
+ type: "session.start",
12921
+ payload: await cb.sessionStartPayload()
12922
+ });
12923
+ }
12518
12924
  const providerRoutes = {
12519
12925
  providerHandlers,
12520
12926
  listProviders: async (ws) => {
@@ -12551,11 +12957,20 @@ function buildRoutes(state, deps2, cb) {
12551
12957
  const provider = await resolveProviderCatalogForModels(deps2.modelsRegistry, providerId, cfg);
12552
12958
  const siblingId = SIBLING_CATALOG[providerId];
12553
12959
  const siblingCatalog = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
12960
+ let resolved = resolveProviderModelList(
12961
+ cfg?.models,
12962
+ provider,
12963
+ cfg?.type ?? providerId,
12964
+ siblingCatalog
12965
+ );
12966
+ if (resolved.length === 0 && cfg?.baseUrl) {
12967
+ resolved = await probeModelDescriptors(cfg);
12968
+ }
12554
12969
  const models = await enrichProviderModelDescriptors(
12555
12970
  deps2.modelsRegistry,
12556
12971
  providerId,
12557
12972
  cfg,
12558
- resolveProviderModelList(cfg?.models, provider, cfg?.type ?? providerId, siblingCatalog)
12973
+ resolved
12559
12974
  );
12560
12975
  send(ws, {
12561
12976
  type: "provider.models",
@@ -12573,21 +12988,7 @@ function buildRoutes(state, deps2, cb) {
12573
12988
  }
12574
12989
  const { provider: newProvider, model: newModel } = parsed.value;
12575
12990
  try {
12576
- const cur = state.getConfig();
12577
- state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
12578
- deps2.configStore.update({ provider: newProvider, model: newModel });
12579
- deps2.context.model = newModel;
12580
- const newCfg = state.getConfig();
12581
- const providerCfg = newCfg.providers?.[newProvider] ?? {
12582
- type: newProvider
12583
- };
12584
- const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
12585
- deps2.context.provider = newProv;
12586
- await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
12587
- await cb.updateGlobalConfig((config) => {
12588
- config.provider = newProvider;
12589
- config.model = newModel;
12590
- }, "model.switch");
12991
+ await applyModelSwitchCore(newProvider, newModel);
12591
12992
  send(ws, {
12592
12993
  type: "key.operation_result",
12593
12994
  payload: { success: true, message: `Switched to ${newProvider} / ${newModel}` }
@@ -12600,45 +13001,100 @@ function buildRoutes(state, deps2, cb) {
12600
13001
  message: `Switch failed: ${errMessage(err)}`
12601
13002
  }
12602
13003
  });
12603
- return;
12604
13004
  }
12605
- broadcast(state.getClients(), {
12606
- type: "session.start",
12607
- payload: await cb.sessionStartPayload()
12608
- });
13005
+ },
13006
+ adoptDefaultProviderIfUnset: async (providerId) => {
13007
+ if (state.getConfig().model) return;
13008
+ const saved = await providerHandlers.loadConfigProviders();
13009
+ state.setConfig(patchConfig(state.getConfig(), { providers: saved }));
13010
+ const cfg = saved[providerId];
13011
+ if (!cfg) return;
13012
+ let model = cfg.models?.[0];
13013
+ if (!model) {
13014
+ const catalogId = cfg.type && cfg.type !== providerId ? cfg.type : providerId;
13015
+ const catalog = await deps2.modelsRegistry.getProvider(catalogId).catch(() => void 0);
13016
+ model = catalog?.models?.[0]?.id;
13017
+ }
13018
+ if (!model) {
13019
+ const probed = await probeModelDescriptors(cfg);
13020
+ model = probed[0]?.id;
13021
+ }
13022
+ if (!model) return;
13023
+ try {
13024
+ await applyModelSwitchCore(providerId, model);
13025
+ } catch {
13026
+ }
12609
13027
  },
12610
13028
  refineModel: async (ws, msg) => {
12611
- const { text } = msg.payload;
13029
+ const payload = msg.payload;
13030
+ const { text } = payload;
12612
13031
  if (!text?.trim()) {
12613
13032
  send(ws, {
12614
13033
  type: "model.refine_result",
12615
- payload: { refined: "", english: "", error: "Empty text" }
13034
+ payload: { refined: "", english: "", error: "Empty text", errorKind: "provider_error" }
12616
13035
  });
12617
13036
  return;
12618
13037
  }
13038
+ const cfg = state.getConfig();
13039
+ const fallbackRef = resolveEnhanceFallbackRef({
13040
+ ...cfg,
13041
+ provider: cfg.provider ?? "",
13042
+ model: cfg.model ?? ""
13043
+ });
13044
+ let provider = deps2.context.provider;
13045
+ let providerId = cfg.provider ?? "";
13046
+ let model = deps2.context.model;
13047
+ if (payload.provider && payload.model) {
13048
+ try {
13049
+ const providerCfg = cfg.providers?.[payload.provider] ?? {
13050
+ type: payload.provider
13051
+ };
13052
+ provider = deps2.providerRegistry.has(payload.provider) ? deps2.providerRegistry.create({ ...providerCfg, type: payload.provider }) : makeProviderFromConfig2(payload.provider, providerCfg);
13053
+ providerId = payload.provider;
13054
+ model = payload.model;
13055
+ } catch (err) {
13056
+ send(ws, {
13057
+ type: "model.refine_result",
13058
+ payload: {
13059
+ refined: text,
13060
+ english: text,
13061
+ error: `Cannot use ${payload.provider}/${payload.model}: ${errMessage(err)}`,
13062
+ errorKind: "provider_error",
13063
+ ...fallbackRef ? { fallbackRef } : {}
13064
+ }
13065
+ });
13066
+ return;
13067
+ }
13068
+ }
13069
+ const baseTimeout = 9e4;
13070
+ const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
12619
13071
  try {
12620
13072
  const history = recentTextTurns(deps2.context.messages);
12621
- const cfg = state.getConfig();
12622
13073
  const resolved = await resolveProviderModelMetadata(
12623
13074
  deps2.modelsRegistry,
12624
- cfg.provider ?? "",
12625
- cfg.model ?? "",
12626
- cfg.providers?.[cfg.provider ?? ""]
13075
+ providerId,
13076
+ model,
13077
+ cfg.providers?.[providerId]
12627
13078
  ).catch(() => void 0);
12628
13079
  const reasoning = gatedEnhancerReasoning(resolved?.capabilities?.reasoningConfig);
13080
+ let failureKind;
12629
13081
  const result = await enhanceUserPrompt({
12630
- provider: deps2.context.provider,
12631
- model: deps2.context.model,
13082
+ provider,
13083
+ model,
12632
13084
  text,
12633
13085
  history,
12634
- timeoutMs: 9e4,
13086
+ timeoutMs,
12635
13087
  ...reasoning ? { reasoning } : {},
12636
- onError: (reason) => {
13088
+ onError: (reason, kind) => {
13089
+ failureKind = kind;
12637
13090
  console.warn(
12638
13091
  JSON.stringify({
12639
13092
  level: "warn",
12640
13093
  event: "model.refine_failed",
12641
13094
  reason,
13095
+ kind,
13096
+ provider: providerId,
13097
+ model,
12642
13098
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
12643
13099
  })
12644
13100
  );
@@ -12647,12 +13103,23 @@ function buildRoutes(state, deps2, cb) {
12647
13103
  if (result) {
12648
13104
  send(ws, {
12649
13105
  type: "model.refine_result",
12650
- payload: { refined: result.refined, english: result.english }
13106
+ payload: {
13107
+ refined: result.refined,
13108
+ english: result.english,
13109
+ refinedWith: { provider: providerId, model }
13110
+ }
12651
13111
  });
12652
13112
  } else {
12653
13113
  send(ws, {
12654
13114
  type: "model.refine_result",
12655
- payload: { refined: text, english: text, error: "Refinement returned no result" }
13115
+ payload: {
13116
+ refined: text,
13117
+ english: text,
13118
+ error: "Refinement returned no result",
13119
+ errorKind: failureKind ?? "empty",
13120
+ ...failureKind === "timeout" ? { retryTimeoutMs: nextEnhanceTimeout(timeoutMs, cfg.autonomy) } : {},
13121
+ ...fallbackRef ? { fallbackRef } : {}
13122
+ }
12656
13123
  });
12657
13124
  }
12658
13125
  } catch (err) {
@@ -12666,7 +13133,13 @@ function buildRoutes(state, deps2, cb) {
12666
13133
  );
12667
13134
  send(ws, {
12668
13135
  type: "model.refine_result",
12669
- payload: { refined: text, english: text, error: errMessage(err) }
13136
+ payload: {
13137
+ refined: text,
13138
+ english: text,
13139
+ error: errMessage(err),
13140
+ errorKind: "provider_error",
13141
+ ...fallbackRef ? { fallbackRef } : {}
13142
+ }
12670
13143
  });
12671
13144
  }
12672
13145
  }
@@ -13028,8 +13501,16 @@ async function startWebUI(opts = {}) {
13028
13501
  console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
13029
13502
  if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
13030
13503
  const firstKey = expectDefined3(Object.keys(config.providers)[0]);
13031
- config = patchConfig(config, { provider: firstKey });
13032
- console.log("[WebUI] No active provider \u2014 auto-selected:", firstKey);
13504
+ const adoptModel = !config.model ? config.providers[firstKey]?.models?.[0] : void 0;
13505
+ config = patchConfig(config, {
13506
+ provider: firstKey,
13507
+ ...adoptModel ? { model: adoptModel } : {}
13508
+ });
13509
+ console.log(
13510
+ "[WebUI] No active provider \u2014 auto-selected:",
13511
+ firstKey,
13512
+ adoptModel ? `/ ${adoptModel}` : ""
13513
+ );
13033
13514
  }
13034
13515
  const needsProvider = !config.provider || !config.model;
13035
13516
  const preContext = await createPreContextServices({
@@ -13288,7 +13769,11 @@ async function startWebUI(opts = {}) {
13288
13769
  };
13289
13770
  const cb = {
13290
13771
  sessionStartPayload,
13291
- onSessionSwapped: (sessionId) => sessionIdentity.activate(sessionId),
13772
+ onSessionSwapped: async (sessionId) => {
13773
+ await sessionIdentity.activate(sessionId);
13774
+ const { hydrateSessionKanban: hydrateSessionKanban2 } = await import("@wrongstack/tools/session-kanban");
13775
+ await hydrateSessionKanban2(deps2.context);
13776
+ },
13292
13777
  updateAutoCompactionMaxContext,
13293
13778
  updateGlobalConfig: updateGlobalConfig2,
13294
13779
  persistPrefsToConfig: persistPrefsToConfig2,