@wrongstack/webui-server 0.287.0 → 0.289.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 (54) hide show
  1. package/dist/index.js +1951 -457
  2. package/dist/index.js.map +4 -4
  3. package/dist/server/backend-services.d.ts +2 -2
  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 +1940 -452
  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 +9 -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.map +1 -1
  31. package/dist/server/mcp-handlers.d.ts +1 -1
  32. package/dist/server/memory-handlers.d.ts +37 -0
  33. package/dist/server/memory-handlers.d.ts.map +1 -1
  34. package/dist/server/message-dispatcher.d.ts.map +1 -1
  35. package/dist/server/mode-handlers.d.ts.map +1 -1
  36. package/dist/server/pref-helpers.d.ts +1 -1
  37. package/dist/server/pref-helpers.d.ts.map +1 -1
  38. package/dist/server/provider-keys.d.ts.map +1 -1
  39. package/dist/server/routes.d.ts +5 -5
  40. package/dist/server/routes.d.ts.map +1 -1
  41. package/dist/server/server-runtime.d.ts +7 -1
  42. package/dist/server/server-runtime.d.ts.map +1 -1
  43. package/dist/server/session-handlers.d.ts.map +1 -1
  44. package/dist/server/setup-events.d.ts +1 -1
  45. package/dist/server/setup-events.d.ts.map +1 -1
  46. package/dist/server/start-webui.d.ts.map +1 -1
  47. package/dist/server/techstack-handlers.d.ts +101 -0
  48. package/dist/server/techstack-handlers.d.ts.map +1 -0
  49. package/dist/server/worktree-ws-handler.d.ts +1 -1
  50. package/dist/server/ws-payload-validation.d.ts.map +1 -1
  51. package/package.json +10 -12
  52. package/dist/server/autophase-routes.d.ts +0 -10
  53. package/dist/server/autophase-routes.d.ts.map +0 -1
  54. 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,9 @@ 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
1911
 
1912
1912
  // src/server/http-server/api-handlers.ts
1913
1913
  async function handleApiSessions(res, globalRoot) {
@@ -2095,7 +2095,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2095
2095
  return;
2096
2096
  }
2097
2097
  try {
2098
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2098
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2099
2099
  const registry = new SessionRegistry(globalRoot);
2100
2100
  const entry = await registry.get(sessionId);
2101
2101
  if (!entry) {
@@ -2103,7 +2103,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2103
2103
  res.end(JSON.stringify({ error: "Session not found" }));
2104
2104
  return;
2105
2105
  }
2106
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2106
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2107
2107
  const store = new DefaultSessionStore2({ dir: paths.projectSessions });
2108
2108
  const reader = new DefaultSessionReader2({ store });
2109
2109
  const rawEntries = [];
@@ -2130,7 +2130,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2130
2130
  }
2131
2131
  }
2132
2132
  function readJsonBody(req) {
2133
- return new Promise((resolve10, reject) => {
2133
+ return new Promise((resolve12, reject) => {
2134
2134
  let data = "";
2135
2135
  req.on("data", (chunk) => {
2136
2136
  data += chunk;
@@ -2141,7 +2141,7 @@ function readJsonBody(req) {
2141
2141
  });
2142
2142
  req.on("end", () => {
2143
2143
  try {
2144
- resolve10(data ? JSON.parse(data) : {});
2144
+ resolve12(data ? JSON.parse(data) : {});
2145
2145
  } catch (err) {
2146
2146
  reject(err instanceof Error ? err : new Error(String(err)));
2147
2147
  }
@@ -2177,7 +2177,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2177
2177
  const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
2178
2178
  const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
2179
2179
  try {
2180
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2180
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2181
2181
  const registry = new SessionRegistry(globalRoot);
2182
2182
  const entry = await registry.get(sessionId);
2183
2183
  if (!entry) {
@@ -2185,7 +2185,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2185
2185
  res.end(JSON.stringify({ error: "Session not found" }));
2186
2186
  return;
2187
2187
  }
2188
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2188
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2189
2189
  const mailbox = new GlobalMailbox4(paths.projectDir);
2190
2190
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
2191
2191
  const sent = await mailbox.send({ from, to, type, subject, body: text, priority });
@@ -2203,7 +2203,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2203
2203
  return;
2204
2204
  }
2205
2205
  try {
2206
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2206
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2207
2207
  const registry = new SessionRegistry(globalRoot);
2208
2208
  const entry = await registry.get(sessionId);
2209
2209
  if (!entry) {
@@ -2211,7 +2211,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2211
2211
  res.end(JSON.stringify({ error: "Session not found" }));
2212
2212
  return;
2213
2213
  }
2214
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2214
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2215
2215
  const mailbox = new GlobalMailbox4(paths.projectDir);
2216
2216
  const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
2217
2217
  const [inbound, outbound] = await Promise.all([
@@ -2261,7 +2261,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2261
2261
  const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
2262
2262
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2263
2263
  try {
2264
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2264
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2265
2265
  const registry = new SessionRegistry(globalRoot);
2266
2266
  const entry = await registry.get(sessionId);
2267
2267
  if (!entry) {
@@ -2269,7 +2269,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2269
2269
  res.end(JSON.stringify({ error: "Session not found" }));
2270
2270
  return;
2271
2271
  }
2272
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2272
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2273
2273
  const mailbox = new GlobalMailbox4(paths.projectDir);
2274
2274
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
2275
2275
  const sent = await mailbox.send({
@@ -2309,7 +2309,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2309
2309
  }
2310
2310
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2311
2311
  try {
2312
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2312
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2313
2313
  const registry = new SessionRegistry(globalRoot);
2314
2314
  const all = await registry.list();
2315
2315
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -2321,7 +2321,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2321
2321
  }
2322
2322
  const mbByDir = /* @__PURE__ */ new Map();
2323
2323
  const mailboxFor = (projectRoot) => {
2324
- const dir = resolveWstackPaths3({ projectRoot, globalRoot }).projectDir;
2324
+ const dir = resolveWstackPaths4({ projectRoot, globalRoot }).projectDir;
2325
2325
  let mb = mbByDir.get(dir);
2326
2326
  if (!mb) {
2327
2327
  mb = new GlobalMailbox4(dir);
@@ -2384,14 +2384,14 @@ function pushEvent(event) {
2384
2384
  }
2385
2385
  }
2386
2386
  function parseBody(req) {
2387
- return new Promise((resolve10, reject) => {
2387
+ return new Promise((resolve12, reject) => {
2388
2388
  let body = "";
2389
2389
  req.on("data", (chunk) => {
2390
2390
  body += chunk.toString("utf-8");
2391
2391
  });
2392
2392
  req.on("end", () => {
2393
2393
  try {
2394
- resolve10(JSON.parse(body));
2394
+ resolve12(JSON.parse(body));
2395
2395
  } catch {
2396
2396
  reject(new Error("Invalid JSON"));
2397
2397
  }
@@ -2458,6 +2458,251 @@ async function handleApiAnalyticsSummary(res) {
2458
2458
  );
2459
2459
  }
2460
2460
 
2461
+ // src/server/codemap-handlers.ts
2462
+ import { packageGraphService, fileGraphService, symbolGraphService } from "@wrongstack/tools";
2463
+ function sendJson(res, status, data) {
2464
+ res.writeHead(status, { "Content-Type": "application/json" });
2465
+ res.end(JSON.stringify(data));
2466
+ }
2467
+ function handleCodemapPackages(res, deps2) {
2468
+ try {
2469
+ const graph = packageGraphService({
2470
+ projectRoot: deps2.projectRoot,
2471
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2472
+ });
2473
+ sendJson(res, 200, graph);
2474
+ } catch (err) {
2475
+ const msg = err instanceof Error ? err.message : String(err);
2476
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2477
+ }
2478
+ }
2479
+ function handleCodemapFiles(res, deps2, pkg) {
2480
+ if (!pkg) {
2481
+ sendJson(res, 400, { error: 'Missing "package" query parameter' });
2482
+ return;
2483
+ }
2484
+ try {
2485
+ const graph = fileGraphService({
2486
+ projectRoot: deps2.projectRoot,
2487
+ packageFilter: pkg,
2488
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2489
+ });
2490
+ sendJson(res, 200, graph);
2491
+ } catch (err) {
2492
+ const msg = err instanceof Error ? err.message : String(err);
2493
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2494
+ }
2495
+ }
2496
+ function handleCodemapSymbols(res, deps2, file) {
2497
+ if (!file) {
2498
+ sendJson(res, 400, { error: 'Missing "file" query parameter' });
2499
+ return;
2500
+ }
2501
+ try {
2502
+ const graph = symbolGraphService({
2503
+ projectRoot: deps2.projectRoot,
2504
+ fileFilter: file,
2505
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2506
+ });
2507
+ sendJson(res, 200, graph);
2508
+ } catch (err) {
2509
+ const msg = err instanceof Error ? err.message : String(err);
2510
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2511
+ }
2512
+ }
2513
+
2514
+ // src/server/techstack-handlers.ts
2515
+ import { randomUUID } from "node:crypto";
2516
+ var DEEP_DIVE_TIMEOUT_MS = 6e4;
2517
+ function sendJson2(res, status, data) {
2518
+ res.writeHead(status, { "Content-Type": "application/json" });
2519
+ res.end(JSON.stringify(data));
2520
+ }
2521
+ async function buildResearcher(deps2, kind) {
2522
+ if (kind !== "analyze" || !deps2.getLlm) return void 0;
2523
+ const { createProviderLlm, createResearcher, createToolSearch } = await import("@wrongstack/techstack");
2524
+ const llm = createProviderLlm(deps2.getLlm);
2525
+ if (!llm) return void 0;
2526
+ return createResearcher({ llm, search: createToolSearch() });
2527
+ }
2528
+ function handleTechStackSnapshot(res, deps2) {
2529
+ try {
2530
+ const snapshot = deps2.store.getSnapshot(deps2.projectId);
2531
+ if (!snapshot) {
2532
+ sendJson2(res, 404, { snapshot: null, stale: false });
2533
+ return;
2534
+ }
2535
+ const ageMs = Date.now() - new Date(snapshot.createdAt).getTime();
2536
+ sendJson2(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
2537
+ } catch (error) {
2538
+ sendJson2(res, 500, {
2539
+ error: "TechStack store unavailable",
2540
+ detail: errorMessage(error)
2541
+ });
2542
+ }
2543
+ }
2544
+ function errorMessage(error) {
2545
+ return error instanceof Error ? error.message : String(error);
2546
+ }
2547
+ function requireJobDeps(res, deps2) {
2548
+ if (!deps2.projectRoot || !deps2.engine) {
2549
+ sendJson2(res, 503, { error: "TechStack engine unavailable" });
2550
+ return false;
2551
+ }
2552
+ return true;
2553
+ }
2554
+ function startJob(res, deps2, kind) {
2555
+ if (!requireJobDeps(res, deps2)) return;
2556
+ const jobId = randomUUID();
2557
+ const controller = new AbortController();
2558
+ deps2.runningJobs?.set(jobId, controller);
2559
+ deps2.emit?.({ type: "techstack.job.started", payload: { jobId, kind } });
2560
+ sendJson2(res, 202, { jobId, kind, status: "queued" });
2561
+ void buildResearcher(deps2, kind).catch(() => void 0).then(
2562
+ (researcher) => deps2.engine.analyze(deps2.projectId, {
2563
+ targetRoot: deps2.projectRoot,
2564
+ requestedBy: "webui",
2565
+ online: kind === "analyze",
2566
+ jobId,
2567
+ signal: controller.signal,
2568
+ researcher,
2569
+ onProgress: (phase, completed, total) => {
2570
+ deps2.emit?.({
2571
+ type: "techstack.job.progress",
2572
+ payload: { jobId, phase, completed, total }
2573
+ });
2574
+ }
2575
+ })
2576
+ ).then(({ snapshot }) => {
2577
+ if (controller.signal.aborted) return;
2578
+ deps2.emit?.({
2579
+ type: "techstack.snapshot.updated",
2580
+ payload: { snapshot, stale: false }
2581
+ });
2582
+ }).catch((error) => {
2583
+ if (controller.signal.aborted) {
2584
+ deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
2585
+ return;
2586
+ }
2587
+ deps2.emit?.({
2588
+ type: "techstack.job.failed",
2589
+ payload: { jobId, error: errorMessage(error) }
2590
+ });
2591
+ }).finally(() => {
2592
+ deps2.runningJobs?.delete(jobId);
2593
+ });
2594
+ }
2595
+ function handleTechStackInventory(res, deps2) {
2596
+ startJob(res, deps2, "inventory");
2597
+ }
2598
+ function handleTechStackAnalyze(res, deps2) {
2599
+ startJob(res, deps2, "analyze");
2600
+ }
2601
+ function handleTechStackCancel(res, deps2, jobId) {
2602
+ const controller = deps2.runningJobs?.get(jobId);
2603
+ if (controller && !controller.signal.aborted) controller.abort();
2604
+ deps2.store.updateJobStatus(jobId, "cancelled");
2605
+ deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
2606
+ sendJson2(res, 200, { jobId, status: "cancelled" });
2607
+ }
2608
+ async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
2609
+ const snapshot = deps2.store.getSnapshot(deps2.projectId);
2610
+ const dependency = snapshot?.dependencies.find((dep) => dep.id === dependencyId);
2611
+ if (!dependency) {
2612
+ sendJson2(res, 404, { error: "Dependency not found in the current snapshot" });
2613
+ return;
2614
+ }
2615
+ let researcher;
2616
+ try {
2617
+ researcher = await buildResearcher(deps2, "analyze");
2618
+ } catch (error) {
2619
+ sendJson2(res, 503, { error: "Research unavailable", detail: errorMessage(error) });
2620
+ return;
2621
+ }
2622
+ if (!researcher) {
2623
+ sendJson2(res, 503, {
2624
+ error: "No model configured \u2014 connect a provider to run LLM analysis."
2625
+ });
2626
+ return;
2627
+ }
2628
+ const controller = new AbortController();
2629
+ const timeout = setTimeout(() => {
2630
+ controller.abort(new Error("research timeout"));
2631
+ }, DEEP_DIVE_TIMEOUT_MS);
2632
+ timeout.unref?.();
2633
+ try {
2634
+ const { triageCandidates } = await import("@wrongstack/techstack");
2635
+ const [triaged] = triageCandidates([dependency], { limit: 1 });
2636
+ const findings = await researcher.research(
2637
+ [triaged ?? { dependency, cluster: "breaking_change", priority: 0 }],
2638
+ { signal: controller.signal }
2639
+ );
2640
+ sendJson2(res, 200, { dependencyId, findings });
2641
+ } catch (error) {
2642
+ sendJson2(res, 500, { error: "Research failed", detail: errorMessage(error) });
2643
+ } finally {
2644
+ clearTimeout(timeout);
2645
+ controller.abort();
2646
+ }
2647
+ }
2648
+ function handleTechStackJobStatus(res, deps2, jobId) {
2649
+ const job = deps2.store.getJob(jobId);
2650
+ if (!job) {
2651
+ sendJson2(res, 404, { error: "Job not found" });
2652
+ return;
2653
+ }
2654
+ sendJson2(res, 200, { job });
2655
+ }
2656
+ function handleTechStackReport(res, deps2, reportId, format) {
2657
+ const snapshot = deps2.store.getSnapshotById(reportId);
2658
+ if (!snapshot) {
2659
+ sendJson2(res, 404, { error: "Report not found" });
2660
+ return;
2661
+ }
2662
+ if (deps2.engine) {
2663
+ const report = deps2.engine.generateReport(snapshot, format);
2664
+ res.writeHead(200, {
2665
+ "Content-Type": format === "json" ? "application/json" : "text/markdown",
2666
+ "Content-Disposition": `attachment; filename="techstack-report.${format}"`
2667
+ });
2668
+ res.end(report);
2669
+ } else {
2670
+ sendJson2(res, 200, snapshot);
2671
+ }
2672
+ }
2673
+
2674
+ // src/server/projects-manifest.ts
2675
+ import * as fs5 from "node:fs/promises";
2676
+ import * as path6 from "node:path";
2677
+ import { projectSlug } from "@wrongstack/core";
2678
+ function projectsJsonPath(globalConfigPath) {
2679
+ const base = path6.dirname(globalConfigPath);
2680
+ return path6.join(base, "projects.json");
2681
+ }
2682
+ async function loadManifest(globalConfigPath) {
2683
+ try {
2684
+ const raw = await fs5.readFile(projectsJsonPath(globalConfigPath), "utf8");
2685
+ const parsed = JSON.parse(raw);
2686
+ return { projects: parsed.projects ?? [] };
2687
+ } catch {
2688
+ return { projects: [] };
2689
+ }
2690
+ }
2691
+ async function saveManifest(manifest, globalConfigPath) {
2692
+ const file = projectsJsonPath(globalConfigPath);
2693
+ await fs5.mkdir(path6.dirname(file), { recursive: true });
2694
+ await fs5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
2695
+ }
2696
+ function generateProjectSlug(rootPath) {
2697
+ return projectSlug(rootPath);
2698
+ }
2699
+ async function ensureProjectDataDir(slug, globalConfigPath) {
2700
+ const base = path6.dirname(globalConfigPath);
2701
+ const dir = path6.join(base, "projects", slug);
2702
+ await fs5.mkdir(dir, { recursive: true });
2703
+ return dir;
2704
+ }
2705
+
2461
2706
  // src/server/ws-auth.ts
2462
2707
  import { Buffer as Buffer2 } from "node:buffer";
2463
2708
  import { timingSafeEqual } from "node:crypto";
@@ -2643,9 +2888,9 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2643
2888
  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
2889
  }
2645
2890
  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);
2891
+ const root = path7.resolve(distDir);
2892
+ const resolved = path7.resolve(candidate);
2893
+ return resolved === root || resolved.startsWith(root + path7.sep);
2649
2894
  }
2650
2895
  function decodeSessionId(segment) {
2651
2896
  try {
@@ -2656,10 +2901,19 @@ function decodeSessionId(segment) {
2656
2901
  }
2657
2902
  function createHttpServer(opts) {
2658
2903
  const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
2659
- const distDir = path6.resolve(opts.distDir);
2904
+ const distDir = path7.resolve(opts.distDir);
2660
2905
  const wsPort = opts.wsPort;
2661
2906
  const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
2662
- return http.createServer(async (req, res) => {
2907
+ let techStackRuntime = null;
2908
+ const getTechStackRuntime = async () => {
2909
+ if (!opts.projectRoot) throw new Error("Project root not configured");
2910
+ techStackRuntime ??= import("@wrongstack/techstack").then(({ TechStackEngine, TechStackStore }) => {
2911
+ const store = new TechStackStore({ projectSlug: generateProjectSlug(opts.projectRoot) });
2912
+ return { store, engine: new TechStackEngine(store), runningJobs: /* @__PURE__ */ new Map() };
2913
+ });
2914
+ return techStackRuntime;
2915
+ };
2916
+ const server = http.createServer(async (req, res) => {
2663
2917
  try {
2664
2918
  const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
2665
2919
  const providedAccessToken = requestToken(req, url);
@@ -2810,6 +3064,127 @@ function createHttpServer(opts) {
2810
3064
  await handleApiAnalyticsSummary(res);
2811
3065
  return;
2812
3066
  }
3067
+ if (url.pathname === "/api/codemap/packages" && req.method === "GET") {
3068
+ if (requireAccessToken && !accessTokenOk) {
3069
+ res.writeHead(401, { "Content-Type": "application/json" });
3070
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3071
+ return;
3072
+ }
3073
+ if (!opts.projectRoot) {
3074
+ res.writeHead(503, { "Content-Type": "application/json" });
3075
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3076
+ return;
3077
+ }
3078
+ handleCodemapPackages(res, {
3079
+ projectRoot: opts.projectRoot,
3080
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3081
+ });
3082
+ return;
3083
+ }
3084
+ if (url.pathname === "/api/codemap/files" && req.method === "GET") {
3085
+ if (requireAccessToken && !accessTokenOk) {
3086
+ res.writeHead(401, { "Content-Type": "application/json" });
3087
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3088
+ return;
3089
+ }
3090
+ if (!opts.projectRoot) {
3091
+ res.writeHead(503, { "Content-Type": "application/json" });
3092
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3093
+ return;
3094
+ }
3095
+ const pkg = url.searchParams.get("package") ?? "";
3096
+ handleCodemapFiles(res, {
3097
+ projectRoot: opts.projectRoot,
3098
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3099
+ }, pkg);
3100
+ return;
3101
+ }
3102
+ if (url.pathname === "/api/codemap/symbols" && req.method === "GET") {
3103
+ if (requireAccessToken && !accessTokenOk) {
3104
+ res.writeHead(401, { "Content-Type": "application/json" });
3105
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3106
+ return;
3107
+ }
3108
+ if (!opts.projectRoot) {
3109
+ res.writeHead(503, { "Content-Type": "application/json" });
3110
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3111
+ return;
3112
+ }
3113
+ const file = url.searchParams.get("file") ?? "";
3114
+ handleCodemapSymbols(res, {
3115
+ projectRoot: opts.projectRoot,
3116
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3117
+ }, file);
3118
+ return;
3119
+ }
3120
+ if (url.pathname.startsWith("/api/techstack/")) {
3121
+ if (requireAccessToken && !accessTokenOk) {
3122
+ res.writeHead(401, { "Content-Type": "application/json" });
3123
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3124
+ return;
3125
+ }
3126
+ if (!opts.projectRoot) {
3127
+ res.writeHead(503, { "Content-Type": "application/json" });
3128
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3129
+ return;
3130
+ }
3131
+ try {
3132
+ const runtime = await getTechStackRuntime();
3133
+ const deps2 = {
3134
+ projectId: opts.projectRoot,
3135
+ projectRoot: opts.projectRoot,
3136
+ store: runtime.store,
3137
+ engine: runtime.engine,
3138
+ runningJobs: runtime.runningJobs,
3139
+ emit: opts.onTechStackEvent,
3140
+ getLlm: opts.getLlm
3141
+ };
3142
+ if (url.pathname === "/api/techstack/snapshot" && req.method === "GET") {
3143
+ handleTechStackSnapshot(res, deps2);
3144
+ return;
3145
+ }
3146
+ if (url.pathname === "/api/techstack/inventory" && req.method === "POST") {
3147
+ handleTechStackInventory(res, deps2);
3148
+ return;
3149
+ }
3150
+ if (url.pathname === "/api/techstack/analyze" && req.method === "POST") {
3151
+ handleTechStackAnalyze(res, deps2);
3152
+ return;
3153
+ }
3154
+ const cancelMatch = /^\/api\/techstack\/jobs\/([^/]+)\/cancel$/.exec(url.pathname);
3155
+ if (cancelMatch && req.method === "POST") {
3156
+ handleTechStackCancel(res, deps2, decodeURIComponent(cancelMatch[1]));
3157
+ return;
3158
+ }
3159
+ const jobMatch = /^\/api\/techstack\/jobs\/([^/]+)$/.exec(url.pathname);
3160
+ if (jobMatch && req.method === "GET") {
3161
+ handleTechStackJobStatus(res, deps2, decodeURIComponent(jobMatch[1]));
3162
+ return;
3163
+ }
3164
+ const reportMatch = /^\/api\/techstack\/reports\/([^/]+)$/.exec(url.pathname);
3165
+ if (reportMatch && req.method === "GET") {
3166
+ const fmt = url.searchParams.get("format") === "json" ? "json" : "md";
3167
+ handleTechStackReport(res, deps2, decodeURIComponent(reportMatch[1]), fmt);
3168
+ return;
3169
+ }
3170
+ const researchMatch = /^\/api\/techstack\/deps\/([^/]+)\/research$/.exec(url.pathname);
3171
+ if (researchMatch && req.method === "POST") {
3172
+ await handleTechStackDependencyResearch(
3173
+ res,
3174
+ deps2,
3175
+ decodeURIComponent(researchMatch[1])
3176
+ );
3177
+ return;
3178
+ }
3179
+ } catch (error) {
3180
+ res.writeHead(503, { "Content-Type": "application/json" });
3181
+ res.end(JSON.stringify({
3182
+ error: "TechStack store unavailable",
3183
+ detail: error instanceof Error ? error.message : String(error)
3184
+ }));
3185
+ return;
3186
+ }
3187
+ }
2813
3188
  if (url.pathname === "/debug/watcher-metrics" && req.method === "GET") {
2814
3189
  if (requireAccessToken && !accessTokenOk) {
2815
3190
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -2833,21 +3208,21 @@ function createHttpServer(opts) {
2833
3208
  }
2834
3209
  let filePath;
2835
3210
  if (url.pathname === "/" || url.pathname === "") {
2836
- filePath = path6.join(distDir, "index.html");
3211
+ filePath = path7.join(distDir, "index.html");
2837
3212
  } else if (url.pathname.startsWith("/assets/")) {
2838
- filePath = path6.join(distDir, url.pathname);
3213
+ filePath = path7.join(distDir, url.pathname);
2839
3214
  } else if (url.pathname.startsWith("/")) {
2840
- filePath = path6.join(distDir, url.pathname);
3215
+ filePath = path7.join(distDir, url.pathname);
2841
3216
  } else {
2842
- filePath = path6.join(distDir, "index.html");
3217
+ filePath = path7.join(distDir, "index.html");
2843
3218
  }
2844
- const resolvedPath = path6.resolve(filePath);
3219
+ const resolvedPath = path7.resolve(filePath);
2845
3220
  if (!isInsideDist(resolvedPath, distDir)) {
2846
3221
  res.writeHead(403, { "Content-Type": "text/plain" });
2847
3222
  res.end("Forbidden");
2848
3223
  return;
2849
3224
  }
2850
- const ext = path6.extname(resolvedPath);
3225
+ const ext = path7.extname(resolvedPath);
2851
3226
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
2852
3227
  res.setHeader("Content-Type", contentType);
2853
3228
  res.setHeader("X-Content-Type-Options", "nosniff");
@@ -2859,18 +3234,18 @@ function createHttpServer(opts) {
2859
3234
  "Content-Security-Policy",
2860
3235
  buildCspHeader(wsPort, requestHostForCsp(req.headers.host), opts.publicWsUrl)
2861
3236
  );
2862
- const html = await fs5.readFile(resolvedPath, "utf8");
3237
+ const html = await fs6.readFile(resolvedPath, "utf8");
2863
3238
  res.writeHead(200);
2864
3239
  res.end(injectWsConfig(html, { wsPort, publicWsUrl: opts.publicWsUrl }));
2865
3240
  return;
2866
3241
  }
2867
- const fileContent = await fs5.readFile(resolvedPath);
3242
+ const fileContent = await fs6.readFile(resolvedPath);
2868
3243
  res.writeHead(200);
2869
3244
  res.end(fileContent);
2870
3245
  } catch (err) {
2871
3246
  if (err.code === "ENOENT") {
2872
3247
  try {
2873
- const html = await fs5.readFile(path6.join(distDir, "index.html"), "utf8");
3248
+ const html = await fs6.readFile(path7.join(distDir, "index.html"), "utf8");
2874
3249
  res.writeHead(200, {
2875
3250
  "Content-Type": "text/html",
2876
3251
  "X-Content-Type-Options": "nosniff",
@@ -2893,18 +3268,26 @@ function createHttpServer(opts) {
2893
3268
  }
2894
3269
  }
2895
3270
  });
3271
+ server.once("close", () => {
3272
+ void techStackRuntime?.then(({ store, runningJobs }) => {
3273
+ for (const controller of runningJobs.values()) controller.abort();
3274
+ runningJobs.clear();
3275
+ store.close();
3276
+ }).catch(() => void 0);
3277
+ });
3278
+ return server;
2896
3279
  }
2897
3280
 
2898
3281
  // src/server/instance-registry.ts
2899
3282
  import * as os from "node:os";
2900
- import * as path7 from "node:path";
2901
- import * as fs6 from "node:fs/promises";
3283
+ import * as path8 from "node:path";
3284
+ import * as fs7 from "node:fs/promises";
2902
3285
  import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
2903
3286
  function defaultBaseDir() {
2904
- return path7.join(os.homedir(), ".wrongstack");
3287
+ return path8.join(os.homedir(), ".wrongstack");
2905
3288
  }
2906
3289
  function registryPath(baseDir = defaultBaseDir()) {
2907
- return path7.join(baseDir, "webui-instances.json");
3290
+ return path8.join(baseDir, "webui-instances.json");
2908
3291
  }
2909
3292
  function isPidAlive(pid) {
2910
3293
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -2917,7 +3300,7 @@ function isPidAlive(pid) {
2917
3300
  }
2918
3301
  async function load(file) {
2919
3302
  try {
2920
- const raw = await fs6.readFile(file, "utf8");
3303
+ const raw = await fs7.readFile(file, "utf8");
2921
3304
  const parsed = JSON.parse(raw);
2922
3305
  if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
2923
3306
  return parsed;
@@ -3210,7 +3593,7 @@ async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
3210
3593
  payload: { name: serverName, resources, resourceTemplates }
3211
3594
  });
3212
3595
  } catch (err) {
3213
- sendContentError(ws, "resources", serverName, errorMessage(err));
3596
+ sendContentError(ws, "resources", serverName, errorMessage2(err));
3214
3597
  }
3215
3598
  }
3216
3599
  async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3224,7 +3607,7 @@ async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
3224
3607
  });
3225
3608
  send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
3226
3609
  } catch (err) {
3227
- sendContentError(ws, "prompts", serverName, errorMessage(err));
3610
+ sendContentError(ws, "prompts", serverName, errorMessage2(err));
3228
3611
  }
3229
3612
  }
3230
3613
  async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3240,7 +3623,7 @@ async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
3240
3623
  );
3241
3624
  send(ws, { type: "mcp.content.selected", payload: insertion });
3242
3625
  } catch (err) {
3243
- sendContentError(ws, "resource.read", serverName, errorMessage(err));
3626
+ sendContentError(ws, "resource.read", serverName, errorMessage2(err));
3244
3627
  }
3245
3628
  }
3246
3629
  async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3256,7 +3639,7 @@ async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
3256
3639
  );
3257
3640
  send(ws, { type: "mcp.content.selected", payload: insertion });
3258
3641
  } catch (err) {
3259
- sendContentError(ws, "prompt.get", serverName, errorMessage(err));
3642
+ sendContentError(ws, "prompt.get", serverName, errorMessage2(err));
3260
3643
  }
3261
3644
  }
3262
3645
  function payloadRecord(msg) {
@@ -3284,14 +3667,14 @@ function promptArguments(value) {
3284
3667
  function sendContentError(ws, action, name2, error) {
3285
3668
  send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
3286
3669
  }
3287
- function errorMessage(err) {
3670
+ function errorMessage2(err) {
3288
3671
  return err instanceof Error ? err.message : String(err);
3289
3672
  }
3290
3673
 
3291
3674
  // src/server/memory-handlers.ts
3292
3675
  function isSuperMemoryStore(store) {
3293
3676
  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";
3677
+ 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
3678
  }
3296
3679
  function requiresSuperMemory(command) {
3297
3680
  return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
@@ -3415,6 +3798,7 @@ async function handleSuperMemoryRemember(ws, msg, memoryStore) {
3415
3798
  importance: payload["importance"],
3416
3799
  confidence: payload["confidence"],
3417
3800
  freshness: payload["freshness"],
3801
+ audience: payload["audience"],
3418
3802
  supersedes: payload["supersedes"],
3419
3803
  contradicts: payload["contradicts"]
3420
3804
  });
@@ -3428,18 +3812,172 @@ async function handleSuperMemoryDelete(ws, msg, memoryStore) {
3428
3812
  send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
3429
3813
  return;
3430
3814
  }
3431
- const { id, reason } = msg.payload;
3815
+ const { id, reason, neverInject } = msg.payload;
3432
3816
  if (!id) {
3433
3817
  send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
3434
3818
  return;
3435
3819
  }
3436
3820
  try {
3437
- await memoryStore.deleteSuperMemory(id, reason);
3821
+ if (neverInject === true) await memoryStore.deleteSuperMemory(id, reason, { neverInject: true });
3822
+ else await memoryStore.deleteSuperMemory(id, reason);
3438
3823
  send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
3439
3824
  } catch (err) {
3440
3825
  send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
3441
3826
  }
3442
3827
  }
3828
+ async function handleSuperMemoryRecover(ws, msg, memoryStore) {
3829
+ if (!isSuperMemoryStore(memoryStore)) {
3830
+ send(ws, { type: "memory.super.recover", payload: { error: requiresSuperMemory("memory.super.recover") } });
3831
+ return;
3832
+ }
3833
+ const payload = msg.payload;
3834
+ const id = payload["id"];
3835
+ if (!id) {
3836
+ send(ws, { type: "memory.super.recover", payload: { error: "id is required" } });
3837
+ return;
3838
+ }
3839
+ const reason = payload["reason"];
3840
+ try {
3841
+ const preExisting = await memoryStore.getSuperMemory(id);
3842
+ if (!preExisting) {
3843
+ send(ws, { type: "memory.super.recover", payload: { error: `Super Memory "${id}" not found.` } });
3844
+ return;
3845
+ }
3846
+ if (preExisting.status === "active") {
3847
+ send(ws, { type: "memory.super.recover", payload: { recovered: true, memory: preExisting, noop: true } });
3848
+ return;
3849
+ }
3850
+ const memory = await memoryStore.recoverSuperMemory(id, reason);
3851
+ const noop = memory.id !== id;
3852
+ const response = { recovered: true, memory };
3853
+ if (noop) {
3854
+ response["activeId"] = memory.id;
3855
+ response["noop"] = true;
3856
+ }
3857
+ send(ws, { type: "memory.super.recover", payload: response });
3858
+ } catch (err) {
3859
+ send(ws, { type: "memory.super.recover", payload: { error: errMessage(err) } });
3860
+ }
3861
+ }
3862
+ async function handleSuperMemoryCandidateResolve(ws, msg, memoryStore) {
3863
+ if (!isSuperMemoryStore(memoryStore)) {
3864
+ send(ws, {
3865
+ type: "memory.super.candidateResolve",
3866
+ payload: { error: requiresSuperMemory("memory.super.candidateResolve") }
3867
+ });
3868
+ return;
3869
+ }
3870
+ const payload = msg.payload;
3871
+ const candidateId = payload["candidateId"];
3872
+ const action = payload["action"];
3873
+ if (!candidateId) {
3874
+ send(ws, {
3875
+ type: "memory.super.candidateResolve",
3876
+ payload: { error: "candidateId is required" }
3877
+ });
3878
+ return;
3879
+ }
3880
+ if (action !== "accept" && action !== "reject") {
3881
+ send(ws, {
3882
+ type: "memory.super.candidateResolve",
3883
+ payload: { error: 'action must be "accept" or "reject"' }
3884
+ });
3885
+ return;
3886
+ }
3887
+ const reason = payload["reason"];
3888
+ try {
3889
+ let candidate;
3890
+ if (action === "accept") {
3891
+ const accepted = await memoryStore.acceptCandidate(candidateId);
3892
+ candidate = accepted ? { id: accepted.id, status: accepted.status ?? "active" } : void 0;
3893
+ } else {
3894
+ const rejected = await memoryStore.rejectCandidate(
3895
+ candidateId,
3896
+ reason ?? "Rejected via WebUI"
3897
+ );
3898
+ candidate = rejected ? { id: candidateId, status: "rejected" } : void 0;
3899
+ }
3900
+ if (!candidate) {
3901
+ send(ws, {
3902
+ type: "memory.super.candidateResolve",
3903
+ payload: { error: `Candidate "${candidateId}" not found` }
3904
+ });
3905
+ return;
3906
+ }
3907
+ send(ws, {
3908
+ type: "memory.super.candidateResolve",
3909
+ payload: { candidate, resolvedAction: action }
3910
+ });
3911
+ } catch (err) {
3912
+ send(ws, {
3913
+ type: "memory.super.candidateResolve",
3914
+ payload: { error: errMessage(err) }
3915
+ });
3916
+ }
3917
+ }
3918
+ async function handleSuperMemoryBackfillRecoverable(ws, msg, memoryStore) {
3919
+ if (!isSuperMemoryStore(memoryStore)) {
3920
+ send(ws, {
3921
+ type: "memory.super.backfillRecoverable",
3922
+ payload: { error: requiresSuperMemory("memory.super.backfillRecoverable") }
3923
+ });
3924
+ return;
3925
+ }
3926
+ const payload = msg.payload ?? {};
3927
+ const apply = payload["apply"] === true;
3928
+ const rawFilter = payload["filter"] ?? {};
3929
+ const filter = {};
3930
+ if (Array.isArray(rawFilter["kinds"])) filter.kinds = rawFilter["kinds"];
3931
+ if (Array.isArray(rawFilter["scopes"])) filter.scopes = rawFilter["scopes"];
3932
+ if (typeof rawFilter["updatedAfter"] === "string") filter.updatedAfter = rawFilter["updatedAfter"];
3933
+ if (typeof rawFilter["updatedBefore"] === "string") filter.updatedBefore = rawFilter["updatedBefore"];
3934
+ try {
3935
+ const report = await memoryStore.backfillRecoverable({
3936
+ apply,
3937
+ ...Object.keys(filter).length > 0 ? { filter } : {}
3938
+ });
3939
+ send(ws, {
3940
+ type: "memory.super.backfillRecoverable",
3941
+ payload: {
3942
+ examined: report.examined,
3943
+ recovered: report.recovered,
3944
+ recoverable: report.recoverable,
3945
+ dryRun: !apply
3946
+ }
3947
+ });
3948
+ } catch (err) {
3949
+ send(ws, {
3950
+ type: "memory.super.backfillRecoverable",
3951
+ payload: { error: errMessage(err) }
3952
+ });
3953
+ }
3954
+ }
3955
+ async function handleSuperMemoryForFile(ws, msg, memoryStore) {
3956
+ if (!isSuperMemoryStore(memoryStore)) {
3957
+ send(ws, {
3958
+ type: "memory.super.forFile",
3959
+ payload: { error: requiresSuperMemory("memory.super.forFile") }
3960
+ });
3961
+ return;
3962
+ }
3963
+ const payload = msg.payload ?? {};
3964
+ const filePath = payload["filePath"];
3965
+ if (!filePath) {
3966
+ send(ws, { type: "memory.super.forFile", payload: { error: "filePath is required" } });
3967
+ return;
3968
+ }
3969
+ try {
3970
+ const response = await memoryStore.findMemoriesForFile(filePath, {
3971
+ ...typeof payload["lineStart"] === "number" ? { lineStart: payload["lineStart"] } : {},
3972
+ ...typeof payload["lineEnd"] === "number" ? { lineEnd: payload["lineEnd"] } : {},
3973
+ ...typeof payload["limit"] === "number" ? { limit: payload["limit"] } : {},
3974
+ ...payload["includeDeleted"] === true ? { includeDeleted: true } : {}
3975
+ });
3976
+ send(ws, { type: "memory.super.forFile", payload: response });
3977
+ } catch (err) {
3978
+ send(ws, { type: "memory.super.forFile", payload: { error: errMessage(err) } });
3979
+ }
3980
+ }
3443
3981
 
3444
3982
  // src/server/open-browser.ts
3445
3983
  import { spawn } from "node:child_process";
@@ -3488,16 +4026,16 @@ function openBrowser(url, platform = process.platform) {
3488
4026
  import * as net from "node:net";
3489
4027
  import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
3490
4028
  function isPortFree(host, port) {
3491
- return new Promise((resolve10) => {
4029
+ return new Promise((resolve12) => {
3492
4030
  const srv = net.createServer();
3493
- srv.once("error", () => resolve10(false));
4031
+ srv.once("error", () => resolve12(false));
3494
4032
  srv.once("listening", () => {
3495
- srv.close(() => resolve10(true));
4033
+ srv.close(() => resolve12(true));
3496
4034
  });
3497
4035
  try {
3498
4036
  srv.listen(port, host);
3499
4037
  } catch {
3500
- resolve10(false);
4038
+ resolve12(false);
3501
4039
  }
3502
4040
  });
3503
4041
  }
@@ -3724,13 +4262,13 @@ async function handlePromptsRecent(ws, ctx) {
3724
4262
  }
3725
4263
 
3726
4264
  // src/server/provider-config-io.ts
3727
- import * as fs7 from "node:fs/promises";
4265
+ import * as fs8 from "node:fs/promises";
3728
4266
  import { ConfigError, atomicWrite as atomicWrite4 } from "@wrongstack/core";
3729
4267
  import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
3730
4268
  async function loadSavedProviders(configPath, vault) {
3731
4269
  let raw;
3732
4270
  try {
3733
- raw = await fs7.readFile(configPath, "utf8");
4271
+ raw = await fs8.readFile(configPath, "utf8");
3734
4272
  } catch {
3735
4273
  return {};
3736
4274
  }
@@ -3747,7 +4285,7 @@ async function saveProviders(configPath, vault, providers) {
3747
4285
  let raw;
3748
4286
  let fileExists = true;
3749
4287
  try {
3750
- raw = await fs7.readFile(configPath, "utf8");
4288
+ raw = await fs8.readFile(configPath, "utf8");
3751
4289
  } catch (err) {
3752
4290
  if (err.code !== "ENOENT") {
3753
4291
  throw new ConfigError({
@@ -3781,6 +4319,10 @@ async function saveProviders(configPath, vault, providers) {
3781
4319
 
3782
4320
  // src/server/provider-keys.ts
3783
4321
  import { expectDefined } from "@wrongstack/core";
4322
+ import {
4323
+ buildProviderConfigFromPreset,
4324
+ resolvePresetForAlias
4325
+ } from "@wrongstack/providers";
3784
4326
  function normalizeKeys(cfg) {
3785
4327
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
3786
4328
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -3809,8 +4351,28 @@ function maskedKey(key) {
3809
4351
  if (key.length <= 8) return "\u2022".repeat(key.length);
3810
4352
  return `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
3811
4353
  }
4354
+ function hydratePresetConfig(providerId, dest) {
4355
+ const preset = resolvePresetForAlias(providerId);
4356
+ if (!preset) return void 0;
4357
+ const template = buildProviderConfigFromPreset(preset);
4358
+ if (!dest.type) dest.type = preset.id;
4359
+ if (!dest.family) dest.family = preset.family;
4360
+ if (dest.baseUrl === void 0) dest.baseUrl = template.baseUrl;
4361
+ if (!dest.envVars || dest.envVars.length === 0) dest.envVars = template.envVars;
4362
+ if (!dest.models || dest.models.length === 0) dest.models = template.models;
4363
+ if (template.customModels && (!dest.customModels || Object.keys(dest.customModels).length === 0)) {
4364
+ dest.customModels = template.customModels;
4365
+ }
4366
+ if (template.quirks && dest.quirks === void 0) dest.quirks = template.quirks;
4367
+ return preset.id;
4368
+ }
3812
4369
  function upsertKey(providers, providerId, label, apiKey, nowIso) {
3813
- const existing = providers[providerId] ?? { type: providerId };
4370
+ let existing = providers[providerId];
4371
+ if (!existing) {
4372
+ existing = { type: providerId };
4373
+ const presetId = hydratePresetConfig(providerId, existing);
4374
+ if (presetId) existing.type = presetId;
4375
+ }
3814
4376
  const keys = normalizeKeys(existing);
3815
4377
  const idx = keys.findIndex((k) => k.label === label);
3816
4378
  if (idx >= 0) {
@@ -3860,6 +4422,8 @@ function addProvider(providers, payload, nowIso) {
3860
4422
  family: payload.family,
3861
4423
  baseUrl: payload.baseUrl
3862
4424
  };
4425
+ const presetId = hydratePresetConfig(payload.id, newProv);
4426
+ if (presetId) newProv.type = presetId;
3863
4427
  if (payload.apiKey) {
3864
4428
  newProv.apiKeys = [{ label: "default", apiKey: payload.apiKey, createdAt: nowIso }];
3865
4429
  newProv.activeKey = "default";
@@ -4026,7 +4590,7 @@ var SddBoardWebSocketHandler = class {
4026
4590
  };
4027
4591
 
4028
4592
  // src/server/sdd-wizard-wiring.ts
4029
- import * as path8 from "node:path";
4593
+ import * as path9 from "node:path";
4030
4594
  import { spawnSync as spawnSync2 } from "node:child_process";
4031
4595
  import {
4032
4596
  DefaultTaskStore,
@@ -4126,7 +4690,7 @@ function buildSddWizardDeps(opts) {
4126
4690
  makeDriver: () => new SddInterviewDriver({
4127
4691
  specStore: new SpecStore({ baseDir: opts.paths.projectSpecs }),
4128
4692
  graphStore: new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs }),
4129
- sessionPath: path8.join(opts.paths.projectDir, "sdd-wizard-session.json")
4693
+ sessionPath: path9.join(opts.paths.projectDir, "sdd-wizard-session.json")
4130
4694
  }),
4131
4695
  runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
4132
4696
  startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels, worktrees: useWorktrees }) => {
@@ -4328,8 +4892,8 @@ function toSessionHistoryEntries(summaries, currentSessionId) {
4328
4892
  }
4329
4893
 
4330
4894
  // src/server/shell-open.ts
4331
- import * as fs8 from "node:fs/promises";
4332
- import * as path9 from "node:path";
4895
+ import * as fs9 from "node:fs/promises";
4896
+ import * as path10 from "node:path";
4333
4897
  import { spawn as spawn2 } from "node:child_process";
4334
4898
  var METACHAR_REGEX = /[&|<>^"'`'\n\r]/;
4335
4899
  function shellQuote(s) {
@@ -4337,8 +4901,8 @@ function shellQuote(s) {
4337
4901
  }
4338
4902
  async function handleShellOpen(req, logger) {
4339
4903
  try {
4340
- const resolved = path9.resolve(req.path);
4341
- await fs8.access(resolved);
4904
+ const resolved = path10.resolve(req.path);
4905
+ await fs9.access(resolved);
4342
4906
  if (METACHAR_REGEX.test(resolved)) {
4343
4907
  return { success: false, message: "Path contains unsupported characters." };
4344
4908
  }
@@ -4389,12 +4953,13 @@ async function handleShellOpen(req, logger) {
4389
4953
  }
4390
4954
 
4391
4955
  // src/server/skills-handlers.ts
4392
- import { promises as fs9 } from "node:fs";
4393
- import path10 from "node:path";
4956
+ import { promises as fs10 } from "node:fs";
4957
+ import path11 from "node:path";
4394
4958
  import { atomicWrite as atomicWrite5 } from "@wrongstack/core";
4395
4959
  import { wstackGlobalRoot } from "@wrongstack/core/utils";
4396
4960
 
4397
4961
  // src/server/ws-payload-validation.ts
4962
+ import { FORBIDDEN_PROTO_KEYS } from "@wrongstack/core/utils";
4398
4963
  function isRecord(value) {
4399
4964
  return typeof value === "object" && value !== null && !Array.isArray(value);
4400
4965
  }
@@ -4600,11 +5165,22 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
4600
5165
  "fallbackAuto",
4601
5166
  "favoriteModelsOnly",
4602
5167
  "breakerEnabled",
4603
- "debugStream"
5168
+ "debugStream",
5169
+ // Chimera + auto-review master toggles
5170
+ "chimeraEnabled",
5171
+ "autoReviewEnabled",
5172
+ "showModelReasoning"
5173
+ ]);
5174
+ var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set([
5175
+ "fallbackModels",
5176
+ "favoriteModels",
5177
+ // Auto-review explicit fallback chain (derived when fallbackProfile is unset;
5178
+ // surfaced for visibility/override).
5179
+ "autoReviewFallbackModels"
4604
5180
  ]);
4605
- var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
4606
5181
  var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
4607
5182
  var MODEL_MATRIX_PREF_KEYS = /* @__PURE__ */ new Set(["modelMatrix"]);
5183
+ var BOOLEAN_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["pluginsEnabled"]);
4608
5184
  var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4609
5185
  "autonomyDelayMs",
4610
5186
  "autoProceedMaxIterations",
@@ -4612,7 +5188,12 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4612
5188
  "maxConcurrent",
4613
5189
  "enhanceDelayMs",
4614
5190
  "tgLongToolMs",
4615
- "breakerAutoKillResetMs"
5191
+ "breakerAutoKillResetMs",
5192
+ // Chimera + auto-review numeric knobs
5193
+ "chimeraMaxFiles",
5194
+ "autoReviewDebounceMs",
5195
+ "autoReviewMaxFilesPerBatch",
5196
+ "autoReviewMaxConcurrentReviews"
4616
5197
  ]);
4617
5198
  var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4618
5199
  "hqUrl",
@@ -4621,7 +5202,13 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4621
5202
  "thinkingWord",
4622
5203
  "refinerProvider",
4623
5204
  "refinerModel",
4624
- "refinerFallbackProfile"
5205
+ "refinerFallbackProfile",
5206
+ // Chimera + auto-review override strings
5207
+ "chimeraProvider",
5208
+ "chimeraModel",
5209
+ "autoReviewProvider",
5210
+ "autoReviewModel",
5211
+ "autoReviewFallbackProfile"
4625
5212
  ]);
4626
5213
  var ENUM_PREF_KEYS = {
4627
5214
  autonomy: AUTONOMY_VALUES,
@@ -4636,36 +5223,39 @@ var ENUM_PREF_KEYS = {
4636
5223
  cacheTtl: CACHE_TTL_VALUES,
4637
5224
  statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
4638
5225
  animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
4639
- fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
5226
+ fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"]),
5227
+ // Chimera autoFix + auto-review cascade threshold
5228
+ chimeraAutoFix: /* @__PURE__ */ new Set(["off", "ask", "auto"]),
5229
+ autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"])
4640
5230
  };
4641
- function validateModelRuntimeValue(modelRuntime, path22) {
5231
+ function validateModelRuntimeValue(modelRuntime, path23) {
4642
5232
  const reasoning = modelRuntime["reasoning"];
4643
5233
  if (reasoning !== void 0) {
4644
- if (!isRecord(reasoning)) return `${path22}.reasoning must be an object when provided`;
5234
+ if (!isRecord(reasoning)) return `${path23}.reasoning must be an object when provided`;
4645
5235
  const mode = reasoning["mode"];
4646
5236
  const effort = reasoning["effort"];
4647
5237
  const preserve = reasoning["preserve"];
4648
5238
  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(", ")}`;
5239
+ return `${path23}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
4650
5240
  }
4651
5241
  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(", ")}`;
5242
+ return `${path23}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
4653
5243
  }
4654
5244
  if (preserve !== void 0 && typeof preserve !== "boolean") {
4655
- return `${path22}.reasoning.preserve must be a boolean when provided`;
5245
+ return `${path23}.reasoning.preserve must be a boolean when provided`;
4656
5246
  }
4657
5247
  }
4658
5248
  const cache = modelRuntime["cache"];
4659
5249
  if (cache !== void 0) {
4660
- if (!isRecord(cache)) return `${path22}.cache must be an object when provided`;
5250
+ if (!isRecord(cache)) return `${path23}.cache must be an object when provided`;
4661
5251
  const ttl = cache["ttl"];
4662
5252
  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`;
5253
+ return `${path23}.cache.ttl must be one of: 5m, 1h`;
4664
5254
  }
4665
5255
  }
4666
5256
  const parameters = modelRuntime["parameters"];
4667
5257
  if (parameters !== void 0 && !isRecord(parameters)) {
4668
- return `${path22}.parameters must be an object when provided`;
5258
+ return `${path23}.parameters must be an object when provided`;
4669
5259
  }
4670
5260
  return null;
4671
5261
  }
@@ -4687,6 +5277,16 @@ function validatePreferenceValue(key, value) {
4687
5277
  (v) => Array.isArray(v) && v.every((item) => typeof item === "string")
4688
5278
  ) ? null : `prefs.update payload.${key} must be an object of string arrays`;
4689
5279
  }
5280
+ if (BOOLEAN_RECORD_PREF_KEYS.has(key)) {
5281
+ if (!isRecord(value) || !Object.values(value).every((v) => typeof v === "boolean")) {
5282
+ return `prefs.update payload.${key} must be an object of booleans`;
5283
+ }
5284
+ const badKey = Object.keys(value).find((k) => FORBIDDEN_PROTO_KEYS.has(k));
5285
+ if (badKey) {
5286
+ return `prefs.update payload.${key} contains a forbidden key: ${badKey}`;
5287
+ }
5288
+ return null;
5289
+ }
4690
5290
  if (MODEL_MATRIX_PREF_KEYS.has(key)) {
4691
5291
  if (!isRecord(value)) return `prefs.update payload.${key} must be an object`;
4692
5292
  for (const entry of Object.values(value)) {
@@ -4945,8 +5545,8 @@ function validateShellOpenPayload(payload) {
4945
5545
  if (!isRecord(payload)) {
4946
5546
  return { ok: false, message: "shell.open payload must be an object with string path" };
4947
5547
  }
4948
- const path22 = payload["path"];
4949
- if (typeof path22 !== "string" || path22.trim().length === 0) {
5548
+ const path23 = payload["path"];
5549
+ if (typeof path23 !== "string" || path23.trim().length === 0) {
4950
5550
  return { ok: false, message: "shell.open payload.path must be a non-empty string" };
4951
5551
  }
4952
5552
  const target = payload["target"];
@@ -4959,7 +5559,7 @@ function validateShellOpenPayload(payload) {
4959
5559
  return {
4960
5560
  ok: true,
4961
5561
  value: {
4962
- path: path22,
5562
+ path: path23,
4963
5563
  ...target !== void 0 ? { target } : {}
4964
5564
  }
4965
5565
  };
@@ -4968,14 +5568,14 @@ function validateGitDiffPayload(payload) {
4968
5568
  if (!isRecord(payload)) {
4969
5569
  return { ok: false, message: "git.diff payload must be an object" };
4970
5570
  }
4971
- const path22 = payload["path"];
4972
- if (path22 === void 0 || path22 === null) {
5571
+ const path23 = payload["path"];
5572
+ if (path23 === void 0 || path23 === null) {
4973
5573
  return { ok: true, value: { path: "" } };
4974
5574
  }
4975
- if (typeof path22 !== "string") {
5575
+ if (typeof path23 !== "string") {
4976
5576
  return { ok: false, message: "git.diff payload.path must be a string when provided" };
4977
5577
  }
4978
- return { ok: true, value: { path: path22 } };
5578
+ return { ok: true, value: { path: path23 } };
4979
5579
  }
4980
5580
 
4981
5581
  // src/server/zip.ts
@@ -5121,19 +5721,19 @@ async function handleSkillsContent(ws, ctx, msg) {
5121
5721
  send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
5122
5722
  return;
5123
5723
  }
5124
- const body = await fs9.readFile(entry.path, "utf8");
5125
- const skillDir = path10.dirname(entry.path);
5724
+ const body = await fs10.readFile(entry.path, "utf8");
5725
+ const skillDir = path11.dirname(entry.path);
5126
5726
  let relatedFiles = [];
5127
5727
  try {
5128
- const files = await fs9.readdir(skillDir);
5129
- relatedFiles = files.filter((f) => f !== path10.basename(entry.path)).map((f) => path10.join(skillDir, f));
5728
+ const files = await fs10.readdir(skillDir);
5729
+ relatedFiles = files.filter((f) => f !== path11.basename(entry.path)).map((f) => path11.join(skillDir, f));
5130
5730
  } catch {
5131
5731
  }
5132
5732
  const nameLower = name2.toLowerCase();
5133
5733
  const refResults = await Promise.all(
5134
5734
  entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
5135
5735
  try {
5136
- const content = await fs9.readFile(e.path, "utf8");
5736
+ const content = await fs10.readFile(e.path, "utf8");
5137
5737
  return [e.name, content.toLowerCase().includes(nameLower)];
5138
5738
  } catch {
5139
5739
  return [e.name, false];
@@ -5223,20 +5823,20 @@ async function handleSkillsCreate(ws, ctx, msg) {
5223
5823
  }
5224
5824
  const createPayload = parsed.value;
5225
5825
  try {
5226
- const targetDir = createPayload.scope === "global" ? path10.join(
5227
- ctx.globalSkillsDir ?? path10.join(wstackGlobalRoot(), "skills"),
5826
+ const targetDir = createPayload.scope === "global" ? path11.join(
5827
+ ctx.globalSkillsDir ?? path11.join(wstackGlobalRoot(), "skills"),
5228
5828
  createPayload.name.trim()
5229
- ) : path10.join(
5230
- ctx.projectSkillsDir ?? path10.join(ctx.projectRoot, ".wrongstack", "skills"),
5829
+ ) : path11.join(
5830
+ ctx.projectSkillsDir ?? path11.join(ctx.projectRoot, ".wrongstack", "skills"),
5231
5831
  createPayload.name.trim()
5232
5832
  );
5233
5833
  try {
5234
- await fs9.access(targetDir);
5834
+ await fs10.access(targetDir);
5235
5835
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
5236
5836
  return;
5237
5837
  } catch {
5238
5838
  }
5239
- await fs9.mkdir(targetDir, { recursive: true });
5839
+ await fs10.mkdir(targetDir, { recursive: true });
5240
5840
  const lines = createPayload.description.trim().split("\n");
5241
5841
  const firstLine = (lines[0] ?? "").trim();
5242
5842
  const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
@@ -5284,13 +5884,13 @@ ${trigger}
5284
5884
  "- `bug-hunter` \u2014 for systematic bug detection patterns",
5285
5885
  "- `output-standards` \u2014 for standardized `<nextsteps>` formatting"
5286
5886
  ].join("\n");
5287
- await atomicWrite5(path10.join(targetDir, "SKILL.md"), skillContent);
5887
+ await atomicWrite5(path11.join(targetDir, "SKILL.md"), skillContent);
5288
5888
  send(ws, {
5289
5889
  type: "skills.created",
5290
5890
  payload: {
5291
5891
  success: true,
5292
5892
  error: null,
5293
- skill: { name: createPayload.name.trim(), path: path10.join(targetDir, "SKILL.md"), scope: createPayload.scope }
5893
+ skill: { name: createPayload.name.trim(), path: path11.join(targetDir, "SKILL.md"), scope: createPayload.scope }
5294
5894
  }
5295
5895
  });
5296
5896
  } catch (err) {
@@ -5567,7 +6167,7 @@ function estimateContextBreakdown(input) {
5567
6167
  }
5568
6168
 
5569
6169
  // src/server/worktree-ws-handler.ts
5570
- import { join as join7, resolve as resolve6, sep as sep3 } from "node:path";
6170
+ import { join as join8, resolve as resolve6, sep as sep3 } from "node:path";
5571
6171
  import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core";
5572
6172
  import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
5573
6173
  import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
@@ -5628,7 +6228,7 @@ var WorktreeWebSocketHandler = class {
5628
6228
  // ── orphan management ─────────────────────────────────────────────────────
5629
6229
  /** Absolute managed-worktrees root for this project. */
5630
6230
  worktreesRoot() {
5631
- return resolve6(join7(this.management.projectRoot, ".wrongstack", "worktrees"));
6231
+ return resolve6(join8(this.management.projectRoot, ".wrongstack", "worktrees"));
5632
6232
  }
5633
6233
  /** True iff `dir` resolves strictly inside the managed worktrees root. */
5634
6234
  underRoot(dir) {
@@ -5899,7 +6499,7 @@ var WorktreeWebSocketHandler = class {
5899
6499
  };
5900
6500
 
5901
6501
  // src/server/server-runtime.ts
5902
- import * as path12 from "node:path";
6502
+ import * as path14 from "node:path";
5903
6503
  import { createRequire } from "node:module";
5904
6504
  import { fileURLToPath } from "node:url";
5905
6505
  import { WebSocketServer } from "ws";
@@ -5941,12 +6541,103 @@ function registerShutdownHandlers(res) {
5941
6541
  }
5942
6542
 
5943
6543
  // src/server/setup-events.ts
5944
- import * as fs10 from "node:fs/promises";
5945
6544
  import { watch as fsWatch } from "node:fs";
5946
- import * as path11 from "node:path";
6545
+ import * as fs11 from "node:fs/promises";
6546
+ import * as path13 from "node:path";
6547
+ import { getBoard, getKanbanDir } from "@wrongstack/kanban";
6548
+
6549
+ // src/server/codemap-telemetry.ts
6550
+ import * as path12 from "node:path";
6551
+ var TOOL_OPERATION = {
6552
+ read: "read",
6553
+ read_file: "read",
6554
+ view: "read",
6555
+ write: "write",
6556
+ write_file: "write",
6557
+ create_file: "write",
6558
+ edit: "edit",
6559
+ replace: "edit",
6560
+ patch: "edit",
6561
+ apply_patch: "edit",
6562
+ delete: "delete",
6563
+ delete_file: "delete",
6564
+ remove: "delete",
6565
+ unlink: "delete",
6566
+ grep: "search",
6567
+ search: "search",
6568
+ codebase_search: "search",
6569
+ "codebase-search": "search"
6570
+ };
6571
+ function numberField(input, names) {
6572
+ for (const name2 of names) {
6573
+ const value = input[name2];
6574
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.floor(value);
6575
+ }
6576
+ return void 0;
6577
+ }
6578
+ function normalizeTarget(projectRoot, filePath) {
6579
+ return path12.normalize(path12.isAbsolute(filePath) ? filePath : path12.resolve(projectRoot, filePath));
6580
+ }
6581
+ function normalizeCodeMapFileTarget(projectRoot, filePath, operation = "edit", line, endLine) {
6582
+ return {
6583
+ filePath: normalizeTarget(projectRoot, filePath),
6584
+ operation: operation === "rename" ? "edit" : operation,
6585
+ ...line ? { line } : {},
6586
+ ...endLine ? { endLine } : {}
6587
+ };
6588
+ }
6589
+ function patchTargets(patch) {
6590
+ const targets = [];
6591
+ for (const line of patch.split(/\r?\n/)) {
6592
+ const match = /^\+\+\+\s+(?:b\/)?(.+?)(?:\t.*)?$/.exec(line);
6593
+ const target = match?.[1]?.trim();
6594
+ if (target && target !== "/dev/null") targets.push(target);
6595
+ }
6596
+ return targets;
6597
+ }
6598
+ function extractCodeMapFileTargets(projectRoot, toolName, rawInput) {
6599
+ const operation = TOOL_OPERATION[toolName.toLowerCase()];
6600
+ if (!operation || !rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) return [];
6601
+ const input = rawInput;
6602
+ const rawPaths = [];
6603
+ for (const key of ["path", "file", "filePath", "target"]) {
6604
+ const value = input[key];
6605
+ if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
6606
+ }
6607
+ const files = input["files"];
6608
+ if (Array.isArray(files)) {
6609
+ for (const value of files)
6610
+ if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
6611
+ } else if (typeof files === "string" && files.trim() && !/[?*{}[\]]/.test(files)) {
6612
+ rawPaths.push(files.trim());
6613
+ }
6614
+ if ((toolName === "patch" || toolName === "apply_patch") && typeof input["patch"] === "string") {
6615
+ rawPaths.push(...patchTargets(input["patch"]));
6616
+ }
6617
+ const line = numberField(input, ["line", "offset", "startLine", "start_line", "line_start"]);
6618
+ const explicitEnd = numberField(input, ["endLine", "end_line", "line_end"]);
6619
+ const limit = numberField(input, ["limit"]);
6620
+ const endLine = explicitEnd ?? (line && limit ? line + limit - 1 : void 0);
6621
+ const seen = /* @__PURE__ */ new Set();
6622
+ const targets = [];
6623
+ for (const rawPath of rawPaths) {
6624
+ const filePath = normalizeTarget(projectRoot, rawPath);
6625
+ if (seen.has(filePath)) continue;
6626
+ seen.add(filePath);
6627
+ targets.push({
6628
+ filePath,
6629
+ operation,
6630
+ ...line ? { line } : {},
6631
+ ...endLine ? { endLine } : {}
6632
+ });
6633
+ }
6634
+ return targets;
6635
+ }
6636
+
6637
+ // src/server/setup-events.ts
5947
6638
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
5948
6639
  const raw = String(filename);
5949
- const relative4 = path11.isAbsolute(raw) ? path11.relative(projectsDir, raw) : raw;
6640
+ const relative4 = path13.isAbsolute(raw) ? path13.relative(projectsDir, raw) : raw;
5950
6641
  const parts = relative4.split(/[\\/]+/).filter(Boolean);
5951
6642
  if (parts.length < 2) return null;
5952
6643
  if (parts[parts.length - 1] !== "status.json") return null;
@@ -5957,12 +6648,76 @@ function shouldLogWatcherStats() {
5957
6648
  return value === "1" || value === "true" || value === "yes" || value === "on";
5958
6649
  }
5959
6650
  function setupEvents(deps2) {
5960
- const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
6651
+ const {
6652
+ events,
6653
+ broadcast: broadcast2,
6654
+ clients,
6655
+ config,
6656
+ context,
6657
+ pendingConfirms,
6658
+ globalConfigPath,
6659
+ sessionBridge,
6660
+ wpaths,
6661
+ watcherMetrics,
6662
+ onFleetBroadcaster
6663
+ } = deps2;
5961
6664
  const disposers = [];
5962
6665
  let disposed = false;
5963
6666
  const on = (event, listener) => {
5964
6667
  disposers.push(events.on(event, listener));
5965
6668
  };
6669
+ const conversationState = context.state;
6670
+ if (typeof conversationState?.onChange === "function") {
6671
+ disposers.push(
6672
+ conversationState.onChange((change) => {
6673
+ if (change.kind !== "todos_replaced") return;
6674
+ broadcast2(clients, {
6675
+ type: "todos.updated",
6676
+ payload: {
6677
+ sessionId: context.session?.id ?? "",
6678
+ todos: [...change.todos],
6679
+ revision: conversationState.revision
6680
+ }
6681
+ });
6682
+ })
6683
+ );
6684
+ }
6685
+ let kanbanWatcher = null;
6686
+ let kanbanDebounce = null;
6687
+ const projectRoot = context.projectRoot;
6688
+ if (projectRoot) {
6689
+ try {
6690
+ const kanbanDir = getKanbanDir(projectRoot);
6691
+ kanbanWatcher = fsWatch(kanbanDir, { persistent: false }, (_eventType, filename) => {
6692
+ const name2 = filename?.toString();
6693
+ if (!name2?.endsWith(".json")) return;
6694
+ const boardId = name2.slice(0, -5);
6695
+ if (kanbanDebounce) clearTimeout(kanbanDebounce);
6696
+ kanbanDebounce = setTimeout(async () => {
6697
+ try {
6698
+ const board = await getBoard(projectRoot, boardId);
6699
+ if (board) {
6700
+ broadcast2(clients, {
6701
+ type: "kanban.get",
6702
+ // Wrap in the { board } envelope like every other kanban
6703
+ // broadcast so the client's isBoardEnvelope path handles it
6704
+ // without hijacking another tab's activeBoardId.
6705
+ payload: { success: true, data: { board } }
6706
+ });
6707
+ }
6708
+ } catch {
6709
+ }
6710
+ }, 60);
6711
+ });
6712
+ kanbanWatcher.on("error", () => kanbanWatcher?.close());
6713
+ disposers.push(() => {
6714
+ if (kanbanDebounce) clearTimeout(kanbanDebounce);
6715
+ kanbanWatcher?.close();
6716
+ kanbanWatcher = null;
6717
+ });
6718
+ } catch {
6719
+ }
6720
+ }
5966
6721
  const currentSessionId = () => context.session?.id ?? "";
5967
6722
  const sessionPayload2 = (payload) => {
5968
6723
  const provided = payload["sessionId"];
@@ -5988,7 +6743,11 @@ function setupEvents(deps2) {
5988
6743
  on("iteration.completed", (e) => {
5989
6744
  broadcast2(clients, {
5990
6745
  type: "iteration.completed",
5991
- payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
6746
+ payload: sessionPayload2({
6747
+ sessionId: e.sessionId,
6748
+ index: e.index,
6749
+ totalIterations: e.index + 1
6750
+ })
5992
6751
  });
5993
6752
  });
5994
6753
  on("iteration.limit_reached", (e) => {
@@ -6002,10 +6761,16 @@ function setupEvents(deps2) {
6002
6761
  });
6003
6762
  });
6004
6763
  on("provider.text_delta", (e) => {
6005
- broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
6764
+ broadcast2(clients, {
6765
+ type: "provider.text_delta",
6766
+ payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" })
6767
+ });
6006
6768
  });
6007
6769
  on("provider.thinking_delta", (e) => {
6008
- broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
6770
+ broadcast2(clients, {
6771
+ type: "provider.thinking_delta",
6772
+ payload: sessionPayload2({ sessionId: e.sessionId, text: e.text })
6773
+ });
6009
6774
  });
6010
6775
  on("provider.stream_error", (e) => {
6011
6776
  broadcast2(clients, {
@@ -6016,7 +6781,17 @@ function setupEvents(deps2) {
6016
6781
  on("tool.started", (e) => {
6017
6782
  broadcast2(clients, {
6018
6783
  type: "tool.started",
6019
- payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
6784
+ payload: sessionPayload2({
6785
+ sessionId: e.sessionId,
6786
+ traceId: e.traceId,
6787
+ agentId: e.agentId,
6788
+ agentName: e.agentName,
6789
+ id: e.id,
6790
+ name: e.name,
6791
+ input: e.input,
6792
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
6793
+ messageId: `tool_${e.id}`
6794
+ })
6020
6795
  });
6021
6796
  appendForCurrentSession(e.sessionId, {
6022
6797
  type: "tool_call_start",
@@ -6027,13 +6802,37 @@ function setupEvents(deps2) {
6027
6802
  });
6028
6803
  });
6029
6804
  on("tool.progress", (e) => {
6805
+ const rawProgressPath = e.event.path ?? (typeof e.event.data?.["path"] === "string" ? e.event.data["path"] : void 0);
6806
+ const progressTarget = rawProgressPath ? normalizeCodeMapFileTarget(
6807
+ context.projectRoot,
6808
+ rawProgressPath,
6809
+ e.event.operation ?? "edit",
6810
+ e.event.line,
6811
+ e.event.endLine
6812
+ ) : void 0;
6030
6813
  broadcast2(clients, {
6031
6814
  type: "tool.progress",
6032
6815
  // Nested `event` shape — the client handler reads `payload.event?.text`
6033
6816
  // and early-returns on a falsy text, so a flat { eventType, text } payload
6034
6817
  // makes live tool progress (bash streaming, partial_output, warnings)
6035
6818
  // 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 } })
6819
+ payload: sessionPayload2({
6820
+ sessionId: e.sessionId,
6821
+ traceId: e.traceId,
6822
+ agentId: e.agentId,
6823
+ agentName: e.agentName,
6824
+ id: e.id,
6825
+ name: e.name,
6826
+ event: {
6827
+ type: e.event.type,
6828
+ text: e.event.text,
6829
+ data: e.event.data,
6830
+ path: progressTarget?.filePath,
6831
+ operation: e.event.operation,
6832
+ line: progressTarget?.line,
6833
+ endLine: progressTarget?.endLine
6834
+ }
6835
+ })
6037
6836
  });
6038
6837
  appendForCurrentSession(e.sessionId, {
6039
6838
  type: "tool_progress",
@@ -6050,7 +6849,23 @@ function setupEvents(deps2) {
6050
6849
  on("tool.executed", (e) => {
6051
6850
  broadcast2(clients, {
6052
6851
  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 })
6852
+ payload: sessionPayload2({
6853
+ sessionId: e.sessionId,
6854
+ traceId: e.traceId,
6855
+ agentId: e.agentId,
6856
+ agentName: e.agentName,
6857
+ id: e.id,
6858
+ name: e.name,
6859
+ durationMs: e.durationMs,
6860
+ ok: e.ok,
6861
+ input: e.input,
6862
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
6863
+ output: e.output,
6864
+ outputBytes: e.outputBytes,
6865
+ outputTokens: e.outputTokens,
6866
+ outputLines: e.outputLines,
6867
+ metadata: e.metadata
6868
+ })
6054
6869
  });
6055
6870
  appendForCurrentSession(e.sessionId, {
6056
6871
  type: "tool_call_end",
@@ -6064,7 +6879,10 @@ function setupEvents(deps2) {
6064
6879
  outputTokens: e.outputTokens,
6065
6880
  outputLines: e.outputLines
6066
6881
  });
6067
- broadcast2(clients, { type: "todos.updated", payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] }) });
6882
+ broadcast2(clients, {
6883
+ type: "todos.updated",
6884
+ payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] })
6885
+ });
6068
6886
  const sideEffects = context.sideEffects ?? [];
6069
6887
  if (sideEffects.length > 0) {
6070
6888
  broadcast2(clients, {
@@ -6089,7 +6907,10 @@ function setupEvents(deps2) {
6089
6907
  if (typeof taskPath === "string" && taskPath) {
6090
6908
  const { loadTasks } = await import("@wrongstack/core");
6091
6909
  const file = await loadTasks(taskPath);
6092
- broadcast2(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
6910
+ broadcast2(clients, {
6911
+ type: "tasks.updated",
6912
+ payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] })
6913
+ });
6093
6914
  }
6094
6915
  } catch {
6095
6916
  }
@@ -6098,13 +6919,27 @@ function setupEvents(deps2) {
6098
6919
  if (typeof planPath === "string" && planPath) {
6099
6920
  const { loadPlan } = await import("@wrongstack/core");
6100
6921
  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: [] } }) });
6922
+ broadcast2(clients, {
6923
+ type: "plan.updated",
6924
+ payload: sessionPayload2({
6925
+ sessionId: e.sessionId,
6926
+ plan: plan ?? {
6927
+ version: 1,
6928
+ sessionId: e.sessionId ?? context.session?.id ?? "",
6929
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6930
+ items: []
6931
+ }
6932
+ })
6933
+ });
6102
6934
  }
6103
6935
  } catch {
6104
6936
  }
6105
6937
  })();
6106
6938
  }
6107
6939
  });
6940
+ on("file.activity", (e) => {
6941
+ broadcast2(clients, { type: "codemap.file_event", payload: e });
6942
+ });
6108
6943
  on("tool.loop_detected", (e) => {
6109
6944
  broadcast2(clients, {
6110
6945
  type: "tool.loop_detected",
@@ -6120,7 +6955,12 @@ function setupEvents(deps2) {
6120
6955
  on("trust.persisted", (e) => {
6121
6956
  broadcast2(clients, {
6122
6957
  type: "trust.persisted",
6123
- payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
6958
+ payload: sessionPayload2({
6959
+ sessionId: e.sessionId,
6960
+ tool: e.tool,
6961
+ pattern: e.pattern,
6962
+ decision: e.decision
6963
+ })
6124
6964
  });
6125
6965
  });
6126
6966
  on("delegate.started", (e) => {
@@ -6162,7 +7002,12 @@ function setupEvents(deps2) {
6162
7002
  on("ctx.pct", (e) => {
6163
7003
  broadcast2(clients, {
6164
7004
  type: "ctx.pct",
6165
- payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
7005
+ payload: sessionPayload2({
7006
+ sessionId: e.sessionId,
7007
+ load: e.load,
7008
+ tokens: e.tokens,
7009
+ maxContext: e.maxContext
7010
+ })
6166
7011
  });
6167
7012
  broadcast2(clients, {
6168
7013
  type: "subagent.event",
@@ -6179,7 +7024,12 @@ function setupEvents(deps2) {
6179
7024
  on("ctx.max_context", (e) => {
6180
7025
  broadcast2(clients, {
6181
7026
  type: "ctx.max_context",
6182
- payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
7027
+ payload: sessionPayload2({
7028
+ sessionId: e.sessionId,
7029
+ providerId: e.providerId,
7030
+ modelId: e.modelId,
7031
+ maxContext: e.maxContext
7032
+ })
6183
7033
  });
6184
7034
  });
6185
7035
  on("token.threshold", (e) => {
@@ -6195,11 +7045,27 @@ function setupEvents(deps2) {
6195
7045
  });
6196
7046
  });
6197
7047
  on("context.repaired", (e) => {
6198
- broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
7048
+ broadcast2(clients, {
7049
+ type: "context.repaired",
7050
+ payload: sessionPayload2({
7051
+ sessionId: e.sessionId,
7052
+ removedToolUses: e.removedToolUses,
7053
+ removedToolResults: e.removedToolResults,
7054
+ removedMessages: e.removedMessages
7055
+ })
7056
+ });
6199
7057
  });
6200
7058
  on("tool.confirm_needed", (e) => {
6201
7059
  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 });
7060
+ const payload = sessionPayload2({
7061
+ sessionId: e.sessionId,
7062
+ id,
7063
+ toolName: e.tool?.name ?? "unknown",
7064
+ input: e.input,
7065
+ suggestedPattern: e.suggestedPattern,
7066
+ decisionSource: e.decisionSource,
7067
+ riskTier: e.riskTier
7068
+ });
6203
7069
  pendingConfirms.set(id, {
6204
7070
  resolve: e.resolve,
6205
7071
  decisionSource: e.decisionSource,
@@ -6209,7 +7075,14 @@ function setupEvents(deps2) {
6209
7075
  broadcast2(clients, { type: "tool.confirm_needed", payload });
6210
7076
  });
6211
7077
  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) }) });
7078
+ broadcast2(clients, {
7079
+ type: "error",
7080
+ payload: sessionPayload2({
7081
+ sessionId: e.sessionId,
7082
+ phase: e.phase,
7083
+ message: e.err instanceof Error ? e.err.message : String(e.err)
7084
+ })
7085
+ });
6213
7086
  appendForCurrentSession(e.sessionId, {
6214
7087
  type: "error",
6215
7088
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6280,6 +7153,34 @@ function setupEvents(deps2) {
6280
7153
  description: e.description
6281
7154
  });
6282
7155
  });
7156
+ on("provider.status_changed", (e) => {
7157
+ broadcast2(clients, {
7158
+ type: "provider.status_changed",
7159
+ payload: sessionPayload2({
7160
+ providerId: e.providerId,
7161
+ model: e.model,
7162
+ oldState: e.oldState,
7163
+ newState: e.newState,
7164
+ reason: e.reason,
7165
+ timestamp: e.timestamp
7166
+ })
7167
+ });
7168
+ });
7169
+ on("provider.active_blocked", (e) => {
7170
+ broadcast2(clients, {
7171
+ type: "provider.active_blocked",
7172
+ payload: sessionPayload2({
7173
+ sessionId: e.sessionId,
7174
+ providerId: e.providerId,
7175
+ model: e.model,
7176
+ state: e.state,
7177
+ fallbackProviderId: e.fallbackProviderId,
7178
+ fallbackModel: e.fallbackModel,
7179
+ lastError: e.lastError,
7180
+ timestamp: e.timestamp
7181
+ })
7182
+ });
7183
+ });
6283
7184
  on("provider.error", (e) => {
6284
7185
  broadcast2(clients, {
6285
7186
  type: "provider.error",
@@ -6385,16 +7286,137 @@ function setupEvents(deps2) {
6385
7286
  broadcast2(clients, { type: "mailbox.agent_registered", payload });
6386
7287
  });
6387
7288
  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 }));
7289
+ on(
7290
+ "subagent.spawned",
7291
+ (e) => forwardSubagent("spawned", {
7292
+ sessionId: e.sessionId,
7293
+ subagentId: e.subagentId,
7294
+ taskId: e.taskId,
7295
+ name: e.name,
7296
+ provider: e.provider,
7297
+ model: e.model,
7298
+ description: e.description
7299
+ })
7300
+ );
7301
+ on(
7302
+ "subagent.task_started",
7303
+ (e) => forwardSubagent("task_started", {
7304
+ sessionId: e.sessionId,
7305
+ subagentId: e.subagentId,
7306
+ taskId: e.taskId,
7307
+ description: e.description
7308
+ })
7309
+ );
7310
+ on("subagent.tool_started", (e) => {
7311
+ broadcast2(clients, {
7312
+ type: "codemap.tool_started",
7313
+ payload: {
7314
+ sessionId: e.agentSessionId ?? e.sessionId ?? "",
7315
+ parentSessionId: e.sessionId,
7316
+ traceId: e.traceId,
7317
+ agentId: e.subagentId,
7318
+ agentName: e.agentName ?? e.subagentId,
7319
+ id: e.id,
7320
+ name: e.name,
7321
+ input: e.input,
7322
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input)
7323
+ }
7324
+ });
7325
+ });
7326
+ on("subagent.tool_executed", (e) => {
7327
+ broadcast2(clients, {
7328
+ type: "codemap.tool_executed",
7329
+ payload: {
7330
+ sessionId: e.agentSessionId ?? e.sessionId ?? "",
7331
+ parentSessionId: e.sessionId,
7332
+ traceId: e.traceId,
7333
+ agentId: e.subagentId,
7334
+ agentName: e.agentName ?? e.subagentId,
7335
+ id: e.id,
7336
+ name: e.name,
7337
+ durationMs: e.durationMs,
7338
+ ok: e.ok,
7339
+ input: e.input,
7340
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
7341
+ output: e.output,
7342
+ outputBytes: e.outputBytes,
7343
+ outputTokens: e.outputTokens,
7344
+ outputLines: e.outputLines
7345
+ }
7346
+ });
7347
+ forwardSubagent("tool_executed", {
7348
+ sessionId: e.sessionId,
7349
+ subagentId: e.subagentId,
7350
+ toolName: e.name,
7351
+ durationMs: e.durationMs,
7352
+ ok: e.ok
7353
+ });
7354
+ });
7355
+ on(
7356
+ "subagent.iteration_summary",
7357
+ (e) => forwardSubagent("iteration_summary", {
7358
+ sessionId: e.sessionId,
7359
+ subagentId: e.subagentId,
7360
+ iteration: e.iteration,
7361
+ toolCalls: e.toolCalls,
7362
+ costUsd: e.costUsd,
7363
+ currentTool: e.currentTool,
7364
+ partialText: e.partialText
7365
+ })
7366
+ );
7367
+ on(
7368
+ "subagent.budget_warning",
7369
+ (e) => forwardSubagent("budget_warning", {
7370
+ sessionId: e.sessionId,
7371
+ subagentId: e.subagentId,
7372
+ budgetKind: e.kind,
7373
+ used: e.used,
7374
+ limit: e.limit
7375
+ })
7376
+ );
7377
+ on(
7378
+ "subagent.budget_extended",
7379
+ (e) => forwardSubagent("budget_extended", {
7380
+ sessionId: e.sessionId,
7381
+ subagentId: e.subagentId,
7382
+ budgetKind: e.kind,
7383
+ newLimit: e.newLimit,
7384
+ totalExtensions: e.totalExtensions
7385
+ })
7386
+ );
7387
+ on(
7388
+ "subagent.ctx_pct",
7389
+ (e) => forwardSubagent("ctx_pct", {
7390
+ sessionId: e.sessionId,
7391
+ subagentId: e.subagentId,
7392
+ load: e.load,
7393
+ tokens: e.tokens,
7394
+ maxContext: e.maxContext
7395
+ })
7396
+ );
7397
+ on(
7398
+ "subagent.task_completed",
7399
+ (e) => forwardSubagent("task_completed", {
7400
+ sessionId: e.sessionId,
7401
+ subagentId: e.subagentId,
7402
+ status: e.status,
7403
+ iterations: e.iterations,
7404
+ toolCalls: e.toolCalls,
7405
+ finalText: e.finalText,
7406
+ failureReason: e.error?.kind,
7407
+ error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0
7408
+ })
7409
+ );
7410
+ on(
7411
+ "subagent.removed",
7412
+ (e) => forwardSubagent("removed", {
7413
+ sessionId: e.sessionId,
7414
+ subagentId: e.subagentId,
7415
+ reason: e.reason
7416
+ })
7417
+ );
6397
7418
  on("agent.timeline.message", (e) => {
7419
+ const timeline = e;
6398
7420
  broadcast2(clients, {
6399
7421
  type: "agent.timeline.message",
6400
7422
  payload: sessionPayload2({
@@ -6406,6 +7428,7 @@ function setupEvents(deps2) {
6406
7428
  iteration: e.iteration,
6407
7429
  ts: e.ts,
6408
7430
  toolName: e.toolName,
7431
+ ...typeof timeline.toolOk === "boolean" ? { toolOk: timeline.toolOk } : {},
6409
7432
  costUsd: e.costUsd
6410
7433
  })
6411
7434
  });
@@ -6514,9 +7537,9 @@ function setupEvents(deps2) {
6514
7537
  if (wpaths?.projectStatus) {
6515
7538
  try {
6516
7539
  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");
7540
+ const dir = path13.dirname(statusFile);
7541
+ await fs11.mkdir(dir, { recursive: true });
7542
+ await fs11.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
6520
7543
  } catch (err) {
6521
7544
  console.error(
6522
7545
  JSON.stringify({
@@ -6530,7 +7553,7 @@ function setupEvents(deps2) {
6530
7553
  }
6531
7554
  });
6532
7555
  if (wpaths?.projectStatus && wpaths.configDir) {
6533
- const projectsDir = path11.join(wpaths.configDir, "projects");
7556
+ const projectsDir = path13.join(wpaths.configDir, "projects");
6534
7557
  const knownProjectHashes = /* @__PURE__ */ new Set();
6535
7558
  const debounceTimers = /* @__PURE__ */ new Map();
6536
7559
  const DEBOUNCE_MS = 150;
@@ -6594,26 +7617,32 @@ function setupEvents(deps2) {
6594
7617
  let watcher;
6595
7618
  const startWatcher = async () => {
6596
7619
  try {
6597
- await fs10.mkdir(projectsDir, { recursive: true });
7620
+ await fs11.mkdir(projectsDir, { recursive: true });
6598
7621
  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 {
7622
+ watcher = fsWatch(
7623
+ projectsDir,
7624
+ { persistent: true, recursive: true },
7625
+ async (eventType, filename) => {
7626
+ if (eventType !== "change" && eventType !== "rename") return;
7627
+ if (filename == null) return;
7628
+ const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
7629
+ if (!projectHash) return;
7630
+ if (watcherMetrics) watcherMetrics.fileChangesDetected++;
7631
+ if (!knownProjectHashes.has(projectHash)) return;
7632
+ if (watcherMetrics) watcherMetrics.filesProcessed++;
7633
+ try {
7634
+ const targetFile = path13.join(projectsDir, projectHash, "status.json");
7635
+ const content = await fs11.readFile(targetFile, "utf-8");
7636
+ const statusData = JSON.parse(content);
7637
+ scheduleBroadcast(projectHash, statusData);
7638
+ } catch {
7639
+ }
6613
7640
  }
6614
- });
7641
+ );
6615
7642
  if (logWatcherMetricsEnabled) {
6616
- console.log(`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`);
7643
+ console.log(
7644
+ `[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`
7645
+ );
6617
7646
  }
6618
7647
  } catch (err) {
6619
7648
  console.error(
@@ -6658,17 +7687,19 @@ function setupEvents(deps2) {
6658
7687
  }
6659
7688
  });
6660
7689
  }
6661
- const globalRoot = globalConfigPath ? path11.dirname(globalConfigPath) : void 0;
7690
+ const globalRoot = globalConfigPath ? path13.dirname(globalConfigPath) : void 0;
6662
7691
  if (globalRoot) {
6663
7692
  const broadcastSessions = async () => {
6664
7693
  try {
6665
7694
  const { SessionRegistry } = await import("@wrongstack/core");
6666
7695
  const registry = new SessionRegistry(globalRoot);
6667
7696
  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) => ({
7697
+ const ownEntry = sessions.find((s) => s.pid === process.pid);
7698
+ const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
7699
+ const myRoot = path13.resolve(context.projectRoot);
7700
+ const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter(
7701
+ (s) => mySlug ? s.projectSlug === mySlug : path13.resolve(s.projectRoot) === myRoot
7702
+ ).map((s) => ({
6672
7703
  sessionId: s.sessionId,
6673
7704
  projectName: s.projectName,
6674
7705
  projectSlug: s.projectSlug,
@@ -6680,12 +7711,15 @@ function setupEvents(deps2) {
6680
7711
  status: s.status,
6681
7712
  pid: s.pid,
6682
7713
  startedAt: s.startedAt,
7714
+ lastHeartbeatAt: s.lastHeartbeatAt,
6683
7715
  agentCount: s.agentCount,
6684
7716
  agents: (s.agents ?? []).map((a) => ({
6685
7717
  id: a.id,
6686
7718
  name: a.name,
6687
7719
  status: a.status,
6688
7720
  currentTool: a.currentTool,
7721
+ currentTask: a.currentTask,
7722
+ taskId: a.taskId,
6689
7723
  iterations: a.iterations,
6690
7724
  toolCalls: a.toolCalls,
6691
7725
  costUsd: a.costUsd,
@@ -6694,6 +7728,12 @@ function setupEvents(deps2) {
6694
7728
  ctxPct: a.ctxPct,
6695
7729
  model: a.model,
6696
7730
  partialText: a.partialText,
7731
+ recentTools: a.recentTools,
7732
+ recentMail: a.recentMail,
7733
+ todos: a.todos,
7734
+ latestPrompt: a.latestPrompt,
7735
+ latestPromptAt: a.latestPromptAt,
7736
+ activity: a.activity,
6697
7737
  lastActivityAt: a.lastActivityAt
6698
7738
  }))
6699
7739
  }));
@@ -6919,7 +7959,7 @@ function createSessionStartPayload(g) {
6919
7959
  inputCost,
6920
7960
  outputCost,
6921
7961
  cacheReadCost,
6922
- projectName: path12.basename(projectRoot) || projectRoot,
7962
+ projectName: path14.basename(projectRoot) || projectRoot,
6923
7963
  projectRoot,
6924
7964
  cwd: g.getWorkingDir(),
6925
7965
  mode: g.getModeId(),
@@ -7017,13 +8057,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
7017
8057
  };
7018
8058
  }
7019
8059
  function resolveWebuiDistDir(fromUrl, explicitDistDir) {
7020
- if (explicitDistDir) return path12.resolve(explicitDistDir);
8060
+ if (explicitDistDir) return path14.resolve(explicitDistDir);
7021
8061
  try {
7022
8062
  const requireFromHere2 = createRequire(fromUrl);
7023
8063
  const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
7024
- return path12.dirname(serverEntry);
8064
+ return path14.dirname(serverEntry);
7025
8065
  } catch {
7026
- return path12.resolve(path12.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
8066
+ return path14.resolve(path14.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
7027
8067
  }
7028
8068
  }
7029
8069
  function startHttpServer(opts) {
@@ -7036,15 +8076,18 @@ function startHttpServer(opts) {
7036
8076
  apiToken: opts.wsToken,
7037
8077
  requireToken: opts.requireToken,
7038
8078
  watcherMetrics: opts.watcherMetrics,
7039
- onFleetPing: opts.onFleetPing
8079
+ onFleetPing: opts.onFleetPing,
8080
+ onTechStackEvent: opts.onTechStackEvent,
8081
+ getLlm: opts.getLlm,
8082
+ projectRoot: opts.projectRoot
7040
8083
  });
7041
- const registryBaseDir = path12.dirname(opts.globalConfigPath);
8084
+ const registryBaseDir = path14.dirname(opts.globalConfigPath);
7042
8085
  httpServer.listen(opts.httpPort, opts.wsHost, () => {
7043
8086
  const openUrl = buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, token: opts.wsToken, publicUrl: opts.publicUrl });
7044
8087
  console.log(`[WebUI] HTTP server running on ${openUrl}`);
7045
8088
  if (opts.openBrowser) openBrowser(openUrl);
7046
8089
  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 }) },
8090
+ { 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
8091
  registryBaseDir
7049
8092
  ).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
7050
8093
  });
@@ -7060,7 +8103,7 @@ function registerShutdown(deps2) {
7060
8103
  }
7061
8104
 
7062
8105
  // src/server/pre-context-services.ts
7063
- import * as path15 from "node:path";
8106
+ import * as path17 from "node:path";
7064
8107
  import { createRequire as createRequire2 } from "node:module";
7065
8108
  import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
7066
8109
  import {
@@ -7206,6 +8249,7 @@ function resolveSetupProvider(opts) {
7206
8249
  }
7207
8250
 
7208
8251
  // src/server/context-meta.ts
8252
+ import { FallbackProfileManager } from "@wrongstack/core";
7209
8253
  function seedContextMeta(config, context) {
7210
8254
  const meta = context.meta;
7211
8255
  const autonomyCfg = config.autonomy ?? {};
@@ -7259,6 +8303,7 @@ function seedContextMeta(config, context) {
7259
8303
  meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
7260
8304
  meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
7261
8305
  meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
8306
+ meta["showModelReasoning"] = autonomyCfg["showModelReasoning"] !== false;
7262
8307
  meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
7263
8308
  meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
7264
8309
  {
@@ -7279,11 +8324,42 @@ function seedContextMeta(config, context) {
7279
8324
  meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
7280
8325
  const tgMs = tgExt?.["longToolThresholdMs"];
7281
8326
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
8327
+ const chimeraExt = config.extensions?.["wstack-chimera"];
8328
+ meta["chimeraEnabled"] = chimeraExt?.["enabled"] !== false;
8329
+ meta["chimeraProvider"] = chimeraExt?.["provider"] ?? "";
8330
+ meta["chimeraModel"] = chimeraExt?.["model"] ?? "";
8331
+ meta["chimeraMaxFiles"] = typeof chimeraExt?.["maxFiles"] === "number" && chimeraExt["maxFiles"] >= 1 ? chimeraExt["maxFiles"] : 15;
8332
+ const autoFix = chimeraExt?.["autoFix"];
8333
+ meta["chimeraAutoFix"] = autoFix === "off" || autoFix === "ask" || autoFix === "auto" ? autoFix : "off";
8334
+ const autoReviewExt = config.extensions?.["wstack-auto-review"];
8335
+ meta["autoReviewEnabled"] = autoReviewExt?.["enabled"] === true;
8336
+ meta["autoReviewProvider"] = autoReviewExt?.["provider"] ?? "";
8337
+ meta["autoReviewModel"] = autoReviewExt?.["model"] ?? "";
8338
+ meta["autoReviewFallbackProfile"] = autoReviewExt?.["fallbackProfile"] ?? "";
8339
+ meta["autoReviewFallbackModels"] = Array.isArray(autoReviewExt?.["fallbackModels"]) ? autoReviewExt?.["fallbackModels"] : [];
8340
+ meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] : 5e3;
8341
+ meta["autoReviewMaxFilesPerBatch"] = typeof autoReviewExt?.["maxFilesPerBatch"] === "number" && autoReviewExt["maxFilesPerBatch"] >= 1 ? autoReviewExt["maxFilesPerBatch"] : 15;
8342
+ meta["autoReviewMaxConcurrentReviews"] = typeof autoReviewExt?.["maxConcurrentReviews"] === "number" && autoReviewExt["maxConcurrentReviews"] >= 1 ? autoReviewExt["maxConcurrentReviews"] : 2;
8343
+ const cascade = autoReviewExt?.["cascadeOn"];
8344
+ meta["autoReviewCascadeOn"] = cascade === "critical" || cascade === "high" ? cascade : "off";
8345
+ {
8346
+ let resolvedChain = [];
8347
+ try {
8348
+ const mgr = new FallbackProfileManager(config);
8349
+ const named = autoReviewExt?.["fallbackProfile"];
8350
+ resolvedChain = typeof named === "string" && named.length > 0 ? mgr.resolve(named) : mgr.resolveEffective({ fallbackAuto: true });
8351
+ } catch {
8352
+ resolvedChain = [];
8353
+ }
8354
+ meta["autoReviewFallbackModels"] = resolvedChain.map(
8355
+ (e) => `${e.providerId}/${e.model}`
8356
+ );
8357
+ }
7282
8358
  }
7283
8359
 
7284
8360
  // src/server/model-auto-discovery.ts
7285
- import * as fs11 from "node:fs/promises";
7286
- import * as path13 from "node:path";
8361
+ import * as fs12 from "node:fs/promises";
8362
+ import * as path15 from "node:path";
7287
8363
  import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
7288
8364
  function isOverlayRegistry(value) {
7289
8365
  return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
@@ -7309,7 +8385,7 @@ function eligibleProviders(config) {
7309
8385
  }
7310
8386
  async function readCache(file) {
7311
8387
  try {
7312
- return JSON.parse(await fs11.readFile(file, "utf8"));
8388
+ return JSON.parse(await fs12.readFile(file, "utf8"));
7313
8389
  } catch {
7314
8390
  return {};
7315
8391
  }
@@ -7319,7 +8395,7 @@ async function discoverAndMergeWebuiProviders(opts) {
7319
8395
  if (!isOverlayRegistry(registry)) return;
7320
8396
  const targets = eligibleProviders(opts.config);
7321
8397
  if (targets.length === 0) return;
7322
- const cacheFile = path13.join(opts.cacheDir, "discovered-models-cache.json");
8398
+ const cacheFile = path15.join(opts.cacheDir, "discovered-models-cache.json");
7323
8399
  const cache = await readCache(cacheFile);
7324
8400
  let cacheDirty = false;
7325
8401
  await Promise.all(
@@ -7356,8 +8432,8 @@ async function discoverAndMergeWebuiProviders(opts) {
7356
8432
  );
7357
8433
  if (cacheDirty) {
7358
8434
  try {
7359
- await fs11.mkdir(path13.dirname(cacheFile), { recursive: true });
7360
- await fs11.writeFile(cacheFile, JSON.stringify(cache), "utf8");
8435
+ await fs12.mkdir(path15.dirname(cacheFile), { recursive: true });
8436
+ await fs12.writeFile(cacheFile, JSON.stringify(cache), "utf8");
7361
8437
  } catch {
7362
8438
  opts.logger?.debug?.("provider auto-discovery cache write failed");
7363
8439
  }
@@ -7365,7 +8441,7 @@ async function discoverAndMergeWebuiProviders(opts) {
7365
8441
  }
7366
8442
 
7367
8443
  // src/server/standalone-session-identity.ts
7368
- import * as path14 from "node:path";
8444
+ import * as path16 from "node:path";
7369
8445
  import {
7370
8446
  AgentStatusTracker,
7371
8447
  FleetNotifier,
@@ -7396,7 +8472,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7396
8472
  sessionId,
7397
8473
  projectSlug: paths.projectSlug,
7398
8474
  projectRoot: paths.projectRoot,
7399
- projectName: path14.basename(paths.projectRoot),
8475
+ projectName: path16.basename(paths.projectRoot),
7400
8476
  workingDir: opts.workingDir,
7401
8477
  clientType: "webui",
7402
8478
  pid: process.pid,
@@ -7405,7 +8481,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7405
8481
  });
7406
8482
  fleetNotifier.notify();
7407
8483
  } catch (err) {
7408
- logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
8484
+ logger.debug?.(`WebUI session registry update failed: ${errorMessage3(err)}`);
7409
8485
  }
7410
8486
  };
7411
8487
  await register(activeSessionId);
@@ -7431,7 +8507,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7431
8507
  const publisher = core.createHqPublisherFromEnv({
7432
8508
  clientKind: "webui",
7433
8509
  projectRoot: paths.projectRoot,
7434
- projectName: path14.basename(paths.projectRoot),
8510
+ projectName: path16.basename(paths.projectRoot),
7435
8511
  appConfig: opts.config,
7436
8512
  socketFactory: (url) => new WebSocket2(url)
7437
8513
  });
@@ -7453,7 +8529,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7453
8529
  events,
7454
8530
  sessionId,
7455
8531
  projectRoot: paths.projectRoot,
7456
- projectName: path14.basename(paths.projectRoot),
8532
+ projectName: path16.basename(paths.projectRoot),
7457
8533
  globalRoot: paths.globalRoot,
7458
8534
  initialAgents: statusTracker.getAgents(),
7459
8535
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7490,7 +8566,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7490
8566
  restartHqBridges(activeSessionId);
7491
8567
  }
7492
8568
  } catch (err) {
7493
- logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
8569
+ logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage3(err)}`);
7494
8570
  }
7495
8571
  }
7496
8572
  const repointRecovery = async (sessionId) => {
@@ -7513,7 +8589,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7513
8589
  try {
7514
8590
  restartHqBridges(sessionId);
7515
8591
  } catch (err) {
7516
- logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
8592
+ logger.debug?.(`WebUI HQ session swap failed: ${errorMessage3(err)}`);
7517
8593
  }
7518
8594
  });
7519
8595
  await transition;
@@ -7538,7 +8614,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7538
8614
  };
7539
8615
  return { statusTracker, activate, stop };
7540
8616
  }
7541
- function errorMessage2(err) {
8617
+ function errorMessage3(err) {
7542
8618
  return err instanceof Error ? err.message : String(err);
7543
8619
  }
7544
8620
 
@@ -7564,7 +8640,7 @@ async function createPreContextServices(input) {
7564
8640
  await discoverAndMergeWebuiProviders({
7565
8641
  config,
7566
8642
  registry: modelsRegistry,
7567
- cacheDir: path15.dirname(wpaths.modelsCache),
8643
+ cacheDir: path17.dirname(wpaths.modelsCache),
7568
8644
  logger
7569
8645
  });
7570
8646
  } catch (err) {
@@ -7609,7 +8685,7 @@ async function createPreContextServices(input) {
7609
8685
  configureChildEnvGitIdentity(config.git?.identity ?? null);
7610
8686
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
7611
8687
  const mcpTokenStore = new MCPVaultTokenStore(
7612
- path15.join(wpaths.projectDir, "mcp-auth.json"),
8688
+ path17.join(wpaths.projectDir, "mcp-auth.json"),
7613
8689
  vault
7614
8690
  );
7615
8691
  const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
@@ -7704,7 +8780,7 @@ async function createPreContextServices(input) {
7704
8780
  const modelCapabilitiesRef = { current: modelCapabilities };
7705
8781
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
7706
8782
  const skillInstaller = config.features.skills ? new SkillInstaller({
7707
- manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
8783
+ manifestPath: path17.join(wpaths.globalRoot, "installed-skills.json"),
7708
8784
  projectSkillsDir: wpaths.inProjectSkills,
7709
8785
  globalSkillsDir: wpaths.globalSkills,
7710
8786
  projectHash: wpaths.projectHash,
@@ -7714,7 +8790,7 @@ async function createPreContextServices(input) {
7714
8790
  const bundledPromptsDir = promptsEnabled ? (() => {
7715
8791
  try {
7716
8792
  const req = createRequire2(import.meta.url);
7717
- return path15.join(path15.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
8793
+ return path17.join(path17.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7718
8794
  } catch {
7719
8795
  return void 0;
7720
8796
  }
@@ -7815,7 +8891,7 @@ function isSuperMemoryService(memoryStore) {
7815
8891
  }
7816
8892
 
7817
8893
  // src/server/start-webui.ts
7818
- import * as path21 from "node:path";
8894
+ import * as path22 from "node:path";
7819
8895
  import {
7820
8896
  createDefaultPipelines,
7821
8897
  createSessionEventBridge,
@@ -7845,7 +8921,7 @@ function patchConfig(config, updates) {
7845
8921
  }
7846
8922
 
7847
8923
  // src/server/backend-services.ts
7848
- import { join as join12 } from "node:path";
8924
+ import { join as join13 } from "node:path";
7849
8925
  import {
7850
8926
  Agent,
7851
8927
  AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
@@ -7871,7 +8947,7 @@ import {
7871
8947
  } from "@wrongstack/core";
7872
8948
 
7873
8949
  // src/server/collaboration-ws-handler.ts
7874
- import { randomUUID } from "node:crypto";
8950
+ import { randomUUID as randomUUID2 } from "node:crypto";
7875
8951
  import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
7876
8952
  var REPLAY_LIMIT = 50;
7877
8953
  var PAUSE_TIMEOUT_MS = 6e4;
@@ -8003,7 +9079,7 @@ var CollaborationWebSocketHandler = class {
8003
9079
  return;
8004
9080
  }
8005
9081
  const participant = {
8006
- participantId: randomUUID(),
9082
+ participantId: randomUUID2(),
8007
9083
  ws,
8008
9084
  sessionId,
8009
9085
  role,
@@ -8623,8 +9699,8 @@ var CollaborationWebSocketHandler = class {
8623
9699
  };
8624
9700
 
8625
9701
  // src/server/codebase-indexing.ts
8626
- import * as fs12 from "node:fs";
8627
- import * as path16 from "node:path";
9702
+ import * as fs13 from "node:fs";
9703
+ import * as path18 from "node:path";
8628
9704
  import {
8629
9705
  cancelPendingReindexes,
8630
9706
  enqueueReindex,
@@ -8644,17 +9720,15 @@ var IGNORE_DIRS = /* @__PURE__ */ new Set([
8644
9720
  ".nyc_output"
8645
9721
  ]);
8646
9722
  function setupWebUICodebaseIndexing(deps2) {
8647
- const indexing = deps2.config.indexing;
8648
- if (!indexing) return noopIndexing();
8649
- const idx = indexing;
9723
+ const idx = deps2.config.indexing;
8650
9724
  const indexDir = typeof deps2.context.meta["codebaseIndexDir"] === "string" ? deps2.context.meta["codebaseIndexDir"] : void 0;
8651
- const debounceMs = idx.debounceMs ?? 400;
9725
+ const debounceMs = idx?.debounceMs ?? 400;
8652
9726
  const onError = (err) => {
8653
9727
  deps2.logger.debug(
8654
9728
  `webui codebase auto-index failed: ${err instanceof Error ? err.message : String(err)}`
8655
9729
  );
8656
9730
  };
8657
- if (idx.onSessionStart) {
9731
+ if (idx?.onSessionStart) {
8658
9732
  void runStartupIndex({
8659
9733
  projectRoot: deps2.projectRoot,
8660
9734
  indexDir,
@@ -8671,14 +9745,27 @@ function setupWebUICodebaseIndexing(deps2) {
8671
9745
  });
8672
9746
  }
8673
9747
  let watcher;
8674
- if (idx.watchExternal) {
9748
+ const lastWatcherEvent = /* @__PURE__ */ new Map();
9749
+ if (idx?.watchExternal || deps2.events) {
8675
9750
  try {
8676
- watcher = fs12.watch(deps2.projectRoot, { recursive: true }, (_event, filename) => {
9751
+ watcher = fs13.watch(deps2.projectRoot, { recursive: true }, (eventType, filename) => {
8677
9752
  if (!filename) return;
8678
9753
  const rel = filename.toString();
8679
9754
  if (isIgnored(rel)) return;
8680
- const abs = path16.resolve(deps2.projectRoot, rel);
8681
- enqueueFile(abs);
9755
+ const abs = path18.resolve(deps2.projectRoot, rel);
9756
+ if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
9757
+ const now = Date.now();
9758
+ if (now - (lastWatcherEvent.get(abs) ?? 0) > 75) {
9759
+ lastWatcherEvent.set(abs, now);
9760
+ deps2.events?.emit("file.activity", {
9761
+ filePath: path18.normalize(abs),
9762
+ operation: eventType === "rename" && !fs13.existsSync(abs) ? "delete" : "edit",
9763
+ phase: "changed",
9764
+ source: "watcher",
9765
+ at: now
9766
+ });
9767
+ }
9768
+ if (idx?.watchExternal) enqueueFile(abs);
8682
9769
  });
8683
9770
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
8684
9771
  watcher.unref?.();
@@ -8689,8 +9776,8 @@ function setupWebUICodebaseIndexing(deps2) {
8689
9776
  }
8690
9777
  }
8691
9778
  function enqueueFile(filePath) {
8692
- if (!idx.onEdit && !idx.watchExternal) return;
8693
- const abs = path16.isAbsolute(filePath) ? path16.normalize(filePath) : path16.resolve(deps2.projectRoot, filePath);
9779
+ if (!idx || !idx.onEdit && !idx.watchExternal) return;
9780
+ const abs = path18.isAbsolute(filePath) ? path18.normalize(filePath) : path18.resolve(deps2.projectRoot, filePath);
8694
9781
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
8695
9782
  enqueueReindex({
8696
9783
  projectRoot: deps2.projectRoot,
@@ -8703,23 +9790,28 @@ function setupWebUICodebaseIndexing(deps2) {
8703
9790
  }
8704
9791
  return {
8705
9792
  onFileWritten(filePath) {
8706
- if (idx.onEdit) enqueueFile(filePath);
9793
+ const abs = path18.isAbsolute(filePath) ? path18.normalize(filePath) : path18.resolve(deps2.projectRoot, filePath);
9794
+ deps2.events?.emit("file.activity", {
9795
+ filePath: abs,
9796
+ operation: "write",
9797
+ phase: "completed",
9798
+ source: "editor",
9799
+ at: Date.now(),
9800
+ sessionId: deps2.context.session?.id,
9801
+ agentId: "webui-editor",
9802
+ agentName: "WebUI Editor"
9803
+ });
9804
+ if (idx?.onEdit) enqueueFile(abs);
8707
9805
  },
8708
9806
  dispose() {
8709
9807
  try {
8710
9808
  watcher?.close();
8711
9809
  } catch {
8712
9810
  }
8713
- cancelPendingReindexes();
8714
- shutdownCodebaseIndexHost();
8715
- }
8716
- };
8717
- }
8718
- function noopIndexing() {
8719
- return {
8720
- onFileWritten() {
8721
- },
8722
- dispose() {
9811
+ if (idx) {
9812
+ cancelPendingReindexes();
9813
+ shutdownCodebaseIndexHost();
9814
+ }
8723
9815
  }
8724
9816
  };
8725
9817
  }
@@ -8727,16 +9819,16 @@ function isIgnored(rel) {
8727
9819
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
8728
9820
  }
8729
9821
  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);
9822
+ const normalizedRoot = path18.resolve(root);
9823
+ const normalizedTarget = path18.resolve(target);
9824
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path18.sep);
8733
9825
  }
8734
9826
 
8735
9827
  // src/server/discover-mailbox-bridge.ts
8736
9828
  import { spawn as spawn3 } from "node:child_process";
8737
9829
  import { createRequire as createRequire3 } from "node:module";
8738
- import { existsSync } from "node:fs";
8739
- import { dirname as dirname7, join as join11 } from "node:path";
9830
+ import { existsSync as existsSync2 } from "node:fs";
9831
+ import { dirname as dirname8, join as join12 } from "node:path";
8740
9832
  import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
8741
9833
  import { readLiveLock } from "@wrongstack/core/coordination";
8742
9834
  var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
@@ -8864,16 +9956,16 @@ function mailboxServeInvocation(projectRoot) {
8864
9956
  function findWorkspaceCliEntry(projectRoot) {
8865
9957
  let dir = projectRoot;
8866
9958
  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);
9959
+ const candidate = join12(dir, "packages", "cli", "dist", "index.js");
9960
+ if (existsSync2(candidate)) return candidate;
9961
+ const parent = dirname8(dir);
8870
9962
  if (parent === dir) return null;
8871
9963
  dir = parent;
8872
9964
  }
8873
9965
  return null;
8874
9966
  }
8875
9967
  function sleep(ms) {
8876
- return new Promise((resolve10) => setTimeout(resolve10, ms));
9968
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
8877
9969
  }
8878
9970
 
8879
9971
  // src/server/terminal-ws-handler.ts
@@ -9128,7 +10220,8 @@ async function createAgentServices(input) {
9128
10220
  config,
9129
10221
  context,
9130
10222
  projectRoot,
9131
- logger
10223
+ logger,
10224
+ events
9132
10225
  });
9133
10226
  const compactor = createStrategyCompactor({
9134
10227
  strategy: config.context?.strategy,
@@ -9265,7 +10358,7 @@ async function createAgentServices(input) {
9265
10358
  const brainCfg = resolveBrainConfigDefaults(config.brain, {
9266
10359
  fallbackModels: config.fallbackModels
9267
10360
  });
9268
- const brainLedgerPath = join12(wpaths.projectDir, "brain-ledger.jsonl");
10361
+ const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
9269
10362
  let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
9270
10363
  let brainLedger;
9271
10364
  const startBrainLedger = () => {
@@ -9375,7 +10468,7 @@ async function createAgentServices(input) {
9375
10468
  });
9376
10469
  brainMonitor.start();
9377
10470
  console.log("[WebUI] Brain initialized (tiered policy \u2192 LLM, monitor active)");
9378
- const autoPhaseHandler = new AutoPhaseWebSocketHandler(
10471
+ const goalHandler = new GoalWebSocketHandler(
9379
10472
  agent,
9380
10473
  context,
9381
10474
  logger,
@@ -9447,7 +10540,7 @@ async function createAgentServices(input) {
9447
10540
  return brainLedger;
9448
10541
  },
9449
10542
  codebaseIndexing,
9450
- autoPhaseHandler,
10543
+ goalHandler,
9451
10544
  specsHandler,
9452
10545
  sddBoardHandler,
9453
10546
  sddWizardHandler,
@@ -9520,6 +10613,9 @@ function createConnectionHandler(opts) {
9520
10613
  }
9521
10614
  void opts.sessionStartPayload().then(async (payload) => {
9522
10615
  const enriched = { ...payload };
10616
+ if (typeof opts.context.lastRequestTokens === "number" && opts.context.lastRequestTokens > 0) {
10617
+ enriched.lastInputTokens = opts.context.lastRequestTokens;
10618
+ }
9523
10619
  try {
9524
10620
  const replay = await opts.loadReplay?.();
9525
10621
  const live = replay?.messages ?? opts.context.messages ?? [];
@@ -9549,7 +10645,7 @@ function createConnectionHandler(opts) {
9549
10645
  })
9550
10646
  );
9551
10647
  });
9552
- opts.autoPhaseHandler.addClient(ws);
10648
+ opts.goalHandler.addClient(ws);
9553
10649
  opts.specsHandler.addClient(ws);
9554
10650
  opts.sddBoardHandler.addClient(ws);
9555
10651
  opts.sddWizardHandler.addClient(ws);
@@ -9567,8 +10663,21 @@ function createConnectionHandler(opts) {
9567
10663
  });
9568
10664
  return;
9569
10665
  }
10666
+ let rawObj;
10667
+ try {
10668
+ rawObj = JSON.parse(data.toString());
10669
+ } catch (err) {
10670
+ console.error(
10671
+ JSON.stringify({
10672
+ level: "error",
10673
+ event: "webui.ws_message_parse_failed",
10674
+ message: err instanceof Error ? err.message : String(err),
10675
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10676
+ })
10677
+ );
10678
+ return;
10679
+ }
9570
10680
  try {
9571
- const rawObj = JSON.parse(data.toString());
9572
10681
  if (typeof rawObj === "object" && rawObj !== null) {
9573
10682
  const obj = rawObj;
9574
10683
  if (Object.hasOwn(obj, "__proto__") || Object.hasOwn(obj, "constructor") || Object.hasOwn(obj, "prototype")) {
@@ -9586,7 +10695,7 @@ function createConnectionHandler(opts) {
9586
10695
  console.error(
9587
10696
  JSON.stringify({
9588
10697
  level: "error",
9589
- event: "webui.ws_message_parse_failed",
10698
+ event: "webui.ws_message_handler_failed",
9590
10699
  message: err instanceof Error ? err.message : String(err),
9591
10700
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
9592
10701
  })
@@ -9621,7 +10730,12 @@ function createConnectionHandler(opts) {
9621
10730
  }
9622
10731
 
9623
10732
  // src/server/message-dispatcher.ts
9624
- import path17 from "node:path";
10733
+ import path19 from "node:path";
10734
+ import {
10735
+ ChronicleQueryEngine,
10736
+ resolveWstackPaths as resolveWstackPaths2
10737
+ } from "@wrongstack/core";
10738
+ import * as os2 from "node:os";
9625
10739
  import {
9626
10740
  buildUserContentBlocks,
9627
10741
  IncomingImageError,
@@ -9634,9 +10748,9 @@ import {
9634
10748
  VisionUrlBlockedError
9635
10749
  } from "@wrongstack/runtime/vision";
9636
10750
 
9637
- // src/server/autophase-routes.ts
9638
- async function handleAutoPhaseRoute(_ws, msg, handlers) {
9639
- if (!msg.type.startsWith("autophase.")) return false;
10751
+ // src/server/goal-routes.ts
10752
+ async function handleGoalRoute(_ws, msg, handlers) {
10753
+ if (!msg.type.startsWith("goal.")) return false;
9640
10754
  await handlers.handleMessage(msg);
9641
10755
  return true;
9642
10756
  }
@@ -9672,9 +10786,9 @@ async function handleGoalGet(projectRoot, broadcast2) {
9672
10786
  const { readFile: readFile10 } = await import("node:fs/promises");
9673
10787
  const raw = await readFile10(goalPath, "utf8");
9674
10788
  const goal = JSON.parse(raw);
9675
- broadcast2({ type: "goal.updated", payload: goal });
10789
+ broadcast2({ type: "goal-state.updated", payload: goal });
9676
10790
  } catch {
9677
- broadcast2({ type: "goal.updated", payload: null });
10791
+ broadcast2({ type: "goal-state.updated", payload: null });
9678
10792
  }
9679
10793
  }
9680
10794
 
@@ -9924,17 +11038,19 @@ import {
9924
11038
  duplicateBoard,
9925
11039
  exportBoardToTaskGraph,
9926
11040
  generateBoardFromDescription,
9927
- getBoard,
11041
+ getBoard as getBoard2,
9928
11042
  getKanbanOrchestrationSnapshot,
9929
11043
  getKanbanQueueHealth,
9930
11044
  getTask,
9931
11045
  getTaskChain,
9932
11046
  listBoards,
9933
11047
  listReadyTasks,
11048
+ listTaskActivity,
9934
11049
  mergeTasks,
9935
11050
  moveTask,
9936
11051
  parseLinesIntoTasks,
9937
11052
  reconcileKanbanBoard,
11053
+ recordTaskActivity,
9938
11054
  recoverStaleTaskAssignments,
9939
11055
  releaseTaskClaim,
9940
11056
  removeBoard,
@@ -9943,7 +11059,9 @@ import {
9943
11059
  setTaskChain,
9944
11060
  splitTask,
9945
11061
  syncBoardFromTaskGraph,
11062
+ touchKanbanPresence,
9946
11063
  transferTaskToBoard,
11064
+ transitionTask,
9947
11065
  updateBoard,
9948
11066
  updateCheckOnTask,
9949
11067
  updateGoalMetricOnTask,
@@ -9970,6 +11088,29 @@ function fail(ws, type, message) {
9970
11088
  function has(payload, key) {
9971
11089
  return payload !== void 0 && Object.hasOwn(payload, key);
9972
11090
  }
11091
+ function activityContext(ctx, actor, note) {
11092
+ const sessionId = ctx.context?.session?.id;
11093
+ return {
11094
+ ...sessionId ? { sessionId } : {},
11095
+ ...actor ? { actor } : {},
11096
+ ...note?.trim() ? { note: note.trim() } : {}
11097
+ };
11098
+ }
11099
+ async function touchTaskPresence(ctx, boardId, taskId) {
11100
+ const context = ctx.context;
11101
+ const sessionId = context?.session?.id;
11102
+ if (!context || !sessionId) return null;
11103
+ try {
11104
+ return await touchKanbanPresence(ctx.projectRoot, boardId, {
11105
+ sessionId,
11106
+ agentId: context.agentId || "webui",
11107
+ agentName: context.agentName || context.agentId || "WebUI",
11108
+ taskId
11109
+ });
11110
+ } catch {
11111
+ return null;
11112
+ }
11113
+ }
9973
11114
  async function handleKanbanRoute(ws, msg, ctx) {
9974
11115
  if (!msg.type.startsWith("kanban.")) return false;
9975
11116
  const payload = msg.payload;
@@ -9985,7 +11126,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
9985
11126
  fail(ws, type, "boardId required");
9986
11127
  return true;
9987
11128
  }
9988
- const board = await getBoard(ctx.projectRoot, boardId);
11129
+ const board = await getBoard2(ctx.projectRoot, boardId);
9989
11130
  board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
9990
11131
  return true;
9991
11132
  }
@@ -10005,7 +11146,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10005
11146
  fail(ws, type, "boardId required");
10006
11147
  return true;
10007
11148
  }
10008
- const board = await getBoard(ctx.projectRoot, boardId);
11149
+ const board = await getBoard2(ctx.projectRoot, boardId);
10009
11150
  if (!board) {
10010
11151
  fail(ws, type, `Board not found: ${boardId}`);
10011
11152
  return true;
@@ -10043,7 +11184,8 @@ async function handleKanbanRoute(ws, msg, ctx) {
10043
11184
  title,
10044
11185
  ...payload?.description ? { description: payload.description } : {},
10045
11186
  ...payload?.tags ? { tags: payload.tags } : {},
10046
- ...payload?.columns ? { columns: payload.columns } : {}
11187
+ ...payload?.columns ? { columns: payload.columns } : {},
11188
+ ...has(payload, "lifecycle") ? { lifecycle: payload?.lifecycle } : {}
10047
11189
  })
10048
11190
  );
10049
11191
  return true;
@@ -10059,6 +11201,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
10059
11201
  ...payload?.description ? { description: payload.description } : {},
10060
11202
  ...payload?.tags ? { tags: payload.tags } : {},
10061
11203
  ...payload?.columns ? { columns: payload.columns } : {},
11204
+ ...has(payload, "lifecycle") ? {
11205
+ lifecycle: payload?.lifecycle ?? null
11206
+ } : {},
10062
11207
  ...has(payload, "supervisor") ? {
10063
11208
  supervisor: payload?.supervisor ?? null
10064
11209
  } : {}
@@ -10087,7 +11232,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10087
11232
  fail(ws, type, "boardId required");
10088
11233
  return true;
10089
11234
  }
10090
- const board = await getBoard(ctx.projectRoot, boardId);
11235
+ const board = await getBoard2(ctx.projectRoot, boardId);
10091
11236
  const activeSessionId = ctx.context?.session?.id;
10092
11237
  if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
10093
11238
  fail(ws, type, "The active session Kanban board cannot be deleted.");
@@ -10119,7 +11264,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10119
11264
  )) {
10120
11265
  await addTask(ctx.projectRoot, board.id, taskInput);
10121
11266
  }
10122
- ok(ws, type, await getBoard(ctx.projectRoot, board.id) ?? board);
11267
+ ok(ws, type, await getBoard2(ctx.projectRoot, board.id) ?? board);
10123
11268
  return true;
10124
11269
  }
10125
11270
  case "kanban.task.ready": {
@@ -10209,14 +11354,20 @@ async function handleKanbanRoute(ws, msg, ctx) {
10209
11354
  fail(ws, type, "boardId and title required");
10210
11355
  return true;
10211
11356
  }
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
- });
11357
+ const result = await addTask(
11358
+ ctx.projectRoot,
11359
+ boardId,
11360
+ {
11361
+ title,
11362
+ columnId: payload?.columnId ?? "backlog",
11363
+ ...payload?.description ? { description: payload.description } : {},
11364
+ ...payload?.dueDate ? { dueDate: payload.dueDate } : {},
11365
+ ...payload?.priority ? { priority: payload.priority } : {},
11366
+ ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
11367
+ ...payload?.labels ? { labels: payload.labels } : {}
11368
+ },
11369
+ activityContext(ctx, "webui", payload?.activityNote)
11370
+ );
10220
11371
  result ? ok(ws, type, result.task) : fail(ws, type, `Board not found: ${boardId}`);
10221
11372
  return true;
10222
11373
  }
@@ -10272,25 +11423,32 @@ async function handleKanbanRoute(ws, msg, ctx) {
10272
11423
  fail(ws, type, "boardId and taskId required");
10273
11424
  return true;
10274
11425
  }
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
- });
11426
+ const board = await updateTask(
11427
+ ctx.projectRoot,
11428
+ boardId,
11429
+ taskId,
11430
+ {
11431
+ ...has(payload, "title") ? { title: payload?.title } : {},
11432
+ ...has(payload, "description") ? { description: payload?.description ?? "" } : {},
11433
+ ...has(payload, "dueDate") ? { dueDate: payload?.dueDate ?? null } : {},
11434
+ ...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
11435
+ ...has(payload, "priority") ? { priority: payload?.priority } : {},
11436
+ ...has(payload, "type") ? { type: payload?.type } : {},
11437
+ ...has(payload, "status") ? { status: payload?.status } : {},
11438
+ ...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
11439
+ ...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
11440
+ ...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
11441
+ ...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
11442
+ ...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
11443
+ ...has(payload, "retryPolicy") ? {
11444
+ retryPolicy: payload?.retryPolicy ?? null
11445
+ } : {},
11446
+ ...has(payload, "costCeilingUsd") ? {
11447
+ costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
11448
+ } : {}
11449
+ },
11450
+ activityContext(ctx, "webui", payload?.activityNote)
11451
+ );
10294
11452
  if (!board) {
10295
11453
  fail(ws, type, "Board or task not found");
10296
11454
  return true;
@@ -10300,6 +11458,32 @@ async function handleKanbanRoute(ws, msg, ctx) {
10300
11458
  ok(ws, type, task);
10301
11459
  return true;
10302
11460
  }
11461
+ case "kanban.task.transition": {
11462
+ const boardId = payload?.boardId;
11463
+ const taskId = payload?.taskId;
11464
+ const to = payload?.to;
11465
+ const actor = payload?.actor;
11466
+ const comment = payload?.comment;
11467
+ if (!boardId || !taskId || !to || !actor || !comment) {
11468
+ fail(ws, type, "boardId, taskId, to, actor, and comment required");
11469
+ return true;
11470
+ }
11471
+ const result = await transitionTask(ctx.projectRoot, boardId, taskId, {
11472
+ to,
11473
+ actor,
11474
+ comment,
11475
+ ...payload?.action ? { action: payload.action } : {},
11476
+ ...payload?.attachment ? { attachment: payload.attachment } : {},
11477
+ ...payload?.patch ? { patch: payload.patch } : {}
11478
+ });
11479
+ if (!result) {
11480
+ fail(ws, type, "Board or task not found");
11481
+ return true;
11482
+ }
11483
+ await syncSessionSource(ctx, result.task);
11484
+ ok(ws, type, result);
11485
+ return true;
11486
+ }
10303
11487
  case "kanban.task.move": {
10304
11488
  const boardId = payload?.boardId;
10305
11489
  const taskId = payload?.taskId;
@@ -10313,7 +11497,8 @@ async function handleKanbanRoute(ws, msg, ctx) {
10313
11497
  boardId,
10314
11498
  taskId,
10315
11499
  columnId,
10316
- payload?.order
11500
+ payload?.order,
11501
+ activityContext(ctx, "webui", payload?.activityNote)
10317
11502
  );
10318
11503
  if (!board) {
10319
11504
  fail(ws, type, "Move failed");
@@ -10418,14 +11603,24 @@ async function handleKanbanRoute(ws, msg, ctx) {
10418
11603
  fail(ws, type, "boardId, taskId, and name required");
10419
11604
  return true;
10420
11605
  }
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
- });
11606
+ const board = await addGoalMetricToTask(
11607
+ ctx.projectRoot,
11608
+ boardId,
11609
+ taskId,
11610
+ {
11611
+ name: name2,
11612
+ ...payload?.status ? { status: payload.status } : {},
11613
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
11614
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
11615
+ ...payload?.unit ? { unit: payload.unit } : {},
11616
+ ...payload?.notes ? { notes: payload.notes } : {}
11617
+ },
11618
+ activityContext(
11619
+ ctx,
11620
+ "webui",
11621
+ payload?.activityNote ?? `Goal metric added: ${name2}.`
11622
+ )
11623
+ );
10429
11624
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10430
11625
  return true;
10431
11626
  }
@@ -10437,14 +11632,25 @@ async function handleKanbanRoute(ws, msg, ctx) {
10437
11632
  fail(ws, type, "boardId, taskId, and metricId required");
10438
11633
  return true;
10439
11634
  }
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
- });
11635
+ const board = await updateGoalMetricOnTask(
11636
+ ctx.projectRoot,
11637
+ boardId,
11638
+ taskId,
11639
+ metricId,
11640
+ {
11641
+ ...payload?.name ? { name: payload.name } : {},
11642
+ ...payload?.status ? { status: payload.status } : {},
11643
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
11644
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
11645
+ ...payload?.unit ? { unit: payload.unit } : {},
11646
+ ...payload?.notes ? { notes: payload.notes } : {}
11647
+ },
11648
+ activityContext(
11649
+ ctx,
11650
+ "webui",
11651
+ payload?.activityNote ?? "Goal metric updated in WebUI."
11652
+ )
11653
+ );
10448
11654
  board ? ok(ws, type, board) : fail(ws, type, "Metric not found");
10449
11655
  return true;
10450
11656
  }
@@ -10455,23 +11661,29 @@ async function handleKanbanRoute(ws, msg, ctx) {
10455
11661
  fail(ws, type, "boardId and taskId required");
10456
11662
  return true;
10457
11663
  }
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
- });
11664
+ const board = await assignTask(
11665
+ ctx.projectRoot,
11666
+ boardId,
11667
+ taskId,
11668
+ {
11669
+ ...payload?.agentId ? { agentId: payload.agentId } : {},
11670
+ ...payload?.name ? { name: payload.name } : {},
11671
+ ...payload?.role ? { role: payload.role } : {},
11672
+ ...payload?.provider ? { provider: payload.provider } : {},
11673
+ ...payload?.model ? { model: payload.model } : {},
11674
+ ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
11675
+ ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
11676
+ ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
11677
+ ...payload?.skills ? { skills: payload.skills } : {},
11678
+ ...payload?.tools ? { tools: payload.tools } : {},
11679
+ ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
11680
+ ...payload?.assignee ? { assignee: payload.assignee } : {},
11681
+ ...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
11682
+ ...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
11683
+ ...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
11684
+ },
11685
+ activityContext(ctx, void 0, payload?.activityNote)
11686
+ );
10475
11687
  board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
10476
11688
  return true;
10477
11689
  }
@@ -10483,11 +11695,21 @@ async function handleKanbanRoute(ws, msg, ctx) {
10483
11695
  fail(ws, type, "boardId, taskId, and description required");
10484
11696
  return true;
10485
11697
  }
10486
- const board = await addCheckToTask(ctx.projectRoot, boardId, taskId, {
10487
- description,
10488
- type: payload?.checkType ?? "manual",
10489
- status: payload?.status ?? "pending"
10490
- });
11698
+ const board = await addCheckToTask(
11699
+ ctx.projectRoot,
11700
+ boardId,
11701
+ taskId,
11702
+ {
11703
+ description,
11704
+ type: payload?.checkType ?? "manual",
11705
+ status: payload?.status ?? "pending"
11706
+ },
11707
+ activityContext(
11708
+ ctx,
11709
+ "webui",
11710
+ payload?.activityNote ?? `Acceptance check added: ${description}.`
11711
+ )
11712
+ );
10491
11713
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10492
11714
  return true;
10493
11715
  }
@@ -10499,9 +11721,20 @@ async function handleKanbanRoute(ws, msg, ctx) {
10499
11721
  fail(ws, type, "boardId, taskId, and checkId required");
10500
11722
  return true;
10501
11723
  }
10502
- const board = await updateCheckOnTask(ctx.projectRoot, boardId, taskId, checkId, {
10503
- ...has(payload, "status") ? { status: payload?.status } : {}
10504
- });
11724
+ const board = await updateCheckOnTask(
11725
+ ctx.projectRoot,
11726
+ boardId,
11727
+ taskId,
11728
+ checkId,
11729
+ {
11730
+ ...has(payload, "status") ? { status: payload?.status } : {}
11731
+ },
11732
+ activityContext(
11733
+ ctx,
11734
+ "webui",
11735
+ payload?.activityNote ?? `Acceptance check updated${payload?.status ? ` to ${String(payload.status)}` : ""}.`
11736
+ )
11737
+ );
10505
11738
  if (!board) fail(ws, type, "Check not found");
10506
11739
  else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
10507
11740
  return true;
@@ -10514,10 +11747,17 @@ async function handleKanbanRoute(ws, msg, ctx) {
10514
11747
  fail(ws, type, "boardId, taskId, and content required");
10515
11748
  return true;
10516
11749
  }
10517
- const board = await addNoteToTask(ctx.projectRoot, boardId, taskId, {
10518
- author: payload?.author ?? "webui",
10519
- content
10520
- });
11750
+ const author = payload?.author ?? "webui";
11751
+ const board = await addNoteToTask(
11752
+ ctx.projectRoot,
11753
+ boardId,
11754
+ taskId,
11755
+ {
11756
+ author,
11757
+ content
11758
+ },
11759
+ activityContext(ctx, author)
11760
+ );
10521
11761
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10522
11762
  return true;
10523
11763
  }
@@ -10560,7 +11800,62 @@ async function handleKanbanRoute(ws, msg, ctx) {
10560
11800
  return true;
10561
11801
  }
10562
11802
  const task = await getTask(ctx.projectRoot, boardId, taskId);
10563
- task ? ok(ws, type, task) : fail(ws, type, "Task not found");
11803
+ if (task) {
11804
+ await touchTaskPresence(ctx, boardId, task.id);
11805
+ ok(ws, type, task);
11806
+ } else {
11807
+ fail(ws, type, "Task not found");
11808
+ }
11809
+ return true;
11810
+ }
11811
+ case "kanban.task.activity": {
11812
+ const boardId = payload?.boardId;
11813
+ const taskId = payload?.taskId;
11814
+ if (!boardId || !taskId) {
11815
+ fail(ws, type, "boardId and taskId required");
11816
+ return true;
11817
+ }
11818
+ const presenceBoard = await touchTaskPresence(ctx, boardId, taskId);
11819
+ const events = await listTaskActivity(ctx.projectRoot, boardId, taskId, {
11820
+ ...typeof payload?.limit === "number" ? { limit: payload.limit } : {}
11821
+ });
11822
+ ok(ws, type, {
11823
+ boardId,
11824
+ taskId,
11825
+ events,
11826
+ presence: presenceBoard?.presence?.filter((entry) => entry.taskId === taskId) ?? []
11827
+ });
11828
+ return true;
11829
+ }
11830
+ case "kanban.task.activity.add": {
11831
+ const boardId = payload?.boardId;
11832
+ const taskId = payload?.taskId;
11833
+ const kind = payload?.kind;
11834
+ const summary = payload?.summary;
11835
+ const allowedKinds = ["decision", "attempt", "result", "blocker", "observation"];
11836
+ const allowedOutcomes = ["succeeded", "failed", "partial", "skipped", "unknown"];
11837
+ if (!boardId || !taskId || !summary?.trim() || !allowedKinds.includes(kind)) {
11838
+ fail(ws, type, "boardId, taskId, summary, and a valid activity kind required");
11839
+ return true;
11840
+ }
11841
+ const requestedOutcome = payload?.outcome;
11842
+ const outcome = allowedOutcomes.includes(requestedOutcome) ? requestedOutcome : "unknown";
11843
+ const board = await recordTaskActivity(
11844
+ ctx.projectRoot,
11845
+ boardId,
11846
+ taskId,
11847
+ {
11848
+ kind,
11849
+ summary: summary.trim(),
11850
+ outcome,
11851
+ ...typeof payload?.details === "string" && payload.details.trim() ? { details: payload.details.trim() } : {}
11852
+ },
11853
+ activityContext(
11854
+ ctx,
11855
+ payload?.actor ?? ctx.context?.agentId ?? "webui"
11856
+ )
11857
+ );
11858
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10564
11859
  return true;
10565
11860
  }
10566
11861
  case "kanban.column.add": {
@@ -11038,6 +12333,18 @@ async function handleSpecsRoute(_ws, msg, handlers) {
11038
12333
  }
11039
12334
 
11040
12335
  // src/server/message-dispatcher.ts
12336
+ var chronicleCache = /* @__PURE__ */ new Map();
12337
+ async function chronicleEngine(projectRoot) {
12338
+ const now = Date.now();
12339
+ const cached = chronicleCache.get(projectRoot);
12340
+ if (cached && now - cached.loadedAt < 1e3) return cached.engine;
12341
+ const paths = resolveWstackPaths2({ projectRoot, userHome: os2.homedir() });
12342
+ const engine = await ChronicleQueryEngine.fromDirectory(
12343
+ path19.join(paths.projectDir, "chronicle")
12344
+ );
12345
+ chronicleCache.set(projectRoot, { loadedAt: now, engine });
12346
+ return engine;
12347
+ }
11041
12348
  function createMessageDispatcher(opts) {
11042
12349
  const { state, deps: deps2, cb, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
11043
12350
  function makeWorklistContext() {
@@ -11049,7 +12356,8 @@ function createMessageDispatcher(opts) {
11049
12356
  state: deps2.context.state
11050
12357
  },
11051
12358
  send: (w, m) => send(w, m),
11052
- broadcast: (m) => broadcast(state.getClients(), m)
12359
+ broadcast: (m) => broadcast(state.getClients(), m),
12360
+ replaceTodos: (todos) => deps2.context.state.replaceTodos(todos)
11053
12361
  };
11054
12362
  }
11055
12363
  function makeSkillsContext() {
@@ -11058,7 +12366,7 @@ function createMessageDispatcher(opts) {
11058
12366
  skillLoader: deps2.skillLoader,
11059
12367
  skillInstaller: deps2.skillInstaller,
11060
12368
  projectRoot,
11061
- projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
12369
+ projectSkillsDir: path19.join(projectRoot, ".wrongstack", "skills"),
11062
12370
  globalSkillsDir: deps2.wpaths.globalSkills
11063
12371
  };
11064
12372
  }
@@ -11096,7 +12404,7 @@ function createMessageDispatcher(opts) {
11096
12404
  if (await handleMailboxRoute(ws, msg, routes.mailboxRoutes)) return;
11097
12405
  if (await handleMcpRoute(ws, msg, routes.mcpRoutes)) return;
11098
12406
  if (await handleBrainRoute(ws, msg, routes.brainRoutes)) return;
11099
- if (await handleAutoPhaseRoute(ws, msg, routes.autoPhaseRoutes)) return;
12407
+ if (await handleGoalRoute(ws, msg, routes.goalRoutes)) return;
11100
12408
  if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
11101
12409
  if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
11102
12410
  if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
@@ -11300,6 +12608,14 @@ function createMessageDispatcher(opts) {
11300
12608
  return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
11301
12609
  case "memory.super.remember":
11302
12610
  return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
12611
+ case "memory.super.recover":
12612
+ return handleSuperMemoryRecover(ws, msg, deps2.memoryStore);
12613
+ case "memory.super.candidateResolve":
12614
+ return handleSuperMemoryCandidateResolve(ws, msg, deps2.memoryStore);
12615
+ case "memory.super.backfillRecoverable":
12616
+ return handleSuperMemoryBackfillRecoverable(ws, msg, deps2.memoryStore);
12617
+ case "memory.super.forFile":
12618
+ return handleSuperMemoryForFile(ws, msg, deps2.memoryStore);
11303
12619
  // ── MCP tripwires — handleMcpRoute claims these upstream. ──
11304
12620
  case "mcp.list":
11305
12621
  throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
@@ -11530,6 +12846,59 @@ function createMessageDispatcher(opts) {
11530
12846
  });
11531
12847
  break;
11532
12848
  }
12849
+ // ── Chronicle journal queries (parity with embedded webui-server) ──
12850
+ // Mirrors packages/cli/src/webui-server/message-router.ts:645-664.
12851
+ // The engine is cached for 1s to avoid re-reading the journal on every
12852
+ // query; the cache is module-scoped so it survives across messages on
12853
+ // the same connection.
12854
+ case "chronicle.query": {
12855
+ const payload = msg.payload ?? {};
12856
+ const engine = await chronicleEngine(state.getProjectRoot());
12857
+ send(ws, { type: "chronicle.query_result", payload: engine.query(payload.query ?? {}) });
12858
+ break;
12859
+ }
12860
+ case "chronicle.facet": {
12861
+ const payload = msg.payload ?? {};
12862
+ const allowed = /* @__PURE__ */ new Set([
12863
+ "eventType",
12864
+ "outcome",
12865
+ "projectId",
12866
+ "sessionId",
12867
+ "agentId",
12868
+ "taskId",
12869
+ "providerId",
12870
+ "modelId",
12871
+ "resourceKind",
12872
+ "resourcePath",
12873
+ "toolCallId"
12874
+ ]);
12875
+ if (!payload.field || !allowed.has(payload.field)) {
12876
+ send(ws, {
12877
+ type: "chronicle.error",
12878
+ payload: { message: "Invalid Chronicle facet field." }
12879
+ });
12880
+ break;
12881
+ }
12882
+ const engine = await chronicleEngine(state.getProjectRoot());
12883
+ send(ws, {
12884
+ type: "chronicle.facet_result",
12885
+ payload: {
12886
+ field: payload.field,
12887
+ values: engine.facet(payload.field, payload.query ?? {}, payload.limit),
12888
+ diagnostics: engine.diagnostics
12889
+ }
12890
+ });
12891
+ break;
12892
+ }
12893
+ case "chronicle.graph": {
12894
+ const payload = msg.payload ?? {};
12895
+ const engine = await chronicleEngine(state.getProjectRoot());
12896
+ send(ws, {
12897
+ type: "chronicle.graph_result",
12898
+ payload: engine.graph(payload.seed ?? {}, payload.hops, payload.maxNodes)
12899
+ });
12900
+ break;
12901
+ }
11533
12902
  case "process.list": {
11534
12903
  await handleProcessList(ws);
11535
12904
  break;
@@ -11547,7 +12916,7 @@ function createMessageDispatcher(opts) {
11547
12916
  process.kill(process.pid, "SIGINT");
11548
12917
  break;
11549
12918
  }
11550
- case "goal.get": {
12919
+ case "goal-state.get": {
11551
12920
  await handleGoalGet(state.getProjectRoot(), (m) => broadcast(state.getClients(), m));
11552
12921
  break;
11553
12922
  }
@@ -11581,9 +12950,9 @@ function createMessageDispatcher(opts) {
11581
12950
  }
11582
12951
 
11583
12952
  // src/server/pref-helpers.ts
11584
- import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
12953
+ import { atomicWrite as atomicWrite6, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
11585
12954
  import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
11586
- import * as fs13 from "node:fs/promises";
12955
+ import * as fs14 from "node:fs/promises";
11587
12956
  var PREF_KEYS = [
11588
12957
  "autonomy",
11589
12958
  "autonomyDelayMs",
@@ -11638,11 +13007,31 @@ var PREF_KEYS = [
11638
13007
  "thinkingWord",
11639
13008
  "statuslineMode",
11640
13009
  "animationStyle",
13010
+ "showModelReasoning",
11641
13011
  // Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
11642
13012
  "breakerEnabled",
11643
13013
  "breakerAutoKillResetMs",
11644
13014
  "fsAccess",
11645
- "debugStream"
13015
+ "debugStream",
13016
+ // Chimera (post-session) + auto-review (mid-session) settings.
13017
+ // Persisted to config.extensions['wstack-chimera'] / ['wstack-auto-review']
13018
+ // so the running plugins pick up changes after a session restart.
13019
+ "chimeraEnabled",
13020
+ "chimeraProvider",
13021
+ "chimeraModel",
13022
+ "chimeraMaxFiles",
13023
+ "chimeraAutoFix",
13024
+ "autoReviewEnabled",
13025
+ "autoReviewProvider",
13026
+ "autoReviewModel",
13027
+ "autoReviewFallbackProfile",
13028
+ "autoReviewFallbackModels",
13029
+ "autoReviewDebounceMs",
13030
+ "autoReviewMaxFilesPerBatch",
13031
+ "autoReviewMaxConcurrentReviews",
13032
+ "autoReviewCascadeOn",
13033
+ // Per-plugin enable/disable map (parity with the embedded server).
13034
+ "pluginsEnabled"
11646
13035
  ];
11647
13036
  function prefSnapshot(contextMeta) {
11648
13037
  const snapshot = {};
@@ -11656,7 +13045,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
11656
13045
  const write = async () => {
11657
13046
  let raw;
11658
13047
  try {
11659
- raw = await fs13.readFile(globalConfigPath, "utf8");
13048
+ raw = await fs14.readFile(globalConfigPath, "utf8");
11660
13049
  } catch {
11661
13050
  raw = "{}";
11662
13051
  }
@@ -11725,6 +13114,8 @@ async function persistPrefsToConfig(deps2, holder, payload) {
11725
13114
  setAutonomy("statuslineMode", payload["statuslineMode"]);
11726
13115
  if (typeof payload["animationStyle"] === "string")
11727
13116
  setAutonomy("animationStyle", payload["animationStyle"]);
13117
+ if (typeof payload["showModelReasoning"] === "boolean")
13118
+ setAutonomy("showModelReasoning", payload["showModelReasoning"]);
11728
13119
  if (autonomyTouched) decrypted.autonomy = autonomyCfg;
11729
13120
  if (typeof payload["nextPrediction"] === "boolean")
11730
13121
  decrypted.nextPrediction = payload["nextPrediction"];
@@ -11860,41 +13251,76 @@ async function persistPrefsToConfig(deps2, holder, payload) {
11860
13251
  }
11861
13252
  if (typeof payload["debugStream"] === "boolean")
11862
13253
  decrypted.debugStream = payload["debugStream"];
13254
+ if (typeof payload["pluginsEnabled"] === "object" && payload["pluginsEnabled"] !== null) {
13255
+ const ext = decrypted.extensions ?? {};
13256
+ for (const [pluginName, enabled] of Object.entries(
13257
+ payload["pluginsEnabled"]
13258
+ )) {
13259
+ if (FORBIDDEN_PROTO_KEYS2.has(pluginName)) continue;
13260
+ const pExt = ext[pluginName] ?? {};
13261
+ pExt["enabled"] = enabled;
13262
+ ext[pluginName] = pExt;
13263
+ }
13264
+ decrypted.extensions = ext;
13265
+ }
13266
+ const chimeraTouched = typeof payload["chimeraEnabled"] === "boolean" || typeof payload["chimeraProvider"] === "string" || typeof payload["chimeraModel"] === "string" || typeof payload["chimeraMaxFiles"] === "number" || typeof payload["chimeraAutoFix"] === "string";
13267
+ if (chimeraTouched) {
13268
+ const ext = decrypted.extensions ?? {};
13269
+ const chimera = ext["wstack-chimera"] ?? {};
13270
+ if (typeof payload["chimeraEnabled"] === "boolean")
13271
+ chimera["enabled"] = payload["chimeraEnabled"];
13272
+ if (typeof payload["chimeraProvider"] === "string")
13273
+ chimera["provider"] = payload["chimeraProvider"];
13274
+ if (typeof payload["chimeraModel"] === "string")
13275
+ chimera["model"] = payload["chimeraModel"];
13276
+ if (typeof payload["chimeraMaxFiles"] === "number" && payload["chimeraMaxFiles"] >= 1) {
13277
+ chimera["maxFiles"] = payload["chimeraMaxFiles"];
13278
+ }
13279
+ if (typeof payload["chimeraAutoFix"] === "string") {
13280
+ if (payload["chimeraAutoFix"] === "off" || payload["chimeraAutoFix"] === "ask" || payload["chimeraAutoFix"] === "auto") {
13281
+ chimera["autoFix"] = payload["chimeraAutoFix"];
13282
+ }
13283
+ }
13284
+ ext["wstack-chimera"] = chimera;
13285
+ decrypted.extensions = ext;
13286
+ }
13287
+ 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";
13288
+ if (autoReviewTouched) {
13289
+ const ext = decrypted.extensions ?? {};
13290
+ const ar = ext["wstack-auto-review"] ?? {};
13291
+ if (typeof payload["autoReviewEnabled"] === "boolean")
13292
+ ar["enabled"] = payload["autoReviewEnabled"];
13293
+ if (typeof payload["autoReviewProvider"] === "string")
13294
+ ar["provider"] = payload["autoReviewProvider"];
13295
+ if (typeof payload["autoReviewModel"] === "string")
13296
+ ar["model"] = payload["autoReviewModel"];
13297
+ if (typeof payload["autoReviewFallbackProfile"] === "string") {
13298
+ if (payload["autoReviewFallbackProfile"] === "") {
13299
+ delete ar["fallbackProfile"];
13300
+ } else {
13301
+ ar["fallbackProfile"] = payload["autoReviewFallbackProfile"];
13302
+ }
13303
+ }
13304
+ if (typeof payload["autoReviewDebounceMs"] === "number" && payload["autoReviewDebounceMs"] >= 0) {
13305
+ ar["debounceMs"] = payload["autoReviewDebounceMs"];
13306
+ }
13307
+ if (typeof payload["autoReviewMaxFilesPerBatch"] === "number" && payload["autoReviewMaxFilesPerBatch"] >= 1) {
13308
+ ar["maxFilesPerBatch"] = payload["autoReviewMaxFilesPerBatch"];
13309
+ }
13310
+ if (typeof payload["autoReviewMaxConcurrentReviews"] === "number" && payload["autoReviewMaxConcurrentReviews"] >= 1) {
13311
+ ar["maxConcurrentReviews"] = payload["autoReviewMaxConcurrentReviews"];
13312
+ }
13313
+ if (typeof payload["autoReviewCascadeOn"] === "string") {
13314
+ if (payload["autoReviewCascadeOn"] === "off" || payload["autoReviewCascadeOn"] === "critical" || payload["autoReviewCascadeOn"] === "high") {
13315
+ ar["cascadeOn"] = payload["autoReviewCascadeOn"];
13316
+ }
13317
+ }
13318
+ ext["wstack-auto-review"] = ar;
13319
+ decrypted.extensions = ext;
13320
+ }
11863
13321
  }, "prefs");
11864
13322
  }
11865
13323
 
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;
11896
- }
11897
-
11898
13324
  // src/server/provider-handlers.ts
11899
13325
  import { DefaultSecretScrubber } from "@wrongstack/core";
11900
13326
  import {
@@ -12223,8 +13649,9 @@ function createProviderHandlers(deps2) {
12223
13649
  }
12224
13650
 
12225
13651
  // src/server/routes.ts
12226
- import path20 from "node:path";
13652
+ import path21 from "node:path";
12227
13653
  import {
13654
+ buildRefinerContextSections,
12228
13655
  enhanceUserPrompt,
12229
13656
  gatedEnhancerReasoning,
12230
13657
  nextEnhanceTimeout,
@@ -12340,7 +13767,7 @@ async function handleMailboxCompact(ws, deps2, opts) {
12340
13767
  // src/server/mode-handlers.ts
12341
13768
  import {
12342
13769
  DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
12343
- resolveWstackPaths as resolveWstackPaths2,
13770
+ resolveWstackPaths as resolveWstackPaths3,
12344
13771
  ToolValidationError as ToolValidationError5
12345
13772
  } from "@wrongstack/core";
12346
13773
  function createModeHandlers(ctx) {
@@ -12380,6 +13807,7 @@ function createModeHandlers(ctx) {
12380
13807
  }
12381
13808
  const { id } = parsed.value;
12382
13809
  try {
13810
+ const prev = await ctx.modeStore.getActiveMode();
12383
13811
  if (id === "default") {
12384
13812
  await ctx.modeStore.setActiveMode(null);
12385
13813
  } else {
@@ -12390,8 +13818,13 @@ function createModeHandlers(ctx) {
12390
13818
  await ctx.modeStore.setActiveMode(id);
12391
13819
  }
12392
13820
  ctx.setModeId(id);
13821
+ const fromMode = prev?.id ?? "default";
13822
+ if (ctx.context.session && fromMode !== id) {
13823
+ void ctx.context.session.append({ type: "mode_changed", ts: (/* @__PURE__ */ new Date()).toISOString(), from: fromMode, to: id }).catch(() => {
13824
+ });
13825
+ }
12393
13826
  const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
12394
- const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
13827
+ const paths = resolveWstackPaths3({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
12395
13828
  const freshBuilder = new DefaultSystemPromptBuilder2({
12396
13829
  memoryStore: ctx.memoryStore,
12397
13830
  // Single injection channel: Super Memory turn middleware, not a static section.
@@ -12426,7 +13859,7 @@ function createModeHandlers(ctx) {
12426
13859
  }
12427
13860
 
12428
13861
  // src/server/project-handlers.ts
12429
- import * as path19 from "node:path";
13862
+ import * as path20 from "node:path";
12430
13863
  function createProjectHandlers(ctx) {
12431
13864
  return {
12432
13865
  listProjects: async (ws) => {
@@ -12452,7 +13885,7 @@ function createProjectHandlers(ctx) {
12452
13885
  selectProject: async (ws, msg) => {
12453
13886
  const payload = msg.payload;
12454
13887
  const root = typeof payload?.root === "string" ? payload.root : "";
12455
- const name2 = typeof payload?.name === "string" ? payload.name : root ? path19.basename(root) : "";
13888
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path20.basename(root) : "";
12456
13889
  send(ws, {
12457
13890
  type: "projects.selected",
12458
13891
  payload: {
@@ -12489,6 +13922,7 @@ function createProjectHandlers(ctx) {
12489
13922
  // src/server/session-handlers.ts
12490
13923
  import {
12491
13924
  DEFAULT_CONTEXT_WINDOW_MODE_ID,
13925
+ loadTodosCheckpoint,
12492
13926
  repairToolUseAdjacency,
12493
13927
  resolveContextWindowPolicy as resolveContextWindowPolicy3
12494
13928
  } from "@wrongstack/core";
@@ -12526,13 +13960,14 @@ function createSessionHandlers(ctx) {
12526
13960
  }).catch(() => void 0);
12527
13961
  await writer.close().catch(() => void 0);
12528
13962
  };
12529
- const activateSession = async (next, messages, usage) => {
13963
+ const activateSession = async (next, messages, usage, todos = []) => {
12530
13964
  const current = ctx.getSession();
12531
13965
  if (current !== next) await finalizeSession(current);
12532
13966
  ctx.setSession(next);
12533
13967
  ctx.context.session = next;
12534
13968
  ctx.context.state.replaceMessages(messages);
12535
- ctx.context.state.replaceTodos([]);
13969
+ await ctx.context.flushConversationJournal?.();
13970
+ ctx.context.state.replaceTodos(todos);
12536
13971
  ctx.context.readFiles.clear();
12537
13972
  ctx.context.fileMtimes.clear();
12538
13973
  ctx.context.state.setMeta(
@@ -12816,7 +14251,15 @@ function createSessionHandlers(ctx) {
12816
14251
  return;
12817
14252
  }
12818
14253
  const resumed = await ctx.getSessionStore().resume(id);
12819
- await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
14254
+ const restoredTodos = await loadTodosCheckpoint(
14255
+ sessionScopedPath2(ctx.sessionsDir, resumed.writer.id, ".todos.json")
14256
+ ).catch(() => null) ?? [];
14257
+ await activateSession(
14258
+ resumed.writer,
14259
+ resumed.data.messages,
14260
+ resumed.data.usage,
14261
+ restoredTodos
14262
+ );
12820
14263
  broadcast(ctx.clients, {
12821
14264
  type: "session.start",
12822
14265
  payload: {
@@ -12826,6 +14269,10 @@ function createSessionHandlers(ctx) {
12826
14269
  replayUsage: resumed.data.usage
12827
14270
  }
12828
14271
  });
14272
+ broadcast(ctx.clients, {
14273
+ type: "todos.updated",
14274
+ payload: { sessionId: resumed.writer.id, todos: restoredTodos }
14275
+ });
12829
14276
  sendResult(ws, true, `Resumed session ${id}`);
12830
14277
  } catch (err) {
12831
14278
  sendResult(ws, false, errMessage(err));
@@ -12851,11 +14298,17 @@ function createSessionHandlers(ctx) {
12851
14298
  if (!ensureCurrentSession(ws, msg, "session.rewind")) return;
12852
14299
  const { checkpointIndex } = msg.payload;
12853
14300
  try {
12854
- const { DefaultSessionRewinder } = await import("@wrongstack/core");
14301
+ const { applyRewindToConversation, DefaultSessionRewinder } = await import("@wrongstack/core");
12855
14302
  const projectRoot = ctx.getProjectRoot();
12856
14303
  const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
12857
- await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
12858
- await ctx.context.session.truncateToCheckpoint(checkpointIndex);
14304
+ const reverted = await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
14305
+ await applyRewindToConversation({
14306
+ session: ctx.context.session,
14307
+ state: ctx.context.state,
14308
+ sessionsDir: ctx.sessionsDir,
14309
+ promptIndex: checkpointIndex,
14310
+ revertedFiles: reverted.revertedFiles
14311
+ });
12859
14312
  sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
12860
14313
  broadcast(ctx.clients, {
12861
14314
  type: "session.start",
@@ -13070,6 +14523,11 @@ function buildRoutes(state, deps2, cb) {
13070
14523
  const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
13071
14524
  try {
13072
14525
  const history = recentTextTurns(deps2.context.messages);
14526
+ const contextSections = await buildRefinerContextSections({
14527
+ text,
14528
+ memoryStore: deps2.memoryStore,
14529
+ context: deps2.context
14530
+ });
13073
14531
  const resolved = await resolveProviderModelMetadata(
13074
14532
  deps2.modelsRegistry,
13075
14533
  providerId,
@@ -13083,6 +14541,14 @@ function buildRoutes(state, deps2, cb) {
13083
14541
  model,
13084
14542
  text,
13085
14543
  history,
14544
+ contextSections,
14545
+ ...payload.previousRefined ? {
14546
+ previousRefinement: {
14547
+ refined: payload.previousRefined,
14548
+ english: payload.previousEnglish || payload.previousRefined
14549
+ }
14550
+ } : {},
14551
+ ...payload.retryFeedback ? { retryFeedback: payload.retryFeedback } : {},
13086
14552
  timeoutMs,
13087
14553
  ...reasoning ? { reasoning } : {},
13088
14554
  onError: (reason, kind) => {
@@ -13242,6 +14708,17 @@ function buildRoutes(state, deps2, cb) {
13242
14708
  cfg.modelMatrix = payload["modelMatrix"];
13243
14709
  }
13244
14710
  if (typeof payload["fallbackAuto"] === "boolean") cfg.fallbackAuto = payload["fallbackAuto"];
14711
+ const routingPatch = {};
14712
+ if (Array.isArray(payload["fallbackModels"])) routingPatch.fallbackModels = payload["fallbackModels"];
14713
+ if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"]))
14714
+ routingPatch.fallbackProfiles = payload["fallbackProfiles"];
14715
+ if (Array.isArray(payload["favoriteModels"])) routingPatch.favoriteModels = payload["favoriteModels"];
14716
+ if (typeof payload["favoriteModelsOnly"] === "boolean") routingPatch.favoriteModelsOnly = payload["favoriteModelsOnly"];
14717
+ if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"]))
14718
+ routingPatch.modelMatrix = payload["modelMatrix"];
14719
+ if (typeof payload["fallbackAuto"] === "boolean") routingPatch.fallbackAuto = payload["fallbackAuto"];
14720
+ if (Object.keys(routingPatch).length > 0)
14721
+ deps2.configStore.update(routingPatch);
13245
14722
  if (typeof payload["contextAutoCompact"] === "boolean") {
13246
14723
  if (payload["contextAutoCompact"] && deps2.autoCompactor) {
13247
14724
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
@@ -13310,7 +14787,7 @@ function buildRoutes(state, deps2, cb) {
13310
14787
  }
13311
14788
  return handleMailboxMessages(
13312
14789
  ws,
13313
- { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
14790
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
13314
14791
  parsed.value
13315
14792
  );
13316
14793
  },
@@ -13322,13 +14799,13 @@ function buildRoutes(state, deps2, cb) {
13322
14799
  }
13323
14800
  return handleMailboxAgents(
13324
14801
  ws,
13325
- { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
14802
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
13326
14803
  parsed.value
13327
14804
  );
13328
14805
  },
13329
14806
  clear: (ws) => handleMailboxClear(ws, {
13330
14807
  projectRoot: state.getProjectRoot(),
13331
- globalRoot: path20.dirname(deps2.globalConfigPath)
14808
+ globalRoot: path21.dirname(deps2.globalConfigPath)
13332
14809
  }),
13333
14810
  purge: (ws, msg) => {
13334
14811
  const parsed = validateMailboxPurgePayload(msg.payload);
@@ -13338,14 +14815,14 @@ function buildRoutes(state, deps2, cb) {
13338
14815
  }
13339
14816
  return handleMailboxPurge(
13340
14817
  ws,
13341
- { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
14818
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
13342
14819
  parsed.value
13343
14820
  );
13344
14821
  },
13345
14822
  compact: (ws, msg) => {
13346
14823
  return handleMailboxCompact(
13347
14824
  ws,
13348
- { projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
14825
+ { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
13349
14826
  msg.payload ?? {}
13350
14827
  );
13351
14828
  }
@@ -13454,8 +14931,8 @@ function buildRoutes(state, deps2, cb) {
13454
14931
  }
13455
14932
  }
13456
14933
  };
13457
- const autoPhaseRoutes = {
13458
- handleMessage: (msg) => deps2.autoPhaseHandler.handleMessage(msg)
14934
+ const goalRoutes = {
14935
+ handleMessage: (msg) => deps2.goalHandler.handleMessage(msg)
13459
14936
  };
13460
14937
  const specsRoutes = {
13461
14938
  handleMessage: (msg) => deps2.specsHandler.handleMessage(msg)
@@ -13476,7 +14953,7 @@ function buildRoutes(state, deps2, cb) {
13476
14953
  mailboxRoutes,
13477
14954
  mcpRoutes,
13478
14955
  brainRoutes,
13479
- autoPhaseRoutes,
14956
+ goalRoutes,
13480
14957
  specsRoutes,
13481
14958
  sddBoardRoutes,
13482
14959
  sddWizardRoutes
@@ -13597,7 +15074,7 @@ async function startWebUI(opts = {}) {
13597
15074
  brainLog,
13598
15075
  brainMonitor,
13599
15076
  codebaseIndexing,
13600
- autoPhaseHandler,
15077
+ goalHandler,
13601
15078
  specsHandler,
13602
15079
  sddBoardHandler,
13603
15080
  sddWizardHandler,
@@ -13658,21 +15135,21 @@ async function startWebUI(opts = {}) {
13658
15135
  wpaths
13659
15136
  }, watcherMetricsRef);
13660
15137
  async function touchProjectEntry(root, workDir) {
13661
- const resolved = path21.resolve(root);
15138
+ const resolved = path22.resolve(root);
13662
15139
  const manifest = await loadManifest(globalConfigPath);
13663
15140
  const now = (/* @__PURE__ */ new Date()).toISOString();
13664
- const existing = manifest.projects.find((p) => path21.resolve(p.root) === resolved);
15141
+ const existing = manifest.projects.find((p) => path22.resolve(p.root) === resolved);
13665
15142
  if (existing) {
13666
15143
  existing.lastSeen = now;
13667
- if (workDir) existing.lastWorkingDir = path21.resolve(workDir);
15144
+ if (workDir) existing.lastWorkingDir = path22.resolve(workDir);
13668
15145
  } else {
13669
15146
  manifest.projects.push({
13670
- name: path21.basename(resolved),
15147
+ name: path22.basename(resolved),
13671
15148
  root: resolved,
13672
15149
  slug: generateProjectSlug(resolved),
13673
15150
  createdAt: now,
13674
15151
  lastSeen: now,
13675
- lastWorkingDir: workDir ? path21.resolve(workDir) : void 0
15152
+ lastWorkingDir: workDir ? path22.resolve(workDir) : void 0
13676
15153
  });
13677
15154
  }
13678
15155
  await saveManifest(manifest, globalConfigPath);
@@ -13755,7 +15232,7 @@ async function startWebUI(opts = {}) {
13755
15232
  httpPort,
13756
15233
  wssPrimary,
13757
15234
  wssSecondary,
13758
- autoPhaseHandler,
15235
+ goalHandler,
13759
15236
  specsHandler,
13760
15237
  sddBoardHandler,
13761
15238
  sddWizardHandler,
@@ -13799,7 +15276,13 @@ async function startWebUI(opts = {}) {
13799
15276
  deps2.configStore.update({
13800
15277
  providers: snapshot.providers,
13801
15278
  ...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
13802
- ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
15279
+ ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {},
15280
+ ...snapshot.fallbackModels !== void 0 ? { fallbackModels: snapshot.fallbackModels } : {},
15281
+ ...snapshot.fallbackProfiles !== void 0 ? { fallbackProfiles: snapshot.fallbackProfiles } : {},
15282
+ ...snapshot.favoriteModels !== void 0 ? { favoriteModels: snapshot.favoriteModels } : {},
15283
+ ...snapshot.favoriteModelsOnly !== void 0 ? { favoriteModelsOnly: snapshot.favoriteModelsOnly } : {},
15284
+ ...snapshot.modelMatrix !== void 0 ? { modelMatrix: snapshot.modelMatrix } : {},
15285
+ ...snapshot.fallbackAuto !== void 0 ? { fallbackAuto: snapshot.fallbackAuto } : {}
13803
15286
  });
13804
15287
  broadcast(clients, {
13805
15288
  type: "providers.saved",
@@ -13860,7 +15343,7 @@ async function startWebUI(opts = {}) {
13860
15343
  },
13861
15344
  clients,
13862
15345
  pendingConfirms,
13863
- autoPhaseHandler,
15346
+ goalHandler,
13864
15347
  specsHandler,
13865
15348
  sddBoardHandler,
13866
15349
  sddWizardHandler,
@@ -13887,6 +15370,11 @@ async function startWebUI(opts = {}) {
13887
15370
  onFleetPing: () => {
13888
15371
  void eventArming.getFleetBroadcast()?.();
13889
15372
  },
15373
+ onTechStackEvent: (event) => broadcast(clients, event),
15374
+ // Read through `context` on every call rather than capturing: the running
15375
+ // loop swaps provider/model when the user switches (same live source the
15376
+ // completion handler reads).
15377
+ getLlm: () => context.provider && context.model ? { provider: context.provider, model: context.model } : void 0,
13890
15378
  distDir: opts.distDir
13891
15379
  });
13892
15380
  registerShutdown({
@@ -13916,7 +15404,7 @@ async function startWebUI(opts = {}) {
13916
15404
  archiveLowConfidenceAfterDays: config.superMemory?.hygiene?.archiveLowConfidenceAfterDays
13917
15405
  }).catch((err) => logger.warn(`super-memory session hygiene failed: ${toErrorMessage10(err)}`));
13918
15406
  }
13919
- await unregisterInstance(process.pid, path21.dirname(globalConfigPath));
15407
+ await unregisterInstance(process.pid, path22.dirname(globalConfigPath));
13920
15408
  }
13921
15409
  });
13922
15410
  }
@@ -13956,7 +15444,7 @@ function envFlag2(name2) {
13956
15444
  return value === "1" || value === "true" || value === "yes" || value === "on";
13957
15445
  }
13958
15446
  function printHelp() {
13959
- console.log(`Usage: wstackui [options]
15447
+ console.log(`Usage: wstack --webui [options]
13960
15448
 
13961
15449
  Options:
13962
15450
  --host <host> Bind host/interface (default: 127.0.0.1)