@wrongstack/webui-server 0.287.0 → 0.291.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 (63) hide show
  1. package/dist/index.js +2270 -642
  2. package/dist/index.js.map +4 -4
  3. package/dist/server/backend-services.d.ts +4 -4
  4. package/dist/server/backend-services.d.ts.map +1 -1
  5. package/dist/server/codebase-indexing.d.ts +2 -1
  6. package/dist/server/codebase-indexing.d.ts.map +1 -1
  7. package/dist/server/codemap-handlers.d.ts +26 -0
  8. package/dist/server/codemap-handlers.d.ts.map +1 -0
  9. package/dist/server/codemap-telemetry.d.ts +15 -0
  10. package/dist/server/codemap-telemetry.d.ts.map +1 -0
  11. package/dist/server/collaboration-ws-handler.d.ts +1 -1
  12. package/dist/server/connection-handler.d.ts +2 -2
  13. package/dist/server/connection-handler.d.ts.map +1 -1
  14. package/dist/server/context-meta.d.ts.map +1 -1
  15. package/dist/server/entry.js +2258 -637
  16. package/dist/server/entry.js.map +4 -4
  17. package/dist/server/goal-handlers.d.ts +1 -1
  18. package/dist/server/goal-routes.d.ts +10 -0
  19. package/dist/server/goal-routes.d.ts.map +1 -0
  20. package/dist/server/{autophase-ws-handler.d.ts → goal-ws-handler.d.ts} +18 -18
  21. package/dist/server/goal-ws-handler.d.ts.map +1 -0
  22. package/dist/server/handlers.js +1 -0
  23. package/dist/server/handlers.js.map +2 -2
  24. package/dist/server/http-server.d.ts +20 -0
  25. package/dist/server/http-server.d.ts.map +1 -1
  26. package/dist/server/index.d.ts +11 -8
  27. package/dist/server/index.d.ts.map +1 -1
  28. package/dist/server/instance-registry.d.ts +2 -2
  29. package/dist/server/instance-registry.d.ts.map +1 -1
  30. package/dist/server/kanban-routes.d.ts +15 -0
  31. package/dist/server/kanban-routes.d.ts.map +1 -1
  32. package/dist/server/mcp-handlers.d.ts +1 -1
  33. package/dist/server/memory-handlers.d.ts +37 -0
  34. package/dist/server/memory-handlers.d.ts.map +1 -1
  35. package/dist/server/message-dispatcher.d.ts +3 -2
  36. package/dist/server/message-dispatcher.d.ts.map +1 -1
  37. package/dist/server/mode-handlers.d.ts.map +1 -1
  38. package/dist/server/pending-confirms.d.ts +1 -0
  39. package/dist/server/pending-confirms.d.ts.map +1 -1
  40. package/dist/server/pre-context-services.d.ts +3 -2
  41. package/dist/server/pre-context-services.d.ts.map +1 -1
  42. package/dist/server/pref-helpers.d.ts +18 -1
  43. package/dist/server/pref-helpers.d.ts.map +1 -1
  44. package/dist/server/provider-keys.d.ts.map +1 -1
  45. package/dist/server/routes.d.ts +5 -5
  46. package/dist/server/routes.d.ts.map +1 -1
  47. package/dist/server/server-runtime.d.ts +11 -20
  48. package/dist/server/server-runtime.d.ts.map +1 -1
  49. package/dist/server/session-handlers.d.ts.map +1 -1
  50. package/dist/server/setup-events.d.ts +1 -1
  51. package/dist/server/setup-events.d.ts.map +1 -1
  52. package/dist/server/start-webui.d.ts.map +1 -1
  53. package/dist/server/techstack-handlers.d.ts +101 -0
  54. package/dist/server/techstack-handlers.d.ts.map +1 -0
  55. package/dist/server/terminal-ws-handler.d.ts +2 -1
  56. package/dist/server/terminal-ws-handler.d.ts.map +1 -1
  57. package/dist/server/worktree-ws-handler.d.ts +1 -1
  58. package/dist/server/ws-payload-validation.d.ts +24 -23
  59. package/dist/server/ws-payload-validation.d.ts.map +1 -1
  60. package/package.json +12 -14
  61. package/dist/server/autophase-routes.d.ts +0 -10
  62. package/dist/server/autophase-routes.d.ts.map +0 -1
  63. package/dist/server/autophase-ws-handler.d.ts.map +0 -1
@@ -2,12 +2,12 @@
2
2
  // src/server/entry.ts
3
3
  import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core";
4
4
 
5
- // src/server/autophase-ws-handler.ts
5
+ // src/server/goal-ws-handler.ts
6
6
  import { spawnSync } from "node:child_process";
7
7
  import { toErrorMessage } from "@wrongstack/core/utils";
8
8
  import {
9
9
  assignNickname,
10
- AutoPhasePlanner,
10
+ GoalPlanner,
11
11
  PhaseGraphBuilder,
12
12
  PhaseOrchestrator,
13
13
  PhaseStore,
@@ -15,10 +15,10 @@ import {
15
15
  } from "@wrongstack/core";
16
16
  function deriveTitle(goal) {
17
17
  const firstLine = goal.split("\n").map((l) => l.trim()).find(Boolean);
18
- if (!firstLine) return "AutoPhase";
18
+ if (!firstLine) return "Goal";
19
19
  const sentence = firstLine.split(/(?<=[.!?])\s/)[0] ?? firstLine;
20
20
  const trimmed = sentence.length <= 64 ? sentence : `${sentence.slice(0, 63).trimEnd()}\u2026`;
21
- return trimmed || "AutoPhase";
21
+ return trimmed || "Goal";
22
22
  }
23
23
  function isGitRepo(cwd) {
24
24
  try {
@@ -41,7 +41,7 @@ function commitsSince(cwd, baseSha, branch) {
41
41
  return [];
42
42
  }
43
43
  }
44
- var AutoPhaseWebSocketHandler = class {
44
+ var GoalWebSocketHandler = class {
45
45
  constructor(agent, context, logger, storeDir, events, projectRoot, onBoardState) {
46
46
  this.agent = agent;
47
47
  this.context = context;
@@ -84,94 +84,94 @@ var AutoPhaseWebSocketHandler = class {
84
84
  }
85
85
  async handleMessage(msg) {
86
86
  switch (msg.type) {
87
- case "autophase.start":
87
+ case "goal.start":
88
88
  await this.handleStart(msg.payload);
89
89
  break;
90
- case "autophase.pause":
90
+ case "goal.pause":
91
91
  this.orchestrator?.pause();
92
- this.broadcast({ type: "autophase.paused", payload: {} });
92
+ this.broadcast({ type: "goal.paused", payload: {} });
93
93
  break;
94
- case "autophase.resume":
94
+ case "goal.resume":
95
95
  this.orchestrator?.resume();
96
- this.broadcast({ type: "autophase.resumed", payload: {} });
96
+ this.broadcast({ type: "goal.resumed", payload: {} });
97
97
  break;
98
- case "autophase.stop":
98
+ case "goal.stop":
99
99
  await this.handleStop();
100
100
  break;
101
- case "autophase.clear":
101
+ case "goal.clear":
102
102
  await this.handleClear();
103
103
  break;
104
- case "autophase.revert":
104
+ case "goal.revert":
105
105
  await this.handleRevert();
106
106
  break;
107
- case "autophase.status":
107
+ case "goal.status":
108
108
  this.broadcastState();
109
109
  break;
110
- case "autophase.selectPhase": {
110
+ case "goal.selectPhase": {
111
111
  const phaseId = msg.payload?.phaseId;
112
112
  if (phaseId && this.graph) {
113
113
  this.broadcastState(phaseId);
114
114
  }
115
115
  break;
116
116
  }
117
- case "autophase.taskStatus": {
117
+ case "goal.taskStatus": {
118
118
  const { taskId, status } = msg.payload;
119
119
  await this.handleTaskStatusChange(taskId, status);
120
120
  break;
121
121
  }
122
- case "autophase.moveTask": {
122
+ case "goal.moveTask": {
123
123
  const { taskId, toPhaseId } = msg.payload;
124
124
  if (this.orchestrator?.moveTask(taskId, toPhaseId)) this.afterBoardMutation();
125
125
  break;
126
126
  }
127
- case "autophase.assignTask": {
127
+ case "goal.assignTask": {
128
128
  const { taskId, agentId, agentName } = msg.payload;
129
129
  if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName)) this.afterBoardMutation();
130
130
  break;
131
131
  }
132
- case "autophase.addTask": {
132
+ case "goal.addTask": {
133
133
  const { phaseId, title, description, type, priority } = msg.payload;
134
134
  if (title?.trim() && this.orchestrator?.addTask(phaseId, { title: title.trim(), description, type, priority })) {
135
135
  this.afterBoardMutation();
136
136
  }
137
137
  break;
138
138
  }
139
- case "autophase.retryTask":
140
- case "autophase.runTask": {
139
+ case "goal.retryTask":
140
+ case "goal.runTask": {
141
141
  const { taskId } = msg.payload;
142
142
  if (this.orchestrator?.requeueTask(taskId)) this.afterBoardMutation();
143
143
  break;
144
144
  }
145
- case "autophase.toggleAutonomous": {
145
+ case "goal.toggleAutonomous": {
146
146
  const autonomous = msg.payload?.autonomous ?? !this.graph?.autonomous;
147
147
  if (this.graph) {
148
148
  this.graph.autonomous = autonomous;
149
149
  await this.store.save(this.graph);
150
- this.broadcast({ type: "autophase.state", payload: this.buildState() });
150
+ this.broadcast({ type: "goal.state", payload: this.buildState() });
151
151
  }
152
152
  break;
153
153
  }
154
- case "autophase.save": {
154
+ case "goal.save": {
155
155
  if (this.graph) {
156
156
  await this.store.save(this.graph);
157
- this.broadcast({ type: "autophase.saved", payload: { graphId: this.graph.id } });
157
+ this.broadcast({ type: "goal.saved", payload: { graphId: this.graph.id } });
158
158
  }
159
159
  break;
160
160
  }
161
- case "autophase.list": {
161
+ case "goal.list": {
162
162
  const graphs = await this.store.list();
163
- this.broadcast({ type: "autophase.list", payload: { graphs } });
163
+ this.broadcast({ type: "goal.list", payload: { graphs } });
164
164
  break;
165
165
  }
166
- case "autophase.load": {
166
+ case "goal.load": {
167
167
  const graphId = msg.payload?.graphId;
168
168
  if (graphId) {
169
169
  const graph = await this.store.load(graphId);
170
170
  if (graph) {
171
171
  this.graph = graph;
172
- this.broadcast({ type: "autophase.state", payload: this.buildState() });
172
+ this.broadcast({ type: "goal.state", payload: this.buildState() });
173
173
  } else {
174
- this.broadcast({ type: "autophase.error", payload: { message: `Graph not found: ${graphId}` } });
174
+ this.broadcast({ type: "goal.error", payload: { message: `Graph not found: ${graphId}` } });
175
175
  }
176
176
  }
177
177
  break;
@@ -186,14 +186,14 @@ var AutoPhaseWebSocketHandler = class {
186
186
  this.stopping = false;
187
187
  const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal, this.abort.signal);
188
188
  if (this.stopping || this.abort.signal.aborted) {
189
- this.broadcast({ type: "autophase.stopped", payload: { title } });
189
+ this.broadcast({ type: "goal.stopped", payload: { title } });
190
190
  return;
191
191
  }
192
- this.logger.info(`[AutoPhase] Starting: ${title}`);
192
+ this.logger.info(`[Goal] Starting: ${title}`);
193
193
  const graph = await new PhaseGraphBuilder({ title, description: goal, phases, autonomous }).build();
194
194
  this.graph = graph;
195
195
  await this.store.save(graph);
196
- const useWorktrees = payload?.worktrees ?? process.env["WRONGSTACK_AUTOPHASE_WORKTREES"] !== "0";
196
+ const useWorktrees = payload?.worktrees ?? process.env["WRONGSTACK_GOAL_WORKTREES"] !== "0";
197
197
  if (!this.worktrees && this.events && this.projectRoot && useWorktrees && isGitRepo(this.projectRoot)) {
198
198
  this.worktrees = new WorktreeManager({
199
199
  projectRoot: this.projectRoot,
@@ -208,18 +208,18 @@ var AutoPhaseWebSocketHandler = class {
208
208
  graph,
209
209
  ctx: {
210
210
  executeTask: async (task, phaseId, env) => {
211
- this.logger.info(`[AutoPhase] [${phaseId}] Executing: ${task.title}`);
211
+ this.logger.info(`[Goal] [${phaseId}] Executing: ${task.title}`);
212
212
  const result = await this.executeTaskWithAgent(task, phaseId, env);
213
- this.logger.info(`[AutoPhase] [${phaseId}] Completed: ${task.title}`);
213
+ this.logger.info(`[Goal] [${phaseId}] Completed: ${task.title}`);
214
214
  return result;
215
215
  },
216
216
  onPhaseComplete: (phase) => {
217
- this.logger.info(`[AutoPhase] Phase completed: ${phase.name}`);
217
+ this.logger.info(`[Goal] Phase completed: ${phase.name}`);
218
218
  void this.store.save(graph);
219
219
  this.broadcastState();
220
220
  },
221
221
  onPhaseFail: (phase, error) => {
222
- this.logger.error(`[AutoPhase] Phase failed: ${phase.name} \u2014 ${error.message}`);
222
+ this.logger.error(`[Goal] Phase failed: ${phase.name} \u2014 ${error.message}`);
223
223
  void this.store.save(graph);
224
224
  this.broadcastState();
225
225
  }
@@ -241,20 +241,20 @@ var AutoPhaseWebSocketHandler = class {
241
241
  this.stopBroadcast();
242
242
  const failed = graph.failedPhaseIds.length > 0;
243
243
  this.broadcast(
244
- failed ? { type: "autophase.failed", payload: { title } } : { type: "autophase.completed", payload: { title } }
244
+ failed ? { type: "goal.failed", payload: { title } } : { type: "goal.completed", payload: { title } }
245
245
  );
246
246
  this.broadcastState();
247
247
  }).catch((err) => {
248
- this.logger.error(`[AutoPhase] Aborted: ${toErrorMessage(err)}`);
248
+ this.logger.error(`[Goal] Aborted: ${toErrorMessage(err)}`);
249
249
  this.stopBroadcast();
250
- this.broadcast({ type: "autophase.failed", payload: { title, error: String(err) } });
250
+ this.broadcast({ type: "goal.failed", payload: { title, error: String(err) } });
251
251
  });
252
252
  }
253
253
  /**
254
254
  * Halt the run NOW — at any phase. Sets `stopping` (so a planning turn that
255
255
  * resolves afterwards bails), aborts in-flight agents, stops the orchestrator
256
256
  * tick, and ends the live broadcast. The board is kept for review; use
257
- * `autophase.clear` to reset or `autophase.revert` to undo the changes.
257
+ * `goal.clear` to reset or `goal.revert` to undo the changes.
258
258
  */
259
259
  async handleStop() {
260
260
  this.stopping = true;
@@ -262,12 +262,12 @@ var AutoPhaseWebSocketHandler = class {
262
262
  this.orchestrator?.stop();
263
263
  this.stopBroadcast();
264
264
  if (this.graph) await this.store.save(this.graph).catch(() => void 0);
265
- this.broadcast({ type: "autophase.stopped", payload: { title: this.graph?.title } });
265
+ this.broadcast({ type: "goal.stopped", payload: { title: this.graph?.title } });
266
266
  }
267
267
  /**
268
268
  * Stop + wipe: tear down phase worktrees and reset to an empty board so the UI
269
269
  * returns to the start screen ("new one"). Does NOT touch already-merged commits
270
- * on the base branch — that is `autophase.revert`.
270
+ * on the base branch — that is `goal.revert`.
271
271
  */
272
272
  async handleClear() {
273
273
  await this.handleStop();
@@ -276,8 +276,8 @@ var AutoPhaseWebSocketHandler = class {
276
276
  this.graph = null;
277
277
  this.runBase = null;
278
278
  this.usedNicknames.clear();
279
- this.broadcast({ type: "autophase.cleared", payload: {} });
280
- this.broadcast({ type: "autophase.state", payload: this.buildState() });
279
+ this.broadcast({ type: "goal.cleared", payload: {} });
280
+ this.broadcast({ type: "goal.state", payload: this.buildState() });
281
281
  }
282
282
  /**
283
283
  * Stop + undo: remove phase worktrees, then history-preservingly `git revert`
@@ -289,7 +289,7 @@ var AutoPhaseWebSocketHandler = class {
289
289
  await this.handleStop();
290
290
  if (!this.worktrees || !this.runBase || !this.projectRoot) {
291
291
  this.broadcast({
292
- type: "autophase.reverted",
292
+ type: "goal.reverted",
293
293
  payload: { ok: false, reverted: 0, reason: "no git baseline was captured for this run" }
294
294
  });
295
295
  return;
@@ -297,13 +297,13 @@ var AutoPhaseWebSocketHandler = class {
297
297
  await this.worktrees.cleanupAllManaged().catch(() => void 0);
298
298
  const shas = commitsSince(this.projectRoot, this.runBase.sha, this.runBase.branch);
299
299
  const res = await this.worktrees.revertCommits(this.runBase.branch, shas);
300
- this.broadcast({ type: "autophase.reverted", payload: res });
300
+ this.broadcast({ type: "goal.reverted", payload: res });
301
301
  if (res.ok) {
302
302
  this.orchestrator = null;
303
303
  this.graph = null;
304
304
  this.runBase = null;
305
- this.broadcast({ type: "autophase.cleared", payload: {} });
306
- this.broadcast({ type: "autophase.state", payload: this.buildState() });
305
+ this.broadcast({ type: "goal.cleared", payload: {} });
306
+ this.broadcast({ type: "goal.state", payload: this.buildState() });
307
307
  }
308
308
  }
309
309
  /** Generic fallback phases when the LLM planner produces nothing usable. */
@@ -322,7 +322,7 @@ var AutoPhaseWebSocketHandler = class {
322
322
  * uninterruptible). */
323
323
  async planPhases(goal, signal) {
324
324
  try {
325
- const planner = new AutoPhasePlanner({
325
+ const planner = new GoalPlanner({
326
326
  goal,
327
327
  runOnce: async (prompt) => {
328
328
  const result = await this.agent.run(prompt, {
@@ -334,12 +334,12 @@ var AutoPhaseWebSocketHandler = class {
334
334
  const { phases, parseFailed } = await planner.plan();
335
335
  if (!parseFailed && phases.length > 0) {
336
336
  const todos = phases.reduce((n, p) => n + (p.taskTemplates?.length ?? 0), 0);
337
- this.logger.info(`[AutoPhase] Planned ${phases.length} phases / ${todos} todos for: ${goal}`);
337
+ this.logger.info(`[Goal] Planned ${phases.length} phases / ${todos} todos for: ${goal}`);
338
338
  return phases;
339
339
  }
340
- this.logger.info(`[AutoPhase] Planner produced no phases; using defaults for: ${goal}`);
340
+ this.logger.info(`[Goal] Planner produced no phases; using defaults for: ${goal}`);
341
341
  } catch (err) {
342
- this.logger.error(`[AutoPhase] Planning failed, using defaults: ${toErrorMessage(err)}`);
342
+ this.logger.error(`[Goal] Planning failed, using defaults: ${toErrorMessage(err)}`);
343
343
  }
344
344
  return this.defaultPhases();
345
345
  }
@@ -387,7 +387,7 @@ Type: ${task.type}`;
387
387
  if (this.broadcastInterval) return;
388
388
  this.broadcastInterval = setInterval(() => {
389
389
  const progress = this.orchestrator?.getProgress();
390
- if (progress) this.broadcast({ type: "autophase.progress", payload: progress });
390
+ if (progress) this.broadcast({ type: "goal.progress", payload: progress });
391
391
  this.broadcastState();
392
392
  }, 2e3);
393
393
  }
@@ -400,13 +400,13 @@ Type: ${task.type}`;
400
400
  broadcastState(activePhaseId) {
401
401
  if (!this.graph) return;
402
402
  const state = this.buildState(activePhaseId);
403
- this.broadcast({ type: "autophase.state", payload: state });
403
+ this.broadcast({ type: "goal.state", payload: state });
404
404
  if (this.onBoardState) {
405
405
  try {
406
406
  this.onBoardState(this.graph.id, state);
407
407
  } catch (err) {
408
408
  this.logger.error(
409
- `[AutoPhase] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
409
+ `[Goal] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
410
410
  );
411
411
  }
412
412
  }
@@ -482,7 +482,7 @@ Type: ${task.type}`;
482
482
  autonomous: this.graph.autonomous,
483
483
  totalTasks,
484
484
  completedTasks,
485
- // Structured progress + lastError consumed by the autophase store (were
485
+ // Structured progress + lastError consumed by the goal store (were
486
486
  // defined client-side but never sent, so they stayed null on the board).
487
487
  progress: {
488
488
  totalPhases: phases.length,
@@ -498,7 +498,7 @@ Type: ${task.type}`;
498
498
  sendState(client) {
499
499
  if (!this.graph) return;
500
500
  const state = this.buildState();
501
- this.send(client, { type: "autophase.state", payload: state });
501
+ this.send(client, { type: "goal.state", payload: state });
502
502
  }
503
503
  broadcast(msg) {
504
504
  const data = JSON.stringify(msg);
@@ -1756,9 +1756,9 @@ async function handleGitInfo(ws, projectRoot) {
1756
1756
  const cwd = projectRoot || void 0;
1757
1757
  try {
1758
1758
  const { execFile: ef } = await import("node:child_process");
1759
- const git = (args) => new Promise((resolve10) => {
1759
+ const git = (args) => new Promise((resolve12) => {
1760
1760
  ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
1761
- resolve10(err ? "" : stdout.trim());
1761
+ resolve12(err ? "" : stdout.trim());
1762
1762
  });
1763
1763
  });
1764
1764
  const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
@@ -1784,12 +1784,12 @@ async function handleGitInfo(ws, projectRoot) {
1784
1784
  function makeGit(cwd) {
1785
1785
  return async (args) => {
1786
1786
  const { execFile: ef } = await import("node:child_process");
1787
- return new Promise((resolve10) => {
1787
+ return new Promise((resolve12) => {
1788
1788
  ef(
1789
1789
  "git",
1790
1790
  args,
1791
1791
  { cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
1792
- (err, stdout) => resolve10(err ? "" : stdout)
1792
+ (err, stdout) => resolve12(err ? "" : stdout)
1793
1793
  );
1794
1794
  });
1795
1795
  };
@@ -1813,15 +1813,15 @@ async function handleGitChanges(ws, projectRoot) {
1813
1813
  if (!m) continue;
1814
1814
  const added = m[1] === "-" ? 0 : Number(m[1]);
1815
1815
  const deleted = m[2] === "-" ? 0 : Number(m[2]);
1816
- let path22 = m[3] ?? "";
1817
- if (path22 === "") {
1816
+ let path23 = m[3] ?? "";
1817
+ if (path23 === "") {
1818
1818
  i += 1;
1819
- path22 = parts[i + 1] ?? parts[i] ?? "";
1819
+ path23 = parts[i + 1] ?? parts[i] ?? "";
1820
1820
  i += 1;
1821
1821
  }
1822
- if (!path22) continue;
1823
- const prev = counts.get(path22) ?? { added: 0, deleted: 0 };
1824
- counts.set(path22, { added: prev.added + added, deleted: prev.deleted + deleted });
1822
+ if (!path23) continue;
1823
+ const prev = counts.get(path23) ?? { added: 0, deleted: 0 };
1824
+ counts.set(path23, { added: prev.added + added, deleted: prev.deleted + deleted });
1825
1825
  }
1826
1826
  };
1827
1827
  parseNumstat(unstagedNumstat);
@@ -1833,7 +1833,7 @@ async function handleGitChanges(ws, projectRoot) {
1833
1833
  if (!rec || rec.length < 3) continue;
1834
1834
  const x = rec[0] ?? " ";
1835
1835
  const y = rec[1] ?? " ";
1836
- const path22 = rec.slice(3);
1836
+ const path23 = rec.slice(3);
1837
1837
  const isRename = x === "R" || x === "C" || y === "R" || y === "C";
1838
1838
  if (isRename) i += 1;
1839
1839
  let status;
@@ -1845,13 +1845,13 @@ async function handleGitChanges(ws, projectRoot) {
1845
1845
  else if (x === "D" || y === "D") status = "D";
1846
1846
  else status = "M";
1847
1847
  const staged = x !== " " && x !== "?";
1848
- let added = counts.get(path22)?.added ?? 0;
1849
- let deleted = counts.get(path22)?.deleted ?? 0;
1848
+ let added = counts.get(path23)?.added ?? 0;
1849
+ let deleted = counts.get(path23)?.deleted ?? 0;
1850
1850
  if (status === "?") {
1851
1851
  added = 0;
1852
1852
  deleted = 0;
1853
1853
  }
1854
- files.push({ path: path22, status, added, deleted, staged });
1854
+ files.push({ path: path23, status, added, deleted, staged });
1855
1855
  }
1856
1856
  send(ws, { type: "git.changes", payload: { files } });
1857
1857
  } catch (err) {
@@ -1862,10 +1862,10 @@ async function handleGitChanges(ws, projectRoot) {
1862
1862
  }
1863
1863
  }
1864
1864
  var MAX_DIFF_BYTES = 2 * 1024 * 1024;
1865
- async function handleGitDiff(ws, projectRoot, path22) {
1865
+ async function handleGitDiff(ws, projectRoot, path23) {
1866
1866
  const cwd = projectRoot || void 0;
1867
- const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path22, ...extra } });
1868
- if (!path22 || path22.includes("\0") || path22.includes("..") || nodePath.isAbsolute(path22)) {
1867
+ const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path23, ...extra } });
1868
+ if (!path23 || path23.includes("\0") || path23.includes("..") || nodePath.isAbsolute(path23)) {
1869
1869
  reply({ oldText: "", newText: "", error: "invalid path" });
1870
1870
  return;
1871
1871
  }
@@ -1873,10 +1873,10 @@ async function handleGitDiff(ws, projectRoot, path22) {
1873
1873
  const git = makeGit(cwd);
1874
1874
  const { readFile: readFile10 } = await import("node:fs/promises");
1875
1875
  const { join: join14 } = await import("node:path");
1876
- const oldText = await git(["show", `HEAD:${path22}`]);
1876
+ const oldText = await git(["show", `HEAD:${path23}`]);
1877
1877
  let newText = "";
1878
1878
  try {
1879
- const abs = cwd ? join14(cwd, path22) : path22;
1879
+ const abs = cwd ? join14(cwd, path23) : path23;
1880
1880
  const buf = await readFile10(abs);
1881
1881
  if (buf.includes(0)) {
1882
1882
  reply({ oldText: "", newText: "", binary: true });
@@ -1905,9 +1905,10 @@ async function handleGitDiff(ws, projectRoot, path22) {
1905
1905
  }
1906
1906
 
1907
1907
  // src/server/http-server.ts
1908
- import * as fs5 from "node:fs/promises";
1908
+ import * as fs6 from "node:fs/promises";
1909
1909
  import * as http from "node:http";
1910
- import * as path6 from "node:path";
1910
+ import * as path7 from "node:path";
1911
+ import * as v8 from "node:v8";
1911
1912
 
1912
1913
  // src/server/http-server/api-handlers.ts
1913
1914
  async function handleApiSessions(res, globalRoot) {
@@ -2095,7 +2096,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2095
2096
  return;
2096
2097
  }
2097
2098
  try {
2098
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2099
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2099
2100
  const registry = new SessionRegistry(globalRoot);
2100
2101
  const entry = await registry.get(sessionId);
2101
2102
  if (!entry) {
@@ -2103,7 +2104,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2103
2104
  res.end(JSON.stringify({ error: "Session not found" }));
2104
2105
  return;
2105
2106
  }
2106
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2107
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2107
2108
  const store = new DefaultSessionStore2({ dir: paths.projectSessions });
2108
2109
  const reader = new DefaultSessionReader2({ store });
2109
2110
  const rawEntries = [];
@@ -2130,7 +2131,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2130
2131
  }
2131
2132
  }
2132
2133
  function readJsonBody(req) {
2133
- return new Promise((resolve10, reject) => {
2134
+ return new Promise((resolve12, reject) => {
2134
2135
  let data = "";
2135
2136
  req.on("data", (chunk) => {
2136
2137
  data += chunk;
@@ -2141,7 +2142,7 @@ function readJsonBody(req) {
2141
2142
  });
2142
2143
  req.on("end", () => {
2143
2144
  try {
2144
- resolve10(data ? JSON.parse(data) : {});
2145
+ resolve12(data ? JSON.parse(data) : {});
2145
2146
  } catch (err) {
2146
2147
  reject(err instanceof Error ? err : new Error(String(err)));
2147
2148
  }
@@ -2177,7 +2178,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2177
2178
  const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
2178
2179
  const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
2179
2180
  try {
2180
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2181
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2181
2182
  const registry = new SessionRegistry(globalRoot);
2182
2183
  const entry = await registry.get(sessionId);
2183
2184
  if (!entry) {
@@ -2185,7 +2186,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2185
2186
  res.end(JSON.stringify({ error: "Session not found" }));
2186
2187
  return;
2187
2188
  }
2188
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2189
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2189
2190
  const mailbox = new GlobalMailbox4(paths.projectDir);
2190
2191
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
2191
2192
  const sent = await mailbox.send({ from, to, type, subject, body: text, priority });
@@ -2203,7 +2204,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2203
2204
  return;
2204
2205
  }
2205
2206
  try {
2206
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2207
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2207
2208
  const registry = new SessionRegistry(globalRoot);
2208
2209
  const entry = await registry.get(sessionId);
2209
2210
  if (!entry) {
@@ -2211,7 +2212,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2211
2212
  res.end(JSON.stringify({ error: "Session not found" }));
2212
2213
  return;
2213
2214
  }
2214
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2215
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2215
2216
  const mailbox = new GlobalMailbox4(paths.projectDir);
2216
2217
  const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
2217
2218
  const [inbound, outbound] = await Promise.all([
@@ -2261,7 +2262,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2261
2262
  const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
2262
2263
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2263
2264
  try {
2264
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2265
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2265
2266
  const registry = new SessionRegistry(globalRoot);
2266
2267
  const entry = await registry.get(sessionId);
2267
2268
  if (!entry) {
@@ -2269,7 +2270,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2269
2270
  res.end(JSON.stringify({ error: "Session not found" }));
2270
2271
  return;
2271
2272
  }
2272
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2273
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2273
2274
  const mailbox = new GlobalMailbox4(paths.projectDir);
2274
2275
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
2275
2276
  const sent = await mailbox.send({
@@ -2309,7 +2310,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2309
2310
  }
2310
2311
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2311
2312
  try {
2312
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2313
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2313
2314
  const registry = new SessionRegistry(globalRoot);
2314
2315
  const all = await registry.list();
2315
2316
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -2321,7 +2322,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2321
2322
  }
2322
2323
  const mbByDir = /* @__PURE__ */ new Map();
2323
2324
  const mailboxFor = (projectRoot) => {
2324
- const dir = resolveWstackPaths3({ projectRoot, globalRoot }).projectDir;
2325
+ const dir = resolveWstackPaths4({ projectRoot, globalRoot }).projectDir;
2325
2326
  let mb = mbByDir.get(dir);
2326
2327
  if (!mb) {
2327
2328
  mb = new GlobalMailbox4(dir);
@@ -2384,14 +2385,14 @@ function pushEvent(event) {
2384
2385
  }
2385
2386
  }
2386
2387
  function parseBody(req) {
2387
- return new Promise((resolve10, reject) => {
2388
+ return new Promise((resolve12, reject) => {
2388
2389
  let body = "";
2389
2390
  req.on("data", (chunk) => {
2390
2391
  body += chunk.toString("utf-8");
2391
2392
  });
2392
2393
  req.on("end", () => {
2393
2394
  try {
2394
- resolve10(JSON.parse(body));
2395
+ resolve12(JSON.parse(body));
2395
2396
  } catch {
2396
2397
  reject(new Error("Invalid JSON"));
2397
2398
  }
@@ -2458,6 +2459,251 @@ async function handleApiAnalyticsSummary(res) {
2458
2459
  );
2459
2460
  }
2460
2461
 
2462
+ // src/server/codemap-handlers.ts
2463
+ import { packageGraphService, fileGraphService, symbolGraphService } from "@wrongstack/tools";
2464
+ function sendJson(res, status, data) {
2465
+ res.writeHead(status, { "Content-Type": "application/json" });
2466
+ res.end(JSON.stringify(data));
2467
+ }
2468
+ function handleCodemapPackages(res, deps2) {
2469
+ try {
2470
+ const graph = packageGraphService({
2471
+ projectRoot: deps2.projectRoot,
2472
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2473
+ });
2474
+ sendJson(res, 200, graph);
2475
+ } catch (err) {
2476
+ const msg = err instanceof Error ? err.message : String(err);
2477
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2478
+ }
2479
+ }
2480
+ function handleCodemapFiles(res, deps2, pkg) {
2481
+ if (!pkg) {
2482
+ sendJson(res, 400, { error: 'Missing "package" query parameter' });
2483
+ return;
2484
+ }
2485
+ try {
2486
+ const graph = fileGraphService({
2487
+ projectRoot: deps2.projectRoot,
2488
+ packageFilter: pkg,
2489
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2490
+ });
2491
+ sendJson(res, 200, graph);
2492
+ } catch (err) {
2493
+ const msg = err instanceof Error ? err.message : String(err);
2494
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2495
+ }
2496
+ }
2497
+ function handleCodemapSymbols(res, deps2, file) {
2498
+ if (!file) {
2499
+ sendJson(res, 400, { error: 'Missing "file" query parameter' });
2500
+ return;
2501
+ }
2502
+ try {
2503
+ const graph = symbolGraphService({
2504
+ projectRoot: deps2.projectRoot,
2505
+ fileFilter: file,
2506
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2507
+ });
2508
+ sendJson(res, 200, graph);
2509
+ } catch (err) {
2510
+ const msg = err instanceof Error ? err.message : String(err);
2511
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2512
+ }
2513
+ }
2514
+
2515
+ // src/server/techstack-handlers.ts
2516
+ import { randomUUID } from "node:crypto";
2517
+ var DEEP_DIVE_TIMEOUT_MS = 6e4;
2518
+ function sendJson2(res, status, data) {
2519
+ res.writeHead(status, { "Content-Type": "application/json" });
2520
+ res.end(JSON.stringify(data));
2521
+ }
2522
+ async function buildResearcher(deps2, kind) {
2523
+ if (kind !== "analyze" || !deps2.getLlm) return void 0;
2524
+ const { createProviderLlm, createResearcher, createToolSearch } = await import("@wrongstack/techstack");
2525
+ const llm = createProviderLlm(deps2.getLlm);
2526
+ if (!llm) return void 0;
2527
+ return createResearcher({ llm, search: createToolSearch() });
2528
+ }
2529
+ function handleTechStackSnapshot(res, deps2) {
2530
+ try {
2531
+ const snapshot = deps2.store.getSnapshot(deps2.projectId);
2532
+ if (!snapshot) {
2533
+ sendJson2(res, 404, { snapshot: null, stale: false });
2534
+ return;
2535
+ }
2536
+ const ageMs = Date.now() - new Date(snapshot.createdAt).getTime();
2537
+ sendJson2(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
2538
+ } catch (error) {
2539
+ sendJson2(res, 500, {
2540
+ error: "TechStack store unavailable",
2541
+ detail: errorMessage(error)
2542
+ });
2543
+ }
2544
+ }
2545
+ function errorMessage(error) {
2546
+ return error instanceof Error ? error.message : String(error);
2547
+ }
2548
+ function requireJobDeps(res, deps2) {
2549
+ if (!deps2.projectRoot || !deps2.engine) {
2550
+ sendJson2(res, 503, { error: "TechStack engine unavailable" });
2551
+ return false;
2552
+ }
2553
+ return true;
2554
+ }
2555
+ function startJob(res, deps2, kind) {
2556
+ if (!requireJobDeps(res, deps2)) return;
2557
+ const jobId = randomUUID();
2558
+ const controller = new AbortController();
2559
+ deps2.runningJobs?.set(jobId, controller);
2560
+ deps2.emit?.({ type: "techstack.job.started", payload: { jobId, kind } });
2561
+ sendJson2(res, 202, { jobId, kind, status: "queued" });
2562
+ void buildResearcher(deps2, kind).catch(() => void 0).then(
2563
+ (researcher) => deps2.engine.analyze(deps2.projectId, {
2564
+ targetRoot: deps2.projectRoot,
2565
+ requestedBy: "webui",
2566
+ online: kind === "analyze",
2567
+ jobId,
2568
+ signal: controller.signal,
2569
+ researcher,
2570
+ onProgress: (phase, completed, total) => {
2571
+ deps2.emit?.({
2572
+ type: "techstack.job.progress",
2573
+ payload: { jobId, phase, completed, total }
2574
+ });
2575
+ }
2576
+ })
2577
+ ).then(({ snapshot }) => {
2578
+ if (controller.signal.aborted) return;
2579
+ deps2.emit?.({
2580
+ type: "techstack.snapshot.updated",
2581
+ payload: { snapshot, stale: false }
2582
+ });
2583
+ }).catch((error) => {
2584
+ if (controller.signal.aborted) {
2585
+ deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
2586
+ return;
2587
+ }
2588
+ deps2.emit?.({
2589
+ type: "techstack.job.failed",
2590
+ payload: { jobId, error: errorMessage(error) }
2591
+ });
2592
+ }).finally(() => {
2593
+ deps2.runningJobs?.delete(jobId);
2594
+ });
2595
+ }
2596
+ function handleTechStackInventory(res, deps2) {
2597
+ startJob(res, deps2, "inventory");
2598
+ }
2599
+ function handleTechStackAnalyze(res, deps2) {
2600
+ startJob(res, deps2, "analyze");
2601
+ }
2602
+ function handleTechStackCancel(res, deps2, jobId) {
2603
+ const controller = deps2.runningJobs?.get(jobId);
2604
+ if (controller && !controller.signal.aborted) controller.abort();
2605
+ deps2.store.updateJobStatus(jobId, "cancelled");
2606
+ deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
2607
+ sendJson2(res, 200, { jobId, status: "cancelled" });
2608
+ }
2609
+ async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
2610
+ const snapshot = deps2.store.getSnapshot(deps2.projectId);
2611
+ const dependency = snapshot?.dependencies.find((dep) => dep.id === dependencyId);
2612
+ if (!dependency) {
2613
+ sendJson2(res, 404, { error: "Dependency not found in the current snapshot" });
2614
+ return;
2615
+ }
2616
+ let researcher;
2617
+ try {
2618
+ researcher = await buildResearcher(deps2, "analyze");
2619
+ } catch (error) {
2620
+ sendJson2(res, 503, { error: "Research unavailable", detail: errorMessage(error) });
2621
+ return;
2622
+ }
2623
+ if (!researcher) {
2624
+ sendJson2(res, 503, {
2625
+ error: "No model configured \u2014 connect a provider to run LLM analysis."
2626
+ });
2627
+ return;
2628
+ }
2629
+ const controller = new AbortController();
2630
+ const timeout = setTimeout(() => {
2631
+ controller.abort(new Error("research timeout"));
2632
+ }, DEEP_DIVE_TIMEOUT_MS);
2633
+ timeout.unref?.();
2634
+ try {
2635
+ const { triageCandidates } = await import("@wrongstack/techstack");
2636
+ const [triaged] = triageCandidates([dependency], { limit: 1 });
2637
+ const findings = await researcher.research(
2638
+ [triaged ?? { dependency, cluster: "breaking_change", priority: 0 }],
2639
+ { signal: controller.signal }
2640
+ );
2641
+ sendJson2(res, 200, { dependencyId, findings });
2642
+ } catch (error) {
2643
+ sendJson2(res, 500, { error: "Research failed", detail: errorMessage(error) });
2644
+ } finally {
2645
+ clearTimeout(timeout);
2646
+ controller.abort();
2647
+ }
2648
+ }
2649
+ function handleTechStackJobStatus(res, deps2, jobId) {
2650
+ const job = deps2.store.getJob(jobId);
2651
+ if (!job) {
2652
+ sendJson2(res, 404, { error: "Job not found" });
2653
+ return;
2654
+ }
2655
+ sendJson2(res, 200, { job });
2656
+ }
2657
+ function handleTechStackReport(res, deps2, reportId, format) {
2658
+ const snapshot = deps2.store.getSnapshotById(reportId);
2659
+ if (!snapshot) {
2660
+ sendJson2(res, 404, { error: "Report not found" });
2661
+ return;
2662
+ }
2663
+ if (deps2.engine) {
2664
+ const report = deps2.engine.generateReport(snapshot, format);
2665
+ res.writeHead(200, {
2666
+ "Content-Type": format === "json" ? "application/json" : "text/markdown",
2667
+ "Content-Disposition": `attachment; filename="techstack-report.${format}"`
2668
+ });
2669
+ res.end(report);
2670
+ } else {
2671
+ sendJson2(res, 200, snapshot);
2672
+ }
2673
+ }
2674
+
2675
+ // src/server/projects-manifest.ts
2676
+ import * as fs5 from "node:fs/promises";
2677
+ import * as path6 from "node:path";
2678
+ import { projectSlug } from "@wrongstack/core";
2679
+ function projectsJsonPath(globalConfigPath) {
2680
+ const base = path6.dirname(globalConfigPath);
2681
+ return path6.join(base, "projects.json");
2682
+ }
2683
+ async function loadManifest(globalConfigPath) {
2684
+ try {
2685
+ const raw = await fs5.readFile(projectsJsonPath(globalConfigPath), "utf8");
2686
+ const parsed = JSON.parse(raw);
2687
+ return { projects: parsed.projects ?? [] };
2688
+ } catch {
2689
+ return { projects: [] };
2690
+ }
2691
+ }
2692
+ async function saveManifest(manifest, globalConfigPath) {
2693
+ const file = projectsJsonPath(globalConfigPath);
2694
+ await fs5.mkdir(path6.dirname(file), { recursive: true });
2695
+ await fs5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
2696
+ }
2697
+ function generateProjectSlug(rootPath) {
2698
+ return projectSlug(rootPath);
2699
+ }
2700
+ async function ensureProjectDataDir(slug, globalConfigPath) {
2701
+ const base = path6.dirname(globalConfigPath);
2702
+ const dir = path6.join(base, "projects", slug);
2703
+ await fs5.mkdir(dir, { recursive: true });
2704
+ return dir;
2705
+ }
2706
+
2461
2707
  // src/server/ws-auth.ts
2462
2708
  import { Buffer as Buffer2 } from "node:buffer";
2463
2709
  import { timingSafeEqual } from "node:crypto";
@@ -2643,9 +2889,9 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2643
2889
  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'`;
2644
2890
  }
2645
2891
  function isInsideDist(candidate, distDir) {
2646
- const root = path6.resolve(distDir);
2647
- const resolved = path6.resolve(candidate);
2648
- return resolved === root || resolved.startsWith(root + path6.sep);
2892
+ const root = path7.resolve(distDir);
2893
+ const resolved = path7.resolve(candidate);
2894
+ return resolved === root || resolved.startsWith(root + path7.sep);
2649
2895
  }
2650
2896
  function decodeSessionId(segment) {
2651
2897
  try {
@@ -2656,10 +2902,19 @@ function decodeSessionId(segment) {
2656
2902
  }
2657
2903
  function createHttpServer(opts) {
2658
2904
  const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
2659
- const distDir = path6.resolve(opts.distDir);
2905
+ const distDir = path7.resolve(opts.distDir);
2660
2906
  const wsPort = opts.wsPort;
2661
2907
  const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
2662
- return http.createServer(async (req, res) => {
2908
+ let techStackRuntime = null;
2909
+ const getTechStackRuntime = async () => {
2910
+ if (!opts.projectRoot) throw new Error("Project root not configured");
2911
+ techStackRuntime ??= import("@wrongstack/techstack").then(({ TechStackEngine, TechStackStore }) => {
2912
+ const store = new TechStackStore({ projectSlug: generateProjectSlug(opts.projectRoot) });
2913
+ return { store, engine: new TechStackEngine(store), runningJobs: /* @__PURE__ */ new Map() };
2914
+ });
2915
+ return techStackRuntime;
2916
+ };
2917
+ const server = http.createServer(async (req, res) => {
2663
2918
  try {
2664
2919
  const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
2665
2920
  const providedAccessToken = requestToken(req, url);
@@ -2810,6 +3065,127 @@ function createHttpServer(opts) {
2810
3065
  await handleApiAnalyticsSummary(res);
2811
3066
  return;
2812
3067
  }
3068
+ if (url.pathname === "/api/codemap/packages" && req.method === "GET") {
3069
+ if (requireAccessToken && !accessTokenOk) {
3070
+ res.writeHead(401, { "Content-Type": "application/json" });
3071
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3072
+ return;
3073
+ }
3074
+ if (!opts.projectRoot) {
3075
+ res.writeHead(503, { "Content-Type": "application/json" });
3076
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3077
+ return;
3078
+ }
3079
+ handleCodemapPackages(res, {
3080
+ projectRoot: opts.projectRoot,
3081
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3082
+ });
3083
+ return;
3084
+ }
3085
+ if (url.pathname === "/api/codemap/files" && req.method === "GET") {
3086
+ if (requireAccessToken && !accessTokenOk) {
3087
+ res.writeHead(401, { "Content-Type": "application/json" });
3088
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3089
+ return;
3090
+ }
3091
+ if (!opts.projectRoot) {
3092
+ res.writeHead(503, { "Content-Type": "application/json" });
3093
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3094
+ return;
3095
+ }
3096
+ const pkg = url.searchParams.get("package") ?? "";
3097
+ handleCodemapFiles(res, {
3098
+ projectRoot: opts.projectRoot,
3099
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3100
+ }, pkg);
3101
+ return;
3102
+ }
3103
+ if (url.pathname === "/api/codemap/symbols" && req.method === "GET") {
3104
+ if (requireAccessToken && !accessTokenOk) {
3105
+ res.writeHead(401, { "Content-Type": "application/json" });
3106
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3107
+ return;
3108
+ }
3109
+ if (!opts.projectRoot) {
3110
+ res.writeHead(503, { "Content-Type": "application/json" });
3111
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3112
+ return;
3113
+ }
3114
+ const file = url.searchParams.get("file") ?? "";
3115
+ handleCodemapSymbols(res, {
3116
+ projectRoot: opts.projectRoot,
3117
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3118
+ }, file);
3119
+ return;
3120
+ }
3121
+ if (url.pathname.startsWith("/api/techstack/")) {
3122
+ if (requireAccessToken && !accessTokenOk) {
3123
+ res.writeHead(401, { "Content-Type": "application/json" });
3124
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3125
+ return;
3126
+ }
3127
+ if (!opts.projectRoot) {
3128
+ res.writeHead(503, { "Content-Type": "application/json" });
3129
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3130
+ return;
3131
+ }
3132
+ try {
3133
+ const runtime = await getTechStackRuntime();
3134
+ const deps2 = {
3135
+ projectId: opts.projectRoot,
3136
+ projectRoot: opts.projectRoot,
3137
+ store: runtime.store,
3138
+ engine: runtime.engine,
3139
+ runningJobs: runtime.runningJobs,
3140
+ emit: opts.onTechStackEvent,
3141
+ getLlm: opts.getLlm
3142
+ };
3143
+ if (url.pathname === "/api/techstack/snapshot" && req.method === "GET") {
3144
+ handleTechStackSnapshot(res, deps2);
3145
+ return;
3146
+ }
3147
+ if (url.pathname === "/api/techstack/inventory" && req.method === "POST") {
3148
+ handleTechStackInventory(res, deps2);
3149
+ return;
3150
+ }
3151
+ if (url.pathname === "/api/techstack/analyze" && req.method === "POST") {
3152
+ handleTechStackAnalyze(res, deps2);
3153
+ return;
3154
+ }
3155
+ const cancelMatch = /^\/api\/techstack\/jobs\/([^/]+)\/cancel$/.exec(url.pathname);
3156
+ if (cancelMatch && req.method === "POST") {
3157
+ handleTechStackCancel(res, deps2, decodeURIComponent(cancelMatch[1]));
3158
+ return;
3159
+ }
3160
+ const jobMatch = /^\/api\/techstack\/jobs\/([^/]+)$/.exec(url.pathname);
3161
+ if (jobMatch && req.method === "GET") {
3162
+ handleTechStackJobStatus(res, deps2, decodeURIComponent(jobMatch[1]));
3163
+ return;
3164
+ }
3165
+ const reportMatch = /^\/api\/techstack\/reports\/([^/]+)$/.exec(url.pathname);
3166
+ if (reportMatch && req.method === "GET") {
3167
+ const fmt = url.searchParams.get("format") === "json" ? "json" : "md";
3168
+ handleTechStackReport(res, deps2, decodeURIComponent(reportMatch[1]), fmt);
3169
+ return;
3170
+ }
3171
+ const researchMatch = /^\/api\/techstack\/deps\/([^/]+)\/research$/.exec(url.pathname);
3172
+ if (researchMatch && req.method === "POST") {
3173
+ await handleTechStackDependencyResearch(
3174
+ res,
3175
+ deps2,
3176
+ decodeURIComponent(researchMatch[1])
3177
+ );
3178
+ return;
3179
+ }
3180
+ } catch (error) {
3181
+ res.writeHead(503, { "Content-Type": "application/json" });
3182
+ res.end(JSON.stringify({
3183
+ error: "TechStack store unavailable",
3184
+ detail: error instanceof Error ? error.message : String(error)
3185
+ }));
3186
+ return;
3187
+ }
3188
+ }
2813
3189
  if (url.pathname === "/debug/watcher-metrics" && req.method === "GET") {
2814
3190
  if (requireAccessToken && !accessTokenOk) {
2815
3191
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -2831,23 +3207,40 @@ function createHttpServer(opts) {
2831
3207
  }
2832
3208
  return;
2833
3209
  }
3210
+ if (url.pathname === "/debug/system" && req.method === "GET") {
3211
+ res.writeHead(200, {
3212
+ "Content-Type": "application/json",
3213
+ "Cache-Control": "no-store"
3214
+ });
3215
+ res.end(
3216
+ JSON.stringify({
3217
+ pid: process.pid,
3218
+ memoryUsage: process.memoryUsage(),
3219
+ heapLimit: v8.getHeapStatistics().heap_size_limit,
3220
+ uptime: process.uptime(),
3221
+ cpuUsage: process.cpuUsage(),
3222
+ timestamp: Date.now()
3223
+ })
3224
+ );
3225
+ return;
3226
+ }
2834
3227
  let filePath;
2835
3228
  if (url.pathname === "/" || url.pathname === "") {
2836
- filePath = path6.join(distDir, "index.html");
3229
+ filePath = path7.join(distDir, "index.html");
2837
3230
  } else if (url.pathname.startsWith("/assets/")) {
2838
- filePath = path6.join(distDir, url.pathname);
3231
+ filePath = path7.join(distDir, url.pathname);
2839
3232
  } else if (url.pathname.startsWith("/")) {
2840
- filePath = path6.join(distDir, url.pathname);
3233
+ filePath = path7.join(distDir, url.pathname);
2841
3234
  } else {
2842
- filePath = path6.join(distDir, "index.html");
3235
+ filePath = path7.join(distDir, "index.html");
2843
3236
  }
2844
- const resolvedPath = path6.resolve(filePath);
3237
+ const resolvedPath = path7.resolve(filePath);
2845
3238
  if (!isInsideDist(resolvedPath, distDir)) {
2846
3239
  res.writeHead(403, { "Content-Type": "text/plain" });
2847
3240
  res.end("Forbidden");
2848
3241
  return;
2849
3242
  }
2850
- const ext = path6.extname(resolvedPath);
3243
+ const ext = path7.extname(resolvedPath);
2851
3244
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
2852
3245
  res.setHeader("Content-Type", contentType);
2853
3246
  res.setHeader("X-Content-Type-Options", "nosniff");
@@ -2859,18 +3252,18 @@ function createHttpServer(opts) {
2859
3252
  "Content-Security-Policy",
2860
3253
  buildCspHeader(wsPort, requestHostForCsp(req.headers.host), opts.publicWsUrl)
2861
3254
  );
2862
- const html = await fs5.readFile(resolvedPath, "utf8");
3255
+ const html = await fs6.readFile(resolvedPath, "utf8");
2863
3256
  res.writeHead(200);
2864
3257
  res.end(injectWsConfig(html, { wsPort, publicWsUrl: opts.publicWsUrl }));
2865
3258
  return;
2866
3259
  }
2867
- const fileContent = await fs5.readFile(resolvedPath);
3260
+ const fileContent = await fs6.readFile(resolvedPath);
2868
3261
  res.writeHead(200);
2869
3262
  res.end(fileContent);
2870
3263
  } catch (err) {
2871
3264
  if (err.code === "ENOENT") {
2872
3265
  try {
2873
- const html = await fs5.readFile(path6.join(distDir, "index.html"), "utf8");
3266
+ const html = await fs6.readFile(path7.join(distDir, "index.html"), "utf8");
2874
3267
  res.writeHead(200, {
2875
3268
  "Content-Type": "text/html",
2876
3269
  "X-Content-Type-Options": "nosniff",
@@ -2893,18 +3286,26 @@ function createHttpServer(opts) {
2893
3286
  }
2894
3287
  }
2895
3288
  });
3289
+ server.once("close", () => {
3290
+ void techStackRuntime?.then(({ store, runningJobs }) => {
3291
+ for (const controller of runningJobs.values()) controller.abort();
3292
+ runningJobs.clear();
3293
+ store.close();
3294
+ }).catch(() => void 0);
3295
+ });
3296
+ return server;
2896
3297
  }
2897
3298
 
2898
3299
  // src/server/instance-registry.ts
2899
3300
  import * as os from "node:os";
2900
- import * as path7 from "node:path";
2901
- import * as fs6 from "node:fs/promises";
3301
+ import * as path8 from "node:path";
3302
+ import * as fs7 from "node:fs/promises";
2902
3303
  import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
2903
3304
  function defaultBaseDir() {
2904
- return path7.join(os.homedir(), ".wrongstack");
3305
+ return path8.join(os.homedir(), ".wrongstack");
2905
3306
  }
2906
3307
  function registryPath(baseDir = defaultBaseDir()) {
2907
- return path7.join(baseDir, "webui-instances.json");
3308
+ return path8.join(baseDir, "webui-instances.json");
2908
3309
  }
2909
3310
  function isPidAlive(pid) {
2910
3311
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -2917,7 +3318,7 @@ function isPidAlive(pid) {
2917
3318
  }
2918
3319
  async function load(file) {
2919
3320
  try {
2920
- const raw = await fs6.readFile(file, "utf8");
3321
+ const raw = await fs7.readFile(file, "utf8");
2921
3322
  const parsed = JSON.parse(raw);
2922
3323
  if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
2923
3324
  return parsed;
@@ -3210,7 +3611,7 @@ async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
3210
3611
  payload: { name: serverName, resources, resourceTemplates }
3211
3612
  });
3212
3613
  } catch (err) {
3213
- sendContentError(ws, "resources", serverName, errorMessage(err));
3614
+ sendContentError(ws, "resources", serverName, errorMessage2(err));
3214
3615
  }
3215
3616
  }
3216
3617
  async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3224,7 +3625,7 @@ async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
3224
3625
  });
3225
3626
  send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
3226
3627
  } catch (err) {
3227
- sendContentError(ws, "prompts", serverName, errorMessage(err));
3628
+ sendContentError(ws, "prompts", serverName, errorMessage2(err));
3228
3629
  }
3229
3630
  }
3230
3631
  async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3240,7 +3641,7 @@ async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
3240
3641
  );
3241
3642
  send(ws, { type: "mcp.content.selected", payload: insertion });
3242
3643
  } catch (err) {
3243
- sendContentError(ws, "resource.read", serverName, errorMessage(err));
3644
+ sendContentError(ws, "resource.read", serverName, errorMessage2(err));
3244
3645
  }
3245
3646
  }
3246
3647
  async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3256,7 +3657,7 @@ async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
3256
3657
  );
3257
3658
  send(ws, { type: "mcp.content.selected", payload: insertion });
3258
3659
  } catch (err) {
3259
- sendContentError(ws, "prompt.get", serverName, errorMessage(err));
3660
+ sendContentError(ws, "prompt.get", serverName, errorMessage2(err));
3260
3661
  }
3261
3662
  }
3262
3663
  function payloadRecord(msg) {
@@ -3284,14 +3685,14 @@ function promptArguments(value) {
3284
3685
  function sendContentError(ws, action, name2, error) {
3285
3686
  send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
3286
3687
  }
3287
- function errorMessage(err) {
3688
+ function errorMessage2(err) {
3288
3689
  return err instanceof Error ? err.message : String(err);
3289
3690
  }
3290
3691
 
3291
3692
  // src/server/memory-handlers.ts
3292
3693
  function isSuperMemoryStore(store) {
3293
3694
  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";
3695
+ return typeof s.stats === "function" && typeof s.listSuper === "function" && typeof s.getSuperMemory === "function" && typeof s.updateSuperMemory === "function" && typeof s.deleteSuperMemory === "function" && typeof s.acceptCandidate === "function" && typeof s.rejectCandidate === "function";
3295
3696
  }
3296
3697
  function requiresSuperMemory(command) {
3297
3698
  return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
@@ -3415,6 +3816,7 @@ async function handleSuperMemoryRemember(ws, msg, memoryStore) {
3415
3816
  importance: payload["importance"],
3416
3817
  confidence: payload["confidence"],
3417
3818
  freshness: payload["freshness"],
3819
+ audience: payload["audience"],
3418
3820
  supersedes: payload["supersedes"],
3419
3821
  contradicts: payload["contradicts"]
3420
3822
  });
@@ -3428,18 +3830,172 @@ async function handleSuperMemoryDelete(ws, msg, memoryStore) {
3428
3830
  send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
3429
3831
  return;
3430
3832
  }
3431
- const { id, reason } = msg.payload;
3833
+ const { id, reason, neverInject } = msg.payload;
3432
3834
  if (!id) {
3433
3835
  send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
3434
3836
  return;
3435
3837
  }
3436
3838
  try {
3437
- await memoryStore.deleteSuperMemory(id, reason);
3839
+ if (neverInject === true) await memoryStore.deleteSuperMemory(id, reason, { neverInject: true });
3840
+ else await memoryStore.deleteSuperMemory(id, reason);
3438
3841
  send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
3439
3842
  } catch (err) {
3440
3843
  send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
3441
3844
  }
3442
3845
  }
3846
+ async function handleSuperMemoryRecover(ws, msg, memoryStore) {
3847
+ if (!isSuperMemoryStore(memoryStore)) {
3848
+ send(ws, { type: "memory.super.recover", payload: { error: requiresSuperMemory("memory.super.recover") } });
3849
+ return;
3850
+ }
3851
+ const payload = msg.payload;
3852
+ const id = payload["id"];
3853
+ if (!id) {
3854
+ send(ws, { type: "memory.super.recover", payload: { error: "id is required" } });
3855
+ return;
3856
+ }
3857
+ const reason = payload["reason"];
3858
+ try {
3859
+ const preExisting = await memoryStore.getSuperMemory(id);
3860
+ if (!preExisting) {
3861
+ send(ws, { type: "memory.super.recover", payload: { error: `Super Memory "${id}" not found.` } });
3862
+ return;
3863
+ }
3864
+ if (preExisting.status === "active") {
3865
+ send(ws, { type: "memory.super.recover", payload: { recovered: true, memory: preExisting, noop: true } });
3866
+ return;
3867
+ }
3868
+ const memory = await memoryStore.recoverSuperMemory(id, reason);
3869
+ const noop = memory.id !== id;
3870
+ const response = { recovered: true, memory };
3871
+ if (noop) {
3872
+ response["activeId"] = memory.id;
3873
+ response["noop"] = true;
3874
+ }
3875
+ send(ws, { type: "memory.super.recover", payload: response });
3876
+ } catch (err) {
3877
+ send(ws, { type: "memory.super.recover", payload: { error: errMessage(err) } });
3878
+ }
3879
+ }
3880
+ async function handleSuperMemoryCandidateResolve(ws, msg, memoryStore) {
3881
+ if (!isSuperMemoryStore(memoryStore)) {
3882
+ send(ws, {
3883
+ type: "memory.super.candidateResolve",
3884
+ payload: { error: requiresSuperMemory("memory.super.candidateResolve") }
3885
+ });
3886
+ return;
3887
+ }
3888
+ const payload = msg.payload;
3889
+ const candidateId = payload["candidateId"];
3890
+ const action = payload["action"];
3891
+ if (!candidateId) {
3892
+ send(ws, {
3893
+ type: "memory.super.candidateResolve",
3894
+ payload: { error: "candidateId is required" }
3895
+ });
3896
+ return;
3897
+ }
3898
+ if (action !== "accept" && action !== "reject") {
3899
+ send(ws, {
3900
+ type: "memory.super.candidateResolve",
3901
+ payload: { error: 'action must be "accept" or "reject"' }
3902
+ });
3903
+ return;
3904
+ }
3905
+ const reason = payload["reason"];
3906
+ try {
3907
+ let candidate;
3908
+ if (action === "accept") {
3909
+ const accepted = await memoryStore.acceptCandidate(candidateId);
3910
+ candidate = accepted ? { id: accepted.id, status: accepted.status ?? "active" } : void 0;
3911
+ } else {
3912
+ const rejected = await memoryStore.rejectCandidate(
3913
+ candidateId,
3914
+ reason ?? "Rejected via WebUI"
3915
+ );
3916
+ candidate = rejected ? { id: candidateId, status: "rejected" } : void 0;
3917
+ }
3918
+ if (!candidate) {
3919
+ send(ws, {
3920
+ type: "memory.super.candidateResolve",
3921
+ payload: { error: `Candidate "${candidateId}" not found` }
3922
+ });
3923
+ return;
3924
+ }
3925
+ send(ws, {
3926
+ type: "memory.super.candidateResolve",
3927
+ payload: { candidate, resolvedAction: action }
3928
+ });
3929
+ } catch (err) {
3930
+ send(ws, {
3931
+ type: "memory.super.candidateResolve",
3932
+ payload: { error: errMessage(err) }
3933
+ });
3934
+ }
3935
+ }
3936
+ async function handleSuperMemoryBackfillRecoverable(ws, msg, memoryStore) {
3937
+ if (!isSuperMemoryStore(memoryStore)) {
3938
+ send(ws, {
3939
+ type: "memory.super.backfillRecoverable",
3940
+ payload: { error: requiresSuperMemory("memory.super.backfillRecoverable") }
3941
+ });
3942
+ return;
3943
+ }
3944
+ const payload = msg.payload ?? {};
3945
+ const apply = payload["apply"] === true;
3946
+ const rawFilter = payload["filter"] ?? {};
3947
+ const filter = {};
3948
+ if (Array.isArray(rawFilter["kinds"])) filter.kinds = rawFilter["kinds"];
3949
+ if (Array.isArray(rawFilter["scopes"])) filter.scopes = rawFilter["scopes"];
3950
+ if (typeof rawFilter["updatedAfter"] === "string") filter.updatedAfter = rawFilter["updatedAfter"];
3951
+ if (typeof rawFilter["updatedBefore"] === "string") filter.updatedBefore = rawFilter["updatedBefore"];
3952
+ try {
3953
+ const report = await memoryStore.backfillRecoverable({
3954
+ apply,
3955
+ ...Object.keys(filter).length > 0 ? { filter } : {}
3956
+ });
3957
+ send(ws, {
3958
+ type: "memory.super.backfillRecoverable",
3959
+ payload: {
3960
+ examined: report.examined,
3961
+ recovered: report.recovered,
3962
+ recoverable: report.recoverable,
3963
+ dryRun: !apply
3964
+ }
3965
+ });
3966
+ } catch (err) {
3967
+ send(ws, {
3968
+ type: "memory.super.backfillRecoverable",
3969
+ payload: { error: errMessage(err) }
3970
+ });
3971
+ }
3972
+ }
3973
+ async function handleSuperMemoryForFile(ws, msg, memoryStore) {
3974
+ if (!isSuperMemoryStore(memoryStore)) {
3975
+ send(ws, {
3976
+ type: "memory.super.forFile",
3977
+ payload: { error: requiresSuperMemory("memory.super.forFile") }
3978
+ });
3979
+ return;
3980
+ }
3981
+ const payload = msg.payload ?? {};
3982
+ const filePath = payload["filePath"];
3983
+ if (!filePath) {
3984
+ send(ws, { type: "memory.super.forFile", payload: { error: "filePath is required" } });
3985
+ return;
3986
+ }
3987
+ try {
3988
+ const response = await memoryStore.findMemoriesForFile(filePath, {
3989
+ ...typeof payload["lineStart"] === "number" ? { lineStart: payload["lineStart"] } : {},
3990
+ ...typeof payload["lineEnd"] === "number" ? { lineEnd: payload["lineEnd"] } : {},
3991
+ ...typeof payload["limit"] === "number" ? { limit: payload["limit"] } : {},
3992
+ ...payload["includeDeleted"] === true ? { includeDeleted: true } : {}
3993
+ });
3994
+ send(ws, { type: "memory.super.forFile", payload: response });
3995
+ } catch (err) {
3996
+ send(ws, { type: "memory.super.forFile", payload: { error: errMessage(err) } });
3997
+ }
3998
+ }
3443
3999
 
3444
4000
  // src/server/open-browser.ts
3445
4001
  import { spawn } from "node:child_process";
@@ -3488,16 +4044,16 @@ function openBrowser(url, platform = process.platform) {
3488
4044
  import * as net from "node:net";
3489
4045
  import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
3490
4046
  function isPortFree(host, port) {
3491
- return new Promise((resolve10) => {
4047
+ return new Promise((resolve12) => {
3492
4048
  const srv = net.createServer();
3493
- srv.once("error", () => resolve10(false));
4049
+ srv.once("error", () => resolve12(false));
3494
4050
  srv.once("listening", () => {
3495
- srv.close(() => resolve10(true));
4051
+ srv.close(() => resolve12(true));
3496
4052
  });
3497
4053
  try {
3498
4054
  srv.listen(port, host);
3499
4055
  } catch {
3500
- resolve10(false);
4056
+ resolve12(false);
3501
4057
  }
3502
4058
  });
3503
4059
  }
@@ -3724,13 +4280,13 @@ async function handlePromptsRecent(ws, ctx) {
3724
4280
  }
3725
4281
 
3726
4282
  // src/server/provider-config-io.ts
3727
- import * as fs7 from "node:fs/promises";
4283
+ import * as fs8 from "node:fs/promises";
3728
4284
  import { ConfigError, atomicWrite as atomicWrite4 } from "@wrongstack/core";
3729
4285
  import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
3730
4286
  async function loadSavedProviders(configPath, vault) {
3731
4287
  let raw;
3732
4288
  try {
3733
- raw = await fs7.readFile(configPath, "utf8");
4289
+ raw = await fs8.readFile(configPath, "utf8");
3734
4290
  } catch {
3735
4291
  return {};
3736
4292
  }
@@ -3747,7 +4303,7 @@ async function saveProviders(configPath, vault, providers) {
3747
4303
  let raw;
3748
4304
  let fileExists = true;
3749
4305
  try {
3750
- raw = await fs7.readFile(configPath, "utf8");
4306
+ raw = await fs8.readFile(configPath, "utf8");
3751
4307
  } catch (err) {
3752
4308
  if (err.code !== "ENOENT") {
3753
4309
  throw new ConfigError({
@@ -3781,6 +4337,10 @@ async function saveProviders(configPath, vault, providers) {
3781
4337
 
3782
4338
  // src/server/provider-keys.ts
3783
4339
  import { expectDefined } from "@wrongstack/core";
4340
+ import {
4341
+ buildProviderConfigFromPreset,
4342
+ resolvePresetForAlias
4343
+ } from "@wrongstack/providers";
3784
4344
  function normalizeKeys(cfg) {
3785
4345
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
3786
4346
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -3809,8 +4369,28 @@ function maskedKey(key) {
3809
4369
  if (key.length <= 8) return "\u2022".repeat(key.length);
3810
4370
  return `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
3811
4371
  }
4372
+ function hydratePresetConfig(providerId, dest) {
4373
+ const preset = resolvePresetForAlias(providerId);
4374
+ if (!preset) return void 0;
4375
+ const template = buildProviderConfigFromPreset(preset);
4376
+ if (!dest.type) dest.type = preset.id;
4377
+ if (!dest.family) dest.family = preset.family;
4378
+ if (dest.baseUrl === void 0) dest.baseUrl = template.baseUrl;
4379
+ if (!dest.envVars || dest.envVars.length === 0) dest.envVars = template.envVars;
4380
+ if (!dest.models || dest.models.length === 0) dest.models = template.models;
4381
+ if (template.customModels && (!dest.customModels || Object.keys(dest.customModels).length === 0)) {
4382
+ dest.customModels = template.customModels;
4383
+ }
4384
+ if (template.quirks && dest.quirks === void 0) dest.quirks = template.quirks;
4385
+ return preset.id;
4386
+ }
3812
4387
  function upsertKey(providers, providerId, label, apiKey, nowIso) {
3813
- const existing = providers[providerId] ?? { type: providerId };
4388
+ let existing = providers[providerId];
4389
+ if (!existing) {
4390
+ existing = { type: providerId };
4391
+ const presetId = hydratePresetConfig(providerId, existing);
4392
+ if (presetId) existing.type = presetId;
4393
+ }
3814
4394
  const keys = normalizeKeys(existing);
3815
4395
  const idx = keys.findIndex((k) => k.label === label);
3816
4396
  if (idx >= 0) {
@@ -3860,6 +4440,8 @@ function addProvider(providers, payload, nowIso) {
3860
4440
  family: payload.family,
3861
4441
  baseUrl: payload.baseUrl
3862
4442
  };
4443
+ const presetId = hydratePresetConfig(payload.id, newProv);
4444
+ if (presetId) newProv.type = presetId;
3863
4445
  if (payload.apiKey) {
3864
4446
  newProv.apiKeys = [{ label: "default", apiKey: payload.apiKey, createdAt: nowIso }];
3865
4447
  newProv.activeKey = "default";
@@ -4026,7 +4608,7 @@ var SddBoardWebSocketHandler = class {
4026
4608
  };
4027
4609
 
4028
4610
  // src/server/sdd-wizard-wiring.ts
4029
- import * as path8 from "node:path";
4611
+ import * as path9 from "node:path";
4030
4612
  import { spawnSync as spawnSync2 } from "node:child_process";
4031
4613
  import {
4032
4614
  DefaultTaskStore,
@@ -4126,7 +4708,7 @@ function buildSddWizardDeps(opts) {
4126
4708
  makeDriver: () => new SddInterviewDriver({
4127
4709
  specStore: new SpecStore({ baseDir: opts.paths.projectSpecs }),
4128
4710
  graphStore: new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs }),
4129
- sessionPath: path8.join(opts.paths.projectDir, "sdd-wizard-session.json")
4711
+ sessionPath: path9.join(opts.paths.projectDir, "sdd-wizard-session.json")
4130
4712
  }),
4131
4713
  runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
4132
4714
  startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels, worktrees: useWorktrees }) => {
@@ -4328,8 +4910,8 @@ function toSessionHistoryEntries(summaries, currentSessionId) {
4328
4910
  }
4329
4911
 
4330
4912
  // src/server/shell-open.ts
4331
- import * as fs8 from "node:fs/promises";
4332
- import * as path9 from "node:path";
4913
+ import * as fs9 from "node:fs/promises";
4914
+ import * as path10 from "node:path";
4333
4915
  import { spawn as spawn2 } from "node:child_process";
4334
4916
  var METACHAR_REGEX = /[&|<>^"'`'\n\r]/;
4335
4917
  function shellQuote(s) {
@@ -4337,8 +4919,8 @@ function shellQuote(s) {
4337
4919
  }
4338
4920
  async function handleShellOpen(req, logger) {
4339
4921
  try {
4340
- const resolved = path9.resolve(req.path);
4341
- await fs8.access(resolved);
4922
+ const resolved = path10.resolve(req.path);
4923
+ await fs9.access(resolved);
4342
4924
  if (METACHAR_REGEX.test(resolved)) {
4343
4925
  return { success: false, message: "Path contains unsupported characters." };
4344
4926
  }
@@ -4389,12 +4971,13 @@ async function handleShellOpen(req, logger) {
4389
4971
  }
4390
4972
 
4391
4973
  // src/server/skills-handlers.ts
4392
- import { promises as fs9 } from "node:fs";
4393
- import path10 from "node:path";
4974
+ import { promises as fs10 } from "node:fs";
4975
+ import path11 from "node:path";
4394
4976
  import { atomicWrite as atomicWrite5 } from "@wrongstack/core";
4395
4977
  import { wstackGlobalRoot } from "@wrongstack/core/utils";
4396
4978
 
4397
4979
  // src/server/ws-payload-validation.ts
4980
+ import { FORBIDDEN_PROTO_KEYS } from "@wrongstack/core/utils";
4398
4981
  function isRecord(value) {
4399
4982
  return typeof value === "object" && value !== null && !Array.isArray(value);
4400
4983
  }
@@ -4599,12 +5182,24 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
4599
5182
  "hqRawContent",
4600
5183
  "fallbackAuto",
4601
5184
  "favoriteModelsOnly",
5185
+ "modelAvailabilitySchedule",
4602
5186
  "breakerEnabled",
4603
- "debugStream"
5187
+ "debugStream",
5188
+ // Chimera + auto-review master toggles
5189
+ "chimeraEnabled",
5190
+ "autoReviewEnabled",
5191
+ "showModelReasoning"
5192
+ ]);
5193
+ var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set([
5194
+ "fallbackModels",
5195
+ "favoriteModels",
5196
+ // Auto-review explicit fallback chain (derived when fallbackProfile is unset;
5197
+ // surfaced for visibility/override).
5198
+ "autoReviewFallbackModels"
4604
5199
  ]);
4605
- var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
4606
5200
  var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
4607
5201
  var MODEL_MATRIX_PREF_KEYS = /* @__PURE__ */ new Set(["modelMatrix"]);
5202
+ var BOOLEAN_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["pluginsEnabled"]);
4608
5203
  var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4609
5204
  "autonomyDelayMs",
4610
5205
  "autoProceedMaxIterations",
@@ -4612,7 +5207,12 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4612
5207
  "maxConcurrent",
4613
5208
  "enhanceDelayMs",
4614
5209
  "tgLongToolMs",
4615
- "breakerAutoKillResetMs"
5210
+ "breakerAutoKillResetMs",
5211
+ // Chimera + auto-review numeric knobs
5212
+ "chimeraMaxFiles",
5213
+ "autoReviewDebounceMs",
5214
+ "autoReviewMaxFilesPerBatch",
5215
+ "autoReviewMaxConcurrentReviews"
4616
5216
  ]);
4617
5217
  var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4618
5218
  "hqUrl",
@@ -4621,7 +5221,13 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4621
5221
  "thinkingWord",
4622
5222
  "refinerProvider",
4623
5223
  "refinerModel",
4624
- "refinerFallbackProfile"
5224
+ "refinerFallbackProfile",
5225
+ // Chimera + auto-review override strings
5226
+ "chimeraProvider",
5227
+ "chimeraModel",
5228
+ "autoReviewProvider",
5229
+ "autoReviewModel",
5230
+ "autoReviewFallbackProfile"
4625
5231
  ]);
4626
5232
  var ENUM_PREF_KEYS = {
4627
5233
  autonomy: AUTONOMY_VALUES,
@@ -4636,36 +5242,39 @@ var ENUM_PREF_KEYS = {
4636
5242
  cacheTtl: CACHE_TTL_VALUES,
4637
5243
  statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
4638
5244
  animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
4639
- fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
5245
+ fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"]),
5246
+ // Chimera autoFix + auto-review cascade threshold
5247
+ chimeraAutoFix: /* @__PURE__ */ new Set(["off", "ask", "auto"]),
5248
+ autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"])
4640
5249
  };
4641
- function validateModelRuntimeValue(modelRuntime, path22) {
5250
+ function validateModelRuntimeValue(modelRuntime, path23) {
4642
5251
  const reasoning = modelRuntime["reasoning"];
4643
5252
  if (reasoning !== void 0) {
4644
- if (!isRecord(reasoning)) return `${path22}.reasoning must be an object when provided`;
5253
+ if (!isRecord(reasoning)) return `${path23}.reasoning must be an object when provided`;
4645
5254
  const mode = reasoning["mode"];
4646
5255
  const effort = reasoning["effort"];
4647
5256
  const preserve = reasoning["preserve"];
4648
5257
  if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
4649
- return `${path22}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
5258
+ return `${path23}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
4650
5259
  }
4651
5260
  if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
4652
- return `${path22}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
5261
+ return `${path23}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
4653
5262
  }
4654
5263
  if (preserve !== void 0 && typeof preserve !== "boolean") {
4655
- return `${path22}.reasoning.preserve must be a boolean when provided`;
5264
+ return `${path23}.reasoning.preserve must be a boolean when provided`;
4656
5265
  }
4657
5266
  }
4658
5267
  const cache = modelRuntime["cache"];
4659
5268
  if (cache !== void 0) {
4660
- if (!isRecord(cache)) return `${path22}.cache must be an object when provided`;
5269
+ if (!isRecord(cache)) return `${path23}.cache must be an object when provided`;
4661
5270
  const ttl = cache["ttl"];
4662
5271
  if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
4663
- return `${path22}.cache.ttl must be one of: 5m, 1h`;
5272
+ return `${path23}.cache.ttl must be one of: 5m, 1h`;
4664
5273
  }
4665
5274
  }
4666
5275
  const parameters = modelRuntime["parameters"];
4667
5276
  if (parameters !== void 0 && !isRecord(parameters)) {
4668
- return `${path22}.parameters must be an object when provided`;
5277
+ return `${path23}.parameters must be an object when provided`;
4669
5278
  }
4670
5279
  return null;
4671
5280
  }
@@ -4687,6 +5296,16 @@ function validatePreferenceValue(key, value) {
4687
5296
  (v) => Array.isArray(v) && v.every((item) => typeof item === "string")
4688
5297
  ) ? null : `prefs.update payload.${key} must be an object of string arrays`;
4689
5298
  }
5299
+ if (BOOLEAN_RECORD_PREF_KEYS.has(key)) {
5300
+ if (!isRecord(value) || !Object.values(value).every((v) => typeof v === "boolean")) {
5301
+ return `prefs.update payload.${key} must be an object of booleans`;
5302
+ }
5303
+ const badKey = Object.keys(value).find((k) => FORBIDDEN_PROTO_KEYS.has(k));
5304
+ if (badKey) {
5305
+ return `prefs.update payload.${key} contains a forbidden key: ${badKey}`;
5306
+ }
5307
+ return null;
5308
+ }
4690
5309
  if (MODEL_MATRIX_PREF_KEYS.has(key)) {
4691
5310
  if (!isRecord(value)) return `prefs.update payload.${key} must be an object`;
4692
5311
  for (const entry of Object.values(value)) {
@@ -4708,7 +5327,10 @@ function validatePreferenceValue(key, value) {
4708
5327
  return `prefs.update payload.${key}.modelRuntime must be an object when provided`;
4709
5328
  }
4710
5329
  if (isRecord(modelRuntime)) {
4711
- const runtimeError = validateModelRuntimeValue(modelRuntime, `prefs.update payload.${key}.modelRuntime`);
5330
+ const runtimeError = validateModelRuntimeValue(
5331
+ modelRuntime,
5332
+ `prefs.update payload.${key}.modelRuntime`
5333
+ );
4712
5334
  if (runtimeError) return runtimeError;
4713
5335
  }
4714
5336
  if (model === void 0 && fallbackProfile === void 0 && modelRuntime === void 0) {
@@ -4945,8 +5567,8 @@ function validateShellOpenPayload(payload) {
4945
5567
  if (!isRecord(payload)) {
4946
5568
  return { ok: false, message: "shell.open payload must be an object with string path" };
4947
5569
  }
4948
- const path22 = payload["path"];
4949
- if (typeof path22 !== "string" || path22.trim().length === 0) {
5570
+ const path23 = payload["path"];
5571
+ if (typeof path23 !== "string" || path23.trim().length === 0) {
4950
5572
  return { ok: false, message: "shell.open payload.path must be a non-empty string" };
4951
5573
  }
4952
5574
  const target = payload["target"];
@@ -4959,7 +5581,7 @@ function validateShellOpenPayload(payload) {
4959
5581
  return {
4960
5582
  ok: true,
4961
5583
  value: {
4962
- path: path22,
5584
+ path: path23,
4963
5585
  ...target !== void 0 ? { target } : {}
4964
5586
  }
4965
5587
  };
@@ -4968,14 +5590,14 @@ function validateGitDiffPayload(payload) {
4968
5590
  if (!isRecord(payload)) {
4969
5591
  return { ok: false, message: "git.diff payload must be an object" };
4970
5592
  }
4971
- const path22 = payload["path"];
4972
- if (path22 === void 0 || path22 === null) {
5593
+ const path23 = payload["path"];
5594
+ if (path23 === void 0 || path23 === null) {
4973
5595
  return { ok: true, value: { path: "" } };
4974
5596
  }
4975
- if (typeof path22 !== "string") {
5597
+ if (typeof path23 !== "string") {
4976
5598
  return { ok: false, message: "git.diff payload.path must be a string when provided" };
4977
5599
  }
4978
- return { ok: true, value: { path: path22 } };
5600
+ return { ok: true, value: { path: path23 } };
4979
5601
  }
4980
5602
 
4981
5603
  // src/server/zip.ts
@@ -5121,19 +5743,19 @@ async function handleSkillsContent(ws, ctx, msg) {
5121
5743
  send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
5122
5744
  return;
5123
5745
  }
5124
- const body = await fs9.readFile(entry.path, "utf8");
5125
- const skillDir = path10.dirname(entry.path);
5746
+ const body = await fs10.readFile(entry.path, "utf8");
5747
+ const skillDir = path11.dirname(entry.path);
5126
5748
  let relatedFiles = [];
5127
5749
  try {
5128
- const files = await fs9.readdir(skillDir);
5129
- relatedFiles = files.filter((f) => f !== path10.basename(entry.path)).map((f) => path10.join(skillDir, f));
5750
+ const files = await fs10.readdir(skillDir);
5751
+ relatedFiles = files.filter((f) => f !== path11.basename(entry.path)).map((f) => path11.join(skillDir, f));
5130
5752
  } catch {
5131
5753
  }
5132
5754
  const nameLower = name2.toLowerCase();
5133
5755
  const refResults = await Promise.all(
5134
5756
  entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
5135
5757
  try {
5136
- const content = await fs9.readFile(e.path, "utf8");
5758
+ const content = await fs10.readFile(e.path, "utf8");
5137
5759
  return [e.name, content.toLowerCase().includes(nameLower)];
5138
5760
  } catch {
5139
5761
  return [e.name, false];
@@ -5223,20 +5845,20 @@ async function handleSkillsCreate(ws, ctx, msg) {
5223
5845
  }
5224
5846
  const createPayload = parsed.value;
5225
5847
  try {
5226
- const targetDir = createPayload.scope === "global" ? path10.join(
5227
- ctx.globalSkillsDir ?? path10.join(wstackGlobalRoot(), "skills"),
5848
+ const targetDir = createPayload.scope === "global" ? path11.join(
5849
+ ctx.globalSkillsDir ?? path11.join(wstackGlobalRoot(), "skills"),
5228
5850
  createPayload.name.trim()
5229
- ) : path10.join(
5230
- ctx.projectSkillsDir ?? path10.join(ctx.projectRoot, ".wrongstack", "skills"),
5851
+ ) : path11.join(
5852
+ ctx.projectSkillsDir ?? path11.join(ctx.projectRoot, ".wrongstack", "skills"),
5231
5853
  createPayload.name.trim()
5232
5854
  );
5233
5855
  try {
5234
- await fs9.access(targetDir);
5856
+ await fs10.access(targetDir);
5235
5857
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
5236
5858
  return;
5237
5859
  } catch {
5238
5860
  }
5239
- await fs9.mkdir(targetDir, { recursive: true });
5861
+ await fs10.mkdir(targetDir, { recursive: true });
5240
5862
  const lines = createPayload.description.trim().split("\n");
5241
5863
  const firstLine = (lines[0] ?? "").trim();
5242
5864
  const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
@@ -5284,13 +5906,13 @@ ${trigger}
5284
5906
  "- `bug-hunter` \u2014 for systematic bug detection patterns",
5285
5907
  "- `output-standards` \u2014 for standardized `<nextsteps>` formatting"
5286
5908
  ].join("\n");
5287
- await atomicWrite5(path10.join(targetDir, "SKILL.md"), skillContent);
5909
+ await atomicWrite5(path11.join(targetDir, "SKILL.md"), skillContent);
5288
5910
  send(ws, {
5289
5911
  type: "skills.created",
5290
5912
  payload: {
5291
5913
  success: true,
5292
5914
  error: null,
5293
- skill: { name: createPayload.name.trim(), path: path10.join(targetDir, "SKILL.md"), scope: createPayload.scope }
5915
+ skill: { name: createPayload.name.trim(), path: path11.join(targetDir, "SKILL.md"), scope: createPayload.scope }
5294
5916
  }
5295
5917
  });
5296
5918
  } catch (err) {
@@ -5567,7 +6189,7 @@ function estimateContextBreakdown(input) {
5567
6189
  }
5568
6190
 
5569
6191
  // src/server/worktree-ws-handler.ts
5570
- import { join as join7, resolve as resolve6, sep as sep3 } from "node:path";
6192
+ import { join as join8, resolve as resolve6, sep as sep3 } from "node:path";
5571
6193
  import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core";
5572
6194
  import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
5573
6195
  import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
@@ -5628,7 +6250,7 @@ var WorktreeWebSocketHandler = class {
5628
6250
  // ── orphan management ─────────────────────────────────────────────────────
5629
6251
  /** Absolute managed-worktrees root for this project. */
5630
6252
  worktreesRoot() {
5631
- return resolve6(join7(this.management.projectRoot, ".wrongstack", "worktrees"));
6253
+ return resolve6(join8(this.management.projectRoot, ".wrongstack", "worktrees"));
5632
6254
  }
5633
6255
  /** True iff `dir` resolves strictly inside the managed worktrees root. */
5634
6256
  underRoot(dir) {
@@ -5899,7 +6521,7 @@ var WorktreeWebSocketHandler = class {
5899
6521
  };
5900
6522
 
5901
6523
  // src/server/server-runtime.ts
5902
- import * as path12 from "node:path";
6524
+ import * as path14 from "node:path";
5903
6525
  import { createRequire } from "node:module";
5904
6526
  import { fileURLToPath } from "node:url";
5905
6527
  import { WebSocketServer } from "ws";
@@ -5941,12 +6563,103 @@ function registerShutdownHandlers(res) {
5941
6563
  }
5942
6564
 
5943
6565
  // src/server/setup-events.ts
5944
- import * as fs10 from "node:fs/promises";
5945
6566
  import { watch as fsWatch } from "node:fs";
5946
- import * as path11 from "node:path";
6567
+ import * as fs11 from "node:fs/promises";
6568
+ import * as path13 from "node:path";
6569
+ import { getBoard, getKanbanDir, recordTaskFileActivity } from "@wrongstack/kanban";
6570
+
6571
+ // src/server/codemap-telemetry.ts
6572
+ import * as path12 from "node:path";
6573
+ var TOOL_OPERATION = {
6574
+ read: "read",
6575
+ read_file: "read",
6576
+ view: "read",
6577
+ write: "write",
6578
+ write_file: "write",
6579
+ create_file: "write",
6580
+ edit: "edit",
6581
+ replace: "edit",
6582
+ patch: "edit",
6583
+ apply_patch: "edit",
6584
+ delete: "delete",
6585
+ delete_file: "delete",
6586
+ remove: "delete",
6587
+ unlink: "delete",
6588
+ grep: "search",
6589
+ search: "search",
6590
+ codebase_search: "search",
6591
+ "codebase-search": "search"
6592
+ };
6593
+ function numberField(input, names) {
6594
+ for (const name2 of names) {
6595
+ const value = input[name2];
6596
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.floor(value);
6597
+ }
6598
+ return void 0;
6599
+ }
6600
+ function normalizeTarget(projectRoot, filePath) {
6601
+ return path12.normalize(path12.isAbsolute(filePath) ? filePath : path12.resolve(projectRoot, filePath));
6602
+ }
6603
+ function normalizeCodeMapFileTarget(projectRoot, filePath, operation = "edit", line, endLine) {
6604
+ return {
6605
+ filePath: normalizeTarget(projectRoot, filePath),
6606
+ operation: operation === "rename" ? "edit" : operation,
6607
+ ...line ? { line } : {},
6608
+ ...endLine ? { endLine } : {}
6609
+ };
6610
+ }
6611
+ function patchTargets(patch) {
6612
+ const targets = [];
6613
+ for (const line of patch.split(/\r?\n/)) {
6614
+ const match = /^\+\+\+\s+(?:b\/)?(.+?)(?:\t.*)?$/.exec(line);
6615
+ const target = match?.[1]?.trim();
6616
+ if (target && target !== "/dev/null") targets.push(target);
6617
+ }
6618
+ return targets;
6619
+ }
6620
+ function extractCodeMapFileTargets(projectRoot, toolName, rawInput) {
6621
+ const operation = TOOL_OPERATION[toolName.toLowerCase()];
6622
+ if (!operation || !rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) return [];
6623
+ const input = rawInput;
6624
+ const rawPaths = [];
6625
+ for (const key of ["path", "file", "filePath", "target"]) {
6626
+ const value = input[key];
6627
+ if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
6628
+ }
6629
+ const files = input["files"];
6630
+ if (Array.isArray(files)) {
6631
+ for (const value of files)
6632
+ if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
6633
+ } else if (typeof files === "string" && files.trim() && !/[?*{}[\]]/.test(files)) {
6634
+ rawPaths.push(files.trim());
6635
+ }
6636
+ if ((toolName === "patch" || toolName === "apply_patch") && typeof input["patch"] === "string") {
6637
+ rawPaths.push(...patchTargets(input["patch"]));
6638
+ }
6639
+ const line = numberField(input, ["line", "offset", "startLine", "start_line", "line_start"]);
6640
+ const explicitEnd = numberField(input, ["endLine", "end_line", "line_end"]);
6641
+ const limit = numberField(input, ["limit"]);
6642
+ const endLine = explicitEnd ?? (line && limit ? line + limit - 1 : void 0);
6643
+ const seen = /* @__PURE__ */ new Set();
6644
+ const targets = [];
6645
+ for (const rawPath of rawPaths) {
6646
+ const filePath = normalizeTarget(projectRoot, rawPath);
6647
+ if (seen.has(filePath)) continue;
6648
+ seen.add(filePath);
6649
+ targets.push({
6650
+ filePath,
6651
+ operation,
6652
+ ...line ? { line } : {},
6653
+ ...endLine ? { endLine } : {}
6654
+ });
6655
+ }
6656
+ return targets;
6657
+ }
6658
+
6659
+ // src/server/setup-events.ts
5947
6660
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
5948
6661
  const raw = String(filename);
5949
- const relative4 = path11.isAbsolute(raw) ? path11.relative(projectsDir, raw) : raw;
6662
+ const relative4 = path13.isAbsolute(raw) ? path13.relative(projectsDir, raw) : raw;
5950
6663
  const parts = relative4.split(/[\\/]+/).filter(Boolean);
5951
6664
  if (parts.length < 2) return null;
5952
6665
  if (parts[parts.length - 1] !== "status.json") return null;
@@ -5957,12 +6670,76 @@ function shouldLogWatcherStats() {
5957
6670
  return value === "1" || value === "true" || value === "yes" || value === "on";
5958
6671
  }
5959
6672
  function setupEvents(deps2) {
5960
- const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
6673
+ const {
6674
+ events,
6675
+ broadcast: broadcast2,
6676
+ clients,
6677
+ config,
6678
+ context,
6679
+ pendingConfirms,
6680
+ globalConfigPath,
6681
+ sessionBridge,
6682
+ wpaths,
6683
+ watcherMetrics,
6684
+ onFleetBroadcaster
6685
+ } = deps2;
5961
6686
  const disposers = [];
5962
6687
  let disposed = false;
5963
6688
  const on = (event, listener) => {
5964
6689
  disposers.push(events.on(event, listener));
5965
6690
  };
6691
+ const conversationState = context.state;
6692
+ if (typeof conversationState?.onChange === "function") {
6693
+ disposers.push(
6694
+ conversationState.onChange((change) => {
6695
+ if (change.kind !== "todos_replaced") return;
6696
+ broadcast2(clients, {
6697
+ type: "todos.updated",
6698
+ payload: {
6699
+ sessionId: context.session?.id ?? "",
6700
+ todos: [...change.todos],
6701
+ revision: conversationState.revision
6702
+ }
6703
+ });
6704
+ })
6705
+ );
6706
+ }
6707
+ let kanbanWatcher = null;
6708
+ let kanbanDebounce = null;
6709
+ const projectRoot = context.projectRoot;
6710
+ if (projectRoot) {
6711
+ try {
6712
+ const kanbanDir = getKanbanDir(projectRoot);
6713
+ kanbanWatcher = fsWatch(kanbanDir, { persistent: false }, (_eventType, filename) => {
6714
+ const name2 = filename?.toString();
6715
+ if (!name2?.endsWith(".json")) return;
6716
+ const boardId = name2.slice(0, -5);
6717
+ if (kanbanDebounce) clearTimeout(kanbanDebounce);
6718
+ kanbanDebounce = setTimeout(async () => {
6719
+ try {
6720
+ const board = await getBoard(projectRoot, boardId);
6721
+ if (board) {
6722
+ broadcast2(clients, {
6723
+ type: "kanban.get",
6724
+ // Wrap in the { board } envelope like every other kanban
6725
+ // broadcast so the client's isBoardEnvelope path handles it
6726
+ // without hijacking another tab's activeBoardId.
6727
+ payload: { success: true, data: { board } }
6728
+ });
6729
+ }
6730
+ } catch {
6731
+ }
6732
+ }, 60);
6733
+ });
6734
+ kanbanWatcher.on("error", () => kanbanWatcher?.close());
6735
+ disposers.push(() => {
6736
+ if (kanbanDebounce) clearTimeout(kanbanDebounce);
6737
+ kanbanWatcher?.close();
6738
+ kanbanWatcher = null;
6739
+ });
6740
+ } catch {
6741
+ }
6742
+ }
5966
6743
  const currentSessionId = () => context.session?.id ?? "";
5967
6744
  const sessionPayload2 = (payload) => {
5968
6745
  const provided = payload["sessionId"];
@@ -5988,7 +6765,11 @@ function setupEvents(deps2) {
5988
6765
  on("iteration.completed", (e) => {
5989
6766
  broadcast2(clients, {
5990
6767
  type: "iteration.completed",
5991
- payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
6768
+ payload: sessionPayload2({
6769
+ sessionId: e.sessionId,
6770
+ index: e.index,
6771
+ totalIterations: e.index + 1
6772
+ })
5992
6773
  });
5993
6774
  });
5994
6775
  on("iteration.limit_reached", (e) => {
@@ -6002,10 +6783,16 @@ function setupEvents(deps2) {
6002
6783
  });
6003
6784
  });
6004
6785
  on("provider.text_delta", (e) => {
6005
- broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
6786
+ broadcast2(clients, {
6787
+ type: "provider.text_delta",
6788
+ payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" })
6789
+ });
6006
6790
  });
6007
6791
  on("provider.thinking_delta", (e) => {
6008
- broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
6792
+ broadcast2(clients, {
6793
+ type: "provider.thinking_delta",
6794
+ payload: sessionPayload2({ sessionId: e.sessionId, text: e.text })
6795
+ });
6009
6796
  });
6010
6797
  on("provider.stream_error", (e) => {
6011
6798
  broadcast2(clients, {
@@ -6016,7 +6803,17 @@ function setupEvents(deps2) {
6016
6803
  on("tool.started", (e) => {
6017
6804
  broadcast2(clients, {
6018
6805
  type: "tool.started",
6019
- payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
6806
+ payload: sessionPayload2({
6807
+ sessionId: e.sessionId,
6808
+ traceId: e.traceId,
6809
+ agentId: e.agentId,
6810
+ agentName: e.agentName,
6811
+ id: e.id,
6812
+ name: e.name,
6813
+ input: e.input,
6814
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
6815
+ messageId: `tool_${e.id}`
6816
+ })
6020
6817
  });
6021
6818
  appendForCurrentSession(e.sessionId, {
6022
6819
  type: "tool_call_start",
@@ -6027,13 +6824,37 @@ function setupEvents(deps2) {
6027
6824
  });
6028
6825
  });
6029
6826
  on("tool.progress", (e) => {
6827
+ const rawProgressPath = e.event.path ?? (typeof e.event.data?.["path"] === "string" ? e.event.data["path"] : void 0);
6828
+ const progressTarget = rawProgressPath ? normalizeCodeMapFileTarget(
6829
+ context.projectRoot,
6830
+ rawProgressPath,
6831
+ e.event.operation ?? "edit",
6832
+ e.event.line,
6833
+ e.event.endLine
6834
+ ) : void 0;
6030
6835
  broadcast2(clients, {
6031
6836
  type: "tool.progress",
6032
6837
  // Nested `event` shape — the client handler reads `payload.event?.text`
6033
6838
  // and early-returns on a falsy text, so a flat { eventType, text } payload
6034
6839
  // makes live tool progress (bash streaming, partial_output, warnings)
6035
6840
  // never render. Must match WSToolProgress and the CLI server.
6036
- payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, event: { type: e.event.type, text: e.event.text, data: e.event.data } })
6841
+ payload: sessionPayload2({
6842
+ sessionId: e.sessionId,
6843
+ traceId: e.traceId,
6844
+ agentId: e.agentId,
6845
+ agentName: e.agentName,
6846
+ id: e.id,
6847
+ name: e.name,
6848
+ event: {
6849
+ type: e.event.type,
6850
+ text: e.event.text,
6851
+ data: e.event.data,
6852
+ path: progressTarget?.filePath,
6853
+ operation: e.event.operation,
6854
+ line: progressTarget?.line,
6855
+ endLine: progressTarget?.endLine
6856
+ }
6857
+ })
6037
6858
  });
6038
6859
  appendForCurrentSession(e.sessionId, {
6039
6860
  type: "tool_progress",
@@ -6050,7 +6871,23 @@ function setupEvents(deps2) {
6050
6871
  on("tool.executed", (e) => {
6051
6872
  broadcast2(clients, {
6052
6873
  type: "tool.executed",
6053
- payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
6874
+ payload: sessionPayload2({
6875
+ sessionId: e.sessionId,
6876
+ traceId: e.traceId,
6877
+ agentId: e.agentId,
6878
+ agentName: e.agentName,
6879
+ id: e.id,
6880
+ name: e.name,
6881
+ durationMs: e.durationMs,
6882
+ ok: e.ok,
6883
+ input: e.input,
6884
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
6885
+ output: e.output,
6886
+ outputBytes: e.outputBytes,
6887
+ outputTokens: e.outputTokens,
6888
+ outputLines: e.outputLines,
6889
+ metadata: e.metadata
6890
+ })
6054
6891
  });
6055
6892
  appendForCurrentSession(e.sessionId, {
6056
6893
  type: "tool_call_end",
@@ -6064,7 +6901,10 @@ function setupEvents(deps2) {
6064
6901
  outputTokens: e.outputTokens,
6065
6902
  outputLines: e.outputLines
6066
6903
  });
6067
- broadcast2(clients, { type: "todos.updated", payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] }) });
6904
+ broadcast2(clients, {
6905
+ type: "todos.updated",
6906
+ payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] })
6907
+ });
6068
6908
  const sideEffects = context.sideEffects ?? [];
6069
6909
  if (sideEffects.length > 0) {
6070
6910
  broadcast2(clients, {
@@ -6089,7 +6929,10 @@ function setupEvents(deps2) {
6089
6929
  if (typeof taskPath === "string" && taskPath) {
6090
6930
  const { loadTasks } = await import("@wrongstack/core");
6091
6931
  const file = await loadTasks(taskPath);
6092
- broadcast2(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
6932
+ broadcast2(clients, {
6933
+ type: "tasks.updated",
6934
+ payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] })
6935
+ });
6093
6936
  }
6094
6937
  } catch {
6095
6938
  }
@@ -6098,13 +6941,39 @@ function setupEvents(deps2) {
6098
6941
  if (typeof planPath === "string" && planPath) {
6099
6942
  const { loadPlan } = await import("@wrongstack/core");
6100
6943
  const plan = await loadPlan(planPath);
6101
- broadcast2(clients, { type: "plan.updated", payload: sessionPayload2({ sessionId: e.sessionId, plan: plan ?? { version: 1, sessionId: e.sessionId ?? context.session?.id ?? "", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items: [] } }) });
6944
+ broadcast2(clients, {
6945
+ type: "plan.updated",
6946
+ payload: sessionPayload2({
6947
+ sessionId: e.sessionId,
6948
+ plan: plan ?? {
6949
+ version: 1,
6950
+ sessionId: e.sessionId ?? context.session?.id ?? "",
6951
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6952
+ items: []
6953
+ }
6954
+ })
6955
+ });
6102
6956
  }
6103
6957
  } catch {
6104
6958
  }
6105
6959
  })();
6106
6960
  }
6107
6961
  });
6962
+ on("file.activity", (e) => {
6963
+ broadcast2(clients, { type: "codemap.file_event", payload: e });
6964
+ });
6965
+ on("file.event", (e) => {
6966
+ if (e.scope !== "task" || !e.boardId || !e.taskId) return;
6967
+ void recordTaskFileActivity(context.projectRoot, e.boardId, e.taskId, e).then((recorded) => {
6968
+ if (recorded) {
6969
+ broadcast2(clients, {
6970
+ type: "kanban.task.activity.changed",
6971
+ payload: { boardId: e.boardId, taskId: e.taskId }
6972
+ });
6973
+ }
6974
+ }).catch(() => {
6975
+ });
6976
+ });
6108
6977
  on("tool.loop_detected", (e) => {
6109
6978
  broadcast2(clients, {
6110
6979
  type: "tool.loop_detected",
@@ -6120,7 +6989,12 @@ function setupEvents(deps2) {
6120
6989
  on("trust.persisted", (e) => {
6121
6990
  broadcast2(clients, {
6122
6991
  type: "trust.persisted",
6123
- payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
6992
+ payload: sessionPayload2({
6993
+ sessionId: e.sessionId,
6994
+ tool: e.tool,
6995
+ pattern: e.pattern,
6996
+ decision: e.decision
6997
+ })
6124
6998
  });
6125
6999
  });
6126
7000
  on("delegate.started", (e) => {
@@ -6162,7 +7036,12 @@ function setupEvents(deps2) {
6162
7036
  on("ctx.pct", (e) => {
6163
7037
  broadcast2(clients, {
6164
7038
  type: "ctx.pct",
6165
- payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
7039
+ payload: sessionPayload2({
7040
+ sessionId: e.sessionId,
7041
+ load: e.load,
7042
+ tokens: e.tokens,
7043
+ maxContext: e.maxContext
7044
+ })
6166
7045
  });
6167
7046
  broadcast2(clients, {
6168
7047
  type: "subagent.event",
@@ -6179,7 +7058,12 @@ function setupEvents(deps2) {
6179
7058
  on("ctx.max_context", (e) => {
6180
7059
  broadcast2(clients, {
6181
7060
  type: "ctx.max_context",
6182
- payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
7061
+ payload: sessionPayload2({
7062
+ sessionId: e.sessionId,
7063
+ providerId: e.providerId,
7064
+ modelId: e.modelId,
7065
+ maxContext: e.maxContext
7066
+ })
6183
7067
  });
6184
7068
  });
6185
7069
  on("token.threshold", (e) => {
@@ -6195,21 +7079,46 @@ function setupEvents(deps2) {
6195
7079
  });
6196
7080
  });
6197
7081
  on("context.repaired", (e) => {
6198
- broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
7082
+ broadcast2(clients, {
7083
+ type: "context.repaired",
7084
+ payload: sessionPayload2({
7085
+ sessionId: e.sessionId,
7086
+ removedToolUses: e.removedToolUses,
7087
+ removedToolResults: e.removedToolResults,
7088
+ removedMessages: e.removedMessages
7089
+ })
7090
+ });
6199
7091
  });
6200
7092
  on("tool.confirm_needed", (e) => {
6201
7093
  const id = e.toolUseId ?? `confirm_${Date.now()}`;
6202
- const payload = sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern, decisionSource: e.decisionSource, riskTier: e.riskTier });
7094
+ const payload = sessionPayload2({
7095
+ sessionId: e.sessionId,
7096
+ id,
7097
+ toolName: e.tool?.name ?? "unknown",
7098
+ input: e.input,
7099
+ suggestedPattern: e.suggestedPattern,
7100
+ decisionSource: e.decisionSource,
7101
+ riskTier: e.riskTier,
7102
+ boundaryReason: e.boundaryReason
7103
+ });
6203
7104
  pendingConfirms.set(id, {
6204
7105
  resolve: e.resolve,
6205
7106
  decisionSource: e.decisionSource,
6206
7107
  riskTier: e.riskTier,
7108
+ boundaryReason: e.boundaryReason,
6207
7109
  payload
6208
7110
  });
6209
7111
  broadcast2(clients, { type: "tool.confirm_needed", payload });
6210
7112
  });
6211
7113
  on("error", (e) => {
6212
- broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
7114
+ broadcast2(clients, {
7115
+ type: "error",
7116
+ payload: sessionPayload2({
7117
+ sessionId: e.sessionId,
7118
+ phase: e.phase,
7119
+ message: e.err instanceof Error ? e.err.message : String(e.err)
7120
+ })
7121
+ });
6213
7122
  appendForCurrentSession(e.sessionId, {
6214
7123
  type: "error",
6215
7124
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6280,6 +7189,35 @@ function setupEvents(deps2) {
6280
7189
  description: e.description
6281
7190
  });
6282
7191
  });
7192
+ on("provider.status_changed", (e) => {
7193
+ broadcast2(clients, {
7194
+ type: "provider.status_changed",
7195
+ payload: sessionPayload2({
7196
+ providerId: e.providerId,
7197
+ model: e.model,
7198
+ oldState: e.oldState,
7199
+ newState: e.newState,
7200
+ reason: e.reason,
7201
+ timestamp: e.timestamp,
7202
+ stateExpiresAt: e.stateExpiresAt
7203
+ })
7204
+ });
7205
+ });
7206
+ on("provider.active_blocked", (e) => {
7207
+ broadcast2(clients, {
7208
+ type: "provider.active_blocked",
7209
+ payload: sessionPayload2({
7210
+ sessionId: e.sessionId,
7211
+ providerId: e.providerId,
7212
+ model: e.model,
7213
+ state: e.state,
7214
+ fallbackProviderId: e.fallbackProviderId,
7215
+ fallbackModel: e.fallbackModel,
7216
+ lastError: e.lastError,
7217
+ timestamp: e.timestamp
7218
+ })
7219
+ });
7220
+ });
6283
7221
  on("provider.error", (e) => {
6284
7222
  broadcast2(clients, {
6285
7223
  type: "provider.error",
@@ -6385,16 +7323,137 @@ function setupEvents(deps2) {
6385
7323
  broadcast2(clients, { type: "mailbox.agent_registered", payload });
6386
7324
  });
6387
7325
  const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
6388
- on("subagent.spawned", (e) => forwardSubagent("spawned", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, name: e.name, provider: e.provider, model: e.model, description: e.description }));
6389
- on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
6390
- on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
6391
- on("subagent.iteration_summary", (e) => forwardSubagent("iteration_summary", { sessionId: e.sessionId, subagentId: e.subagentId, iteration: e.iteration, toolCalls: e.toolCalls, costUsd: e.costUsd, currentTool: e.currentTool, partialText: e.partialText }));
6392
- on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
6393
- on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
6394
- on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
6395
- on("subagent.task_completed", (e) => forwardSubagent("task_completed", { sessionId: e.sessionId, subagentId: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, finalText: e.finalText, failureReason: e.error?.kind, error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0 }));
6396
- on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
7326
+ on(
7327
+ "subagent.spawned",
7328
+ (e) => forwardSubagent("spawned", {
7329
+ sessionId: e.sessionId,
7330
+ subagentId: e.subagentId,
7331
+ taskId: e.taskId,
7332
+ name: e.name,
7333
+ provider: e.provider,
7334
+ model: e.model,
7335
+ description: e.description
7336
+ })
7337
+ );
7338
+ on(
7339
+ "subagent.task_started",
7340
+ (e) => forwardSubagent("task_started", {
7341
+ sessionId: e.sessionId,
7342
+ subagentId: e.subagentId,
7343
+ taskId: e.taskId,
7344
+ description: e.description
7345
+ })
7346
+ );
7347
+ on("subagent.tool_started", (e) => {
7348
+ broadcast2(clients, {
7349
+ type: "codemap.tool_started",
7350
+ payload: {
7351
+ sessionId: e.agentSessionId ?? e.sessionId ?? "",
7352
+ parentSessionId: e.sessionId,
7353
+ traceId: e.traceId,
7354
+ agentId: e.subagentId,
7355
+ agentName: e.agentName ?? e.subagentId,
7356
+ id: e.id,
7357
+ name: e.name,
7358
+ input: e.input,
7359
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input)
7360
+ }
7361
+ });
7362
+ });
7363
+ on("subagent.tool_executed", (e) => {
7364
+ broadcast2(clients, {
7365
+ type: "codemap.tool_executed",
7366
+ payload: {
7367
+ sessionId: e.agentSessionId ?? e.sessionId ?? "",
7368
+ parentSessionId: e.sessionId,
7369
+ traceId: e.traceId,
7370
+ agentId: e.subagentId,
7371
+ agentName: e.agentName ?? e.subagentId,
7372
+ id: e.id,
7373
+ name: e.name,
7374
+ durationMs: e.durationMs,
7375
+ ok: e.ok,
7376
+ input: e.input,
7377
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
7378
+ output: e.output,
7379
+ outputBytes: e.outputBytes,
7380
+ outputTokens: e.outputTokens,
7381
+ outputLines: e.outputLines
7382
+ }
7383
+ });
7384
+ forwardSubagent("tool_executed", {
7385
+ sessionId: e.sessionId,
7386
+ subagentId: e.subagentId,
7387
+ toolName: e.name,
7388
+ durationMs: e.durationMs,
7389
+ ok: e.ok
7390
+ });
7391
+ });
7392
+ on(
7393
+ "subagent.iteration_summary",
7394
+ (e) => forwardSubagent("iteration_summary", {
7395
+ sessionId: e.sessionId,
7396
+ subagentId: e.subagentId,
7397
+ iteration: e.iteration,
7398
+ toolCalls: e.toolCalls,
7399
+ costUsd: e.costUsd,
7400
+ currentTool: e.currentTool,
7401
+ partialText: e.partialText
7402
+ })
7403
+ );
7404
+ on(
7405
+ "subagent.budget_warning",
7406
+ (e) => forwardSubagent("budget_warning", {
7407
+ sessionId: e.sessionId,
7408
+ subagentId: e.subagentId,
7409
+ budgetKind: e.kind,
7410
+ used: e.used,
7411
+ limit: e.limit
7412
+ })
7413
+ );
7414
+ on(
7415
+ "subagent.budget_extended",
7416
+ (e) => forwardSubagent("budget_extended", {
7417
+ sessionId: e.sessionId,
7418
+ subagentId: e.subagentId,
7419
+ budgetKind: e.kind,
7420
+ newLimit: e.newLimit,
7421
+ totalExtensions: e.totalExtensions
7422
+ })
7423
+ );
7424
+ on(
7425
+ "subagent.ctx_pct",
7426
+ (e) => forwardSubagent("ctx_pct", {
7427
+ sessionId: e.sessionId,
7428
+ subagentId: e.subagentId,
7429
+ load: e.load,
7430
+ tokens: e.tokens,
7431
+ maxContext: e.maxContext
7432
+ })
7433
+ );
7434
+ on(
7435
+ "subagent.task_completed",
7436
+ (e) => forwardSubagent("task_completed", {
7437
+ sessionId: e.sessionId,
7438
+ subagentId: e.subagentId,
7439
+ status: e.status,
7440
+ iterations: e.iterations,
7441
+ toolCalls: e.toolCalls,
7442
+ finalText: e.finalText,
7443
+ failureReason: e.error?.kind,
7444
+ error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0
7445
+ })
7446
+ );
7447
+ on(
7448
+ "subagent.removed",
7449
+ (e) => forwardSubagent("removed", {
7450
+ sessionId: e.sessionId,
7451
+ subagentId: e.subagentId,
7452
+ reason: e.reason
7453
+ })
7454
+ );
6397
7455
  on("agent.timeline.message", (e) => {
7456
+ const timeline = e;
6398
7457
  broadcast2(clients, {
6399
7458
  type: "agent.timeline.message",
6400
7459
  payload: sessionPayload2({
@@ -6406,6 +7465,7 @@ function setupEvents(deps2) {
6406
7465
  iteration: e.iteration,
6407
7466
  ts: e.ts,
6408
7467
  toolName: e.toolName,
7468
+ ...typeof timeline.toolOk === "boolean" ? { toolOk: timeline.toolOk } : {},
6409
7469
  costUsd: e.costUsd
6410
7470
  })
6411
7471
  });
@@ -6514,9 +7574,9 @@ function setupEvents(deps2) {
6514
7574
  if (wpaths?.projectStatus) {
6515
7575
  try {
6516
7576
  const statusFile = wpaths.projectStatus(e.projectHash);
6517
- const dir = path11.dirname(statusFile);
6518
- await fs10.mkdir(dir, { recursive: true });
6519
- await fs10.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
7577
+ const dir = path13.dirname(statusFile);
7578
+ await fs11.mkdir(dir, { recursive: true });
7579
+ await fs11.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
6520
7580
  } catch (err) {
6521
7581
  console.error(
6522
7582
  JSON.stringify({
@@ -6530,7 +7590,7 @@ function setupEvents(deps2) {
6530
7590
  }
6531
7591
  });
6532
7592
  if (wpaths?.projectStatus && wpaths.configDir) {
6533
- const projectsDir = path11.join(wpaths.configDir, "projects");
7593
+ const projectsDir = path13.join(wpaths.configDir, "projects");
6534
7594
  const knownProjectHashes = /* @__PURE__ */ new Set();
6535
7595
  const debounceTimers = /* @__PURE__ */ new Map();
6536
7596
  const DEBOUNCE_MS = 150;
@@ -6594,26 +7654,32 @@ function setupEvents(deps2) {
6594
7654
  let watcher;
6595
7655
  const startWatcher = async () => {
6596
7656
  try {
6597
- await fs10.mkdir(projectsDir, { recursive: true });
7657
+ await fs11.mkdir(projectsDir, { recursive: true });
6598
7658
  if (disposed) return;
6599
- watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6600
- if (eventType !== "change" && eventType !== "rename") return;
6601
- if (filename == null) return;
6602
- const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
6603
- if (!projectHash) return;
6604
- if (watcherMetrics) watcherMetrics.fileChangesDetected++;
6605
- if (!knownProjectHashes.has(projectHash)) return;
6606
- if (watcherMetrics) watcherMetrics.filesProcessed++;
6607
- try {
6608
- const targetFile = path11.join(projectsDir, projectHash, "status.json");
6609
- const content = await fs10.readFile(targetFile, "utf-8");
6610
- const statusData = JSON.parse(content);
6611
- scheduleBroadcast(projectHash, statusData);
6612
- } catch {
7659
+ watcher = fsWatch(
7660
+ projectsDir,
7661
+ { persistent: true, recursive: true },
7662
+ async (eventType, filename) => {
7663
+ if (eventType !== "change" && eventType !== "rename") return;
7664
+ if (filename == null) return;
7665
+ const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
7666
+ if (!projectHash) return;
7667
+ if (watcherMetrics) watcherMetrics.fileChangesDetected++;
7668
+ if (!knownProjectHashes.has(projectHash)) return;
7669
+ if (watcherMetrics) watcherMetrics.filesProcessed++;
7670
+ try {
7671
+ const targetFile = path13.join(projectsDir, projectHash, "status.json");
7672
+ const content = await fs11.readFile(targetFile, "utf-8");
7673
+ const statusData = JSON.parse(content);
7674
+ scheduleBroadcast(projectHash, statusData);
7675
+ } catch {
7676
+ }
6613
7677
  }
6614
- });
7678
+ );
6615
7679
  if (logWatcherMetricsEnabled) {
6616
- console.log(`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`);
7680
+ console.log(
7681
+ `[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`
7682
+ );
6617
7683
  }
6618
7684
  } catch (err) {
6619
7685
  console.error(
@@ -6658,17 +7724,19 @@ function setupEvents(deps2) {
6658
7724
  }
6659
7725
  });
6660
7726
  }
6661
- const globalRoot = globalConfigPath ? path11.dirname(globalConfigPath) : void 0;
7727
+ const globalRoot = globalConfigPath ? path13.dirname(globalConfigPath) : void 0;
6662
7728
  if (globalRoot) {
6663
7729
  const broadcastSessions = async () => {
6664
7730
  try {
6665
7731
  const { SessionRegistry } = await import("@wrongstack/core");
6666
7732
  const registry = new SessionRegistry(globalRoot);
6667
7733
  const sessions = await registry.list();
6668
- const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
6669
- const live = sessions.filter(
6670
- (s) => s.status === "active" || s.status === "idle"
6671
- ).filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
7734
+ const ownEntry = sessions.find((s) => s.pid === process.pid);
7735
+ const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
7736
+ const myRoot = path13.resolve(context.projectRoot);
7737
+ const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter(
7738
+ (s) => mySlug ? s.projectSlug === mySlug : path13.resolve(s.projectRoot) === myRoot
7739
+ ).map((s) => ({
6672
7740
  sessionId: s.sessionId,
6673
7741
  projectName: s.projectName,
6674
7742
  projectSlug: s.projectSlug,
@@ -6680,12 +7748,15 @@ function setupEvents(deps2) {
6680
7748
  status: s.status,
6681
7749
  pid: s.pid,
6682
7750
  startedAt: s.startedAt,
7751
+ lastHeartbeatAt: s.lastHeartbeatAt,
6683
7752
  agentCount: s.agentCount,
6684
7753
  agents: (s.agents ?? []).map((a) => ({
6685
7754
  id: a.id,
6686
7755
  name: a.name,
6687
7756
  status: a.status,
6688
7757
  currentTool: a.currentTool,
7758
+ currentTask: a.currentTask,
7759
+ taskId: a.taskId,
6689
7760
  iterations: a.iterations,
6690
7761
  toolCalls: a.toolCalls,
6691
7762
  costUsd: a.costUsd,
@@ -6694,6 +7765,12 @@ function setupEvents(deps2) {
6694
7765
  ctxPct: a.ctxPct,
6695
7766
  model: a.model,
6696
7767
  partialText: a.partialText,
7768
+ recentTools: a.recentTools,
7769
+ recentMail: a.recentMail,
7770
+ todos: a.todos,
7771
+ latestPrompt: a.latestPrompt,
7772
+ latestPromptAt: a.latestPromptAt,
7773
+ activity: a.activity,
6697
7774
  lastActivityAt: a.lastActivityAt
6698
7775
  }))
6699
7776
  }));
@@ -6919,7 +7996,7 @@ function createSessionStartPayload(g) {
6919
7996
  inputCost,
6920
7997
  outputCost,
6921
7998
  cacheReadCost,
6922
- projectName: path12.basename(projectRoot) || projectRoot,
7999
+ projectName: path14.basename(projectRoot) || projectRoot,
6923
8000
  projectRoot,
6924
8001
  cwd: g.getWorkingDir(),
6925
8002
  mode: g.getModeId(),
@@ -7017,13 +8094,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
7017
8094
  };
7018
8095
  }
7019
8096
  function resolveWebuiDistDir(fromUrl, explicitDistDir) {
7020
- if (explicitDistDir) return path12.resolve(explicitDistDir);
8097
+ if (explicitDistDir) return path14.resolve(explicitDistDir);
7021
8098
  try {
7022
8099
  const requireFromHere2 = createRequire(fromUrl);
7023
8100
  const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
7024
- return path12.dirname(serverEntry);
8101
+ return path14.dirname(serverEntry);
7025
8102
  } catch {
7026
- return path12.resolve(path12.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
8103
+ return path14.resolve(path14.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
7027
8104
  }
7028
8105
  }
7029
8106
  function startHttpServer(opts) {
@@ -7036,15 +8113,18 @@ function startHttpServer(opts) {
7036
8113
  apiToken: opts.wsToken,
7037
8114
  requireToken: opts.requireToken,
7038
8115
  watcherMetrics: opts.watcherMetrics,
7039
- onFleetPing: opts.onFleetPing
8116
+ onFleetPing: opts.onFleetPing,
8117
+ onTechStackEvent: opts.onTechStackEvent,
8118
+ getLlm: opts.getLlm,
8119
+ projectRoot: opts.projectRoot
7040
8120
  });
7041
- const registryBaseDir = path12.dirname(opts.globalConfigPath);
8121
+ const registryBaseDir = path14.dirname(opts.globalConfigPath);
7042
8122
  httpServer.listen(opts.httpPort, opts.wsHost, () => {
7043
8123
  const openUrl = buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, token: opts.wsToken, publicUrl: opts.publicUrl });
7044
8124
  console.log(`[WebUI] HTTP server running on ${openUrl}`);
7045
8125
  if (opts.openBrowser) openBrowser(openUrl);
7046
8126
  void registerInstance(
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 }) },
8127
+ { pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path14.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
7048
8128
  registryBaseDir
7049
8129
  ).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
7050
8130
  });
@@ -7060,7 +8140,7 @@ function registerShutdown(deps2) {
7060
8140
  }
7061
8141
 
7062
8142
  // src/server/pre-context-services.ts
7063
- import * as path15 from "node:path";
8143
+ import * as path17 from "node:path";
7064
8144
  import { createRequire as createRequire2 } from "node:module";
7065
8145
  import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
7066
8146
  import {
@@ -7206,6 +8286,7 @@ function resolveSetupProvider(opts) {
7206
8286
  }
7207
8287
 
7208
8288
  // src/server/context-meta.ts
8289
+ import { FallbackProfileManager } from "@wrongstack/core";
7209
8290
  function seedContextMeta(config, context) {
7210
8291
  const meta = context.meta;
7211
8292
  const autonomyCfg = config.autonomy ?? {};
@@ -7225,6 +8306,7 @@ function seedContextMeta(config, context) {
7225
8306
  meta["fallbackProfiles"] = config.fallbackProfiles ?? {};
7226
8307
  meta["favoriteModels"] = config.favoriteModels ?? [];
7227
8308
  meta["favoriteModelsOnly"] = config.favoriteModelsOnly === true;
8309
+ meta["modelAvailabilitySchedule"] = config.modelAvailabilitySchedule ?? [];
7228
8310
  meta["modelMatrix"] = config.modelMatrix ?? {};
7229
8311
  meta["fallbackAuto"] = config.fallbackAuto !== false;
7230
8312
  if (typeof config.uiLocale === "string" && config.uiLocale) meta["uiLocale"] = config.uiLocale;
@@ -7259,6 +8341,7 @@ function seedContextMeta(config, context) {
7259
8341
  meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
7260
8342
  meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
7261
8343
  meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
8344
+ meta["showModelReasoning"] = autonomyCfg["showModelReasoning"] !== false;
7262
8345
  meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
7263
8346
  meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
7264
8347
  {
@@ -7279,11 +8362,40 @@ function seedContextMeta(config, context) {
7279
8362
  meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
7280
8363
  const tgMs = tgExt?.["longToolThresholdMs"];
7281
8364
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
8365
+ const chimeraExt = config.extensions?.["wstack-chimera"];
8366
+ meta["chimeraEnabled"] = chimeraExt?.["enabled"] !== false;
8367
+ meta["chimeraProvider"] = chimeraExt?.["provider"] ?? "";
8368
+ meta["chimeraModel"] = chimeraExt?.["model"] ?? "";
8369
+ meta["chimeraMaxFiles"] = typeof chimeraExt?.["maxFiles"] === "number" && chimeraExt["maxFiles"] >= 1 ? chimeraExt["maxFiles"] : 15;
8370
+ const autoFix = chimeraExt?.["autoFix"];
8371
+ meta["chimeraAutoFix"] = autoFix === "off" || autoFix === "ask" || autoFix === "auto" ? autoFix : "off";
8372
+ const autoReviewExt = config.extensions?.["wstack-auto-review"];
8373
+ meta["autoReviewEnabled"] = autoReviewExt?.["enabled"] === true;
8374
+ meta["autoReviewProvider"] = autoReviewExt?.["provider"] ?? "";
8375
+ meta["autoReviewModel"] = autoReviewExt?.["model"] ?? "";
8376
+ meta["autoReviewFallbackProfile"] = autoReviewExt?.["fallbackProfile"] ?? "";
8377
+ meta["autoReviewFallbackModels"] = Array.isArray(autoReviewExt?.["fallbackModels"]) ? autoReviewExt?.["fallbackModels"] : [];
8378
+ meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] : 5e3;
8379
+ meta["autoReviewMaxFilesPerBatch"] = typeof autoReviewExt?.["maxFilesPerBatch"] === "number" && autoReviewExt["maxFilesPerBatch"] >= 1 ? autoReviewExt["maxFilesPerBatch"] : 15;
8380
+ meta["autoReviewMaxConcurrentReviews"] = typeof autoReviewExt?.["maxConcurrentReviews"] === "number" && autoReviewExt["maxConcurrentReviews"] >= 1 ? autoReviewExt["maxConcurrentReviews"] : 2;
8381
+ const cascade = autoReviewExt?.["cascadeOn"];
8382
+ meta["autoReviewCascadeOn"] = cascade === "critical" || cascade === "high" ? cascade : "off";
8383
+ {
8384
+ let resolvedChain = [];
8385
+ try {
8386
+ const mgr = new FallbackProfileManager(config);
8387
+ const named = autoReviewExt?.["fallbackProfile"];
8388
+ resolvedChain = typeof named === "string" && named.length > 0 ? mgr.resolve(named) : mgr.resolveEffective({ fallbackAuto: true });
8389
+ } catch {
8390
+ resolvedChain = [];
8391
+ }
8392
+ meta["autoReviewFallbackModels"] = resolvedChain.map((e) => `${e.providerId}/${e.model}`);
8393
+ }
7282
8394
  }
7283
8395
 
7284
8396
  // src/server/model-auto-discovery.ts
7285
- import * as fs11 from "node:fs/promises";
7286
- import * as path13 from "node:path";
8397
+ import * as fs12 from "node:fs/promises";
8398
+ import * as path15 from "node:path";
7287
8399
  import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
7288
8400
  function isOverlayRegistry(value) {
7289
8401
  return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
@@ -7309,7 +8421,7 @@ function eligibleProviders(config) {
7309
8421
  }
7310
8422
  async function readCache(file) {
7311
8423
  try {
7312
- return JSON.parse(await fs11.readFile(file, "utf8"));
8424
+ return JSON.parse(await fs12.readFile(file, "utf8"));
7313
8425
  } catch {
7314
8426
  return {};
7315
8427
  }
@@ -7319,7 +8431,7 @@ async function discoverAndMergeWebuiProviders(opts) {
7319
8431
  if (!isOverlayRegistry(registry)) return;
7320
8432
  const targets = eligibleProviders(opts.config);
7321
8433
  if (targets.length === 0) return;
7322
- const cacheFile = path13.join(opts.cacheDir, "discovered-models-cache.json");
8434
+ const cacheFile = path15.join(opts.cacheDir, "discovered-models-cache.json");
7323
8435
  const cache = await readCache(cacheFile);
7324
8436
  let cacheDirty = false;
7325
8437
  await Promise.all(
@@ -7356,8 +8468,8 @@ async function discoverAndMergeWebuiProviders(opts) {
7356
8468
  );
7357
8469
  if (cacheDirty) {
7358
8470
  try {
7359
- await fs11.mkdir(path13.dirname(cacheFile), { recursive: true });
7360
- await fs11.writeFile(cacheFile, JSON.stringify(cache), "utf8");
8471
+ await fs12.mkdir(path15.dirname(cacheFile), { recursive: true });
8472
+ await fs12.writeFile(cacheFile, JSON.stringify(cache), "utf8");
7361
8473
  } catch {
7362
8474
  opts.logger?.debug?.("provider auto-discovery cache write failed");
7363
8475
  }
@@ -7365,7 +8477,7 @@ async function discoverAndMergeWebuiProviders(opts) {
7365
8477
  }
7366
8478
 
7367
8479
  // src/server/standalone-session-identity.ts
7368
- import * as path14 from "node:path";
8480
+ import * as path16 from "node:path";
7369
8481
  import {
7370
8482
  AgentStatusTracker,
7371
8483
  FleetNotifier,
@@ -7396,7 +8508,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7396
8508
  sessionId,
7397
8509
  projectSlug: paths.projectSlug,
7398
8510
  projectRoot: paths.projectRoot,
7399
- projectName: path14.basename(paths.projectRoot),
8511
+ projectName: path16.basename(paths.projectRoot),
7400
8512
  workingDir: opts.workingDir,
7401
8513
  clientType: "webui",
7402
8514
  pid: process.pid,
@@ -7405,7 +8517,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7405
8517
  });
7406
8518
  fleetNotifier.notify();
7407
8519
  } catch (err) {
7408
- logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
8520
+ logger.debug?.(`WebUI session registry update failed: ${errorMessage3(err)}`);
7409
8521
  }
7410
8522
  };
7411
8523
  await register(activeSessionId);
@@ -7431,7 +8543,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7431
8543
  const publisher = core.createHqPublisherFromEnv({
7432
8544
  clientKind: "webui",
7433
8545
  projectRoot: paths.projectRoot,
7434
- projectName: path14.basename(paths.projectRoot),
8546
+ projectName: path16.basename(paths.projectRoot),
7435
8547
  appConfig: opts.config,
7436
8548
  socketFactory: (url) => new WebSocket2(url)
7437
8549
  });
@@ -7453,7 +8565,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7453
8565
  events,
7454
8566
  sessionId,
7455
8567
  projectRoot: paths.projectRoot,
7456
- projectName: path14.basename(paths.projectRoot),
8568
+ projectName: path16.basename(paths.projectRoot),
7457
8569
  globalRoot: paths.globalRoot,
7458
8570
  initialAgents: statusTracker.getAgents(),
7459
8571
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7490,7 +8602,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7490
8602
  restartHqBridges(activeSessionId);
7491
8603
  }
7492
8604
  } catch (err) {
7493
- logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
8605
+ logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage3(err)}`);
7494
8606
  }
7495
8607
  }
7496
8608
  const repointRecovery = async (sessionId) => {
@@ -7513,7 +8625,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7513
8625
  try {
7514
8626
  restartHqBridges(sessionId);
7515
8627
  } catch (err) {
7516
- logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
8628
+ logger.debug?.(`WebUI HQ session swap failed: ${errorMessage3(err)}`);
7517
8629
  }
7518
8630
  });
7519
8631
  await transition;
@@ -7538,7 +8650,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7538
8650
  };
7539
8651
  return { statusTracker, activate, stop };
7540
8652
  }
7541
- function errorMessage2(err) {
8653
+ function errorMessage3(err) {
7542
8654
  return err instanceof Error ? err.message : String(err);
7543
8655
  }
7544
8656
 
@@ -7564,7 +8676,7 @@ async function createPreContextServices(input) {
7564
8676
  await discoverAndMergeWebuiProviders({
7565
8677
  config,
7566
8678
  registry: modelsRegistry,
7567
- cacheDir: path15.dirname(wpaths.modelsCache),
8679
+ cacheDir: path17.dirname(wpaths.modelsCache),
7568
8680
  logger
7569
8681
  });
7570
8682
  } catch (err) {
@@ -7609,7 +8721,7 @@ async function createPreContextServices(input) {
7609
8721
  configureChildEnvGitIdentity(config.git?.identity ?? null);
7610
8722
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
7611
8723
  const mcpTokenStore = new MCPVaultTokenStore(
7612
- path15.join(wpaths.projectDir, "mcp-auth.json"),
8724
+ path17.join(wpaths.projectDir, "mcp-auth.json"),
7613
8725
  vault
7614
8726
  );
7615
8727
  const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
@@ -7704,7 +8816,7 @@ async function createPreContextServices(input) {
7704
8816
  const modelCapabilitiesRef = { current: modelCapabilities };
7705
8817
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
7706
8818
  const skillInstaller = config.features.skills ? new SkillInstaller({
7707
- manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
8819
+ manifestPath: path17.join(wpaths.globalRoot, "installed-skills.json"),
7708
8820
  projectSkillsDir: wpaths.inProjectSkills,
7709
8821
  globalSkillsDir: wpaths.globalSkills,
7710
8822
  projectHash: wpaths.projectHash,
@@ -7714,7 +8826,7 @@ async function createPreContextServices(input) {
7714
8826
  const bundledPromptsDir = promptsEnabled ? (() => {
7715
8827
  try {
7716
8828
  const req = createRequire2(import.meta.url);
7717
- return path15.join(path15.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
8829
+ return path17.join(path17.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7718
8830
  } catch {
7719
8831
  return void 0;
7720
8832
  }
@@ -7815,7 +8927,7 @@ function isSuperMemoryService(memoryStore) {
7815
8927
  }
7816
8928
 
7817
8929
  // src/server/start-webui.ts
7818
- import * as path21 from "node:path";
8930
+ import * as path22 from "node:path";
7819
8931
  import {
7820
8932
  createDefaultPipelines,
7821
8933
  createSessionEventBridge,
@@ -7845,7 +8957,7 @@ function patchConfig(config, updates) {
7845
8957
  }
7846
8958
 
7847
8959
  // src/server/backend-services.ts
7848
- import { join as join12 } from "node:path";
8960
+ import { join as join13 } from "node:path";
7849
8961
  import {
7850
8962
  Agent,
7851
8963
  AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
@@ -7871,7 +8983,7 @@ import {
7871
8983
  } from "@wrongstack/core";
7872
8984
 
7873
8985
  // src/server/collaboration-ws-handler.ts
7874
- import { randomUUID } from "node:crypto";
8986
+ import { randomUUID as randomUUID2 } from "node:crypto";
7875
8987
  import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
7876
8988
  var REPLAY_LIMIT = 50;
7877
8989
  var PAUSE_TIMEOUT_MS = 6e4;
@@ -8003,7 +9115,7 @@ var CollaborationWebSocketHandler = class {
8003
9115
  return;
8004
9116
  }
8005
9117
  const participant = {
8006
- participantId: randomUUID(),
9118
+ participantId: randomUUID2(),
8007
9119
  ws,
8008
9120
  sessionId,
8009
9121
  role,
@@ -8623,8 +9735,8 @@ var CollaborationWebSocketHandler = class {
8623
9735
  };
8624
9736
 
8625
9737
  // src/server/codebase-indexing.ts
8626
- import * as fs12 from "node:fs";
8627
- import * as path16 from "node:path";
9738
+ import * as fs13 from "node:fs";
9739
+ import * as path18 from "node:path";
8628
9740
  import {
8629
9741
  cancelPendingReindexes,
8630
9742
  enqueueReindex,
@@ -8644,17 +9756,15 @@ var IGNORE_DIRS = /* @__PURE__ */ new Set([
8644
9756
  ".nyc_output"
8645
9757
  ]);
8646
9758
  function setupWebUICodebaseIndexing(deps2) {
8647
- const indexing = deps2.config.indexing;
8648
- if (!indexing) return noopIndexing();
8649
- const idx = indexing;
9759
+ const idx = deps2.config.indexing;
8650
9760
  const indexDir = typeof deps2.context.meta["codebaseIndexDir"] === "string" ? deps2.context.meta["codebaseIndexDir"] : void 0;
8651
- const debounceMs = idx.debounceMs ?? 400;
9761
+ const debounceMs = idx?.debounceMs ?? 400;
8652
9762
  const onError = (err) => {
8653
9763
  deps2.logger.debug(
8654
9764
  `webui codebase auto-index failed: ${err instanceof Error ? err.message : String(err)}`
8655
9765
  );
8656
9766
  };
8657
- if (idx.onSessionStart) {
9767
+ if (idx?.onSessionStart) {
8658
9768
  void runStartupIndex({
8659
9769
  projectRoot: deps2.projectRoot,
8660
9770
  indexDir,
@@ -8671,14 +9781,27 @@ function setupWebUICodebaseIndexing(deps2) {
8671
9781
  });
8672
9782
  }
8673
9783
  let watcher;
8674
- if (idx.watchExternal) {
9784
+ const lastWatcherEvent = /* @__PURE__ */ new Map();
9785
+ if (idx?.watchExternal || deps2.events) {
8675
9786
  try {
8676
- watcher = fs12.watch(deps2.projectRoot, { recursive: true }, (_event, filename) => {
9787
+ watcher = fs13.watch(deps2.projectRoot, { recursive: true }, (eventType, filename) => {
8677
9788
  if (!filename) return;
8678
9789
  const rel = filename.toString();
8679
9790
  if (isIgnored(rel)) return;
8680
- const abs = path16.resolve(deps2.projectRoot, rel);
8681
- enqueueFile(abs);
9791
+ const abs = path18.resolve(deps2.projectRoot, rel);
9792
+ if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
9793
+ const now = Date.now();
9794
+ if (now - (lastWatcherEvent.get(abs) ?? 0) > 75) {
9795
+ lastWatcherEvent.set(abs, now);
9796
+ deps2.events?.emit("file.activity", {
9797
+ filePath: path18.normalize(abs),
9798
+ operation: eventType === "rename" && !fs13.existsSync(abs) ? "delete" : "edit",
9799
+ phase: "changed",
9800
+ source: "watcher",
9801
+ at: now
9802
+ });
9803
+ }
9804
+ if (idx?.watchExternal) enqueueFile(abs);
8682
9805
  });
8683
9806
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
8684
9807
  watcher.unref?.();
@@ -8689,8 +9812,8 @@ function setupWebUICodebaseIndexing(deps2) {
8689
9812
  }
8690
9813
  }
8691
9814
  function enqueueFile(filePath) {
8692
- if (!idx.onEdit && !idx.watchExternal) return;
8693
- const abs = path16.isAbsolute(filePath) ? path16.normalize(filePath) : path16.resolve(deps2.projectRoot, filePath);
9815
+ if (!idx || !idx.onEdit && !idx.watchExternal) return;
9816
+ const abs = path18.isAbsolute(filePath) ? path18.normalize(filePath) : path18.resolve(deps2.projectRoot, filePath);
8694
9817
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
8695
9818
  enqueueReindex({
8696
9819
  projectRoot: deps2.projectRoot,
@@ -8703,23 +9826,28 @@ function setupWebUICodebaseIndexing(deps2) {
8703
9826
  }
8704
9827
  return {
8705
9828
  onFileWritten(filePath) {
8706
- if (idx.onEdit) enqueueFile(filePath);
9829
+ const abs = path18.isAbsolute(filePath) ? path18.normalize(filePath) : path18.resolve(deps2.projectRoot, filePath);
9830
+ deps2.events?.emit("file.activity", {
9831
+ filePath: abs,
9832
+ operation: "write",
9833
+ phase: "completed",
9834
+ source: "editor",
9835
+ at: Date.now(),
9836
+ sessionId: deps2.context.session?.id,
9837
+ agentId: "webui-editor",
9838
+ agentName: "WebUI Editor"
9839
+ });
9840
+ if (idx?.onEdit) enqueueFile(abs);
8707
9841
  },
8708
9842
  dispose() {
8709
9843
  try {
8710
9844
  watcher?.close();
8711
9845
  } catch {
8712
9846
  }
8713
- cancelPendingReindexes();
8714
- shutdownCodebaseIndexHost();
8715
- }
8716
- };
8717
- }
8718
- function noopIndexing() {
8719
- return {
8720
- onFileWritten() {
8721
- },
8722
- dispose() {
9847
+ if (idx) {
9848
+ cancelPendingReindexes();
9849
+ void shutdownCodebaseIndexHost();
9850
+ }
8723
9851
  }
8724
9852
  };
8725
9853
  }
@@ -8727,16 +9855,16 @@ function isIgnored(rel) {
8727
9855
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
8728
9856
  }
8729
9857
  function isInside2(root, target) {
8730
- const normalizedRoot = path16.resolve(root);
8731
- const normalizedTarget = path16.resolve(target);
8732
- return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path16.sep);
9858
+ const normalizedRoot = path18.resolve(root);
9859
+ const normalizedTarget = path18.resolve(target);
9860
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path18.sep);
8733
9861
  }
8734
9862
 
8735
9863
  // src/server/discover-mailbox-bridge.ts
8736
9864
  import { spawn as spawn3 } from "node:child_process";
8737
9865
  import { createRequire as createRequire3 } from "node:module";
8738
- import { existsSync } from "node:fs";
8739
- import { dirname as dirname7, join as join11 } from "node:path";
9866
+ import { existsSync as existsSync2 } from "node:fs";
9867
+ import { dirname as dirname8, join as join12 } from "node:path";
8740
9868
  import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
8741
9869
  import { readLiveLock } from "@wrongstack/core/coordination";
8742
9870
  var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
@@ -8864,16 +9992,16 @@ function mailboxServeInvocation(projectRoot) {
8864
9992
  function findWorkspaceCliEntry(projectRoot) {
8865
9993
  let dir = projectRoot;
8866
9994
  for (let i = 0; i < 6; i++) {
8867
- const candidate = join11(dir, "packages", "cli", "dist", "index.js");
8868
- if (existsSync(candidate)) return candidate;
8869
- const parent = dirname7(dir);
9995
+ const candidate = join12(dir, "packages", "cli", "dist", "index.js");
9996
+ if (existsSync2(candidate)) return candidate;
9997
+ const parent = dirname8(dir);
8870
9998
  if (parent === dir) return null;
8871
9999
  dir = parent;
8872
10000
  }
8873
10001
  return null;
8874
10002
  }
8875
10003
  function sleep(ms) {
8876
- return new Promise((resolve10) => setTimeout(resolve10, ms));
10004
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
8877
10005
  }
8878
10006
 
8879
10007
  // src/server/terminal-ws-handler.ts
@@ -8885,6 +10013,9 @@ var DEFAULT_COLS = 80;
8885
10013
  var DEFAULT_ROWS = 24;
8886
10014
  var requireFromHere = createRequire4(import.meta.url);
8887
10015
  var cachedNodePty;
10016
+ function resolveTerminalShell(platform = process.platform, env = process.env) {
10017
+ return platform === "win32" ? env.COMSPEC || "cmd.exe" : env.SHELL || "/bin/sh";
10018
+ }
8888
10019
  var TerminalWebSocketHandler = class {
8889
10020
  constructor(getCwd, logger, loadNodePty = defaultLoadNodePty, killProcessTree = defaultKillProcessTree) {
8890
10021
  this.getCwd = getCwd;
@@ -8940,7 +10071,7 @@ var TerminalWebSocketHandler = class {
8940
10071
  });
8941
10072
  return;
8942
10073
  }
8943
- const shell = process.platform === "win32" ? process.env.COMSPEC || "cmd.exe" : process.env.SHELL || "/bin/bash";
10074
+ const shell = resolveTerminalShell();
8944
10075
  const nodePty = this.loadNodePty();
8945
10076
  if (!nodePty) {
8946
10077
  const msg = "Integrated terminal unavailable: optional dependency node-pty is not installed. Install node-pty to enable WebUI terminal sessions.";
@@ -9128,7 +10259,8 @@ async function createAgentServices(input) {
9128
10259
  config,
9129
10260
  context,
9130
10261
  projectRoot,
9131
- logger
10262
+ logger,
10263
+ events
9132
10264
  });
9133
10265
  const compactor = createStrategyCompactor({
9134
10266
  strategy: config.context?.strategy,
@@ -9259,13 +10391,17 @@ async function createAgentServices(input) {
9259
10391
  toolExecutor
9260
10392
  });
9261
10393
  if (config.features.memory && config.features.memoryConsolidation !== false) {
9262
- agent.extensions.register(new SessionMemoryConsolidator({ memoryStore }));
10394
+ const consSuperMemory = typeof memoryStore["rememberSuper"] === "function" ? memoryStore : void 0;
10395
+ agent.extensions.register(new SessionMemoryConsolidator({
10396
+ memoryStore,
10397
+ ...consSuperMemory ? { superMemory: consSuperMemory } : {}
10398
+ }));
9263
10399
  }
9264
10400
  console.log("[WebUI] Agent initialized");
9265
10401
  const brainCfg = resolveBrainConfigDefaults(config.brain, {
9266
10402
  fallbackModels: config.fallbackModels
9267
10403
  });
9268
- const brainLedgerPath = join12(wpaths.projectDir, "brain-ledger.jsonl");
10404
+ const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
9269
10405
  let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
9270
10406
  let brainLedger;
9271
10407
  const startBrainLedger = () => {
@@ -9375,7 +10511,7 @@ async function createAgentServices(input) {
9375
10511
  });
9376
10512
  brainMonitor.start();
9377
10513
  console.log("[WebUI] Brain initialized (tiered policy \u2192 LLM, monitor active)");
9378
- const autoPhaseHandler = new AutoPhaseWebSocketHandler(
10514
+ const goalHandler = new GoalWebSocketHandler(
9379
10515
  agent,
9380
10516
  context,
9381
10517
  logger,
@@ -9447,7 +10583,7 @@ async function createAgentServices(input) {
9447
10583
  return brainLedger;
9448
10584
  },
9449
10585
  codebaseIndexing,
9450
- autoPhaseHandler,
10586
+ goalHandler,
9451
10587
  specsHandler,
9452
10588
  sddBoardHandler,
9453
10589
  sddWizardHandler,
@@ -9465,6 +10601,7 @@ function isSuperMemoryRetriever(memoryStore) {
9465
10601
  // src/server/pending-confirms.ts
9466
10602
  function resolveYoloEligiblePendingConfirms(pendingConfirms) {
9467
10603
  for (const [id, confirm] of pendingConfirms) {
10604
+ if (confirm.boundaryReason) continue;
9468
10605
  pendingConfirms.delete(id);
9469
10606
  confirm.resolve("yes");
9470
10607
  }
@@ -9520,6 +10657,9 @@ function createConnectionHandler(opts) {
9520
10657
  }
9521
10658
  void opts.sessionStartPayload().then(async (payload) => {
9522
10659
  const enriched = { ...payload };
10660
+ if (typeof opts.context.lastRequestTokens === "number" && opts.context.lastRequestTokens > 0) {
10661
+ enriched.lastInputTokens = opts.context.lastRequestTokens;
10662
+ }
9523
10663
  try {
9524
10664
  const replay = await opts.loadReplay?.();
9525
10665
  const live = replay?.messages ?? opts.context.messages ?? [];
@@ -9549,7 +10689,7 @@ function createConnectionHandler(opts) {
9549
10689
  })
9550
10690
  );
9551
10691
  });
9552
- opts.autoPhaseHandler.addClient(ws);
10692
+ opts.goalHandler.addClient(ws);
9553
10693
  opts.specsHandler.addClient(ws);
9554
10694
  opts.sddBoardHandler.addClient(ws);
9555
10695
  opts.sddWizardHandler.addClient(ws);
@@ -9567,8 +10707,21 @@ function createConnectionHandler(opts) {
9567
10707
  });
9568
10708
  return;
9569
10709
  }
10710
+ let rawObj;
10711
+ try {
10712
+ rawObj = JSON.parse(data.toString());
10713
+ } catch (err) {
10714
+ console.error(
10715
+ JSON.stringify({
10716
+ level: "error",
10717
+ event: "webui.ws_message_parse_failed",
10718
+ message: err instanceof Error ? err.message : String(err),
10719
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10720
+ })
10721
+ );
10722
+ return;
10723
+ }
9570
10724
  try {
9571
- const rawObj = JSON.parse(data.toString());
9572
10725
  if (typeof rawObj === "object" && rawObj !== null) {
9573
10726
  const obj = rawObj;
9574
10727
  if (Object.hasOwn(obj, "__proto__") || Object.hasOwn(obj, "constructor") || Object.hasOwn(obj, "prototype")) {
@@ -9586,7 +10739,7 @@ function createConnectionHandler(opts) {
9586
10739
  console.error(
9587
10740
  JSON.stringify({
9588
10741
  level: "error",
9589
- event: "webui.ws_message_parse_failed",
10742
+ event: "webui.ws_message_handler_failed",
9590
10743
  message: err instanceof Error ? err.message : String(err),
9591
10744
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
9592
10745
  })
@@ -9621,7 +10774,12 @@ function createConnectionHandler(opts) {
9621
10774
  }
9622
10775
 
9623
10776
  // src/server/message-dispatcher.ts
9624
- import path17 from "node:path";
10777
+ import path19 from "node:path";
10778
+ import {
10779
+ ChronicleQueryEngine,
10780
+ resolveWstackPaths as resolveWstackPaths2
10781
+ } from "@wrongstack/core";
10782
+ import * as os2 from "node:os";
9625
10783
  import {
9626
10784
  buildUserContentBlocks,
9627
10785
  IncomingImageError,
@@ -9634,9 +10792,9 @@ import {
9634
10792
  VisionUrlBlockedError
9635
10793
  } from "@wrongstack/runtime/vision";
9636
10794
 
9637
- // src/server/autophase-routes.ts
9638
- async function handleAutoPhaseRoute(_ws, msg, handlers) {
9639
- if (!msg.type.startsWith("autophase.")) return false;
10795
+ // src/server/goal-routes.ts
10796
+ async function handleGoalRoute(_ws, msg, handlers) {
10797
+ if (!msg.type.startsWith("goal.")) return false;
9640
10798
  await handlers.handleMessage(msg);
9641
10799
  return true;
9642
10800
  }
@@ -9672,9 +10830,9 @@ async function handleGoalGet(projectRoot, broadcast2) {
9672
10830
  const { readFile: readFile10 } = await import("node:fs/promises");
9673
10831
  const raw = await readFile10(goalPath, "utf8");
9674
10832
  const goal = JSON.parse(raw);
9675
- broadcast2({ type: "goal.updated", payload: goal });
10833
+ broadcast2({ type: "goal-state.updated", payload: goal });
9676
10834
  } catch {
9677
- broadcast2({ type: "goal.updated", payload: null });
10835
+ broadcast2({ type: "goal-state.updated", payload: null });
9678
10836
  }
9679
10837
  }
9680
10838
 
@@ -9923,18 +11081,20 @@ import {
9923
11081
  createBoard,
9924
11082
  duplicateBoard,
9925
11083
  exportBoardToTaskGraph,
9926
- generateBoardFromDescription,
9927
- getBoard,
11084
+ createBoardFromText,
11085
+ getBoard as getBoard2,
9928
11086
  getKanbanOrchestrationSnapshot,
9929
11087
  getKanbanQueueHealth,
9930
11088
  getTask,
9931
11089
  getTaskChain,
9932
11090
  listBoards,
9933
11091
  listReadyTasks,
11092
+ listTaskActivity,
9934
11093
  mergeTasks,
9935
11094
  moveTask,
9936
11095
  parseLinesIntoTasks,
9937
11096
  reconcileKanbanBoard,
11097
+ recordTaskActivity,
9938
11098
  recoverStaleTaskAssignments,
9939
11099
  releaseTaskClaim,
9940
11100
  removeBoard,
@@ -9943,13 +11103,41 @@ import {
9943
11103
  setTaskChain,
9944
11104
  splitTask,
9945
11105
  syncBoardFromTaskGraph,
11106
+ touchKanbanPresence,
9946
11107
  transferTaskToBoard,
11108
+ transitionTask,
9947
11109
  updateBoard,
9948
11110
  updateCheckOnTask,
9949
11111
  updateGoalMetricOnTask,
9950
11112
  updateTask
9951
11113
  } from "@wrongstack/kanban";
9952
11114
  import { applySessionKanbanTaskToSource } from "@wrongstack/tools/session-kanban";
11115
+ function paginateKanbanBoards(boards, input) {
11116
+ const pageSize = Math.min(100, Math.max(1, Math.floor(input.pageSize)));
11117
+ const activeSessionIds = new Set(input.activeSessionIds ?? []);
11118
+ const isActive = (board) => board.presence?.some((entry) => entry.active) === true || board.tags?.some(
11119
+ (tag) => tag.startsWith("session:") && activeSessionIds.has(tag.slice(8))
11120
+ ) === true;
11121
+ const sorted = [...boards].sort((left, right) => {
11122
+ const activityOrder = Number(isActive(right)) - Number(isActive(left));
11123
+ return activityOrder || right.updatedAt.localeCompare(left.updatedAt);
11124
+ });
11125
+ const activeTotal = sorted.filter(isActive).length;
11126
+ const total = sorted.length;
11127
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
11128
+ const requestedPage = Number.isFinite(input.page) ? Math.floor(input.page) : 1;
11129
+ const page = Math.min(totalPages, Math.max(1, requestedPage));
11130
+ const start = (page - 1) * pageSize;
11131
+ return {
11132
+ items: sorted.slice(start, start + pageSize),
11133
+ total,
11134
+ page,
11135
+ pageSize,
11136
+ totalPages,
11137
+ activeTotal,
11138
+ orphanedTotal: total - activeTotal
11139
+ };
11140
+ }
9953
11141
  async function syncSessionSource(ctx, task, remove = false) {
9954
11142
  if (!ctx.context) return;
9955
11143
  const update = await applySessionKanbanTaskToSource(ctx.context, task, { remove });
@@ -9970,22 +11158,62 @@ function fail(ws, type, message) {
9970
11158
  function has(payload, key) {
9971
11159
  return payload !== void 0 && Object.hasOwn(payload, key);
9972
11160
  }
11161
+ function activityContext(ctx, actor, note) {
11162
+ const sessionId = ctx.context?.session?.id;
11163
+ return {
11164
+ ...sessionId ? { sessionId } : {},
11165
+ ...actor ? { actor } : {},
11166
+ ...note?.trim() ? { note: note.trim() } : {}
11167
+ };
11168
+ }
11169
+ async function touchTaskPresence(ctx, boardId, taskId) {
11170
+ const context = ctx.context;
11171
+ const sessionId = context?.session?.id;
11172
+ if (!context || !sessionId) return null;
11173
+ try {
11174
+ return await touchKanbanPresence(ctx.projectRoot, boardId, {
11175
+ sessionId,
11176
+ agentId: context.agentId || "webui",
11177
+ agentName: context.agentName || context.agentId || "WebUI",
11178
+ taskId
11179
+ });
11180
+ } catch {
11181
+ return null;
11182
+ }
11183
+ }
9973
11184
  async function handleKanbanRoute(ws, msg, ctx) {
9974
11185
  if (!msg.type.startsWith("kanban.")) return false;
9975
11186
  const payload = msg.payload;
9976
11187
  const type = msg.type;
9977
11188
  try {
9978
11189
  switch (type) {
9979
- case "kanban.list":
9980
- ok(ws, type, await listBoards(ctx.projectRoot));
11190
+ case "kanban.list": {
11191
+ const boards = await listBoards(ctx.projectRoot);
11192
+ const requestedPage = Number(payload?.page);
11193
+ const requestedPageSize = Number(payload?.pageSize);
11194
+ if (!Number.isFinite(requestedPage) || !Number.isFinite(requestedPageSize)) {
11195
+ ok(ws, type, boards);
11196
+ return true;
11197
+ }
11198
+ const activeSessionIds = Array.isArray(payload?.activeSessionIds) ? payload.activeSessionIds.filter((id) => typeof id === "string") : [];
11199
+ ok(
11200
+ ws,
11201
+ type,
11202
+ paginateKanbanBoards(boards, {
11203
+ page: requestedPage,
11204
+ pageSize: requestedPageSize,
11205
+ activeSessionIds
11206
+ })
11207
+ );
9981
11208
  return true;
11209
+ }
9982
11210
  case "kanban.get": {
9983
11211
  const boardId = payload?.boardId;
9984
11212
  if (!boardId) {
9985
11213
  fail(ws, type, "boardId required");
9986
11214
  return true;
9987
11215
  }
9988
- const board = await getBoard(ctx.projectRoot, boardId);
11216
+ const board = await getBoard2(ctx.projectRoot, boardId);
9989
11217
  board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
9990
11218
  return true;
9991
11219
  }
@@ -10005,7 +11233,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10005
11233
  fail(ws, type, "boardId required");
10006
11234
  return true;
10007
11235
  }
10008
- const board = await getBoard(ctx.projectRoot, boardId);
11236
+ const board = await getBoard2(ctx.projectRoot, boardId);
10009
11237
  if (!board) {
10010
11238
  fail(ws, type, `Board not found: ${boardId}`);
10011
11239
  return true;
@@ -10043,7 +11271,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
10043
11271
  title,
10044
11272
  ...payload?.description ? { description: payload.description } : {},
10045
11273
  ...payload?.tags ? { tags: payload.tags } : {},
10046
- ...payload?.columns ? { columns: payload.columns } : {}
11274
+ ...payload?.columns ? { columns: payload.columns } : {},
11275
+ ...has(payload, "lifecycle") ? { lifecycle: payload?.lifecycle } : {},
11276
+ ...has(payload, "boundary") ? { boundary: payload?.boundary } : {}
10047
11277
  })
10048
11278
  );
10049
11279
  return true;
@@ -10059,8 +11289,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
10059
11289
  ...payload?.description ? { description: payload.description } : {},
10060
11290
  ...payload?.tags ? { tags: payload.tags } : {},
10061
11291
  ...payload?.columns ? { columns: payload.columns } : {},
11292
+ ...has(payload, "lifecycle") ? {
11293
+ lifecycle: payload?.lifecycle ?? null
11294
+ } : {},
10062
11295
  ...has(payload, "supervisor") ? {
10063
11296
  supervisor: payload?.supervisor ?? null
11297
+ } : {},
11298
+ ...has(payload, "boundary") ? {
11299
+ boundary: payload?.boundary ?? null
10064
11300
  } : {}
10065
11301
  });
10066
11302
  board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
@@ -10087,7 +11323,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10087
11323
  fail(ws, type, "boardId required");
10088
11324
  return true;
10089
11325
  }
10090
- const board = await getBoard(ctx.projectRoot, boardId);
11326
+ const board = await getBoard2(ctx.projectRoot, boardId);
10091
11327
  const activeSessionId = ctx.context?.session?.id;
10092
11328
  if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
10093
11329
  fail(ws, type, "The active session Kanban board cannot be deleted.");
@@ -10107,7 +11343,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10107
11343
  }
10108
11344
  const board = await createBoard(
10109
11345
  ctx.projectRoot,
10110
- generateBoardFromDescription({
11346
+ createBoardFromText({
10111
11347
  description,
10112
11348
  ...payload?.title ? { title: payload.title } : {},
10113
11349
  ...payload?.context ? { context: payload.context } : {}
@@ -10119,7 +11355,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10119
11355
  )) {
10120
11356
  await addTask(ctx.projectRoot, board.id, taskInput);
10121
11357
  }
10122
- ok(ws, type, await getBoard(ctx.projectRoot, board.id) ?? board);
11358
+ ok(ws, type, await getBoard2(ctx.projectRoot, board.id) ?? board);
10123
11359
  return true;
10124
11360
  }
10125
11361
  case "kanban.task.ready": {
@@ -10209,14 +11445,21 @@ async function handleKanbanRoute(ws, msg, ctx) {
10209
11445
  fail(ws, type, "boardId and title required");
10210
11446
  return true;
10211
11447
  }
10212
- const result = await addTask(ctx.projectRoot, boardId, {
10213
- title,
10214
- columnId: payload?.columnId ?? "backlog",
10215
- ...payload?.description ? { description: payload.description } : {},
10216
- ...payload?.priority ? { priority: payload.priority } : {},
10217
- ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
10218
- ...payload?.labels ? { labels: payload.labels } : {}
10219
- });
11448
+ const result = await addTask(
11449
+ ctx.projectRoot,
11450
+ boardId,
11451
+ {
11452
+ title,
11453
+ columnId: payload?.columnId ?? "backlog",
11454
+ ...payload?.description ? { description: payload.description } : {},
11455
+ ...payload?.dueDate ? { dueDate: payload.dueDate } : {},
11456
+ ...payload?.priority ? { priority: payload.priority } : {},
11457
+ ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
11458
+ ...payload?.labels ? { labels: payload.labels } : {},
11459
+ ...has(payload, "boundary") ? { boundary: payload?.boundary } : {}
11460
+ },
11461
+ activityContext(ctx, "webui", payload?.activityNote)
11462
+ );
10220
11463
  result ? ok(ws, type, result.task) : fail(ws, type, `Board not found: ${boardId}`);
10221
11464
  return true;
10222
11465
  }
@@ -10272,25 +11515,35 @@ async function handleKanbanRoute(ws, msg, ctx) {
10272
11515
  fail(ws, type, "boardId and taskId required");
10273
11516
  return true;
10274
11517
  }
10275
- const board = await updateTask(ctx.projectRoot, boardId, taskId, {
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
- } : {}
10293
- });
11518
+ const board = await updateTask(
11519
+ ctx.projectRoot,
11520
+ boardId,
11521
+ taskId,
11522
+ {
11523
+ ...has(payload, "title") ? { title: payload?.title } : {},
11524
+ ...has(payload, "description") ? { description: payload?.description ?? "" } : {},
11525
+ ...has(payload, "dueDate") ? { dueDate: payload?.dueDate ?? null } : {},
11526
+ ...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
11527
+ ...has(payload, "priority") ? { priority: payload?.priority } : {},
11528
+ ...has(payload, "type") ? { type: payload?.type } : {},
11529
+ ...has(payload, "status") ? { status: payload?.status } : {},
11530
+ ...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
11531
+ ...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
11532
+ ...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
11533
+ ...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
11534
+ ...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
11535
+ ...has(payload, "retryPolicy") ? {
11536
+ retryPolicy: payload?.retryPolicy ?? null
11537
+ } : {},
11538
+ ...has(payload, "costCeilingUsd") ? {
11539
+ costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
11540
+ } : {},
11541
+ ...has(payload, "boundary") ? {
11542
+ boundary: payload?.boundary ?? null
11543
+ } : {}
11544
+ },
11545
+ activityContext(ctx, "webui", payload?.activityNote)
11546
+ );
10294
11547
  if (!board) {
10295
11548
  fail(ws, type, "Board or task not found");
10296
11549
  return true;
@@ -10300,6 +11553,32 @@ async function handleKanbanRoute(ws, msg, ctx) {
10300
11553
  ok(ws, type, task);
10301
11554
  return true;
10302
11555
  }
11556
+ case "kanban.task.transition": {
11557
+ const boardId = payload?.boardId;
11558
+ const taskId = payload?.taskId;
11559
+ const to = payload?.to;
11560
+ const actor = payload?.actor;
11561
+ const comment = payload?.comment;
11562
+ if (!boardId || !taskId || !to || !actor || !comment) {
11563
+ fail(ws, type, "boardId, taskId, to, actor, and comment required");
11564
+ return true;
11565
+ }
11566
+ const result = await transitionTask(ctx.projectRoot, boardId, taskId, {
11567
+ to,
11568
+ actor,
11569
+ comment,
11570
+ ...payload?.action ? { action: payload.action } : {},
11571
+ ...payload?.attachment ? { attachment: payload.attachment } : {},
11572
+ ...payload?.patch ? { patch: payload.patch } : {}
11573
+ });
11574
+ if (!result) {
11575
+ fail(ws, type, "Board or task not found");
11576
+ return true;
11577
+ }
11578
+ await syncSessionSource(ctx, result.task);
11579
+ ok(ws, type, result);
11580
+ return true;
11581
+ }
10303
11582
  case "kanban.task.move": {
10304
11583
  const boardId = payload?.boardId;
10305
11584
  const taskId = payload?.taskId;
@@ -10313,7 +11592,8 @@ async function handleKanbanRoute(ws, msg, ctx) {
10313
11592
  boardId,
10314
11593
  taskId,
10315
11594
  columnId,
10316
- payload?.order
11595
+ payload?.order,
11596
+ activityContext(ctx, "webui", payload?.activityNote)
10317
11597
  );
10318
11598
  if (!board) {
10319
11599
  fail(ws, type, "Move failed");
@@ -10418,14 +11698,24 @@ async function handleKanbanRoute(ws, msg, ctx) {
10418
11698
  fail(ws, type, "boardId, taskId, and name required");
10419
11699
  return true;
10420
11700
  }
10421
- const board = await addGoalMetricToTask(ctx.projectRoot, boardId, taskId, {
10422
- name: name2,
10423
- ...payload?.status ? { status: payload.status } : {},
10424
- ...payload?.target !== void 0 ? { target: payload.target } : {},
10425
- ...payload?.current !== void 0 ? { current: payload.current } : {},
10426
- ...payload?.unit ? { unit: payload.unit } : {},
10427
- ...payload?.notes ? { notes: payload.notes } : {}
10428
- });
11701
+ const board = await addGoalMetricToTask(
11702
+ ctx.projectRoot,
11703
+ boardId,
11704
+ taskId,
11705
+ {
11706
+ name: name2,
11707
+ ...payload?.status ? { status: payload.status } : {},
11708
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
11709
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
11710
+ ...payload?.unit ? { unit: payload.unit } : {},
11711
+ ...payload?.notes ? { notes: payload.notes } : {}
11712
+ },
11713
+ activityContext(
11714
+ ctx,
11715
+ "webui",
11716
+ payload?.activityNote ?? `Goal metric added: ${name2}.`
11717
+ )
11718
+ );
10429
11719
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10430
11720
  return true;
10431
11721
  }
@@ -10437,14 +11727,25 @@ async function handleKanbanRoute(ws, msg, ctx) {
10437
11727
  fail(ws, type, "boardId, taskId, and metricId required");
10438
11728
  return true;
10439
11729
  }
10440
- const board = await updateGoalMetricOnTask(ctx.projectRoot, boardId, taskId, metricId, {
10441
- ...payload?.name ? { name: payload.name } : {},
10442
- ...payload?.status ? { status: payload.status } : {},
10443
- ...payload?.target !== void 0 ? { target: payload.target } : {},
10444
- ...payload?.current !== void 0 ? { current: payload.current } : {},
10445
- ...payload?.unit ? { unit: payload.unit } : {},
10446
- ...payload?.notes ? { notes: payload.notes } : {}
10447
- });
11730
+ const board = await updateGoalMetricOnTask(
11731
+ ctx.projectRoot,
11732
+ boardId,
11733
+ taskId,
11734
+ metricId,
11735
+ {
11736
+ ...payload?.name ? { name: payload.name } : {},
11737
+ ...payload?.status ? { status: payload.status } : {},
11738
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
11739
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
11740
+ ...payload?.unit ? { unit: payload.unit } : {},
11741
+ ...payload?.notes ? { notes: payload.notes } : {}
11742
+ },
11743
+ activityContext(
11744
+ ctx,
11745
+ "webui",
11746
+ payload?.activityNote ?? "Goal metric updated in WebUI."
11747
+ )
11748
+ );
10448
11749
  board ? ok(ws, type, board) : fail(ws, type, "Metric not found");
10449
11750
  return true;
10450
11751
  }
@@ -10455,23 +11756,29 @@ async function handleKanbanRoute(ws, msg, ctx) {
10455
11756
  fail(ws, type, "boardId and taskId required");
10456
11757
  return true;
10457
11758
  }
10458
- const board = await assignTask(ctx.projectRoot, boardId, taskId, {
10459
- ...payload?.agentId ? { agentId: payload.agentId } : {},
10460
- ...payload?.name ? { name: payload.name } : {},
10461
- ...payload?.role ? { role: payload.role } : {},
10462
- ...payload?.provider ? { provider: payload.provider } : {},
10463
- ...payload?.model ? { model: payload.model } : {},
10464
- ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
10465
- ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
10466
- ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
10467
- ...payload?.skills ? { skills: payload.skills } : {},
10468
- ...payload?.tools ? { tools: payload.tools } : {},
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 } : {}
10474
- });
11759
+ const board = await assignTask(
11760
+ ctx.projectRoot,
11761
+ boardId,
11762
+ taskId,
11763
+ {
11764
+ ...payload?.agentId ? { agentId: payload.agentId } : {},
11765
+ ...payload?.name ? { name: payload.name } : {},
11766
+ ...payload?.role ? { role: payload.role } : {},
11767
+ ...payload?.provider ? { provider: payload.provider } : {},
11768
+ ...payload?.model ? { model: payload.model } : {},
11769
+ ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
11770
+ ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
11771
+ ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
11772
+ ...payload?.skills ? { skills: payload.skills } : {},
11773
+ ...payload?.tools ? { tools: payload.tools } : {},
11774
+ ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
11775
+ ...payload?.assignee ? { assignee: payload.assignee } : {},
11776
+ ...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
11777
+ ...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
11778
+ ...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
11779
+ },
11780
+ activityContext(ctx, void 0, payload?.activityNote)
11781
+ );
10475
11782
  board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
10476
11783
  return true;
10477
11784
  }
@@ -10483,11 +11790,21 @@ async function handleKanbanRoute(ws, msg, ctx) {
10483
11790
  fail(ws, type, "boardId, taskId, and description required");
10484
11791
  return true;
10485
11792
  }
10486
- const board = await addCheckToTask(ctx.projectRoot, boardId, taskId, {
10487
- description,
10488
- type: payload?.checkType ?? "manual",
10489
- status: payload?.status ?? "pending"
10490
- });
11793
+ const board = await addCheckToTask(
11794
+ ctx.projectRoot,
11795
+ boardId,
11796
+ taskId,
11797
+ {
11798
+ description,
11799
+ type: payload?.checkType ?? "manual",
11800
+ status: payload?.status ?? "pending"
11801
+ },
11802
+ activityContext(
11803
+ ctx,
11804
+ "webui",
11805
+ payload?.activityNote ?? `Acceptance check added: ${description}.`
11806
+ )
11807
+ );
10491
11808
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10492
11809
  return true;
10493
11810
  }
@@ -10499,9 +11816,20 @@ async function handleKanbanRoute(ws, msg, ctx) {
10499
11816
  fail(ws, type, "boardId, taskId, and checkId required");
10500
11817
  return true;
10501
11818
  }
10502
- const board = await updateCheckOnTask(ctx.projectRoot, boardId, taskId, checkId, {
10503
- ...has(payload, "status") ? { status: payload?.status } : {}
10504
- });
11819
+ const board = await updateCheckOnTask(
11820
+ ctx.projectRoot,
11821
+ boardId,
11822
+ taskId,
11823
+ checkId,
11824
+ {
11825
+ ...has(payload, "status") ? { status: payload?.status } : {}
11826
+ },
11827
+ activityContext(
11828
+ ctx,
11829
+ "webui",
11830
+ payload?.activityNote ?? `Acceptance check updated${payload?.status ? ` to ${String(payload.status)}` : ""}.`
11831
+ )
11832
+ );
10505
11833
  if (!board) fail(ws, type, "Check not found");
10506
11834
  else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
10507
11835
  return true;
@@ -10514,10 +11842,17 @@ async function handleKanbanRoute(ws, msg, ctx) {
10514
11842
  fail(ws, type, "boardId, taskId, and content required");
10515
11843
  return true;
10516
11844
  }
10517
- const board = await addNoteToTask(ctx.projectRoot, boardId, taskId, {
10518
- author: payload?.author ?? "webui",
10519
- content
10520
- });
11845
+ const author = payload?.author ?? "webui";
11846
+ const board = await addNoteToTask(
11847
+ ctx.projectRoot,
11848
+ boardId,
11849
+ taskId,
11850
+ {
11851
+ author,
11852
+ content
11853
+ },
11854
+ activityContext(ctx, author)
11855
+ );
10521
11856
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10522
11857
  return true;
10523
11858
  }
@@ -10560,7 +11895,62 @@ async function handleKanbanRoute(ws, msg, ctx) {
10560
11895
  return true;
10561
11896
  }
10562
11897
  const task = await getTask(ctx.projectRoot, boardId, taskId);
10563
- task ? ok(ws, type, task) : fail(ws, type, "Task not found");
11898
+ if (task) {
11899
+ await touchTaskPresence(ctx, boardId, task.id);
11900
+ ok(ws, type, task);
11901
+ } else {
11902
+ fail(ws, type, "Task not found");
11903
+ }
11904
+ return true;
11905
+ }
11906
+ case "kanban.task.activity": {
11907
+ const boardId = payload?.boardId;
11908
+ const taskId = payload?.taskId;
11909
+ if (!boardId || !taskId) {
11910
+ fail(ws, type, "boardId and taskId required");
11911
+ return true;
11912
+ }
11913
+ const presenceBoard = await touchTaskPresence(ctx, boardId, taskId);
11914
+ const events = await listTaskActivity(ctx.projectRoot, boardId, taskId, {
11915
+ ...typeof payload?.limit === "number" ? { limit: payload.limit } : {}
11916
+ });
11917
+ ok(ws, type, {
11918
+ boardId,
11919
+ taskId,
11920
+ events,
11921
+ presence: presenceBoard?.presence?.filter((entry) => entry.taskId === taskId) ?? []
11922
+ });
11923
+ return true;
11924
+ }
11925
+ case "kanban.task.activity.add": {
11926
+ const boardId = payload?.boardId;
11927
+ const taskId = payload?.taskId;
11928
+ const kind = payload?.kind;
11929
+ const summary = payload?.summary;
11930
+ const allowedKinds = ["decision", "attempt", "result", "blocker", "observation"];
11931
+ const allowedOutcomes = ["succeeded", "failed", "partial", "skipped", "unknown"];
11932
+ if (!boardId || !taskId || !summary?.trim() || !allowedKinds.includes(kind)) {
11933
+ fail(ws, type, "boardId, taskId, summary, and a valid activity kind required");
11934
+ return true;
11935
+ }
11936
+ const requestedOutcome = payload?.outcome;
11937
+ const outcome = allowedOutcomes.includes(requestedOutcome) ? requestedOutcome : "unknown";
11938
+ const board = await recordTaskActivity(
11939
+ ctx.projectRoot,
11940
+ boardId,
11941
+ taskId,
11942
+ {
11943
+ kind,
11944
+ summary: summary.trim(),
11945
+ outcome,
11946
+ ...typeof payload?.details === "string" && payload.details.trim() ? { details: payload.details.trim() } : {}
11947
+ },
11948
+ activityContext(
11949
+ ctx,
11950
+ payload?.actor ?? ctx.context?.agentId ?? "webui"
11951
+ )
11952
+ );
11953
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10564
11954
  return true;
10565
11955
  }
10566
11956
  case "kanban.column.add": {
@@ -11038,6 +12428,18 @@ async function handleSpecsRoute(_ws, msg, handlers) {
11038
12428
  }
11039
12429
 
11040
12430
  // src/server/message-dispatcher.ts
12431
+ var chronicleCache = /* @__PURE__ */ new Map();
12432
+ async function chronicleEngine(projectRoot) {
12433
+ const now = Date.now();
12434
+ const cached = chronicleCache.get(projectRoot);
12435
+ if (cached && now - cached.loadedAt < 6e4) return cached.engine;
12436
+ const paths = resolveWstackPaths2({ projectRoot, userHome: os2.homedir() });
12437
+ const engine = await ChronicleQueryEngine.fromDirectory(
12438
+ path19.join(paths.projectDir, "chronicle")
12439
+ );
12440
+ chronicleCache.set(projectRoot, { loadedAt: now, engine });
12441
+ return engine;
12442
+ }
11041
12443
  function createMessageDispatcher(opts) {
11042
12444
  const { state, deps: deps2, cb, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
11043
12445
  function makeWorklistContext() {
@@ -11049,7 +12451,8 @@ function createMessageDispatcher(opts) {
11049
12451
  state: deps2.context.state
11050
12452
  },
11051
12453
  send: (w, m) => send(w, m),
11052
- broadcast: (m) => broadcast(state.getClients(), m)
12454
+ broadcast: (m) => broadcast(state.getClients(), m),
12455
+ replaceTodos: (todos) => deps2.context.state.replaceTodos(todos)
11053
12456
  };
11054
12457
  }
11055
12458
  function makeSkillsContext() {
@@ -11058,7 +12461,7 @@ function createMessageDispatcher(opts) {
11058
12461
  skillLoader: deps2.skillLoader,
11059
12462
  skillInstaller: deps2.skillInstaller,
11060
12463
  projectRoot,
11061
- projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
12464
+ projectSkillsDir: path19.join(projectRoot, ".wrongstack", "skills"),
11062
12465
  globalSkillsDir: deps2.wpaths.globalSkills
11063
12466
  };
11064
12467
  }
@@ -11096,7 +12499,7 @@ function createMessageDispatcher(opts) {
11096
12499
  if (await handleMailboxRoute(ws, msg, routes.mailboxRoutes)) return;
11097
12500
  if (await handleMcpRoute(ws, msg, routes.mcpRoutes)) return;
11098
12501
  if (await handleBrainRoute(ws, msg, routes.brainRoutes)) return;
11099
- if (await handleAutoPhaseRoute(ws, msg, routes.autoPhaseRoutes)) return;
12502
+ if (await handleGoalRoute(ws, msg, routes.goalRoutes)) return;
11100
12503
  if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
11101
12504
  if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
11102
12505
  if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
@@ -11300,6 +12703,14 @@ function createMessageDispatcher(opts) {
11300
12703
  return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
11301
12704
  case "memory.super.remember":
11302
12705
  return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
12706
+ case "memory.super.recover":
12707
+ return handleSuperMemoryRecover(ws, msg, deps2.memoryStore);
12708
+ case "memory.super.candidateResolve":
12709
+ return handleSuperMemoryCandidateResolve(ws, msg, deps2.memoryStore);
12710
+ case "memory.super.backfillRecoverable":
12711
+ return handleSuperMemoryBackfillRecoverable(ws, msg, deps2.memoryStore);
12712
+ case "memory.super.forFile":
12713
+ return handleSuperMemoryForFile(ws, msg, deps2.memoryStore);
11303
12714
  // ── MCP tripwires — handleMcpRoute claims these upstream. ──
11304
12715
  case "mcp.list":
11305
12716
  throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
@@ -11530,6 +12941,59 @@ function createMessageDispatcher(opts) {
11530
12941
  });
11531
12942
  break;
11532
12943
  }
12944
+ // ── Chronicle journal queries (parity with embedded webui-server) ──
12945
+ // Mirrors packages/cli/src/webui-server/message-router.ts:645-664.
12946
+ // The engine is cached for 1s to avoid re-reading the journal on every
12947
+ // query; the cache is module-scoped so it survives across messages on
12948
+ // the same connection.
12949
+ case "chronicle.query": {
12950
+ const payload = msg.payload ?? {};
12951
+ const engine = await chronicleEngine(state.getProjectRoot());
12952
+ send(ws, { type: "chronicle.query_result", payload: await engine.query(payload.query ?? {}) });
12953
+ break;
12954
+ }
12955
+ case "chronicle.facet": {
12956
+ const payload = msg.payload ?? {};
12957
+ const allowed = /* @__PURE__ */ new Set([
12958
+ "eventType",
12959
+ "outcome",
12960
+ "projectId",
12961
+ "sessionId",
12962
+ "agentId",
12963
+ "taskId",
12964
+ "providerId",
12965
+ "modelId",
12966
+ "resourceKind",
12967
+ "resourcePath",
12968
+ "toolCallId"
12969
+ ]);
12970
+ if (!payload.field || !allowed.has(payload.field)) {
12971
+ send(ws, {
12972
+ type: "chronicle.error",
12973
+ payload: { message: "Invalid Chronicle facet field." }
12974
+ });
12975
+ break;
12976
+ }
12977
+ const engine = await chronicleEngine(state.getProjectRoot());
12978
+ send(ws, {
12979
+ type: "chronicle.facet_result",
12980
+ payload: {
12981
+ field: payload.field,
12982
+ values: await engine.facet(payload.field, payload.query ?? {}, payload.limit),
12983
+ diagnostics: engine.diagnostics
12984
+ }
12985
+ });
12986
+ break;
12987
+ }
12988
+ case "chronicle.graph": {
12989
+ const payload = msg.payload ?? {};
12990
+ const engine = await chronicleEngine(state.getProjectRoot());
12991
+ send(ws, {
12992
+ type: "chronicle.graph_result",
12993
+ payload: await engine.graph(payload.seed ?? {}, payload.hops, payload.maxNodes)
12994
+ });
12995
+ break;
12996
+ }
11533
12997
  case "process.list": {
11534
12998
  await handleProcessList(ws);
11535
12999
  break;
@@ -11547,7 +13011,7 @@ function createMessageDispatcher(opts) {
11547
13011
  process.kill(process.pid, "SIGINT");
11548
13012
  break;
11549
13013
  }
11550
- case "goal.get": {
13014
+ case "goal-state.get": {
11551
13015
  await handleGoalGet(state.getProjectRoot(), (m) => broadcast(state.getClients(), m));
11552
13016
  break;
11553
13017
  }
@@ -11581,9 +13045,9 @@ function createMessageDispatcher(opts) {
11581
13045
  }
11582
13046
 
11583
13047
  // src/server/pref-helpers.ts
11584
- import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
13048
+ import * as fs14 from "node:fs/promises";
11585
13049
  import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
11586
- import * as fs13 from "node:fs/promises";
13050
+ import { atomicWrite as atomicWrite6, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
11587
13051
  var PREF_KEYS = [
11588
13052
  "autonomy",
11589
13053
  "autonomyDelayMs",
@@ -11628,6 +13092,7 @@ var PREF_KEYS = [
11628
13092
  "fallbackProfiles",
11629
13093
  "favoriteModels",
11630
13094
  "favoriteModelsOnly",
13095
+ "modelAvailabilitySchedule",
11631
13096
  "modelMatrix",
11632
13097
  "fallbackAuto",
11633
13098
  // Refiner + TUI visual prefs (parity with the CLI's embedded server —
@@ -11638,11 +13103,31 @@ var PREF_KEYS = [
11638
13103
  "thinkingWord",
11639
13104
  "statuslineMode",
11640
13105
  "animationStyle",
13106
+ "showModelReasoning",
11641
13107
  // Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
11642
13108
  "breakerEnabled",
11643
13109
  "breakerAutoKillResetMs",
11644
13110
  "fsAccess",
11645
- "debugStream"
13111
+ "debugStream",
13112
+ // Chimera (post-session) + auto-review (mid-session) settings.
13113
+ // Persisted to config.extensions['wstack-chimera'] / ['wstack-auto-review']
13114
+ // so the running plugins pick up changes after a session restart.
13115
+ "chimeraEnabled",
13116
+ "chimeraProvider",
13117
+ "chimeraModel",
13118
+ "chimeraMaxFiles",
13119
+ "chimeraAutoFix",
13120
+ "autoReviewEnabled",
13121
+ "autoReviewProvider",
13122
+ "autoReviewModel",
13123
+ "autoReviewFallbackProfile",
13124
+ "autoReviewFallbackModels",
13125
+ "autoReviewDebounceMs",
13126
+ "autoReviewMaxFilesPerBatch",
13127
+ "autoReviewMaxConcurrentReviews",
13128
+ "autoReviewCascadeOn",
13129
+ // Per-plugin enable/disable map (parity with the embedded server).
13130
+ "pluginsEnabled"
11646
13131
  ];
11647
13132
  function prefSnapshot(contextMeta) {
11648
13133
  const snapshot = {};
@@ -11656,7 +13141,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
11656
13141
  const write = async () => {
11657
13142
  let raw;
11658
13143
  try {
11659
- raw = await fs13.readFile(globalConfigPath, "utf8");
13144
+ raw = await fs14.readFile(globalConfigPath, "utf8");
11660
13145
  } catch {
11661
13146
  raw = "{}";
11662
13147
  }
@@ -11680,219 +13165,264 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
11680
13165
  try {
11681
13166
  await next;
11682
13167
  } catch (err) {
11683
- logger.warn(`${errorLabel}: failed to persist to config: ${err instanceof Error ? err.message : String(err)}`);
13168
+ logger.warn(
13169
+ `${errorLabel}: failed to persist to config: ${err instanceof Error ? err.message : String(err)}`
13170
+ );
11684
13171
  }
11685
13172
  }
11686
13173
  async function persistPrefsToConfig(deps2, holder, payload) {
11687
- return updateGlobalConfig(deps2, holder, (decrypted) => {
11688
- const autonomyCfg = decrypted.autonomy ?? {};
11689
- let autonomyTouched = false;
11690
- const setAutonomy = (key, val) => {
11691
- autonomyCfg[key] = val;
11692
- autonomyTouched = true;
11693
- };
11694
- if (typeof payload["autonomy"] === "string" && ["off", "suggest", "auto"].includes(payload["autonomy"])) {
11695
- setAutonomy("defaultMode", payload["autonomy"]);
11696
- }
11697
- if (typeof payload["autonomyDelayMs"] === "number")
11698
- setAutonomy("autoProceedDelayMs", payload["autonomyDelayMs"]);
11699
- if (typeof payload["autoProceedMaxIterations"] === "number")
11700
- setAutonomy("autoProceedMaxIterations", payload["autoProceedMaxIterations"]);
11701
- if (typeof payload["yolo"] === "boolean") {
11702
- setAutonomy("yolo", payload["yolo"]);
11703
- decrypted.yolo = payload["yolo"];
11704
- }
11705
- if (typeof payload["chime"] === "boolean") setAutonomy("chime", payload["chime"]);
11706
- if (typeof payload["confirmExit"] === "boolean")
11707
- setAutonomy("confirmExit", payload["confirmExit"]);
11708
- if (typeof payload["streamFleet"] === "boolean")
11709
- setAutonomy("streamFleet", payload["streamFleet"]);
11710
- if (typeof payload["enhanceEnabled"] === "boolean")
11711
- setAutonomy("enhance", payload["enhanceEnabled"]);
11712
- if (typeof payload["enhanceDelayMs"] === "number")
11713
- setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
11714
- if (typeof payload["enhanceLanguage"] === "string")
11715
- setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
11716
- if (typeof payload["refinerProvider"] === "string")
11717
- setAutonomy("refinerProvider", payload["refinerProvider"]);
11718
- if (typeof payload["refinerModel"] === "string")
11719
- setAutonomy("refinerModel", payload["refinerModel"]);
11720
- if (typeof payload["refinerFallbackProfile"] === "string")
11721
- setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
11722
- if (typeof payload["thinkingWord"] === "string")
11723
- setAutonomy("thinkingWord", payload["thinkingWord"]);
11724
- if (typeof payload["statuslineMode"] === "string")
11725
- setAutonomy("statuslineMode", payload["statuslineMode"]);
11726
- if (typeof payload["animationStyle"] === "string")
11727
- setAutonomy("animationStyle", payload["animationStyle"]);
11728
- if (autonomyTouched) decrypted.autonomy = autonomyCfg;
11729
- if (typeof payload["nextPrediction"] === "boolean")
11730
- decrypted.nextPrediction = payload["nextPrediction"];
11731
- if (typeof payload["uiLocale"] === "string") decrypted.uiLocale = payload["uiLocale"];
11732
- if (Array.isArray(payload["fallbackModels"]))
11733
- decrypted.fallbackModels = payload["fallbackModels"];
11734
- if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"])) {
11735
- decrypted.fallbackProfiles = payload["fallbackProfiles"];
11736
- }
11737
- if (Array.isArray(payload["favoriteModels"]))
11738
- decrypted.favoriteModels = payload["favoriteModels"];
11739
- if (typeof payload["favoriteModelsOnly"] === "boolean")
11740
- decrypted.favoriteModelsOnly = payload["favoriteModelsOnly"];
11741
- if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"])) {
11742
- decrypted.modelMatrix = payload["modelMatrix"];
11743
- }
11744
- if (typeof payload["fallbackAuto"] === "boolean")
11745
- decrypted.fallbackAuto = payload["fallbackAuto"];
11746
- const FEATURE_MAP = {
11747
- featureMcp: "mcp",
11748
- featurePlugins: "plugins",
11749
- featureMemory: "memory",
11750
- featureSkills: "skills",
11751
- featureModelsRegistry: "modelsRegistry"
11752
- };
11753
- for (const [prefKey, cfgKey] of Object.entries(FEATURE_MAP)) {
11754
- if (typeof payload[prefKey] === "boolean") {
11755
- const feats = decrypted.features ?? {};
11756
- feats[cfgKey] = payload[prefKey];
11757
- decrypted.features = feats;
11758
- }
11759
- }
11760
- if (typeof payload["contextAutoCompact"] === "boolean" || typeof payload["contextStrategy"] === "string" || typeof payload["contextMode"] === "string") {
11761
- const ctxCfg = decrypted.context ?? {};
11762
- if (typeof payload["contextAutoCompact"] === "boolean")
11763
- ctxCfg.autoCompact = payload["contextAutoCompact"];
11764
- if (typeof payload["contextStrategy"] === "string")
11765
- ctxCfg.strategy = payload["contextStrategy"];
11766
- if (typeof payload["contextMode"] === "string") ctxCfg.mode = payload["contextMode"];
11767
- decrypted.context = ctxCfg;
11768
- }
11769
- if (typeof payload["tokenSavingTier"] === "string") {
11770
- const featsCfg = decrypted.features ?? {};
11771
- featsCfg.tokenSavingMode = payload["tokenSavingTier"];
11772
- decrypted.features = featsCfg;
11773
- }
11774
- if (typeof payload["maxConcurrent"] === "number") {
11775
- decrypted.maxConcurrent = payload["maxConcurrent"];
11776
- }
11777
- if (typeof payload["titleAnimation"] === "boolean") {
11778
- const autoCfg = decrypted.autonomy ?? {};
11779
- autoCfg.terminalTitleAnimation = payload["titleAnimation"];
11780
- decrypted.autonomy = autoCfg;
11781
- }
11782
- if (typeof payload["logLevel"] === "string") {
11783
- const logCfg = decrypted.log ?? {};
11784
- logCfg.level = payload["logLevel"];
11785
- decrypted.log = logCfg;
11786
- }
11787
- if (typeof payload["auditLevel"] === "string") {
11788
- const sessionCfg = decrypted.session ?? {};
11789
- sessionCfg.auditLevel = payload["auditLevel"];
11790
- decrypted.session = sessionCfg;
11791
- }
11792
- if (typeof payload["indexOnStart"] === "boolean") {
11793
- const indexingCfg = decrypted.indexing ?? {};
11794
- indexingCfg.onSessionStart = payload["indexOnStart"];
11795
- decrypted.indexing = indexingCfg;
11796
- }
11797
- if (typeof payload["maxIterations"] === "number") {
11798
- const toolsCfg = decrypted.tools ?? {};
11799
- toolsCfg.maxIterations = payload["maxIterations"];
11800
- decrypted.tools = toolsCfg;
11801
- }
11802
- const hqTouched = typeof payload["hqEnabled"] === "boolean" || typeof payload["hqUrl"] === "string" || typeof payload["hqToken"] === "string" || typeof payload["hqRawContent"] === "boolean";
11803
- if (hqTouched) {
11804
- const hqCfg = decrypted.hq ?? {};
11805
- if (typeof payload["hqEnabled"] === "boolean") hqCfg.enabled = payload["hqEnabled"];
11806
- if (typeof payload["hqUrl"] === "string") hqCfg.url = payload["hqUrl"];
11807
- if (typeof payload["hqToken"] === "string") hqCfg.token = payload["hqToken"];
11808
- if (typeof payload["hqRawContent"] === "boolean")
11809
- hqCfg.rawContent = payload["hqRawContent"];
11810
- decrypted.hq = hqCfg;
11811
- }
11812
- const tgTouched = typeof payload["tgSessionEnd"] === "boolean" || typeof payload["tgDelegate"] === "boolean" || typeof payload["tgLongToolMs"] === "number";
11813
- if (tgTouched) {
11814
- const ext = decrypted.extensions ?? {};
11815
- const tg = ext["telegram"] ?? {};
11816
- if (typeof payload["tgSessionEnd"] === "boolean") {
11817
- tg["notifyOnSessionEnd"] = payload["tgSessionEnd"];
11818
- }
11819
- if (typeof payload["tgDelegate"] === "boolean") {
11820
- tg["notifyOnDelegate"] = payload["tgDelegate"];
11821
- }
11822
- if (typeof payload["tgLongToolMs"] === "number") {
11823
- tg["longToolThresholdMs"] = payload["tgLongToolMs"];
11824
- }
11825
- ext["telegram"] = tg;
11826
- decrypted.extensions = ext;
11827
- }
11828
- const modelRuntimeTouched = typeof payload["reasoningMode"] === "string" || typeof payload["reasoningEffort"] === "string" || typeof payload["reasoningPreserve"] === "boolean" || typeof payload["cacheTtl"] === "string";
11829
- if (modelRuntimeTouched) {
11830
- const mr = decrypted.modelRuntime ?? {};
11831
- const reasoning = mr.reasoning ?? {};
11832
- if (typeof payload["reasoningMode"] === "string") reasoning.mode = payload["reasoningMode"];
11833
- if (typeof payload["reasoningEffort"] === "string")
11834
- reasoning.effort = payload["reasoningEffort"];
11835
- if (typeof payload["reasoningPreserve"] === "boolean")
11836
- reasoning.preserve = payload["reasoningPreserve"];
11837
- mr.reasoning = reasoning;
11838
- if (typeof payload["cacheTtl"] === "string" && payload["cacheTtl"] !== "default") {
11839
- mr.cache = { ttl: payload["cacheTtl"] };
11840
- } else if (payload["cacheTtl"] === "default") {
11841
- delete mr.cache;
11842
- }
11843
- decrypted.modelRuntime = mr;
11844
- }
11845
- if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
11846
- const cb = decrypted.circuitBreaker ?? {};
11847
- if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
11848
- if (typeof payload["breakerAutoKillResetMs"] === "number")
11849
- cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
11850
- decrypted.circuitBreaker = cb;
11851
- }
11852
- if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
11853
- const restrict = payload["fsAccess"] === "project";
11854
- const toolsCfg = decrypted.tools ?? {};
11855
- toolsCfg.restrictToProjectRoot = restrict;
11856
- decrypted.tools = toolsCfg;
11857
- const featsCfg = decrypted.features ?? {};
11858
- featsCfg.allowOutsideProjectRoot = !restrict;
11859
- decrypted.features = featsCfg;
11860
- }
11861
- if (typeof payload["debugStream"] === "boolean")
11862
- decrypted.debugStream = payload["debugStream"];
11863
- }, "prefs");
11864
- }
11865
-
11866
- // src/server/projects-manifest.ts
11867
- import * as fs14 from "node:fs/promises";
11868
- import * as path18 from "node:path";
11869
- import { projectSlug } from "@wrongstack/core";
11870
- function projectsJsonPath(globalConfigPath) {
11871
- const base = path18.dirname(globalConfigPath);
11872
- return path18.join(base, "projects.json");
11873
- }
11874
- async function loadManifest(globalConfigPath) {
11875
- try {
11876
- const raw = await fs14.readFile(projectsJsonPath(globalConfigPath), "utf8");
11877
- const parsed = JSON.parse(raw);
11878
- return { projects: parsed.projects ?? [] };
11879
- } catch {
11880
- return { projects: [] };
11881
- }
11882
- }
11883
- async function saveManifest(manifest, globalConfigPath) {
11884
- const file = projectsJsonPath(globalConfigPath);
11885
- await fs14.mkdir(path18.dirname(file), { recursive: true });
11886
- await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
11887
- }
11888
- function generateProjectSlug(rootPath) {
11889
- return projectSlug(rootPath);
11890
- }
11891
- async function ensureProjectDataDir(slug, globalConfigPath) {
11892
- const base = path18.dirname(globalConfigPath);
11893
- const dir = path18.join(base, "projects", slug);
11894
- await fs14.mkdir(dir, { recursive: true });
11895
- return dir;
13174
+ return updateGlobalConfig(
13175
+ deps2,
13176
+ holder,
13177
+ (decrypted) => {
13178
+ const autonomyCfg = decrypted.autonomy ?? {};
13179
+ let autonomyTouched = false;
13180
+ const setAutonomy = (key, val) => {
13181
+ autonomyCfg[key] = val;
13182
+ autonomyTouched = true;
13183
+ };
13184
+ if (typeof payload["autonomy"] === "string" && ["off", "suggest", "auto"].includes(payload["autonomy"])) {
13185
+ setAutonomy("defaultMode", payload["autonomy"]);
13186
+ }
13187
+ if (typeof payload["autonomyDelayMs"] === "number")
13188
+ setAutonomy("autoProceedDelayMs", payload["autonomyDelayMs"]);
13189
+ if (typeof payload["autoProceedMaxIterations"] === "number")
13190
+ setAutonomy("autoProceedMaxIterations", payload["autoProceedMaxIterations"]);
13191
+ if (typeof payload["yolo"] === "boolean") {
13192
+ setAutonomy("yolo", payload["yolo"]);
13193
+ decrypted.yolo = payload["yolo"];
13194
+ }
13195
+ if (typeof payload["chime"] === "boolean") setAutonomy("chime", payload["chime"]);
13196
+ if (typeof payload["confirmExit"] === "boolean")
13197
+ setAutonomy("confirmExit", payload["confirmExit"]);
13198
+ if (typeof payload["streamFleet"] === "boolean")
13199
+ setAutonomy("streamFleet", payload["streamFleet"]);
13200
+ if (typeof payload["enhanceEnabled"] === "boolean")
13201
+ setAutonomy("enhance", payload["enhanceEnabled"]);
13202
+ if (typeof payload["enhanceDelayMs"] === "number")
13203
+ setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
13204
+ if (typeof payload["enhanceLanguage"] === "string")
13205
+ setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
13206
+ if (typeof payload["refinerProvider"] === "string")
13207
+ setAutonomy("refinerProvider", payload["refinerProvider"]);
13208
+ if (typeof payload["refinerModel"] === "string")
13209
+ setAutonomy("refinerModel", payload["refinerModel"]);
13210
+ if (typeof payload["refinerFallbackProfile"] === "string")
13211
+ setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
13212
+ if (typeof payload["thinkingWord"] === "string")
13213
+ setAutonomy("thinkingWord", payload["thinkingWord"]);
13214
+ if (typeof payload["statuslineMode"] === "string")
13215
+ setAutonomy("statuslineMode", payload["statuslineMode"]);
13216
+ if (typeof payload["animationStyle"] === "string")
13217
+ setAutonomy("animationStyle", payload["animationStyle"]);
13218
+ if (typeof payload["showModelReasoning"] === "boolean")
13219
+ setAutonomy("showModelReasoning", payload["showModelReasoning"]);
13220
+ if (autonomyTouched) decrypted.autonomy = autonomyCfg;
13221
+ if (typeof payload["nextPrediction"] === "boolean")
13222
+ decrypted.nextPrediction = payload["nextPrediction"];
13223
+ if (typeof payload["uiLocale"] === "string") decrypted.uiLocale = payload["uiLocale"];
13224
+ if (Array.isArray(payload["fallbackModels"]))
13225
+ decrypted.fallbackModels = payload["fallbackModels"];
13226
+ if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"])) {
13227
+ decrypted.fallbackProfiles = payload["fallbackProfiles"];
13228
+ }
13229
+ if (Array.isArray(payload["favoriteModels"]))
13230
+ decrypted.favoriteModels = payload["favoriteModels"];
13231
+ if (typeof payload["favoriteModelsOnly"] === "boolean")
13232
+ decrypted.favoriteModelsOnly = payload["favoriteModelsOnly"];
13233
+ if (Array.isArray(payload["modelAvailabilitySchedule"]))
13234
+ decrypted.modelAvailabilitySchedule = payload["modelAvailabilitySchedule"];
13235
+ if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"])) {
13236
+ decrypted.modelMatrix = payload["modelMatrix"];
13237
+ }
13238
+ if (typeof payload["fallbackAuto"] === "boolean")
13239
+ decrypted.fallbackAuto = payload["fallbackAuto"];
13240
+ const FEATURE_MAP = {
13241
+ featureMcp: "mcp",
13242
+ featurePlugins: "plugins",
13243
+ featureMemory: "memory",
13244
+ featureSkills: "skills",
13245
+ featureModelsRegistry: "modelsRegistry"
13246
+ };
13247
+ for (const [prefKey, cfgKey] of Object.entries(FEATURE_MAP)) {
13248
+ if (typeof payload[prefKey] === "boolean") {
13249
+ const feats = decrypted.features ?? {};
13250
+ feats[cfgKey] = payload[prefKey];
13251
+ decrypted.features = feats;
13252
+ }
13253
+ }
13254
+ if (typeof payload["contextAutoCompact"] === "boolean" || typeof payload["contextStrategy"] === "string" || typeof payload["contextMode"] === "string") {
13255
+ const ctxCfg = decrypted.context ?? {};
13256
+ if (typeof payload["contextAutoCompact"] === "boolean")
13257
+ ctxCfg.autoCompact = payload["contextAutoCompact"];
13258
+ if (typeof payload["contextStrategy"] === "string")
13259
+ ctxCfg.strategy = payload["contextStrategy"];
13260
+ if (typeof payload["contextMode"] === "string") ctxCfg.mode = payload["contextMode"];
13261
+ decrypted.context = ctxCfg;
13262
+ }
13263
+ if (typeof payload["tokenSavingTier"] === "string") {
13264
+ const featsCfg = decrypted.features ?? {};
13265
+ featsCfg.tokenSavingMode = payload["tokenSavingTier"];
13266
+ decrypted.features = featsCfg;
13267
+ }
13268
+ if (typeof payload["maxConcurrent"] === "number") {
13269
+ decrypted.maxConcurrent = payload["maxConcurrent"];
13270
+ }
13271
+ if (typeof payload["titleAnimation"] === "boolean") {
13272
+ const autoCfg = decrypted.autonomy ?? {};
13273
+ autoCfg.terminalTitleAnimation = payload["titleAnimation"];
13274
+ decrypted.autonomy = autoCfg;
13275
+ }
13276
+ if (typeof payload["logLevel"] === "string") {
13277
+ const logCfg = decrypted.log ?? {};
13278
+ logCfg.level = payload["logLevel"];
13279
+ decrypted.log = logCfg;
13280
+ }
13281
+ if (typeof payload["auditLevel"] === "string") {
13282
+ const sessionCfg = decrypted.session ?? {};
13283
+ sessionCfg.auditLevel = payload["auditLevel"];
13284
+ decrypted.session = sessionCfg;
13285
+ }
13286
+ if (typeof payload["indexOnStart"] === "boolean") {
13287
+ const indexingCfg = decrypted.indexing ?? {};
13288
+ indexingCfg.onSessionStart = payload["indexOnStart"];
13289
+ decrypted.indexing = indexingCfg;
13290
+ }
13291
+ if (typeof payload["maxIterations"] === "number") {
13292
+ const toolsCfg = decrypted.tools ?? {};
13293
+ toolsCfg.maxIterations = payload["maxIterations"];
13294
+ decrypted.tools = toolsCfg;
13295
+ }
13296
+ const hqTouched = typeof payload["hqEnabled"] === "boolean" || typeof payload["hqUrl"] === "string" || typeof payload["hqToken"] === "string" || typeof payload["hqRawContent"] === "boolean";
13297
+ if (hqTouched) {
13298
+ const hqCfg = decrypted.hq ?? {};
13299
+ if (typeof payload["hqEnabled"] === "boolean") hqCfg.enabled = payload["hqEnabled"];
13300
+ if (typeof payload["hqUrl"] === "string") hqCfg.url = payload["hqUrl"];
13301
+ if (typeof payload["hqToken"] === "string") hqCfg.token = payload["hqToken"];
13302
+ if (typeof payload["hqRawContent"] === "boolean")
13303
+ hqCfg.rawContent = payload["hqRawContent"];
13304
+ decrypted.hq = hqCfg;
13305
+ }
13306
+ const tgTouched = typeof payload["tgSessionEnd"] === "boolean" || typeof payload["tgDelegate"] === "boolean" || typeof payload["tgLongToolMs"] === "number";
13307
+ if (tgTouched) {
13308
+ const ext = decrypted.extensions ?? {};
13309
+ const tg = ext["telegram"] ?? {};
13310
+ if (typeof payload["tgSessionEnd"] === "boolean") {
13311
+ tg["notifyOnSessionEnd"] = payload["tgSessionEnd"];
13312
+ }
13313
+ if (typeof payload["tgDelegate"] === "boolean") {
13314
+ tg["notifyOnDelegate"] = payload["tgDelegate"];
13315
+ }
13316
+ if (typeof payload["tgLongToolMs"] === "number") {
13317
+ tg["longToolThresholdMs"] = payload["tgLongToolMs"];
13318
+ }
13319
+ ext["telegram"] = tg;
13320
+ decrypted.extensions = ext;
13321
+ }
13322
+ const modelRuntimeTouched = typeof payload["reasoningMode"] === "string" || typeof payload["reasoningEffort"] === "string" || typeof payload["reasoningPreserve"] === "boolean" || typeof payload["cacheTtl"] === "string";
13323
+ if (modelRuntimeTouched) {
13324
+ const mr = decrypted.modelRuntime ?? {};
13325
+ const reasoning = mr.reasoning ?? {};
13326
+ if (typeof payload["reasoningMode"] === "string") reasoning.mode = payload["reasoningMode"];
13327
+ if (typeof payload["reasoningEffort"] === "string")
13328
+ reasoning.effort = payload["reasoningEffort"];
13329
+ if (typeof payload["reasoningPreserve"] === "boolean")
13330
+ reasoning.preserve = payload["reasoningPreserve"];
13331
+ mr.reasoning = reasoning;
13332
+ if (typeof payload["cacheTtl"] === "string" && payload["cacheTtl"] !== "default") {
13333
+ mr.cache = { ttl: payload["cacheTtl"] };
13334
+ } else if (payload["cacheTtl"] === "default") {
13335
+ delete mr.cache;
13336
+ }
13337
+ decrypted.modelRuntime = mr;
13338
+ }
13339
+ if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
13340
+ const cb = decrypted.circuitBreaker ?? {};
13341
+ if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
13342
+ if (typeof payload["breakerAutoKillResetMs"] === "number")
13343
+ cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
13344
+ decrypted.circuitBreaker = cb;
13345
+ }
13346
+ if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
13347
+ const restrict = payload["fsAccess"] === "project";
13348
+ const toolsCfg = decrypted.tools ?? {};
13349
+ toolsCfg.restrictToProjectRoot = restrict;
13350
+ decrypted.tools = toolsCfg;
13351
+ const featsCfg = decrypted.features ?? {};
13352
+ featsCfg.allowOutsideProjectRoot = !restrict;
13353
+ decrypted.features = featsCfg;
13354
+ }
13355
+ if (typeof payload["debugStream"] === "boolean")
13356
+ decrypted.debugStream = payload["debugStream"];
13357
+ if (typeof payload["pluginsEnabled"] === "object" && payload["pluginsEnabled"] !== null) {
13358
+ const ext = decrypted.extensions ?? {};
13359
+ for (const [pluginName, enabled] of Object.entries(
13360
+ payload["pluginsEnabled"]
13361
+ )) {
13362
+ if (FORBIDDEN_PROTO_KEYS2.has(pluginName)) continue;
13363
+ const pExt = ext[pluginName] ?? {};
13364
+ pExt["enabled"] = enabled;
13365
+ ext[pluginName] = pExt;
13366
+ }
13367
+ decrypted.extensions = ext;
13368
+ }
13369
+ const chimeraTouched = typeof payload["chimeraEnabled"] === "boolean" || typeof payload["chimeraProvider"] === "string" || typeof payload["chimeraModel"] === "string" || typeof payload["chimeraMaxFiles"] === "number" || typeof payload["chimeraAutoFix"] === "string";
13370
+ if (chimeraTouched) {
13371
+ const ext = decrypted.extensions ?? {};
13372
+ const chimera = ext["wstack-chimera"] ?? {};
13373
+ if (typeof payload["chimeraEnabled"] === "boolean")
13374
+ chimera["enabled"] = payload["chimeraEnabled"];
13375
+ if (typeof payload["chimeraProvider"] === "string")
13376
+ chimera["provider"] = payload["chimeraProvider"];
13377
+ if (typeof payload["chimeraModel"] === "string") chimera["model"] = payload["chimeraModel"];
13378
+ if (typeof payload["chimeraMaxFiles"] === "number" && payload["chimeraMaxFiles"] >= 1) {
13379
+ chimera["maxFiles"] = payload["chimeraMaxFiles"];
13380
+ }
13381
+ if (typeof payload["chimeraAutoFix"] === "string") {
13382
+ if (payload["chimeraAutoFix"] === "off" || payload["chimeraAutoFix"] === "ask" || payload["chimeraAutoFix"] === "auto") {
13383
+ chimera["autoFix"] = payload["chimeraAutoFix"];
13384
+ }
13385
+ }
13386
+ ext["wstack-chimera"] = chimera;
13387
+ decrypted.extensions = ext;
13388
+ }
13389
+ const autoReviewTouched = typeof payload["autoReviewEnabled"] === "boolean" || typeof payload["autoReviewProvider"] === "string" || typeof payload["autoReviewModel"] === "string" || typeof payload["autoReviewFallbackProfile"] === "string" || Array.isArray(payload["autoReviewFallbackModels"]) || typeof payload["autoReviewDebounceMs"] === "number" || typeof payload["autoReviewMaxFilesPerBatch"] === "number" || typeof payload["autoReviewMaxConcurrentReviews"] === "number" || typeof payload["autoReviewCascadeOn"] === "string";
13390
+ if (autoReviewTouched) {
13391
+ const ext = decrypted.extensions ?? {};
13392
+ const ar = ext["wstack-auto-review"] ?? {};
13393
+ if (typeof payload["autoReviewEnabled"] === "boolean")
13394
+ ar["enabled"] = payload["autoReviewEnabled"];
13395
+ if (typeof payload["autoReviewProvider"] === "string")
13396
+ ar["provider"] = payload["autoReviewProvider"];
13397
+ if (typeof payload["autoReviewModel"] === "string")
13398
+ ar["model"] = payload["autoReviewModel"];
13399
+ if (typeof payload["autoReviewFallbackProfile"] === "string") {
13400
+ if (payload["autoReviewFallbackProfile"] === "") {
13401
+ delete ar["fallbackProfile"];
13402
+ } else {
13403
+ ar["fallbackProfile"] = payload["autoReviewFallbackProfile"];
13404
+ }
13405
+ }
13406
+ if (typeof payload["autoReviewDebounceMs"] === "number" && payload["autoReviewDebounceMs"] >= 0) {
13407
+ ar["debounceMs"] = payload["autoReviewDebounceMs"];
13408
+ }
13409
+ if (typeof payload["autoReviewMaxFilesPerBatch"] === "number" && payload["autoReviewMaxFilesPerBatch"] >= 1) {
13410
+ ar["maxFilesPerBatch"] = payload["autoReviewMaxFilesPerBatch"];
13411
+ }
13412
+ if (typeof payload["autoReviewMaxConcurrentReviews"] === "number" && payload["autoReviewMaxConcurrentReviews"] >= 1) {
13413
+ ar["maxConcurrentReviews"] = payload["autoReviewMaxConcurrentReviews"];
13414
+ }
13415
+ if (typeof payload["autoReviewCascadeOn"] === "string") {
13416
+ if (payload["autoReviewCascadeOn"] === "off" || payload["autoReviewCascadeOn"] === "critical" || payload["autoReviewCascadeOn"] === "high") {
13417
+ ar["cascadeOn"] = payload["autoReviewCascadeOn"];
13418
+ }
13419
+ }
13420
+ ext["wstack-auto-review"] = ar;
13421
+ decrypted.extensions = ext;
13422
+ }
13423
+ },
13424
+ "prefs"
13425
+ );
11896
13426
  }
11897
13427
 
11898
13428
  // src/server/provider-handlers.ts
@@ -12223,12 +13753,14 @@ function createProviderHandlers(deps2) {
12223
13753
  }
12224
13754
 
12225
13755
  // src/server/routes.ts
12226
- import path20 from "node:path";
13756
+ import path21 from "node:path";
12227
13757
  import {
13758
+ buildRefinerContextSections,
12228
13759
  enhanceUserPrompt,
12229
13760
  gatedEnhancerReasoning,
12230
13761
  nextEnhanceTimeout,
12231
13762
  recentTextTurns,
13763
+ resolveConfiguredRefinerRef,
12232
13764
  resolveEnhanceFallbackRef,
12233
13765
  resolveProviderModelList
12234
13766
  } from "@wrongstack/core";
@@ -12340,7 +13872,7 @@ async function handleMailboxCompact(ws, deps2, opts) {
12340
13872
  // src/server/mode-handlers.ts
12341
13873
  import {
12342
13874
  DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
12343
- resolveWstackPaths as resolveWstackPaths2,
13875
+ resolveWstackPaths as resolveWstackPaths3,
12344
13876
  ToolValidationError as ToolValidationError5
12345
13877
  } from "@wrongstack/core";
12346
13878
  function createModeHandlers(ctx) {
@@ -12380,6 +13912,7 @@ function createModeHandlers(ctx) {
12380
13912
  }
12381
13913
  const { id } = parsed.value;
12382
13914
  try {
13915
+ const prev = await ctx.modeStore.getActiveMode();
12383
13916
  if (id === "default") {
12384
13917
  await ctx.modeStore.setActiveMode(null);
12385
13918
  } else {
@@ -12390,8 +13923,13 @@ function createModeHandlers(ctx) {
12390
13923
  await ctx.modeStore.setActiveMode(id);
12391
13924
  }
12392
13925
  ctx.setModeId(id);
13926
+ const fromMode = prev?.id ?? "default";
13927
+ if (ctx.context.session && fromMode !== id) {
13928
+ void ctx.context.session.append({ type: "mode_changed", ts: (/* @__PURE__ */ new Date()).toISOString(), from: fromMode, to: id }).catch(() => {
13929
+ });
13930
+ }
12393
13931
  const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
12394
- const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
13932
+ const paths = resolveWstackPaths3({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
12395
13933
  const freshBuilder = new DefaultSystemPromptBuilder2({
12396
13934
  memoryStore: ctx.memoryStore,
12397
13935
  // Single injection channel: Super Memory turn middleware, not a static section.
@@ -12426,7 +13964,7 @@ function createModeHandlers(ctx) {
12426
13964
  }
12427
13965
 
12428
13966
  // src/server/project-handlers.ts
12429
- import * as path19 from "node:path";
13967
+ import * as path20 from "node:path";
12430
13968
  function createProjectHandlers(ctx) {
12431
13969
  return {
12432
13970
  listProjects: async (ws) => {
@@ -12452,7 +13990,7 @@ function createProjectHandlers(ctx) {
12452
13990
  selectProject: async (ws, msg) => {
12453
13991
  const payload = msg.payload;
12454
13992
  const root = typeof payload?.root === "string" ? payload.root : "";
12455
- const name2 = typeof payload?.name === "string" ? payload.name : root ? path19.basename(root) : "";
13993
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path20.basename(root) : "";
12456
13994
  send(ws, {
12457
13995
  type: "projects.selected",
12458
13996
  payload: {
@@ -12489,6 +14027,7 @@ function createProjectHandlers(ctx) {
12489
14027
  // src/server/session-handlers.ts
12490
14028
  import {
12491
14029
  DEFAULT_CONTEXT_WINDOW_MODE_ID,
14030
+ loadTodosCheckpoint,
12492
14031
  repairToolUseAdjacency,
12493
14032
  resolveContextWindowPolicy as resolveContextWindowPolicy3
12494
14033
  } from "@wrongstack/core";
@@ -12526,13 +14065,14 @@ function createSessionHandlers(ctx) {
12526
14065
  }).catch(() => void 0);
12527
14066
  await writer.close().catch(() => void 0);
12528
14067
  };
12529
- const activateSession = async (next, messages, usage) => {
14068
+ const activateSession = async (next, messages, usage, todos = []) => {
12530
14069
  const current = ctx.getSession();
12531
14070
  if (current !== next) await finalizeSession(current);
12532
14071
  ctx.setSession(next);
12533
14072
  ctx.context.session = next;
12534
14073
  ctx.context.state.replaceMessages(messages);
12535
- ctx.context.state.replaceTodos([]);
14074
+ await ctx.context.flushConversationJournal?.();
14075
+ ctx.context.state.replaceTodos(todos);
12536
14076
  ctx.context.readFiles.clear();
12537
14077
  ctx.context.fileMtimes.clear();
12538
14078
  ctx.context.state.setMeta(
@@ -12816,7 +14356,15 @@ function createSessionHandlers(ctx) {
12816
14356
  return;
12817
14357
  }
12818
14358
  const resumed = await ctx.getSessionStore().resume(id);
12819
- await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
14359
+ const restoredTodos = await loadTodosCheckpoint(
14360
+ sessionScopedPath2(ctx.sessionsDir, resumed.writer.id, ".todos.json")
14361
+ ).catch(() => null) ?? [];
14362
+ await activateSession(
14363
+ resumed.writer,
14364
+ resumed.data.messages,
14365
+ resumed.data.usage,
14366
+ restoredTodos
14367
+ );
12820
14368
  broadcast(ctx.clients, {
12821
14369
  type: "session.start",
12822
14370
  payload: {
@@ -12826,6 +14374,10 @@ function createSessionHandlers(ctx) {
12826
14374
  replayUsage: resumed.data.usage
12827
14375
  }
12828
14376
  });
14377
+ broadcast(ctx.clients, {
14378
+ type: "todos.updated",
14379
+ payload: { sessionId: resumed.writer.id, todos: restoredTodos }
14380
+ });
12829
14381
  sendResult(ws, true, `Resumed session ${id}`);
12830
14382
  } catch (err) {
12831
14383
  sendResult(ws, false, errMessage(err));
@@ -12851,11 +14403,17 @@ function createSessionHandlers(ctx) {
12851
14403
  if (!ensureCurrentSession(ws, msg, "session.rewind")) return;
12852
14404
  const { checkpointIndex } = msg.payload;
12853
14405
  try {
12854
- const { DefaultSessionRewinder } = await import("@wrongstack/core");
14406
+ const { applyRewindToConversation, DefaultSessionRewinder } = await import("@wrongstack/core");
12855
14407
  const projectRoot = ctx.getProjectRoot();
12856
14408
  const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
12857
- await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
12858
- await ctx.context.session.truncateToCheckpoint(checkpointIndex);
14409
+ const reverted = await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
14410
+ await applyRewindToConversation({
14411
+ session: ctx.context.session,
14412
+ state: ctx.context.state,
14413
+ sessionsDir: ctx.sessionsDir,
14414
+ promptIndex: checkpointIndex,
14415
+ revertedFiles: reverted.revertedFiles
14416
+ });
12859
14417
  sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
12860
14418
  broadcast(ctx.clients, {
12861
14419
  type: "session.start",
@@ -13065,11 +14623,36 @@ function buildRoutes(state, deps2, cb) {
13065
14623
  });
13066
14624
  return;
13067
14625
  }
14626
+ } else {
14627
+ const configuredRef = resolveConfiguredRefinerRef({
14628
+ ...cfg,
14629
+ provider: providerId,
14630
+ model
14631
+ });
14632
+ if (configuredRef) {
14633
+ const slash = configuredRef.indexOf("/");
14634
+ const configuredProvider = slash > 0 ? configuredRef.slice(0, slash) : providerId;
14635
+ const configuredModel = slash > 0 ? configuredRef.slice(slash + 1) : configuredRef;
14636
+ try {
14637
+ const providerCfg = cfg.providers?.[configuredProvider] ?? {
14638
+ type: configuredProvider
14639
+ };
14640
+ provider = deps2.providerRegistry.has(configuredProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: configuredProvider }) : makeProviderFromConfig2(configuredProvider, providerCfg);
14641
+ providerId = configuredProvider;
14642
+ model = configuredModel;
14643
+ } catch {
14644
+ }
14645
+ }
13068
14646
  }
13069
14647
  const baseTimeout = 9e4;
13070
14648
  const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
13071
14649
  try {
13072
14650
  const history = recentTextTurns(deps2.context.messages);
14651
+ const contextSections = await buildRefinerContextSections({
14652
+ text,
14653
+ memoryStore: deps2.memoryStore,
14654
+ context: deps2.context
14655
+ });
13073
14656
  const resolved = await resolveProviderModelMetadata(
13074
14657
  deps2.modelsRegistry,
13075
14658
  providerId,
@@ -13083,6 +14666,14 @@ function buildRoutes(state, deps2, cb) {
13083
14666
  model,
13084
14667
  text,
13085
14668
  history,
14669
+ contextSections,
14670
+ ...payload.previousRefined ? {
14671
+ previousRefinement: {
14672
+ refined: payload.previousRefined,
14673
+ english: payload.previousEnglish || payload.previousRefined
14674
+ }
14675
+ } : {},
14676
+ ...payload.retryFeedback ? { retryFeedback: payload.retryFeedback } : {},
13086
14677
  timeoutMs,
13087
14678
  ...reasoning ? { reasoning } : {},
13088
14679
  onError: (reason, kind) => {
@@ -13238,10 +14829,29 @@ function buildRoutes(state, deps2, cb) {
13238
14829
  cfg.favoriteModels = payload["favoriteModels"];
13239
14830
  if (typeof payload["favoriteModelsOnly"] === "boolean")
13240
14831
  cfg.favoriteModelsOnly = payload["favoriteModelsOnly"];
14832
+ if (Array.isArray(payload["modelAvailabilitySchedule"]))
14833
+ cfg.modelAvailabilitySchedule = payload["modelAvailabilitySchedule"];
13241
14834
  if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"])) {
13242
14835
  cfg.modelMatrix = payload["modelMatrix"];
13243
14836
  }
13244
14837
  if (typeof payload["fallbackAuto"] === "boolean") cfg.fallbackAuto = payload["fallbackAuto"];
14838
+ const routingPatch = {};
14839
+ if (Array.isArray(payload["fallbackModels"]))
14840
+ routingPatch.fallbackModels = payload["fallbackModels"];
14841
+ if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"]))
14842
+ routingPatch.fallbackProfiles = payload["fallbackProfiles"];
14843
+ if (Array.isArray(payload["favoriteModels"]))
14844
+ routingPatch.favoriteModels = payload["favoriteModels"];
14845
+ if (typeof payload["favoriteModelsOnly"] === "boolean")
14846
+ routingPatch.favoriteModelsOnly = payload["favoriteModelsOnly"];
14847
+ if (Array.isArray(payload["modelAvailabilitySchedule"]))
14848
+ routingPatch.modelAvailabilitySchedule = payload["modelAvailabilitySchedule"];
14849
+ if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"]))
14850
+ routingPatch.modelMatrix = payload["modelMatrix"];
14851
+ if (typeof payload["fallbackAuto"] === "boolean")
14852
+ routingPatch.fallbackAuto = payload["fallbackAuto"];
14853
+ if (Object.keys(routingPatch).length > 0)
14854
+ deps2.configStore.update(routingPatch);
13245
14855
  if (typeof payload["contextAutoCompact"] === "boolean") {
13246
14856
  if (payload["contextAutoCompact"] && deps2.autoCompactor) {
13247
14857
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
@@ -13310,7 +14920,7 @@ function buildRoutes(state, deps2, cb) {
13310
14920
  }
13311
14921
  return handleMailboxMessages(
13312
14922
  ws,
13313
- { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
14923
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
13314
14924
  parsed.value
13315
14925
  );
13316
14926
  },
@@ -13322,13 +14932,13 @@ function buildRoutes(state, deps2, cb) {
13322
14932
  }
13323
14933
  return handleMailboxAgents(
13324
14934
  ws,
13325
- { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
14935
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
13326
14936
  parsed.value
13327
14937
  );
13328
14938
  },
13329
14939
  clear: (ws) => handleMailboxClear(ws, {
13330
14940
  projectRoot: state.getProjectRoot(),
13331
- globalRoot: path20.dirname(deps2.globalConfigPath)
14941
+ globalRoot: path21.dirname(deps2.globalConfigPath)
13332
14942
  }),
13333
14943
  purge: (ws, msg) => {
13334
14944
  const parsed = validateMailboxPurgePayload(msg.payload);
@@ -13338,14 +14948,14 @@ function buildRoutes(state, deps2, cb) {
13338
14948
  }
13339
14949
  return handleMailboxPurge(
13340
14950
  ws,
13341
- { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
14951
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
13342
14952
  parsed.value
13343
14953
  );
13344
14954
  },
13345
14955
  compact: (ws, msg) => {
13346
14956
  return handleMailboxCompact(
13347
14957
  ws,
13348
- { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
14958
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
13349
14959
  msg.payload ?? {}
13350
14960
  );
13351
14961
  }
@@ -13454,8 +15064,8 @@ function buildRoutes(state, deps2, cb) {
13454
15064
  }
13455
15065
  }
13456
15066
  };
13457
- const autoPhaseRoutes = {
13458
- handleMessage: (msg) => deps2.autoPhaseHandler.handleMessage(msg)
15067
+ const goalRoutes = {
15068
+ handleMessage: (msg) => deps2.goalHandler.handleMessage(msg)
13459
15069
  };
13460
15070
  const specsRoutes = {
13461
15071
  handleMessage: (msg) => deps2.specsHandler.handleMessage(msg)
@@ -13476,7 +15086,7 @@ function buildRoutes(state, deps2, cb) {
13476
15086
  mailboxRoutes,
13477
15087
  mcpRoutes,
13478
15088
  brainRoutes,
13479
- autoPhaseRoutes,
15089
+ goalRoutes,
13480
15090
  specsRoutes,
13481
15091
  sddBoardRoutes,
13482
15092
  sddWizardRoutes
@@ -13597,7 +15207,7 @@ async function startWebUI(opts = {}) {
13597
15207
  brainLog,
13598
15208
  brainMonitor,
13599
15209
  codebaseIndexing,
13600
- autoPhaseHandler,
15210
+ goalHandler,
13601
15211
  specsHandler,
13602
15212
  sddBoardHandler,
13603
15213
  sddWizardHandler,
@@ -13658,21 +15268,21 @@ async function startWebUI(opts = {}) {
13658
15268
  wpaths
13659
15269
  }, watcherMetricsRef);
13660
15270
  async function touchProjectEntry(root, workDir) {
13661
- const resolved = path21.resolve(root);
15271
+ const resolved = path22.resolve(root);
13662
15272
  const manifest = await loadManifest(globalConfigPath);
13663
15273
  const now = (/* @__PURE__ */ new Date()).toISOString();
13664
- const existing = manifest.projects.find((p) => path21.resolve(p.root) === resolved);
15274
+ const existing = manifest.projects.find((p) => path22.resolve(p.root) === resolved);
13665
15275
  if (existing) {
13666
15276
  existing.lastSeen = now;
13667
- if (workDir) existing.lastWorkingDir = path21.resolve(workDir);
15277
+ if (workDir) existing.lastWorkingDir = path22.resolve(workDir);
13668
15278
  } else {
13669
15279
  manifest.projects.push({
13670
- name: path21.basename(resolved),
15280
+ name: path22.basename(resolved),
13671
15281
  root: resolved,
13672
15282
  slug: generateProjectSlug(resolved),
13673
15283
  createdAt: now,
13674
15284
  lastSeen: now,
13675
- lastWorkingDir: workDir ? path21.resolve(workDir) : void 0
15285
+ lastWorkingDir: workDir ? path22.resolve(workDir) : void 0
13676
15286
  });
13677
15287
  }
13678
15288
  await saveManifest(manifest, globalConfigPath);
@@ -13755,7 +15365,7 @@ async function startWebUI(opts = {}) {
13755
15365
  httpPort,
13756
15366
  wssPrimary,
13757
15367
  wssSecondary,
13758
- autoPhaseHandler,
15368
+ goalHandler,
13759
15369
  specsHandler,
13760
15370
  sddBoardHandler,
13761
15371
  sddWizardHandler,
@@ -13799,7 +15409,13 @@ async function startWebUI(opts = {}) {
13799
15409
  deps2.configStore.update({
13800
15410
  providers: snapshot.providers,
13801
15411
  ...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
13802
- ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
15412
+ ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {},
15413
+ ...snapshot.fallbackModels !== void 0 ? { fallbackModels: snapshot.fallbackModels } : {},
15414
+ ...snapshot.fallbackProfiles !== void 0 ? { fallbackProfiles: snapshot.fallbackProfiles } : {},
15415
+ ...snapshot.favoriteModels !== void 0 ? { favoriteModels: snapshot.favoriteModels } : {},
15416
+ ...snapshot.favoriteModelsOnly !== void 0 ? { favoriteModelsOnly: snapshot.favoriteModelsOnly } : {},
15417
+ ...snapshot.modelMatrix !== void 0 ? { modelMatrix: snapshot.modelMatrix } : {},
15418
+ ...snapshot.fallbackAuto !== void 0 ? { fallbackAuto: snapshot.fallbackAuto } : {}
13803
15419
  });
13804
15420
  broadcast(clients, {
13805
15421
  type: "providers.saved",
@@ -13860,7 +15476,7 @@ async function startWebUI(opts = {}) {
13860
15476
  },
13861
15477
  clients,
13862
15478
  pendingConfirms,
13863
- autoPhaseHandler,
15479
+ goalHandler,
13864
15480
  specsHandler,
13865
15481
  sddBoardHandler,
13866
15482
  sddWizardHandler,
@@ -13887,6 +15503,11 @@ async function startWebUI(opts = {}) {
13887
15503
  onFleetPing: () => {
13888
15504
  void eventArming.getFleetBroadcast()?.();
13889
15505
  },
15506
+ onTechStackEvent: (event) => broadcast(clients, event),
15507
+ // Read through `context` on every call rather than capturing: the running
15508
+ // loop swaps provider/model when the user switches (same live source the
15509
+ // completion handler reads).
15510
+ getLlm: () => context.provider && context.model ? { provider: context.provider, model: context.model } : void 0,
13890
15511
  distDir: opts.distDir
13891
15512
  });
13892
15513
  registerShutdown({
@@ -13916,7 +15537,7 @@ async function startWebUI(opts = {}) {
13916
15537
  archiveLowConfidenceAfterDays: config.superMemory?.hygiene?.archiveLowConfidenceAfterDays
13917
15538
  }).catch((err) => logger.warn(`super-memory session hygiene failed: ${toErrorMessage10(err)}`));
13918
15539
  }
13919
- await unregisterInstance(process.pid, path21.dirname(globalConfigPath));
15540
+ await unregisterInstance(process.pid, path22.dirname(globalConfigPath));
13920
15541
  }
13921
15542
  });
13922
15543
  }
@@ -13956,7 +15577,7 @@ function envFlag2(name2) {
13956
15577
  return value === "1" || value === "true" || value === "yes" || value === "on";
13957
15578
  }
13958
15579
  function printHelp() {
13959
- console.log(`Usage: wstackui [options]
15580
+ console.log(`Usage: wstack --webui [options]
13960
15581
 
13961
15582
  Options:
13962
15583
  --host <host> Bind host/interface (default: 127.0.0.1)