@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
package/dist/index.js CHANGED
@@ -38,12 +38,13 @@ function commitsSince(cwd, baseSha, branch) {
38
38
  }
39
39
  }
40
40
  var AutoPhaseWebSocketHandler = class {
41
- constructor(agent, context, logger, storeDir, events, projectRoot) {
41
+ constructor(agent, context, logger, storeDir, events, projectRoot, onBoardState) {
42
42
  this.agent = agent;
43
43
  this.context = context;
44
44
  this.logger = logger;
45
45
  this.events = events;
46
46
  this.projectRoot = projectRoot;
47
+ this.onBoardState = onBoardState;
47
48
  this.store = new PhaseStore({ baseDir: storeDir });
48
49
  }
49
50
  agent;
@@ -51,6 +52,7 @@ var AutoPhaseWebSocketHandler = class {
51
52
  logger;
52
53
  events;
53
54
  projectRoot;
55
+ onBoardState;
54
56
  orchestrator = null;
55
57
  graph = null;
56
58
  store;
@@ -395,6 +397,15 @@ Type: ${task.type}`;
395
397
  if (!this.graph) return;
396
398
  const state = this.buildState(activePhaseId);
397
399
  this.broadcast({ type: "autophase.state", payload: state });
400
+ if (this.onBoardState) {
401
+ try {
402
+ this.onBoardState(this.graph.id, state);
403
+ } catch (err) {
404
+ this.logger.error(
405
+ `[AutoPhase] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
406
+ );
407
+ }
408
+ }
398
409
  }
399
410
  buildState(activePhaseId) {
400
411
  if (!this.graph) {
@@ -2618,10 +2629,7 @@ function cspSourceFromUrl(rawUrl) {
2618
2629
  return void 0;
2619
2630
  }
2620
2631
  }
2621
- var ALLOWED_INLINE_SCRIPT_HASHES = [
2622
- "'sha256-6PXDy0zrpXa6mvYOl11bZ8nubNUL7ushPUhGDZtaexg='",
2623
- "'sha256-6sIdwbEBx7jj0drqSHHm7MqvmoYD3CQ4lp8Zp8blcb0='"
2624
- ];
2632
+ var EXTRA_SCRIPT_SOURCES = ["'wasm-unsafe-eval'"];
2625
2633
  function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2626
2634
  const connect = /* @__PURE__ */ new Set([
2627
2635
  "'self'",
@@ -2635,7 +2643,7 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2635
2643
  }
2636
2644
  const publicWsSource = publicWsUrl ? cspSourceFromUrl(publicWsUrl) : void 0;
2637
2645
  if (publicWsSource) connect.add(publicWsSource);
2638
- const scriptSrc = ["'self'", ...ALLOWED_INLINE_SCRIPT_HASHES].join(" ");
2646
+ const scriptSrc = ["'self'", ...EXTRA_SCRIPT_SOURCES].join(" ");
2639
2647
  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'`;
2640
2648
  }
2641
2649
  function isInsideDist(candidate, distDir) {
@@ -3285,8 +3293,24 @@ function errorMessage(err) {
3285
3293
  }
3286
3294
 
3287
3295
  // src/server/memory-handlers.ts
3296
+ function isSuperMemoryStore(store) {
3297
+ const s = store;
3298
+ return typeof s.stats === "function" && typeof s.listSuper === "function" && typeof s.getSuperMemory === "function" && typeof s.updateSuperMemory === "function" && typeof s.deleteSuperMemory === "function";
3299
+ }
3300
+ function requiresSuperMemory(command) {
3301
+ return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
3302
+ }
3288
3303
  async function handleMemoryList(ws, memoryStore) {
3289
3304
  try {
3305
+ if (isSuperMemoryStore(memoryStore)) {
3306
+ const [stats, memories] = await Promise.all([
3307
+ memoryStore.stats(),
3308
+ memoryStore.listSuper()
3309
+ ]);
3310
+ const text2 = memories.length === 0 ? "\u{1F9E0} Super Memory is empty." : formatSuperMemoryText(stats, memories);
3311
+ send(ws, { type: "memory.list", payload: { text: text2 } });
3312
+ return;
3313
+ }
3290
3314
  const text = await memoryStore.readAll();
3291
3315
  send(ws, { type: "memory.list", payload: { text } });
3292
3316
  } catch (err) {
@@ -3296,26 +3320,128 @@ async function handleMemoryList(ws, memoryStore) {
3296
3320
  });
3297
3321
  }
3298
3322
  }
3299
- async function handleMemoryRemember(ws, msg, memoryStore) {
3300
- const { text, scope } = msg.payload;
3323
+ function formatSuperMemoryText(stats, memories) {
3324
+ const active = stats.byStatus["active"] ?? 0;
3325
+ const stale = stats.byStatus["stale"] ?? 0;
3326
+ const archived = stats.byStatus["archived"] ?? 0;
3327
+ const lines = [
3328
+ "## \u{1F9E0} Super Memory",
3329
+ "",
3330
+ `**Total:** ${stats.total} \xB7 \u{1F7E2} ${active} active \xB7 \u{1F7E1} ${stale} stale \xB7 \u{1F535} ${archived} archived \xB7 **edges:** ${stats.edges}`,
3331
+ ""
3332
+ ];
3333
+ for (const m of memories) {
3334
+ const tags = m.tags.length > 0 ? ` \`${m.tags.slice(0, 3).join("` `")}\`` : "";
3335
+ const icon = m.status === "active" ? "\u{1F7E2}" : m.status === "stale" ? "\u{1F7E1}" : m.status === "archived" ? "\u{1F535}" : "\u26AA";
3336
+ const preview = m.text.replace(/\s+/g, " ").trim().slice(0, 140);
3337
+ lines.push(`- ${icon} \`${m.id.slice(0, 12)}\u2026\` [${m.kind}] ${preview}${tags}`);
3338
+ }
3339
+ return lines.join("\n");
3340
+ }
3341
+ async function handleSuperMemoryList(ws, memoryStore) {
3342
+ if (!isSuperMemoryStore(memoryStore)) {
3343
+ send(ws, { type: "memory.super.list", payload: { error: requiresSuperMemory("memory.super.list") } });
3344
+ return;
3345
+ }
3301
3346
  try {
3302
- await memoryStore.remember(text, scope ?? "project-memory");
3303
- sendResult(ws, true, "Saved to memory");
3347
+ const [stats, memories] = await Promise.all([
3348
+ memoryStore.stats(),
3349
+ memoryStore.listSuper()
3350
+ ]);
3351
+ send(ws, { type: "memory.super.list", payload: { memories, stats } });
3304
3352
  } catch (err) {
3305
- sendResult(ws, false, errMessage(err));
3353
+ send(ws, { type: "memory.super.list", payload: { error: errMessage(err) } });
3306
3354
  }
3307
3355
  }
3308
- async function handleMemoryForget(ws, msg, memoryStore) {
3309
- const { text, scope } = msg.payload;
3356
+ async function handleSuperMemoryGet(ws, msg, memoryStore) {
3357
+ if (!isSuperMemoryStore(memoryStore)) {
3358
+ send(ws, { type: "memory.super.get", payload: { error: requiresSuperMemory("memory.super.get") } });
3359
+ return;
3360
+ }
3361
+ const { id } = msg.payload;
3362
+ if (!id) {
3363
+ send(ws, { type: "memory.super.get", payload: { error: "id is required" } });
3364
+ return;
3365
+ }
3310
3366
  try {
3311
- const removed = await memoryStore.forget(text, scope ?? "project-memory");
3312
- sendResult(
3313
- ws,
3314
- removed > 0,
3315
- removed > 0 ? `Removed ${removed} entr${removed === 1 ? "y" : "ies"}` : "No matching entries"
3316
- );
3367
+ const memory = await memoryStore.getSuperMemory(id);
3368
+ if (!memory) {
3369
+ send(ws, { type: "memory.super.get", payload: { error: `Memory "${id}" not found.` } });
3370
+ return;
3371
+ }
3372
+ send(ws, { type: "memory.super.get", payload: { memory } });
3317
3373
  } catch (err) {
3318
- sendResult(ws, false, errMessage(err));
3374
+ send(ws, { type: "memory.super.get", payload: { error: errMessage(err) } });
3375
+ }
3376
+ }
3377
+ async function handleSuperMemoryUpdate(ws, msg, memoryStore) {
3378
+ if (!isSuperMemoryStore(memoryStore)) {
3379
+ send(ws, { type: "memory.super.update", payload: { error: requiresSuperMemory("memory.super.update") } });
3380
+ return;
3381
+ }
3382
+ const payload = msg.payload;
3383
+ const id = payload["id"];
3384
+ if (!id) {
3385
+ send(ws, { type: "memory.super.update", payload: { error: "id is required" } });
3386
+ return;
3387
+ }
3388
+ const patch = { ...payload };
3389
+ delete patch["id"];
3390
+ if (Object.keys(patch).length === 0) {
3391
+ send(ws, { type: "memory.super.update", payload: { error: "No fields to update." } });
3392
+ return;
3393
+ }
3394
+ try {
3395
+ const memory = await memoryStore.updateSuperMemory(id, patch);
3396
+ send(ws, { type: "memory.super.update", payload: { memory } });
3397
+ } catch (err) {
3398
+ send(ws, { type: "memory.super.update", payload: { error: errMessage(err) } });
3399
+ }
3400
+ }
3401
+ async function handleSuperMemoryRemember(ws, msg, memoryStore) {
3402
+ if (!isSuperMemoryStore(memoryStore)) {
3403
+ send(ws, { type: "memory.super.remember", payload: { error: requiresSuperMemory("memory.super.remember") } });
3404
+ return;
3405
+ }
3406
+ const payload = msg.payload;
3407
+ const text = payload["text"];
3408
+ if (!text || !text.trim()) {
3409
+ send(ws, { type: "memory.super.remember", payload: { error: "text is required" } });
3410
+ return;
3411
+ }
3412
+ try {
3413
+ const memory = await memoryStore.rememberSuper({
3414
+ text: text.trim(),
3415
+ kind: payload["kind"],
3416
+ scope: payload["scope"],
3417
+ tags: payload["tags"],
3418
+ anchors: payload["anchors"],
3419
+ importance: payload["importance"],
3420
+ confidence: payload["confidence"],
3421
+ freshness: payload["freshness"],
3422
+ supersedes: payload["supersedes"],
3423
+ contradicts: payload["contradicts"]
3424
+ });
3425
+ send(ws, { type: "memory.super.remember", payload: { memory } });
3426
+ } catch (err) {
3427
+ send(ws, { type: "memory.super.remember", payload: { error: errMessage(err) } });
3428
+ }
3429
+ }
3430
+ async function handleSuperMemoryDelete(ws, msg, memoryStore) {
3431
+ if (!isSuperMemoryStore(memoryStore)) {
3432
+ send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
3433
+ return;
3434
+ }
3435
+ const { id, reason } = msg.payload;
3436
+ if (!id) {
3437
+ send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
3438
+ return;
3439
+ }
3440
+ try {
3441
+ await memoryStore.deleteSuperMemory(id, reason);
3442
+ send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
3443
+ } catch (err) {
3444
+ send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
3319
3445
  }
3320
3446
  }
3321
3447
 
@@ -3365,6 +3491,16 @@ function openBrowser(url, platform = process.platform) {
3365
3491
  // src/server/port-utils.ts
3366
3492
  import * as net from "node:net";
3367
3493
  import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
3494
+ var SURFACE_DEFAULT_PORTS = {
3495
+ webui: { http: 3456, ws: 3457 },
3496
+ simpleui: { http: 3466, ws: 3467 }
3497
+ };
3498
+ function surfaceLabel(surface) {
3499
+ return surface === "webui" ? "WebUI" : "SimpleUI";
3500
+ }
3501
+ function getSurfaceDefaultPorts(surface) {
3502
+ return { ...SURFACE_DEFAULT_PORTS[surface] };
3503
+ }
3368
3504
  function isPortFree(host, port) {
3369
3505
  return new Promise((resolve10) => {
3370
3506
  const srv = net.createServer();
@@ -3921,6 +4057,8 @@ var SddBoardWebSocketHandler = class {
3921
4057
  import * as path9 from "node:path";
3922
4058
  import { spawnSync as spawnSync2 } from "node:child_process";
3923
4059
  import {
4060
+ DefaultTaskStore,
4061
+ TaskTracker,
3924
4062
  WorktreeManager as WorktreeManager2,
3925
4063
  ToolValidationError as ToolValidationError4
3926
4064
  } from "@wrongstack/core";
@@ -3936,6 +4074,63 @@ import {
3936
4074
  startSddRun,
3937
4075
  TaskGraphStore
3938
4076
  } from "@wrongstack/sdd";
4077
+ function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
4078
+ const runTracker = tracker ?? (() => {
4079
+ const t = new TaskTracker({ store: new DefaultTaskStore() });
4080
+ t.setGraph(graph);
4081
+ return t;
4082
+ })();
4083
+ const worktreesEnabled = config.worktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
4084
+ let worktrees;
4085
+ if (worktreesEnabled) {
4086
+ const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
4087
+ cwd: deps2.projectRoot,
4088
+ encoding: "utf8",
4089
+ windowsHide: true
4090
+ }).stdout?.trim() === "true";
4091
+ if (inGit) {
4092
+ void cleanupStaleSddWorktrees({
4093
+ projectRoot: deps2.projectRoot,
4094
+ boardsDir: deps2.projectSddBoards
4095
+ }).catch(() => void 0);
4096
+ worktrees = new WorktreeManager2({
4097
+ projectRoot: deps2.projectRoot,
4098
+ events: deps2.events,
4099
+ sessionId: () => deps2.agent.ctx.session?.id
4100
+ });
4101
+ }
4102
+ }
4103
+ const superviseFailure = deps2.brain && deps2.runIsolatedTurn ? new SddSupervisor({
4104
+ brain: deps2.brain,
4105
+ reassignModels: config.fallbackModels,
4106
+ generateSubtasks: makeLlmSubtaskGenerator({
4107
+ run: (prompt) => deps2.runIsolatedTurn(prompt, "Task Splitter")
4108
+ }),
4109
+ requestLlmVerdict: true
4110
+ }).superviseFailure : deps2.brain ? new SddSupervisor({
4111
+ brain: deps2.brain,
4112
+ reassignModels: config.fallbackModels,
4113
+ requestLlmVerdict: true
4114
+ }).superviseFailure : void 0;
4115
+ return startSddRun({
4116
+ tracker: runTracker,
4117
+ graph,
4118
+ agent: deps2.agent,
4119
+ projectRoot: deps2.projectRoot,
4120
+ events: deps2.events,
4121
+ sessionId: () => deps2.agent.ctx.session?.id,
4122
+ subagentFactory: deps2.subagentFactory,
4123
+ boardStore: new SddBoardStore2({ baseDir: deps2.projectSddBoards }),
4124
+ registry: deps2.registry ?? new SddRunRegistry(),
4125
+ verifyTask: makeCommandVerifier(),
4126
+ ...superviseFailure ? { superviseFailure } : {},
4127
+ ...config.parallelSlots !== void 0 ? { parallelSlots: config.parallelSlots } : {},
4128
+ ...config.defaultModel !== void 0 ? { defaultModel: config.defaultModel } : {},
4129
+ ...config.defaultProvider !== void 0 ? { defaultProvider: config.defaultProvider } : {},
4130
+ ...config.fallbackModels !== void 0 ? { fallbackModels: config.fallbackModels } : {},
4131
+ ...worktrees ? { worktrees } : {}
4132
+ });
4133
+ }
3939
4134
  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";
3940
4135
  function buildSddWizardDeps(opts) {
3941
4136
  const registry = new SddRunRegistry();
@@ -3971,64 +4166,27 @@ function buildSddWizardDeps(opts) {
3971
4166
  field: "taskGraph"
3972
4167
  });
3973
4168
  }
3974
- const worktreesEnabled = useWorktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
3975
- let worktrees;
3976
- if (worktreesEnabled) {
3977
- const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
3978
- cwd: opts.projectRoot,
3979
- encoding: "utf8",
3980
- windowsHide: true
3981
- }).stdout?.trim() === "true";
3982
- if (inGit) {
3983
- await cleanupStaleSddWorktrees({
3984
- projectRoot: opts.projectRoot,
3985
- boardsDir: opts.paths.projectSddBoards
3986
- }).catch(() => void 0);
3987
- worktrees = new WorktreeManager2({
3988
- projectRoot: opts.projectRoot,
3989
- events: opts.events,
3990
- sessionId: () => opts.agent.ctx.session?.id
3991
- });
3992
- }
3993
- }
3994
- const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
3995
- const verifyTask = makeCommandVerifier();
3996
- const superviseFailure = opts.brain ? new SddSupervisor({
3997
- brain: opts.brain,
3998
- // The run-level fallback chain (chosen in the wizard) doubles as the
3999
- // supervisor's reassign options — a `reassign` verdict rotates the
4000
- // worker model on retry. Empty/undefined → reassign option dropped.
4001
- reassignModels: fallbackModels,
4002
- // LLM auto-split: decompose a retry-exhausted task into smaller
4003
- // sub-tasks on an isolated read-only turn. Heavily validated +
4004
- // bounded; an empty result degrades the split into a retry.
4005
- generateSubtasks: makeLlmSubtaskGenerator({
4006
- run: (prompt) => runIsolatedTurn(prompt, "Task Splitter")
4007
- }),
4008
- // The standalone brain is a tiered policy→LLM arbiter with NO
4009
- // human-escalation wrapper (see index.ts), so it never blocks on a
4010
- // human prompt — an unresolved verdict degrades to a bounded retry.
4011
- // Safe to let the LLM layer actually pick reassign/split.
4012
- requestLlmVerdict: true
4013
- }).superviseFailure : void 0;
4014
- const handle = startSddRun({
4015
- tracker,
4169
+ const handle = startSddRunFromGraph(
4016
4170
  graph,
4017
- agent: opts.agent,
4018
- projectRoot: opts.projectRoot,
4019
- events: opts.events,
4020
- sessionId: () => opts.agent.ctx.session?.id,
4021
- subagentFactory: opts.subagentFactory,
4022
- worktrees,
4023
- boardStore,
4024
- registry,
4025
- parallelSlots,
4026
- defaultModel,
4027
- defaultProvider,
4028
- fallbackModels,
4029
- verifyTask,
4030
- superviseFailure
4031
- });
4171
+ {
4172
+ agent: opts.agent,
4173
+ events: opts.events,
4174
+ projectRoot: opts.projectRoot,
4175
+ subagentFactory: opts.subagentFactory,
4176
+ projectSddBoards: opts.paths.projectSddBoards,
4177
+ registry,
4178
+ runIsolatedTurn,
4179
+ ...opts.brain ? { brain: opts.brain } : {}
4180
+ },
4181
+ {
4182
+ ...parallelSlots !== void 0 ? { parallelSlots } : {},
4183
+ ...defaultModel !== void 0 ? { defaultModel } : {},
4184
+ ...defaultProvider !== void 0 ? { defaultProvider } : {},
4185
+ ...fallbackModels !== void 0 ? { fallbackModels } : {},
4186
+ ...useWorktrees !== void 0 ? { worktrees: useWorktrees } : {}
4187
+ },
4188
+ tracker
4189
+ );
4032
4190
  void handle.completion.catch(() => {
4033
4191
  });
4034
4192
  return { runId: handle.runId };
@@ -4172,6 +4330,31 @@ var SddWizardWebSocketHandler = class {
4172
4330
  }
4173
4331
  };
4174
4332
 
4333
+ // src/server/session-history.ts
4334
+ function toSessionHistoryEntry(summary, currentSessionId) {
4335
+ return {
4336
+ id: summary.id,
4337
+ title: summary.title,
4338
+ ...summary.name !== void 0 ? { name: summary.name } : {},
4339
+ startedAt: summary.startedAt,
4340
+ ...summary.endedAt !== void 0 ? { endedAt: summary.endedAt } : {},
4341
+ model: summary.model,
4342
+ provider: summary.provider,
4343
+ tokenTotal: summary.tokenTotal,
4344
+ ...summary.iterationCount !== void 0 ? { iterationCount: summary.iterationCount } : {},
4345
+ ...summary.toolCallCount !== void 0 ? { toolCallCount: summary.toolCallCount } : {},
4346
+ ...summary.toolErrorCount !== void 0 ? { toolErrorCount: summary.toolErrorCount } : {},
4347
+ ...summary.fileChangeCount !== void 0 ? { fileChangeCount: summary.fileChangeCount } : {},
4348
+ ...summary.toolBreakdown !== void 0 ? { toolBreakdown: summary.toolBreakdown } : {},
4349
+ ...summary.compactionCount !== void 0 ? { compactionCount: summary.compactionCount } : {},
4350
+ ...summary.outcome !== void 0 ? { outcome: summary.outcome } : {},
4351
+ isCurrent: summary.id === currentSessionId
4352
+ };
4353
+ }
4354
+ function toSessionHistoryEntries(summaries, currentSessionId) {
4355
+ return summaries.map((summary) => toSessionHistoryEntry(summary, currentSessionId));
4356
+ }
4357
+
4175
4358
  // src/server/shell-open.ts
4176
4359
  import * as fs8 from "node:fs/promises";
4177
4360
  import * as path10 from "node:path";
@@ -4465,7 +4648,8 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4465
4648
  "uiLocale",
4466
4649
  "thinkingWord",
4467
4650
  "refinerProvider",
4468
- "refinerModel"
4651
+ "refinerModel",
4652
+ "refinerFallbackProfile"
4469
4653
  ]);
4470
4654
  var ENUM_PREF_KEYS = {
4471
4655
  autonomy: AUTONOMY_VALUES,
@@ -6417,6 +6601,18 @@ function setupEvents(deps2) {
6417
6601
  payload: sessionPayload2({ event: eventName, ...payload })
6418
6602
  });
6419
6603
  });
6604
+ events.onPattern("cron:state_snapshot", (_eventName, payload) => {
6605
+ broadcast2(clients, {
6606
+ type: "cron.snapshot",
6607
+ payload
6608
+ });
6609
+ });
6610
+ events.onPattern("cron:job_fired", (_eventName, payload) => {
6611
+ broadcast2(clients, {
6612
+ type: "cron.job_fired",
6613
+ payload
6614
+ });
6615
+ });
6420
6616
  on("client.status", async (e) => {
6421
6617
  broadcast2(clients, { type: "client.status_update", payload: e });
6422
6618
  if (wpaths?.projectStatus) {
@@ -6766,9 +6962,11 @@ function positiveNumber(value) {
6766
6962
  // src/server/server-runtime.ts
6767
6963
  import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
6768
6964
  async function resolvePorts(opts) {
6769
- const requestedWsPort = opts.wsPort ?? 3457;
6965
+ const surface = opts.surface ?? "webui";
6966
+ const surfaceDefaults = surface === "simpleui" ? { http: 3466, ws: 3467 } : { http: 3456, ws: 3457 };
6967
+ const requestedWsPort = opts.wsPort ?? surfaceDefaults.ws;
6770
6968
  const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
6771
- const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? "3456", 10);
6969
+ const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? String(surfaceDefaults.http), 10);
6772
6970
  const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
6773
6971
  const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
6774
6972
  const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
@@ -6926,8 +7124,8 @@ function resolveWebuiDistDir(fromUrl, explicitDistDir) {
6926
7124
  if (explicitDistDir) return path13.resolve(explicitDistDir);
6927
7125
  try {
6928
7126
  const requireFromHere2 = createRequire(fromUrl);
6929
- const serverEntry = requireFromHere2.resolve("@wrongstack/webui/server");
6930
- return path13.resolve(path13.dirname(serverEntry), "..");
7127
+ const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
7128
+ return path13.dirname(serverEntry);
6931
7129
  } catch {
6932
7130
  return path13.resolve(path13.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
6933
7131
  }
@@ -6950,7 +7148,7 @@ function startHttpServer(opts) {
6950
7148
  console.log(`[WebUI] HTTP server running on ${openUrl}`);
6951
7149
  if (opts.openBrowser) openBrowser(openUrl);
6952
7150
  void registerInstance(
6953
- { pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path13.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
7151
+ { pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path13.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
6954
7152
  registryBaseDir
6955
7153
  ).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
6956
7154
  });
@@ -7017,6 +7215,10 @@ import {
7017
7215
  rememberTool,
7018
7216
  searchMemoryTool
7019
7217
  } from "@wrongstack/tools";
7218
+ import {
7219
+ attachSessionKanbanMirror,
7220
+ hydrateSessionKanban
7221
+ } from "@wrongstack/tools/session-kanban";
7020
7222
  import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
7021
7223
 
7022
7224
  // src/server/setup-screen.ts
@@ -7157,6 +7359,7 @@ function seedContextMeta(config, context) {
7157
7359
  }
7158
7360
  meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
7159
7361
  meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
7362
+ meta["refinerFallbackProfile"] = autonomyCfg["refinerFallbackProfile"] ?? "";
7160
7363
  meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
7161
7364
  meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
7162
7365
  meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
@@ -7490,12 +7693,13 @@ async function createPreContextServices(input) {
7490
7693
  })();
7491
7694
  const memoryStore = container.resolve(TOKENS.MemoryStore);
7492
7695
  if (config.features.memory) {
7493
- toolRegistry.register(rememberTool(memoryStore));
7494
- toolRegistry.register(forgetTool(memoryStore));
7495
- toolRegistry.register(searchMemoryTool(memoryStore));
7496
- toolRegistry.register(relatedMemoryTool(memoryStore));
7497
7696
  if (isSuperMemoryService(memoryStore)) {
7498
7697
  for (const tool of createSuperMemoryTools(memoryStore)) toolRegistry.register(tool);
7698
+ } else {
7699
+ toolRegistry.register(rememberTool(memoryStore));
7700
+ toolRegistry.register(forgetTool(memoryStore));
7701
+ toolRegistry.register(searchMemoryTool(memoryStore));
7702
+ toolRegistry.register(relatedMemoryTool(memoryStore));
7499
7703
  }
7500
7704
  }
7501
7705
  toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
@@ -7624,6 +7828,9 @@ async function createPreContextServices(input) {
7624
7828
  const promptsCtx = { promptLoader, promptUsage };
7625
7829
  const systemPromptBuilder = new DefaultSystemPromptBuilder({
7626
7830
  memoryStore,
7831
+ // Super Memory's turn middleware is the single memory-injection channel;
7832
+ // don't also inject a static memory section here (avoids double injection).
7833
+ injectMemory: false,
7627
7834
  skillLoader,
7628
7835
  modeStore,
7629
7836
  modeId,
@@ -7674,6 +7881,8 @@ async function createPreContextServices(input) {
7674
7881
  "task.path",
7675
7882
  sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
7676
7883
  );
7884
+ await hydrateSessionKanban(context);
7885
+ attachSessionKanbanMirror(context);
7677
7886
  seedContextMeta(config, context);
7678
7887
  return {
7679
7888
  modelsRegistry,
@@ -9807,8 +10016,10 @@ import {
9807
10016
  serializeTaskGraph
9808
10017
  } from "@wrongstack/core";
9809
10018
  import {
10019
+ addCheckToTask,
9810
10020
  addColumn,
9811
10021
  addGoalMetricToTask,
10022
+ addNoteToTask,
9812
10023
  addTask,
9813
10024
  assignTask,
9814
10025
  claimReadyTask,
@@ -9822,29 +10033,47 @@ import {
9822
10033
  getKanbanQueueHealth,
9823
10034
  getTask,
9824
10035
  getTaskChain,
9825
- listReadyTasks,
9826
10036
  listBoards,
10037
+ listReadyTasks,
9827
10038
  mergeTasks,
9828
10039
  moveTask,
9829
10040
  parseLinesIntoTasks,
10041
+ reconcileKanbanBoard,
10042
+ recoverStaleTaskAssignments,
10043
+ releaseTaskClaim,
9830
10044
  removeBoard,
9831
10045
  removeColumn,
9832
10046
  removeTask,
9833
- releaseTaskClaim,
9834
10047
  setTaskChain,
9835
10048
  splitTask,
9836
10049
  syncBoardFromTaskGraph,
9837
10050
  transferTaskToBoard,
9838
10051
  updateBoard,
10052
+ updateCheckOnTask,
9839
10053
  updateGoalMetricOnTask,
9840
10054
  updateTask
9841
10055
  } from "@wrongstack/kanban";
10056
+ import { applySessionKanbanTaskToSource } from "@wrongstack/tools/session-kanban";
10057
+ async function syncSessionSource(ctx, task, remove = false) {
10058
+ if (!ctx.context) return;
10059
+ const update = await applySessionKanbanTaskToSource(ctx.context, task, { remove });
10060
+ const sessionId = ctx.context.session?.id ?? "";
10061
+ if (update.todos)
10062
+ ctx.broadcast?.({ type: "todos.updated", payload: { sessionId, todos: update.todos } });
10063
+ if (update.tasks)
10064
+ ctx.broadcast?.({ type: "tasks.updated", payload: { sessionId, tasks: update.tasks.tasks } });
10065
+ if (update.plan)
10066
+ ctx.broadcast?.({ type: "plan.updated", payload: { sessionId, plan: update.plan } });
10067
+ }
9842
10068
  function ok(ws, type, data) {
9843
10069
  send(ws, { type, payload: { success: true, data: data ?? null } });
9844
10070
  }
9845
10071
  function fail(ws, type, message) {
9846
10072
  send(ws, { type, payload: { success: false, error: message } });
9847
10073
  }
10074
+ function has(payload, key) {
10075
+ return payload !== void 0 && Object.hasOwn(payload, key);
10076
+ }
9848
10077
  async function handleKanbanRoute(ws, msg, ctx) {
9849
10078
  if (!msg.type.startsWith("kanban.")) return false;
9850
10079
  const payload = msg.payload;
@@ -9873,6 +10102,38 @@ async function handleKanbanRoute(ws, msg, ctx) {
9873
10102
  ok(ws, type, await getKanbanQueueHealth(ctx.projectRoot, { boardId: hBoardId }));
9874
10103
  return true;
9875
10104
  }
10105
+ case "kanban.supervisor.status":
10106
+ case "kanban.supervisor.audit": {
10107
+ const boardId = payload?.boardId;
10108
+ if (!boardId) {
10109
+ fail(ws, type, "boardId required");
10110
+ return true;
10111
+ }
10112
+ const board = await getBoard(ctx.projectRoot, boardId);
10113
+ if (!board) {
10114
+ fail(ws, type, `Board not found: ${boardId}`);
10115
+ return true;
10116
+ }
10117
+ const reconciled = await reconcileKanbanBoard(ctx.projectRoot, boardId);
10118
+ let health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
10119
+ const recovered = health.staleAssignments.count ? await recoverStaleTaskAssignments(ctx.projectRoot, boardId, {
10120
+ mode: board.supervisor?.recoveryMode ?? "auto",
10121
+ reason: "On-demand standalone Kanban supervisor audit."
10122
+ }) : null;
10123
+ if (recovered) health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
10124
+ const anomalyCount = health.staleAssignments.count + health.dependencyBlocked.count + health.failedRetryable.count + health.counts.failed + health.counts.blocked;
10125
+ ok(ws, type, {
10126
+ boardId,
10127
+ status: board.supervisor?.enabled === false ? "disabled" : anomalyCount ? "attention" : "healthy",
10128
+ mode: board.supervisor?.mode ?? "deterministic",
10129
+ lastAuditAt: (/* @__PURE__ */ new Date()).toISOString(),
10130
+ reconciledTaskIds: reconciled?.tasks.map((task) => task.id) ?? [],
10131
+ staleRecoveredTaskIds: recovered?.tasks.map((task) => task.id) ?? [],
10132
+ anomalyCount,
10133
+ 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`
10134
+ });
10135
+ return true;
10136
+ }
9876
10137
  case "kanban.create": {
9877
10138
  const title = payload?.title;
9878
10139
  if (!title) {
@@ -9901,7 +10162,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
9901
10162
  ...payload?.title ? { title: payload.title } : {},
9902
10163
  ...payload?.description ? { description: payload.description } : {},
9903
10164
  ...payload?.tags ? { tags: payload.tags } : {},
9904
- ...payload?.columns ? { columns: payload.columns } : {}
10165
+ ...payload?.columns ? { columns: payload.columns } : {},
10166
+ ...has(payload, "supervisor") ? {
10167
+ supervisor: payload?.supervisor ?? null
10168
+ } : {}
9905
10169
  });
9906
10170
  board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
9907
10171
  return true;
@@ -9928,6 +10192,11 @@ async function handleKanbanRoute(ws, msg, ctx) {
9928
10192
  return true;
9929
10193
  }
9930
10194
  const board = await getBoard(ctx.projectRoot, boardId);
10195
+ const activeSessionId = ctx.context?.session?.id;
10196
+ if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
10197
+ fail(ws, type, "The active session Kanban board cannot be deleted.");
10198
+ return true;
10199
+ }
9931
10200
  ok(ws, type, {
9932
10201
  removed: board ? await removeBoard(ctx.projectRoot, board.id) : false,
9933
10202
  boardId: board?.id ?? boardId
@@ -10108,15 +10377,31 @@ async function handleKanbanRoute(ws, msg, ctx) {
10108
10377
  return true;
10109
10378
  }
10110
10379
  const board = await updateTask(ctx.projectRoot, boardId, taskId, {
10111
- ...payload?.title ? { title: payload.title } : {},
10112
- ...payload?.description ? { description: payload.description } : {},
10113
- ...payload?.columnId ? { columnId: payload.columnId } : {},
10114
- ...payload?.priority ? { priority: payload.priority } : {},
10115
- ...payload?.status ? { status: payload.status } : {},
10116
- ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
10117
- ...payload?.labels ? { labels: payload.labels } : {}
10380
+ ...has(payload, "title") ? { title: payload?.title } : {},
10381
+ ...has(payload, "description") ? { description: payload?.description ?? "" } : {},
10382
+ ...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
10383
+ ...has(payload, "priority") ? { priority: payload?.priority } : {},
10384
+ ...has(payload, "type") ? { type: payload?.type } : {},
10385
+ ...has(payload, "status") ? { status: payload?.status } : {},
10386
+ ...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
10387
+ ...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
10388
+ ...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
10389
+ ...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
10390
+ ...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
10391
+ ...has(payload, "retryPolicy") ? {
10392
+ retryPolicy: payload?.retryPolicy ?? null
10393
+ } : {},
10394
+ ...has(payload, "costCeilingUsd") ? {
10395
+ costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
10396
+ } : {}
10118
10397
  });
10119
- board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
10398
+ if (!board) {
10399
+ fail(ws, type, "Board or task not found");
10400
+ return true;
10401
+ }
10402
+ const task = findTask(board.tasks, taskId);
10403
+ if (task) await syncSessionSource(ctx, task);
10404
+ ok(ws, type, task);
10120
10405
  return true;
10121
10406
  }
10122
10407
  case "kanban.task.move": {
@@ -10134,7 +10419,13 @@ async function handleKanbanRoute(ws, msg, ctx) {
10134
10419
  columnId,
10135
10420
  payload?.order
10136
10421
  );
10137
- board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Move failed");
10422
+ if (!board) {
10423
+ fail(ws, type, "Move failed");
10424
+ return true;
10425
+ }
10426
+ const task = findTask(board.tasks, taskId);
10427
+ if (task) await syncSessionSource(ctx, task);
10428
+ ok(ws, type, task);
10138
10429
  return true;
10139
10430
  }
10140
10431
  case "kanban.task.copy":
@@ -10193,8 +10484,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
10193
10484
  ...payload?.role ? { role: payload.role } : {},
10194
10485
  ...payload?.provider ? { provider: payload.provider } : {},
10195
10486
  ...payload?.model ? { model: payload.model } : {},
10487
+ ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
10196
10488
  ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
10197
10489
  ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
10490
+ ...payload?.skills ? { skills: payload.skills } : {},
10198
10491
  ...payload?.tools ? { tools: payload.tools } : {},
10199
10492
  ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
10200
10493
  ...payload?.assignee ? { assignee: payload.assignee } : {},
@@ -10213,7 +10506,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
10213
10506
  const board = await releaseTaskClaim(ctx.projectRoot, boardId, taskId, {
10214
10507
  ...payload?.status ? { status: payload.status } : {},
10215
10508
  ...payload?.reason ? { reason: payload.reason } : {},
10216
- ...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {}
10509
+ ...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {},
10510
+ ...typeof payload?.maxAttempts === "number" ? { maxAttempts: payload.maxAttempts } : {},
10511
+ ...typeof payload?.costCeilingUsd === "number" ? { costCeilingUsd: payload.costCeilingUsd } : {},
10512
+ ...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
10217
10513
  });
10218
10514
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10219
10515
  return true;
@@ -10269,14 +10565,66 @@ async function handleKanbanRoute(ws, msg, ctx) {
10269
10565
  ...payload?.role ? { role: payload.role } : {},
10270
10566
  ...payload?.provider ? { provider: payload.provider } : {},
10271
10567
  ...payload?.model ? { model: payload.model } : {},
10568
+ ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
10272
10569
  ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
10273
10570
  ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
10571
+ ...payload?.skills ? { skills: payload.skills } : {},
10274
10572
  ...payload?.tools ? { tools: payload.tools } : {},
10275
- ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {}
10573
+ ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
10574
+ ...payload?.assignee ? { assignee: payload.assignee } : {},
10575
+ ...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
10576
+ ...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
10577
+ ...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
10276
10578
  });
10277
10579
  board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
10278
10580
  return true;
10279
10581
  }
10582
+ case "kanban.task.check.add": {
10583
+ const boardId = payload?.boardId;
10584
+ const taskId = payload?.taskId;
10585
+ const description = payload?.description;
10586
+ if (!boardId || !taskId || !description) {
10587
+ fail(ws, type, "boardId, taskId, and description required");
10588
+ return true;
10589
+ }
10590
+ const board = await addCheckToTask(ctx.projectRoot, boardId, taskId, {
10591
+ description,
10592
+ type: payload?.checkType ?? "manual",
10593
+ status: payload?.status ?? "pending"
10594
+ });
10595
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10596
+ return true;
10597
+ }
10598
+ case "kanban.task.check.update": {
10599
+ const boardId = payload?.boardId;
10600
+ const taskId = payload?.taskId;
10601
+ const checkId = payload?.checkId;
10602
+ if (!boardId || !taskId || !checkId) {
10603
+ fail(ws, type, "boardId, taskId, and checkId required");
10604
+ return true;
10605
+ }
10606
+ const board = await updateCheckOnTask(ctx.projectRoot, boardId, taskId, checkId, {
10607
+ ...has(payload, "status") ? { status: payload?.status } : {}
10608
+ });
10609
+ if (!board) fail(ws, type, "Check not found");
10610
+ else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
10611
+ return true;
10612
+ }
10613
+ case "kanban.task.note.add": {
10614
+ const boardId = payload?.boardId;
10615
+ const taskId = payload?.taskId;
10616
+ const content = payload?.content;
10617
+ if (!boardId || !taskId || !content) {
10618
+ fail(ws, type, "boardId, taskId, and content required");
10619
+ return true;
10620
+ }
10621
+ const board = await addNoteToTask(ctx.projectRoot, boardId, taskId, {
10622
+ author: payload?.author ?? "webui",
10623
+ content
10624
+ });
10625
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10626
+ return true;
10627
+ }
10280
10628
  case "kanban.task.dispatch":
10281
10629
  fail(
10282
10630
  ws,
@@ -10300,7 +10648,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
10300
10648
  return true;
10301
10649
  }
10302
10650
  const board = await removeTask(ctx.projectRoot, boardId, taskId);
10303
- board ? ok(ws, type, { removed: true, boardId: board.id, taskId: task.id, board }) : fail(ws, type, "Board or task not found");
10651
+ if (!board) {
10652
+ fail(ws, type, "Board or task not found");
10653
+ return true;
10654
+ }
10655
+ await syncSessionSource(ctx, task, true);
10656
+ ok(ws, type, { removed: true, boardId: board.id, taskId: task.id, board });
10304
10657
  return true;
10305
10658
  }
10306
10659
  case "kanban.task.get": {
@@ -10612,6 +10965,7 @@ async function handleProviderRoute(ws, msg, routes) {
10612
10965
  baseUrl,
10613
10966
  apiKey
10614
10967
  });
10968
+ await routes.adoptDefaultProviderIfUnset(id);
10615
10969
  return true;
10616
10970
  }
10617
10971
  case "provider.remove": {
@@ -10850,7 +11204,12 @@ function createMessageDispatcher(opts) {
10850
11204
  if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
10851
11205
  if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
10852
11206
  if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
10853
- if (await handleKanbanRoute(ws, msg, { projectRoot: state.getProjectRoot() })) return;
11207
+ if (await handleKanbanRoute(ws, msg, {
11208
+ projectRoot: state.getProjectRoot(),
11209
+ context: deps2.context,
11210
+ broadcast: (message) => broadcast(state.getClients(), message)
11211
+ }))
11212
+ return;
10854
11213
  if (msg.type.startsWith("worktree.") && await deps2.worktreeHandler.handleMessage(
10855
11214
  msg
10856
11215
  ))
@@ -10899,17 +11258,14 @@ function createMessageDispatcher(opts) {
10899
11258
  let input = content;
10900
11259
  const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
10901
11260
  if (imageBlocks.length > 0) {
10902
- const routed = await routeImagesForModel(
10903
- buildUserContentBlocks(content, imageBlocks),
10904
- {
10905
- supportsVision: deps2.agent.ctx.provider.capabilities.vision,
10906
- adapters: () => createToolVisionAdapters(deps2.agent.tools),
10907
- ctx: deps2.agent.ctx,
10908
- signal: thisRun.signal,
10909
- providerId: deps2.agent.ctx.provider.id,
10910
- model: deps2.agent.ctx.model
10911
- }
10912
- );
11261
+ const routed = await routeImagesForModel(buildUserContentBlocks(content, imageBlocks), {
11262
+ supportsVision: deps2.agent.ctx.provider.capabilities.vision,
11263
+ adapters: () => createToolVisionAdapters(deps2.agent.tools),
11264
+ ctx: deps2.agent.ctx,
11265
+ signal: thisRun.signal,
11266
+ providerId: deps2.agent.ctx.provider.id,
11267
+ model: deps2.agent.ctx.model
11268
+ });
10913
11269
  input = routed.blocks;
10914
11270
  }
10915
11271
  const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
@@ -11005,7 +11361,9 @@ function createMessageDispatcher(opts) {
11005
11361
  const currentCfg = deps2.configStore.get().tools ?? {};
11006
11362
  const currentDisabled = currentCfg.disabledTools ?? [];
11007
11363
  if (ok2 && !currentDisabled.includes(name2)) {
11008
- deps2.configStore.update({ tools: { ...currentCfg, disabledTools: [...currentDisabled, name2] } });
11364
+ deps2.configStore.update({
11365
+ tools: { ...currentCfg, disabledTools: [...currentDisabled, name2] }
11366
+ });
11009
11367
  }
11010
11368
  send(ws, { type: "tool.disabled", payload: { name: name2, ok: ok2 } });
11011
11369
  break;
@@ -11021,19 +11379,31 @@ function createMessageDispatcher(opts) {
11021
11379
  const currentCfg = deps2.configStore.get().tools ?? {};
11022
11380
  const currentDisabled = currentCfg.disabledTools ?? [];
11023
11381
  deps2.configStore.update({
11024
- tools: { ...currentCfg, disabledTools: currentDisabled.filter((n) => n !== name2) }
11382
+ tools: {
11383
+ ...currentCfg,
11384
+ disabledTools: currentDisabled.filter((n) => n !== name2)
11385
+ }
11025
11386
  });
11026
11387
  }
11027
11388
  send(ws, { type: "tool.enabled", payload: { name: name2, ok: ok2 } });
11028
11389
  break;
11029
11390
  }
11030
11391
  // ── Memory operations — delegated to shared handlers (memory-handlers.ts) ──
11392
+ // `memory.list` renders the single Super Memory store; all writes go
11393
+ // through the structured `memory.super.*` operations below.
11031
11394
  case "memory.list":
11032
11395
  return handleMemoryList(ws, deps2.memoryStore);
11033
- case "memory.remember":
11034
- return handleMemoryRemember(ws, msg, deps2.memoryStore);
11035
- case "memory.forget":
11036
- return handleMemoryForget(ws, msg, deps2.memoryStore);
11396
+ // ── SuperMemory operations ──
11397
+ case "memory.super.list":
11398
+ return handleSuperMemoryList(ws, deps2.memoryStore);
11399
+ case "memory.super.get":
11400
+ return handleSuperMemoryGet(ws, msg, deps2.memoryStore);
11401
+ case "memory.super.update":
11402
+ return handleSuperMemoryUpdate(ws, msg, deps2.memoryStore);
11403
+ case "memory.super.delete":
11404
+ return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
11405
+ case "memory.super.remember":
11406
+ return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
11037
11407
  // ── MCP tripwires — handleMcpRoute claims these upstream. ──
11038
11408
  case "mcp.list":
11039
11409
  throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
@@ -11368,6 +11738,7 @@ var PREF_KEYS = [
11368
11738
  // these were browser-editable there but rejected as unknown keys here).
11369
11739
  "refinerProvider",
11370
11740
  "refinerModel",
11741
+ "refinerFallbackProfile",
11371
11742
  "thinkingWord",
11372
11743
  "statuslineMode",
11373
11744
  "animationStyle",
@@ -11450,6 +11821,8 @@ async function persistPrefsToConfig(deps2, holder, payload) {
11450
11821
  setAutonomy("refinerProvider", payload["refinerProvider"]);
11451
11822
  if (typeof payload["refinerModel"] === "string")
11452
11823
  setAutonomy("refinerModel", payload["refinerModel"]);
11824
+ if (typeof payload["refinerFallbackProfile"] === "string")
11825
+ setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
11453
11826
  if (typeof payload["thinkingWord"] === "string")
11454
11827
  setAutonomy("thinkingWord", payload["thinkingWord"]);
11455
11828
  if (typeof payload["statuslineMode"] === "string")
@@ -11655,6 +12028,23 @@ function projectSavedProviders(providers) {
11655
12028
  });
11656
12029
  }
11657
12030
  var probeScrubber = new DefaultSecretScrubber();
12031
+ async function probeModelDescriptors(cfg) {
12032
+ if (!cfg.baseUrl) return [];
12033
+ try {
12034
+ const keys = normalizeKeys(cfg);
12035
+ const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
12036
+ const result = await probeLocalLlm({
12037
+ baseUrl: cfg.baseUrl,
12038
+ apiKey: active?.apiKey,
12039
+ noAuth: false,
12040
+ scrubber: probeScrubber
12041
+ });
12042
+ if (!result.ok || !result.modelIds) return [];
12043
+ return result.modelIds.map((id) => ({ id, name: id, capabilities: [] }));
12044
+ } catch {
12045
+ return [];
12046
+ }
12047
+ }
11658
12048
  function createProviderHandlers(deps2) {
11659
12049
  const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
11660
12050
  let configWriteLock = deps2.getConfigWriteLock();
@@ -11941,7 +12331,9 @@ import path21 from "node:path";
11941
12331
  import {
11942
12332
  enhanceUserPrompt,
11943
12333
  gatedEnhancerReasoning,
12334
+ nextEnhanceTimeout,
11944
12335
  recentTextTurns,
12336
+ resolveEnhanceFallbackRef,
11945
12337
  resolveProviderModelList
11946
12338
  } from "@wrongstack/core";
11947
12339
  import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
@@ -12106,6 +12498,8 @@ function createModeHandlers(ctx) {
12106
12498
  const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
12107
12499
  const freshBuilder = new DefaultSystemPromptBuilder2({
12108
12500
  memoryStore: ctx.memoryStore,
12501
+ // Single injection channel: Super Memory turn middleware, not a static section.
12502
+ injectMemory: false,
12109
12503
  skillLoader: ctx.skillLoader,
12110
12504
  modeStore: ctx.modeStore,
12111
12505
  modeId: id,
@@ -12348,7 +12742,9 @@ function createSessionHandlers(ctx) {
12348
12742
  },
12349
12743
  listContextModes: async (ws, msg) => {
12350
12744
  if (!ensureCurrentSession(ws, msg, "context.modes.list")) return;
12351
- const active = String(ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID);
12745
+ const active = String(
12746
+ ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID
12747
+ );
12352
12748
  const allModes = ctx.customModeStore.list().map((m) => ({
12353
12749
  id: m.id,
12354
12750
  name: m.name,
@@ -12359,7 +12755,10 @@ function createSessionHandlers(ctx) {
12359
12755
  eliseThreshold: m.eliseThreshold,
12360
12756
  custom: m.custom === true
12361
12757
  }));
12362
- send(ws, { type: "context.modes.list", payload: sessionPayload2({ activeId: active, modes: allModes }) });
12758
+ send(ws, {
12759
+ type: "context.modes.list",
12760
+ payload: sessionPayload2({ activeId: active, modes: allModes })
12761
+ });
12363
12762
  },
12364
12763
  switchContextMode: async (ws, msg) => {
12365
12764
  if (!ensureCurrentSession(ws, msg, "context.mode.switch")) return;
@@ -12441,7 +12840,10 @@ function createSessionHandlers(ctx) {
12441
12840
  const { id } = parsed.value;
12442
12841
  if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
12443
12842
  ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
12444
- ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3({}, DEFAULT_CONTEXT_WINDOW_MODE_ID);
12843
+ ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
12844
+ {},
12845
+ DEFAULT_CONTEXT_WINDOW_MODE_ID
12846
+ );
12445
12847
  }
12446
12848
  const result = ctx.customModeStore.remove(id);
12447
12849
  sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
@@ -12454,15 +12856,7 @@ function createSessionHandlers(ctx) {
12454
12856
  send(ws, {
12455
12857
  type: "sessions.list",
12456
12858
  payload: {
12457
- sessions: list.map((s) => ({
12458
- id: s.id,
12459
- title: s.title,
12460
- startedAt: s.startedAt,
12461
- model: s.model,
12462
- provider: s.provider,
12463
- tokenTotal: s.tokenTotal,
12464
- isCurrent: s.id === currentId
12465
- }))
12859
+ sessions: toSessionHistoryEntries(list, currentId)
12466
12860
  }
12467
12861
  });
12468
12862
  } catch (err) {
@@ -12476,8 +12870,17 @@ function createSessionHandlers(ctx) {
12476
12870
  sendResult(ws, false, "Cannot delete the active session");
12477
12871
  return;
12478
12872
  }
12479
- await ctx.getSessionStore().delete(id);
12873
+ const store = ctx.getSessionStore();
12874
+ await store.delete(id);
12480
12875
  sendResult(ws, true, `Session ${id} deleted`);
12876
+ try {
12877
+ const list = await store.list(200);
12878
+ broadcast(ctx.clients, {
12879
+ type: "sessions.list",
12880
+ payload: { sessions: toSessionHistoryEntries(list, ctx.getSession().id) }
12881
+ });
12882
+ } catch {
12883
+ }
12481
12884
  } catch (err) {
12482
12885
  sendResult(ws, false, errMessage(err));
12483
12886
  }
@@ -12493,23 +12896,17 @@ function createSessionHandlers(ctx) {
12493
12896
  try {
12494
12897
  await ctx.getSessionStore().rename(id, name2);
12495
12898
  sendResult(ws, true, name2 ? `Renamed session to "${name2}"` : `Cleared session name`);
12496
- const list = await ctx.getSessionStore().list(50);
12497
- const currentId = ctx.getSession().id;
12498
- broadcast(ctx.clients, {
12499
- type: "sessions.list",
12500
- payload: {
12501
- sessions: list.map((s) => ({
12502
- id: s.id,
12503
- name: s.name,
12504
- title: s.title,
12505
- startedAt: s.startedAt,
12506
- model: s.model,
12507
- provider: s.provider,
12508
- tokenTotal: s.tokenTotal,
12509
- isCurrent: s.id === currentId
12510
- }))
12511
- }
12512
- });
12899
+ try {
12900
+ const list = await ctx.getSessionStore().list(200);
12901
+ const currentId = ctx.getSession().id;
12902
+ broadcast(ctx.clients, {
12903
+ type: "sessions.list",
12904
+ payload: {
12905
+ sessions: toSessionHistoryEntries(list, currentId)
12906
+ }
12907
+ });
12908
+ } catch {
12909
+ }
12513
12910
  } catch (err) {
12514
12911
  sendResult(ws, false, errMessage(err));
12515
12912
  }
@@ -12609,6 +13006,25 @@ function buildRoutes(state, deps2, cb) {
12609
13006
  clients: state.getClients(),
12610
13007
  modelsRegistry: deps2.modelsRegistry
12611
13008
  });
13009
+ async function applyModelSwitchCore(newProvider, newModel) {
13010
+ const cur = state.getConfig();
13011
+ state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
13012
+ deps2.configStore.update({ provider: newProvider, model: newModel });
13013
+ deps2.context.model = newModel;
13014
+ const newCfg = state.getConfig();
13015
+ const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
13016
+ const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
13017
+ deps2.context.provider = newProv;
13018
+ await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
13019
+ await cb.updateGlobalConfig((config) => {
13020
+ config.provider = newProvider;
13021
+ config.model = newModel;
13022
+ }, "model.switch");
13023
+ broadcast(state.getClients(), {
13024
+ type: "session.start",
13025
+ payload: await cb.sessionStartPayload()
13026
+ });
13027
+ }
12612
13028
  const providerRoutes = {
12613
13029
  providerHandlers,
12614
13030
  listProviders: async (ws) => {
@@ -12645,11 +13061,20 @@ function buildRoutes(state, deps2, cb) {
12645
13061
  const provider = await resolveProviderCatalogForModels(deps2.modelsRegistry, providerId, cfg);
12646
13062
  const siblingId = SIBLING_CATALOG[providerId];
12647
13063
  const siblingCatalog = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
13064
+ let resolved = resolveProviderModelList(
13065
+ cfg?.models,
13066
+ provider,
13067
+ cfg?.type ?? providerId,
13068
+ siblingCatalog
13069
+ );
13070
+ if (resolved.length === 0 && cfg?.baseUrl) {
13071
+ resolved = await probeModelDescriptors(cfg);
13072
+ }
12648
13073
  const models = await enrichProviderModelDescriptors(
12649
13074
  deps2.modelsRegistry,
12650
13075
  providerId,
12651
13076
  cfg,
12652
- resolveProviderModelList(cfg?.models, provider, cfg?.type ?? providerId, siblingCatalog)
13077
+ resolved
12653
13078
  );
12654
13079
  send(ws, {
12655
13080
  type: "provider.models",
@@ -12667,21 +13092,7 @@ function buildRoutes(state, deps2, cb) {
12667
13092
  }
12668
13093
  const { provider: newProvider, model: newModel } = parsed.value;
12669
13094
  try {
12670
- const cur = state.getConfig();
12671
- state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
12672
- deps2.configStore.update({ provider: newProvider, model: newModel });
12673
- deps2.context.model = newModel;
12674
- const newCfg = state.getConfig();
12675
- const providerCfg = newCfg.providers?.[newProvider] ?? {
12676
- type: newProvider
12677
- };
12678
- const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
12679
- deps2.context.provider = newProv;
12680
- await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
12681
- await cb.updateGlobalConfig((config) => {
12682
- config.provider = newProvider;
12683
- config.model = newModel;
12684
- }, "model.switch");
13095
+ await applyModelSwitchCore(newProvider, newModel);
12685
13096
  send(ws, {
12686
13097
  type: "key.operation_result",
12687
13098
  payload: { success: true, message: `Switched to ${newProvider} / ${newModel}` }
@@ -12694,45 +13105,100 @@ function buildRoutes(state, deps2, cb) {
12694
13105
  message: `Switch failed: ${errMessage(err)}`
12695
13106
  }
12696
13107
  });
12697
- return;
12698
13108
  }
12699
- broadcast(state.getClients(), {
12700
- type: "session.start",
12701
- payload: await cb.sessionStartPayload()
12702
- });
13109
+ },
13110
+ adoptDefaultProviderIfUnset: async (providerId) => {
13111
+ if (state.getConfig().model) return;
13112
+ const saved = await providerHandlers.loadConfigProviders();
13113
+ state.setConfig(patchConfig(state.getConfig(), { providers: saved }));
13114
+ const cfg = saved[providerId];
13115
+ if (!cfg) return;
13116
+ let model = cfg.models?.[0];
13117
+ if (!model) {
13118
+ const catalogId = cfg.type && cfg.type !== providerId ? cfg.type : providerId;
13119
+ const catalog = await deps2.modelsRegistry.getProvider(catalogId).catch(() => void 0);
13120
+ model = catalog?.models?.[0]?.id;
13121
+ }
13122
+ if (!model) {
13123
+ const probed = await probeModelDescriptors(cfg);
13124
+ model = probed[0]?.id;
13125
+ }
13126
+ if (!model) return;
13127
+ try {
13128
+ await applyModelSwitchCore(providerId, model);
13129
+ } catch {
13130
+ }
12703
13131
  },
12704
13132
  refineModel: async (ws, msg) => {
12705
- const { text } = msg.payload;
13133
+ const payload = msg.payload;
13134
+ const { text } = payload;
12706
13135
  if (!text?.trim()) {
12707
13136
  send(ws, {
12708
13137
  type: "model.refine_result",
12709
- payload: { refined: "", english: "", error: "Empty text" }
13138
+ payload: { refined: "", english: "", error: "Empty text", errorKind: "provider_error" }
12710
13139
  });
12711
13140
  return;
12712
13141
  }
13142
+ const cfg = state.getConfig();
13143
+ const fallbackRef = resolveEnhanceFallbackRef({
13144
+ ...cfg,
13145
+ provider: cfg.provider ?? "",
13146
+ model: cfg.model ?? ""
13147
+ });
13148
+ let provider = deps2.context.provider;
13149
+ let providerId = cfg.provider ?? "";
13150
+ let model = deps2.context.model;
13151
+ if (payload.provider && payload.model) {
13152
+ try {
13153
+ const providerCfg = cfg.providers?.[payload.provider] ?? {
13154
+ type: payload.provider
13155
+ };
13156
+ provider = deps2.providerRegistry.has(payload.provider) ? deps2.providerRegistry.create({ ...providerCfg, type: payload.provider }) : makeProviderFromConfig2(payload.provider, providerCfg);
13157
+ providerId = payload.provider;
13158
+ model = payload.model;
13159
+ } catch (err) {
13160
+ send(ws, {
13161
+ type: "model.refine_result",
13162
+ payload: {
13163
+ refined: text,
13164
+ english: text,
13165
+ error: `Cannot use ${payload.provider}/${payload.model}: ${errMessage(err)}`,
13166
+ errorKind: "provider_error",
13167
+ ...fallbackRef ? { fallbackRef } : {}
13168
+ }
13169
+ });
13170
+ return;
13171
+ }
13172
+ }
13173
+ const baseTimeout = 9e4;
13174
+ const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
12713
13175
  try {
12714
13176
  const history = recentTextTurns(deps2.context.messages);
12715
- const cfg = state.getConfig();
12716
13177
  const resolved = await resolveProviderModelMetadata(
12717
13178
  deps2.modelsRegistry,
12718
- cfg.provider ?? "",
12719
- cfg.model ?? "",
12720
- cfg.providers?.[cfg.provider ?? ""]
13179
+ providerId,
13180
+ model,
13181
+ cfg.providers?.[providerId]
12721
13182
  ).catch(() => void 0);
12722
13183
  const reasoning = gatedEnhancerReasoning(resolved?.capabilities?.reasoningConfig);
13184
+ let failureKind;
12723
13185
  const result = await enhanceUserPrompt({
12724
- provider: deps2.context.provider,
12725
- model: deps2.context.model,
13186
+ provider,
13187
+ model,
12726
13188
  text,
12727
13189
  history,
12728
- timeoutMs: 9e4,
13190
+ timeoutMs,
12729
13191
  ...reasoning ? { reasoning } : {},
12730
- onError: (reason) => {
13192
+ onError: (reason, kind) => {
13193
+ failureKind = kind;
12731
13194
  console.warn(
12732
13195
  JSON.stringify({
12733
13196
  level: "warn",
12734
13197
  event: "model.refine_failed",
12735
13198
  reason,
13199
+ kind,
13200
+ provider: providerId,
13201
+ model,
12736
13202
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
12737
13203
  })
12738
13204
  );
@@ -12741,12 +13207,23 @@ function buildRoutes(state, deps2, cb) {
12741
13207
  if (result) {
12742
13208
  send(ws, {
12743
13209
  type: "model.refine_result",
12744
- payload: { refined: result.refined, english: result.english }
13210
+ payload: {
13211
+ refined: result.refined,
13212
+ english: result.english,
13213
+ refinedWith: { provider: providerId, model }
13214
+ }
12745
13215
  });
12746
13216
  } else {
12747
13217
  send(ws, {
12748
13218
  type: "model.refine_result",
12749
- payload: { refined: text, english: text, error: "Refinement returned no result" }
13219
+ payload: {
13220
+ refined: text,
13221
+ english: text,
13222
+ error: "Refinement returned no result",
13223
+ errorKind: failureKind ?? "empty",
13224
+ ...failureKind === "timeout" ? { retryTimeoutMs: nextEnhanceTimeout(timeoutMs, cfg.autonomy) } : {},
13225
+ ...fallbackRef ? { fallbackRef } : {}
13226
+ }
12750
13227
  });
12751
13228
  }
12752
13229
  } catch (err) {
@@ -12760,7 +13237,13 @@ function buildRoutes(state, deps2, cb) {
12760
13237
  );
12761
13238
  send(ws, {
12762
13239
  type: "model.refine_result",
12763
- payload: { refined: text, english: text, error: errMessage(err) }
13240
+ payload: {
13241
+ refined: text,
13242
+ english: text,
13243
+ error: errMessage(err),
13244
+ errorKind: "provider_error",
13245
+ ...fallbackRef ? { fallbackRef } : {}
13246
+ }
12764
13247
  });
12765
13248
  }
12766
13249
  }
@@ -13122,8 +13605,16 @@ async function startWebUI(opts = {}) {
13122
13605
  console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
13123
13606
  if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
13124
13607
  const firstKey = expectDefined3(Object.keys(config.providers)[0]);
13125
- config = patchConfig(config, { provider: firstKey });
13126
- console.log("[WebUI] No active provider \u2014 auto-selected:", firstKey);
13608
+ const adoptModel = !config.model ? config.providers[firstKey]?.models?.[0] : void 0;
13609
+ config = patchConfig(config, {
13610
+ provider: firstKey,
13611
+ ...adoptModel ? { model: adoptModel } : {}
13612
+ });
13613
+ console.log(
13614
+ "[WebUI] No active provider \u2014 auto-selected:",
13615
+ firstKey,
13616
+ adoptModel ? `/ ${adoptModel}` : ""
13617
+ );
13127
13618
  }
13128
13619
  const needsProvider = !config.provider || !config.model;
13129
13620
  const preContext = await createPreContextServices({
@@ -13382,7 +13873,11 @@ async function startWebUI(opts = {}) {
13382
13873
  };
13383
13874
  const cb = {
13384
13875
  sessionStartPayload,
13385
- onSessionSwapped: (sessionId) => sessionIdentity.activate(sessionId),
13876
+ onSessionSwapped: async (sessionId) => {
13877
+ await sessionIdentity.activate(sessionId);
13878
+ const { hydrateSessionKanban: hydrateSessionKanban2 } = await import("@wrongstack/tools/session-kanban");
13879
+ await hydrateSessionKanban2(deps2.context);
13880
+ },
13386
13881
  updateAutoCompactionMaxContext,
13387
13882
  updateGlobalConfig: updateGlobalConfig2,
13388
13883
  persistPrefsToConfig: persistPrefsToConfig2,
@@ -13622,6 +14117,7 @@ export {
13622
14117
  AutoPhaseWebSocketHandler,
13623
14118
  CollaborationWebSocketHandler,
13624
14119
  SKIP_DIRS,
14120
+ SURFACE_DEFAULT_PORTS,
13625
14121
  SddBoardWebSocketHandler,
13626
14122
  SddWizardWebSocketHandler,
13627
14123
  SpecsWebSocketHandler,
@@ -13661,6 +14157,7 @@ export {
13661
14157
  generateAuthToken,
13662
14158
  getAnalyticsBuffer,
13663
14159
  getCostRates,
14160
+ getSurfaceDefaultPorts,
13664
14161
  handleApiAnalyticsGet,
13665
14162
  handleApiAnalyticsPost,
13666
14163
  handleApiAnalyticsSummary,
@@ -13698,9 +14195,7 @@ export {
13698
14195
  handleMcpSleep,
13699
14196
  handleMcpUpdate,
13700
14197
  handleMcpWake,
13701
- handleMemoryForget,
13702
14198
  handleMemoryList,
13703
- handleMemoryRemember,
13704
14199
  handleModeRoute,
13705
14200
  handlePrefsRoute,
13706
14201
  handleProcessKill,
@@ -13729,6 +14224,11 @@ export {
13729
14224
  handleSkillsUninstall,
13730
14225
  handleSkillsUpdate,
13731
14226
  handleSpecsRoute,
14227
+ handleSuperMemoryDelete,
14228
+ handleSuperMemoryGet,
14229
+ handleSuperMemoryList,
14230
+ handleSuperMemoryRemember,
14231
+ handleSuperMemoryUpdate,
13732
14232
  handleWorklistMessage,
13733
14233
  hostForBrowserUrl,
13734
14234
  hostHeaderOk,
@@ -13773,9 +14273,13 @@ export {
13773
14273
  setActiveKey,
13774
14274
  setupEvents,
13775
14275
  setupWebUICodebaseIndexing,
14276
+ startSddRunFromGraph,
13776
14277
  startWebUI,
13777
14278
  statusProjectHashFromWatchFilename,
13778
14279
  stringifyContent,
14280
+ surfaceLabel,
14281
+ toSessionHistoryEntries,
14282
+ toSessionHistoryEntry,
13779
14283
  tokenMatches,
13780
14284
  unregisterInstance,
13781
14285
  upsertKey,