@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
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- // src/server/autophase-ws-handler.ts
1
+ // src/server/goal-ws-handler.ts
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { toErrorMessage } from "@wrongstack/core/utils";
4
4
  import {
5
5
  assignNickname,
6
- AutoPhasePlanner,
6
+ GoalPlanner,
7
7
  PhaseGraphBuilder,
8
8
  PhaseOrchestrator,
9
9
  PhaseStore,
@@ -11,10 +11,10 @@ import {
11
11
  } from "@wrongstack/core";
12
12
  function deriveTitle(goal) {
13
13
  const firstLine = goal.split("\n").map((l) => l.trim()).find(Boolean);
14
- if (!firstLine) return "AutoPhase";
14
+ if (!firstLine) return "Goal";
15
15
  const sentence = firstLine.split(/(?<=[.!?])\s/)[0] ?? firstLine;
16
16
  const trimmed = sentence.length <= 64 ? sentence : `${sentence.slice(0, 63).trimEnd()}\u2026`;
17
- return trimmed || "AutoPhase";
17
+ return trimmed || "Goal";
18
18
  }
19
19
  function isGitRepo(cwd) {
20
20
  try {
@@ -37,7 +37,7 @@ function commitsSince(cwd, baseSha, branch) {
37
37
  return [];
38
38
  }
39
39
  }
40
- var AutoPhaseWebSocketHandler = class {
40
+ var GoalWebSocketHandler = class {
41
41
  constructor(agent, context, logger, storeDir, events, projectRoot, onBoardState) {
42
42
  this.agent = agent;
43
43
  this.context = context;
@@ -80,94 +80,94 @@ var AutoPhaseWebSocketHandler = class {
80
80
  }
81
81
  async handleMessage(msg) {
82
82
  switch (msg.type) {
83
- case "autophase.start":
83
+ case "goal.start":
84
84
  await this.handleStart(msg.payload);
85
85
  break;
86
- case "autophase.pause":
86
+ case "goal.pause":
87
87
  this.orchestrator?.pause();
88
- this.broadcast({ type: "autophase.paused", payload: {} });
88
+ this.broadcast({ type: "goal.paused", payload: {} });
89
89
  break;
90
- case "autophase.resume":
90
+ case "goal.resume":
91
91
  this.orchestrator?.resume();
92
- this.broadcast({ type: "autophase.resumed", payload: {} });
92
+ this.broadcast({ type: "goal.resumed", payload: {} });
93
93
  break;
94
- case "autophase.stop":
94
+ case "goal.stop":
95
95
  await this.handleStop();
96
96
  break;
97
- case "autophase.clear":
97
+ case "goal.clear":
98
98
  await this.handleClear();
99
99
  break;
100
- case "autophase.revert":
100
+ case "goal.revert":
101
101
  await this.handleRevert();
102
102
  break;
103
- case "autophase.status":
103
+ case "goal.status":
104
104
  this.broadcastState();
105
105
  break;
106
- case "autophase.selectPhase": {
106
+ case "goal.selectPhase": {
107
107
  const phaseId = msg.payload?.phaseId;
108
108
  if (phaseId && this.graph) {
109
109
  this.broadcastState(phaseId);
110
110
  }
111
111
  break;
112
112
  }
113
- case "autophase.taskStatus": {
113
+ case "goal.taskStatus": {
114
114
  const { taskId, status } = msg.payload;
115
115
  await this.handleTaskStatusChange(taskId, status);
116
116
  break;
117
117
  }
118
- case "autophase.moveTask": {
118
+ case "goal.moveTask": {
119
119
  const { taskId, toPhaseId } = msg.payload;
120
120
  if (this.orchestrator?.moveTask(taskId, toPhaseId)) this.afterBoardMutation();
121
121
  break;
122
122
  }
123
- case "autophase.assignTask": {
123
+ case "goal.assignTask": {
124
124
  const { taskId, agentId, agentName } = msg.payload;
125
125
  if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName)) this.afterBoardMutation();
126
126
  break;
127
127
  }
128
- case "autophase.addTask": {
128
+ case "goal.addTask": {
129
129
  const { phaseId, title, description, type, priority } = msg.payload;
130
130
  if (title?.trim() && this.orchestrator?.addTask(phaseId, { title: title.trim(), description, type, priority })) {
131
131
  this.afterBoardMutation();
132
132
  }
133
133
  break;
134
134
  }
135
- case "autophase.retryTask":
136
- case "autophase.runTask": {
135
+ case "goal.retryTask":
136
+ case "goal.runTask": {
137
137
  const { taskId } = msg.payload;
138
138
  if (this.orchestrator?.requeueTask(taskId)) this.afterBoardMutation();
139
139
  break;
140
140
  }
141
- case "autophase.toggleAutonomous": {
141
+ case "goal.toggleAutonomous": {
142
142
  const autonomous = msg.payload?.autonomous ?? !this.graph?.autonomous;
143
143
  if (this.graph) {
144
144
  this.graph.autonomous = autonomous;
145
145
  await this.store.save(this.graph);
146
- this.broadcast({ type: "autophase.state", payload: this.buildState() });
146
+ this.broadcast({ type: "goal.state", payload: this.buildState() });
147
147
  }
148
148
  break;
149
149
  }
150
- case "autophase.save": {
150
+ case "goal.save": {
151
151
  if (this.graph) {
152
152
  await this.store.save(this.graph);
153
- this.broadcast({ type: "autophase.saved", payload: { graphId: this.graph.id } });
153
+ this.broadcast({ type: "goal.saved", payload: { graphId: this.graph.id } });
154
154
  }
155
155
  break;
156
156
  }
157
- case "autophase.list": {
157
+ case "goal.list": {
158
158
  const graphs = await this.store.list();
159
- this.broadcast({ type: "autophase.list", payload: { graphs } });
159
+ this.broadcast({ type: "goal.list", payload: { graphs } });
160
160
  break;
161
161
  }
162
- case "autophase.load": {
162
+ case "goal.load": {
163
163
  const graphId = msg.payload?.graphId;
164
164
  if (graphId) {
165
165
  const graph = await this.store.load(graphId);
166
166
  if (graph) {
167
167
  this.graph = graph;
168
- this.broadcast({ type: "autophase.state", payload: this.buildState() });
168
+ this.broadcast({ type: "goal.state", payload: this.buildState() });
169
169
  } else {
170
- this.broadcast({ type: "autophase.error", payload: { message: `Graph not found: ${graphId}` } });
170
+ this.broadcast({ type: "goal.error", payload: { message: `Graph not found: ${graphId}` } });
171
171
  }
172
172
  }
173
173
  break;
@@ -182,14 +182,14 @@ var AutoPhaseWebSocketHandler = class {
182
182
  this.stopping = false;
183
183
  const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal, this.abort.signal);
184
184
  if (this.stopping || this.abort.signal.aborted) {
185
- this.broadcast({ type: "autophase.stopped", payload: { title } });
185
+ this.broadcast({ type: "goal.stopped", payload: { title } });
186
186
  return;
187
187
  }
188
- this.logger.info(`[AutoPhase] Starting: ${title}`);
188
+ this.logger.info(`[Goal] Starting: ${title}`);
189
189
  const graph = await new PhaseGraphBuilder({ title, description: goal, phases, autonomous }).build();
190
190
  this.graph = graph;
191
191
  await this.store.save(graph);
192
- const useWorktrees = payload?.worktrees ?? process.env["WRONGSTACK_AUTOPHASE_WORKTREES"] !== "0";
192
+ const useWorktrees = payload?.worktrees ?? process.env["WRONGSTACK_GOAL_WORKTREES"] !== "0";
193
193
  if (!this.worktrees && this.events && this.projectRoot && useWorktrees && isGitRepo(this.projectRoot)) {
194
194
  this.worktrees = new WorktreeManager({
195
195
  projectRoot: this.projectRoot,
@@ -204,18 +204,18 @@ var AutoPhaseWebSocketHandler = class {
204
204
  graph,
205
205
  ctx: {
206
206
  executeTask: async (task, phaseId, env) => {
207
- this.logger.info(`[AutoPhase] [${phaseId}] Executing: ${task.title}`);
207
+ this.logger.info(`[Goal] [${phaseId}] Executing: ${task.title}`);
208
208
  const result = await this.executeTaskWithAgent(task, phaseId, env);
209
- this.logger.info(`[AutoPhase] [${phaseId}] Completed: ${task.title}`);
209
+ this.logger.info(`[Goal] [${phaseId}] Completed: ${task.title}`);
210
210
  return result;
211
211
  },
212
212
  onPhaseComplete: (phase) => {
213
- this.logger.info(`[AutoPhase] Phase completed: ${phase.name}`);
213
+ this.logger.info(`[Goal] Phase completed: ${phase.name}`);
214
214
  void this.store.save(graph);
215
215
  this.broadcastState();
216
216
  },
217
217
  onPhaseFail: (phase, error) => {
218
- this.logger.error(`[AutoPhase] Phase failed: ${phase.name} \u2014 ${error.message}`);
218
+ this.logger.error(`[Goal] Phase failed: ${phase.name} \u2014 ${error.message}`);
219
219
  void this.store.save(graph);
220
220
  this.broadcastState();
221
221
  }
@@ -237,20 +237,20 @@ var AutoPhaseWebSocketHandler = class {
237
237
  this.stopBroadcast();
238
238
  const failed = graph.failedPhaseIds.length > 0;
239
239
  this.broadcast(
240
- failed ? { type: "autophase.failed", payload: { title } } : { type: "autophase.completed", payload: { title } }
240
+ failed ? { type: "goal.failed", payload: { title } } : { type: "goal.completed", payload: { title } }
241
241
  );
242
242
  this.broadcastState();
243
243
  }).catch((err) => {
244
- this.logger.error(`[AutoPhase] Aborted: ${toErrorMessage(err)}`);
244
+ this.logger.error(`[Goal] Aborted: ${toErrorMessage(err)}`);
245
245
  this.stopBroadcast();
246
- this.broadcast({ type: "autophase.failed", payload: { title, error: String(err) } });
246
+ this.broadcast({ type: "goal.failed", payload: { title, error: String(err) } });
247
247
  });
248
248
  }
249
249
  /**
250
250
  * Halt the run NOW — at any phase. Sets `stopping` (so a planning turn that
251
251
  * resolves afterwards bails), aborts in-flight agents, stops the orchestrator
252
252
  * tick, and ends the live broadcast. The board is kept for review; use
253
- * `autophase.clear` to reset or `autophase.revert` to undo the changes.
253
+ * `goal.clear` to reset or `goal.revert` to undo the changes.
254
254
  */
255
255
  async handleStop() {
256
256
  this.stopping = true;
@@ -258,12 +258,12 @@ var AutoPhaseWebSocketHandler = class {
258
258
  this.orchestrator?.stop();
259
259
  this.stopBroadcast();
260
260
  if (this.graph) await this.store.save(this.graph).catch(() => void 0);
261
- this.broadcast({ type: "autophase.stopped", payload: { title: this.graph?.title } });
261
+ this.broadcast({ type: "goal.stopped", payload: { title: this.graph?.title } });
262
262
  }
263
263
  /**
264
264
  * Stop + wipe: tear down phase worktrees and reset to an empty board so the UI
265
265
  * returns to the start screen ("new one"). Does NOT touch already-merged commits
266
- * on the base branch — that is `autophase.revert`.
266
+ * on the base branch — that is `goal.revert`.
267
267
  */
268
268
  async handleClear() {
269
269
  await this.handleStop();
@@ -272,8 +272,8 @@ var AutoPhaseWebSocketHandler = class {
272
272
  this.graph = null;
273
273
  this.runBase = null;
274
274
  this.usedNicknames.clear();
275
- this.broadcast({ type: "autophase.cleared", payload: {} });
276
- this.broadcast({ type: "autophase.state", payload: this.buildState() });
275
+ this.broadcast({ type: "goal.cleared", payload: {} });
276
+ this.broadcast({ type: "goal.state", payload: this.buildState() });
277
277
  }
278
278
  /**
279
279
  * Stop + undo: remove phase worktrees, then history-preservingly `git revert`
@@ -285,7 +285,7 @@ var AutoPhaseWebSocketHandler = class {
285
285
  await this.handleStop();
286
286
  if (!this.worktrees || !this.runBase || !this.projectRoot) {
287
287
  this.broadcast({
288
- type: "autophase.reverted",
288
+ type: "goal.reverted",
289
289
  payload: { ok: false, reverted: 0, reason: "no git baseline was captured for this run" }
290
290
  });
291
291
  return;
@@ -293,13 +293,13 @@ var AutoPhaseWebSocketHandler = class {
293
293
  await this.worktrees.cleanupAllManaged().catch(() => void 0);
294
294
  const shas = commitsSince(this.projectRoot, this.runBase.sha, this.runBase.branch);
295
295
  const res = await this.worktrees.revertCommits(this.runBase.branch, shas);
296
- this.broadcast({ type: "autophase.reverted", payload: res });
296
+ this.broadcast({ type: "goal.reverted", payload: res });
297
297
  if (res.ok) {
298
298
  this.orchestrator = null;
299
299
  this.graph = null;
300
300
  this.runBase = null;
301
- this.broadcast({ type: "autophase.cleared", payload: {} });
302
- this.broadcast({ type: "autophase.state", payload: this.buildState() });
301
+ this.broadcast({ type: "goal.cleared", payload: {} });
302
+ this.broadcast({ type: "goal.state", payload: this.buildState() });
303
303
  }
304
304
  }
305
305
  /** Generic fallback phases when the LLM planner produces nothing usable. */
@@ -318,7 +318,7 @@ var AutoPhaseWebSocketHandler = class {
318
318
  * uninterruptible). */
319
319
  async planPhases(goal, signal) {
320
320
  try {
321
- const planner = new AutoPhasePlanner({
321
+ const planner = new GoalPlanner({
322
322
  goal,
323
323
  runOnce: async (prompt) => {
324
324
  const result = await this.agent.run(prompt, {
@@ -330,12 +330,12 @@ var AutoPhaseWebSocketHandler = class {
330
330
  const { phases, parseFailed } = await planner.plan();
331
331
  if (!parseFailed && phases.length > 0) {
332
332
  const todos = phases.reduce((n, p) => n + (p.taskTemplates?.length ?? 0), 0);
333
- this.logger.info(`[AutoPhase] Planned ${phases.length} phases / ${todos} todos for: ${goal}`);
333
+ this.logger.info(`[Goal] Planned ${phases.length} phases / ${todos} todos for: ${goal}`);
334
334
  return phases;
335
335
  }
336
- this.logger.info(`[AutoPhase] Planner produced no phases; using defaults for: ${goal}`);
336
+ this.logger.info(`[Goal] Planner produced no phases; using defaults for: ${goal}`);
337
337
  } catch (err) {
338
- this.logger.error(`[AutoPhase] Planning failed, using defaults: ${toErrorMessage(err)}`);
338
+ this.logger.error(`[Goal] Planning failed, using defaults: ${toErrorMessage(err)}`);
339
339
  }
340
340
  return this.defaultPhases();
341
341
  }
@@ -383,7 +383,7 @@ Type: ${task.type}`;
383
383
  if (this.broadcastInterval) return;
384
384
  this.broadcastInterval = setInterval(() => {
385
385
  const progress = this.orchestrator?.getProgress();
386
- if (progress) this.broadcast({ type: "autophase.progress", payload: progress });
386
+ if (progress) this.broadcast({ type: "goal.progress", payload: progress });
387
387
  this.broadcastState();
388
388
  }, 2e3);
389
389
  }
@@ -396,13 +396,13 @@ Type: ${task.type}`;
396
396
  broadcastState(activePhaseId) {
397
397
  if (!this.graph) return;
398
398
  const state = this.buildState(activePhaseId);
399
- this.broadcast({ type: "autophase.state", payload: state });
399
+ this.broadcast({ type: "goal.state", payload: state });
400
400
  if (this.onBoardState) {
401
401
  try {
402
402
  this.onBoardState(this.graph.id, state);
403
403
  } catch (err) {
404
404
  this.logger.error(
405
- `[AutoPhase] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
405
+ `[Goal] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
406
406
  );
407
407
  }
408
408
  }
@@ -478,7 +478,7 @@ Type: ${task.type}`;
478
478
  autonomous: this.graph.autonomous,
479
479
  totalTasks,
480
480
  completedTasks,
481
- // Structured progress + lastError consumed by the autophase store (were
481
+ // Structured progress + lastError consumed by the goal store (were
482
482
  // defined client-side but never sent, so they stayed null on the board).
483
483
  progress: {
484
484
  totalPhases: phases.length,
@@ -494,7 +494,7 @@ Type: ${task.type}`;
494
494
  sendState(client) {
495
495
  if (!this.graph) return;
496
496
  const state = this.buildState();
497
- this.send(client, { type: "autophase.state", payload: state });
497
+ this.send(client, { type: "goal.state", payload: state });
498
498
  }
499
499
  broadcast(msg) {
500
500
  const data = JSON.stringify(msg);
@@ -1752,9 +1752,9 @@ async function handleGitInfo(ws, projectRoot) {
1752
1752
  const cwd = projectRoot || void 0;
1753
1753
  try {
1754
1754
  const { execFile: ef } = await import("node:child_process");
1755
- const git = (args) => new Promise((resolve10) => {
1755
+ const git = (args) => new Promise((resolve12) => {
1756
1756
  ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
1757
- resolve10(err ? "" : stdout.trim());
1757
+ resolve12(err ? "" : stdout.trim());
1758
1758
  });
1759
1759
  });
1760
1760
  const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
@@ -1780,12 +1780,12 @@ async function handleGitInfo(ws, projectRoot) {
1780
1780
  function makeGit(cwd) {
1781
1781
  return async (args) => {
1782
1782
  const { execFile: ef } = await import("node:child_process");
1783
- return new Promise((resolve10) => {
1783
+ return new Promise((resolve12) => {
1784
1784
  ef(
1785
1785
  "git",
1786
1786
  args,
1787
1787
  { cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
1788
- (err, stdout) => resolve10(err ? "" : stdout)
1788
+ (err, stdout) => resolve12(err ? "" : stdout)
1789
1789
  );
1790
1790
  });
1791
1791
  };
@@ -1809,15 +1809,15 @@ async function handleGitChanges(ws, projectRoot) {
1809
1809
  if (!m) continue;
1810
1810
  const added = m[1] === "-" ? 0 : Number(m[1]);
1811
1811
  const deleted = m[2] === "-" ? 0 : Number(m[2]);
1812
- let path23 = m[3] ?? "";
1813
- if (path23 === "") {
1812
+ let path24 = m[3] ?? "";
1813
+ if (path24 === "") {
1814
1814
  i += 1;
1815
- path23 = parts[i + 1] ?? parts[i] ?? "";
1815
+ path24 = parts[i + 1] ?? parts[i] ?? "";
1816
1816
  i += 1;
1817
1817
  }
1818
- if (!path23) continue;
1819
- const prev = counts.get(path23) ?? { added: 0, deleted: 0 };
1820
- counts.set(path23, { added: prev.added + added, deleted: prev.deleted + deleted });
1818
+ if (!path24) continue;
1819
+ const prev = counts.get(path24) ?? { added: 0, deleted: 0 };
1820
+ counts.set(path24, { added: prev.added + added, deleted: prev.deleted + deleted });
1821
1821
  }
1822
1822
  };
1823
1823
  parseNumstat(unstagedNumstat);
@@ -1829,7 +1829,7 @@ async function handleGitChanges(ws, projectRoot) {
1829
1829
  if (!rec || rec.length < 3) continue;
1830
1830
  const x = rec[0] ?? " ";
1831
1831
  const y = rec[1] ?? " ";
1832
- const path23 = rec.slice(3);
1832
+ const path24 = rec.slice(3);
1833
1833
  const isRename = x === "R" || x === "C" || y === "R" || y === "C";
1834
1834
  if (isRename) i += 1;
1835
1835
  let status;
@@ -1841,13 +1841,13 @@ async function handleGitChanges(ws, projectRoot) {
1841
1841
  else if (x === "D" || y === "D") status = "D";
1842
1842
  else status = "M";
1843
1843
  const staged = x !== " " && x !== "?";
1844
- let added = counts.get(path23)?.added ?? 0;
1845
- let deleted = counts.get(path23)?.deleted ?? 0;
1844
+ let added = counts.get(path24)?.added ?? 0;
1845
+ let deleted = counts.get(path24)?.deleted ?? 0;
1846
1846
  if (status === "?") {
1847
1847
  added = 0;
1848
1848
  deleted = 0;
1849
1849
  }
1850
- files.push({ path: path23, status, added, deleted, staged });
1850
+ files.push({ path: path24, status, added, deleted, staged });
1851
1851
  }
1852
1852
  send(ws, { type: "git.changes", payload: { files } });
1853
1853
  } catch (err) {
@@ -1858,10 +1858,10 @@ async function handleGitChanges(ws, projectRoot) {
1858
1858
  }
1859
1859
  }
1860
1860
  var MAX_DIFF_BYTES = 2 * 1024 * 1024;
1861
- async function handleGitDiff(ws, projectRoot, path23) {
1861
+ async function handleGitDiff(ws, projectRoot, path24) {
1862
1862
  const cwd = projectRoot || void 0;
1863
- const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path23, ...extra } });
1864
- if (!path23 || path23.includes("\0") || path23.includes("..") || nodePath.isAbsolute(path23)) {
1863
+ const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path24, ...extra } });
1864
+ if (!path24 || path24.includes("\0") || path24.includes("..") || nodePath.isAbsolute(path24)) {
1865
1865
  reply({ oldText: "", newText: "", error: "invalid path" });
1866
1866
  return;
1867
1867
  }
@@ -1869,10 +1869,10 @@ async function handleGitDiff(ws, projectRoot, path23) {
1869
1869
  const git = makeGit(cwd);
1870
1870
  const { readFile: readFile11 } = await import("node:fs/promises");
1871
1871
  const { join: join15 } = await import("node:path");
1872
- const oldText = await git(["show", `HEAD:${path23}`]);
1872
+ const oldText = await git(["show", `HEAD:${path24}`]);
1873
1873
  let newText = "";
1874
1874
  try {
1875
- const abs = cwd ? join15(cwd, path23) : path23;
1875
+ const abs = cwd ? join15(cwd, path24) : path24;
1876
1876
  const buf = await readFile11(abs);
1877
1877
  if (buf.includes(0)) {
1878
1878
  reply({ oldText: "", newText: "", binary: true });
@@ -1901,9 +1901,9 @@ async function handleGitDiff(ws, projectRoot, path23) {
1901
1901
  }
1902
1902
 
1903
1903
  // src/server/http-server.ts
1904
- import * as fs5 from "node:fs/promises";
1904
+ import * as fs6 from "node:fs/promises";
1905
1905
  import * as http from "node:http";
1906
- import * as path6 from "node:path";
1906
+ import * as path7 from "node:path";
1907
1907
 
1908
1908
  // src/server/http-server/api-handlers.ts
1909
1909
  async function handleApiSessions(res, globalRoot) {
@@ -2091,7 +2091,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2091
2091
  return;
2092
2092
  }
2093
2093
  try {
2094
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2094
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2095
2095
  const registry = new SessionRegistry(globalRoot);
2096
2096
  const entry = await registry.get(sessionId);
2097
2097
  if (!entry) {
@@ -2099,7 +2099,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2099
2099
  res.end(JSON.stringify({ error: "Session not found" }));
2100
2100
  return;
2101
2101
  }
2102
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2102
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2103
2103
  const store = new DefaultSessionStore2({ dir: paths.projectSessions });
2104
2104
  const reader = new DefaultSessionReader2({ store });
2105
2105
  const rawEntries = [];
@@ -2126,7 +2126,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2126
2126
  }
2127
2127
  }
2128
2128
  function readJsonBody(req) {
2129
- return new Promise((resolve10, reject) => {
2129
+ return new Promise((resolve12, reject) => {
2130
2130
  let data = "";
2131
2131
  req.on("data", (chunk) => {
2132
2132
  data += chunk;
@@ -2137,7 +2137,7 @@ function readJsonBody(req) {
2137
2137
  });
2138
2138
  req.on("end", () => {
2139
2139
  try {
2140
- resolve10(data ? JSON.parse(data) : {});
2140
+ resolve12(data ? JSON.parse(data) : {});
2141
2141
  } catch (err) {
2142
2142
  reject(err instanceof Error ? err : new Error(String(err)));
2143
2143
  }
@@ -2173,7 +2173,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2173
2173
  const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
2174
2174
  const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
2175
2175
  try {
2176
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2176
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2177
2177
  const registry = new SessionRegistry(globalRoot);
2178
2178
  const entry = await registry.get(sessionId);
2179
2179
  if (!entry) {
@@ -2181,7 +2181,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2181
2181
  res.end(JSON.stringify({ error: "Session not found" }));
2182
2182
  return;
2183
2183
  }
2184
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2184
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2185
2185
  const mailbox = new GlobalMailbox4(paths.projectDir);
2186
2186
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
2187
2187
  const sent = await mailbox.send({ from, to, type, subject, body: text, priority });
@@ -2199,7 +2199,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2199
2199
  return;
2200
2200
  }
2201
2201
  try {
2202
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2202
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2203
2203
  const registry = new SessionRegistry(globalRoot);
2204
2204
  const entry = await registry.get(sessionId);
2205
2205
  if (!entry) {
@@ -2207,7 +2207,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2207
2207
  res.end(JSON.stringify({ error: "Session not found" }));
2208
2208
  return;
2209
2209
  }
2210
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2210
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2211
2211
  const mailbox = new GlobalMailbox4(paths.projectDir);
2212
2212
  const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
2213
2213
  const [inbound, outbound] = await Promise.all([
@@ -2257,7 +2257,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2257
2257
  const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
2258
2258
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2259
2259
  try {
2260
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2260
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2261
2261
  const registry = new SessionRegistry(globalRoot);
2262
2262
  const entry = await registry.get(sessionId);
2263
2263
  if (!entry) {
@@ -2265,7 +2265,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2265
2265
  res.end(JSON.stringify({ error: "Session not found" }));
2266
2266
  return;
2267
2267
  }
2268
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2268
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2269
2269
  const mailbox = new GlobalMailbox4(paths.projectDir);
2270
2270
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
2271
2271
  const sent = await mailbox.send({
@@ -2305,7 +2305,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2305
2305
  }
2306
2306
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2307
2307
  try {
2308
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2308
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2309
2309
  const registry = new SessionRegistry(globalRoot);
2310
2310
  const all = await registry.list();
2311
2311
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -2317,7 +2317,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2317
2317
  }
2318
2318
  const mbByDir = /* @__PURE__ */ new Map();
2319
2319
  const mailboxFor = (projectRoot) => {
2320
- const dir = resolveWstackPaths3({ projectRoot, globalRoot }).projectDir;
2320
+ const dir = resolveWstackPaths4({ projectRoot, globalRoot }).projectDir;
2321
2321
  let mb = mbByDir.get(dir);
2322
2322
  if (!mb) {
2323
2323
  mb = new GlobalMailbox4(dir);
@@ -2380,14 +2380,14 @@ function pushEvent(event) {
2380
2380
  }
2381
2381
  }
2382
2382
  function parseBody(req) {
2383
- return new Promise((resolve10, reject) => {
2383
+ return new Promise((resolve12, reject) => {
2384
2384
  let body = "";
2385
2385
  req.on("data", (chunk) => {
2386
2386
  body += chunk.toString("utf-8");
2387
2387
  });
2388
2388
  req.on("end", () => {
2389
2389
  try {
2390
- resolve10(JSON.parse(body));
2390
+ resolve12(JSON.parse(body));
2391
2391
  } catch {
2392
2392
  reject(new Error("Invalid JSON"));
2393
2393
  }
@@ -2462,6 +2462,251 @@ function getAnalyticsBuffer() {
2462
2462
  return [...EVENT_BUFFER];
2463
2463
  }
2464
2464
 
2465
+ // src/server/codemap-handlers.ts
2466
+ import { packageGraphService, fileGraphService, symbolGraphService } from "@wrongstack/tools";
2467
+ function sendJson(res, status, data) {
2468
+ res.writeHead(status, { "Content-Type": "application/json" });
2469
+ res.end(JSON.stringify(data));
2470
+ }
2471
+ function handleCodemapPackages(res, deps2) {
2472
+ try {
2473
+ const graph = packageGraphService({
2474
+ projectRoot: deps2.projectRoot,
2475
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2476
+ });
2477
+ sendJson(res, 200, graph);
2478
+ } catch (err) {
2479
+ const msg = err instanceof Error ? err.message : String(err);
2480
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2481
+ }
2482
+ }
2483
+ function handleCodemapFiles(res, deps2, pkg) {
2484
+ if (!pkg) {
2485
+ sendJson(res, 400, { error: 'Missing "package" query parameter' });
2486
+ return;
2487
+ }
2488
+ try {
2489
+ const graph = fileGraphService({
2490
+ projectRoot: deps2.projectRoot,
2491
+ packageFilter: pkg,
2492
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2493
+ });
2494
+ sendJson(res, 200, graph);
2495
+ } catch (err) {
2496
+ const msg = err instanceof Error ? err.message : String(err);
2497
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2498
+ }
2499
+ }
2500
+ function handleCodemapSymbols(res, deps2, file) {
2501
+ if (!file) {
2502
+ sendJson(res, 400, { error: 'Missing "file" query parameter' });
2503
+ return;
2504
+ }
2505
+ try {
2506
+ const graph = symbolGraphService({
2507
+ projectRoot: deps2.projectRoot,
2508
+ fileFilter: file,
2509
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2510
+ });
2511
+ sendJson(res, 200, graph);
2512
+ } catch (err) {
2513
+ const msg = err instanceof Error ? err.message : String(err);
2514
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2515
+ }
2516
+ }
2517
+
2518
+ // src/server/techstack-handlers.ts
2519
+ import { randomUUID } from "node:crypto";
2520
+ var DEEP_DIVE_TIMEOUT_MS = 6e4;
2521
+ function sendJson2(res, status, data) {
2522
+ res.writeHead(status, { "Content-Type": "application/json" });
2523
+ res.end(JSON.stringify(data));
2524
+ }
2525
+ async function buildResearcher(deps2, kind) {
2526
+ if (kind !== "analyze" || !deps2.getLlm) return void 0;
2527
+ const { createProviderLlm, createResearcher, createToolSearch } = await import("@wrongstack/techstack");
2528
+ const llm = createProviderLlm(deps2.getLlm);
2529
+ if (!llm) return void 0;
2530
+ return createResearcher({ llm, search: createToolSearch() });
2531
+ }
2532
+ function handleTechStackSnapshot(res, deps2) {
2533
+ try {
2534
+ const snapshot = deps2.store.getSnapshot(deps2.projectId);
2535
+ if (!snapshot) {
2536
+ sendJson2(res, 404, { snapshot: null, stale: false });
2537
+ return;
2538
+ }
2539
+ const ageMs = Date.now() - new Date(snapshot.createdAt).getTime();
2540
+ sendJson2(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
2541
+ } catch (error) {
2542
+ sendJson2(res, 500, {
2543
+ error: "TechStack store unavailable",
2544
+ detail: errorMessage(error)
2545
+ });
2546
+ }
2547
+ }
2548
+ function errorMessage(error) {
2549
+ return error instanceof Error ? error.message : String(error);
2550
+ }
2551
+ function requireJobDeps(res, deps2) {
2552
+ if (!deps2.projectRoot || !deps2.engine) {
2553
+ sendJson2(res, 503, { error: "TechStack engine unavailable" });
2554
+ return false;
2555
+ }
2556
+ return true;
2557
+ }
2558
+ function startJob(res, deps2, kind) {
2559
+ if (!requireJobDeps(res, deps2)) return;
2560
+ const jobId = randomUUID();
2561
+ const controller = new AbortController();
2562
+ deps2.runningJobs?.set(jobId, controller);
2563
+ deps2.emit?.({ type: "techstack.job.started", payload: { jobId, kind } });
2564
+ sendJson2(res, 202, { jobId, kind, status: "queued" });
2565
+ void buildResearcher(deps2, kind).catch(() => void 0).then(
2566
+ (researcher) => deps2.engine.analyze(deps2.projectId, {
2567
+ targetRoot: deps2.projectRoot,
2568
+ requestedBy: "webui",
2569
+ online: kind === "analyze",
2570
+ jobId,
2571
+ signal: controller.signal,
2572
+ researcher,
2573
+ onProgress: (phase, completed, total) => {
2574
+ deps2.emit?.({
2575
+ type: "techstack.job.progress",
2576
+ payload: { jobId, phase, completed, total }
2577
+ });
2578
+ }
2579
+ })
2580
+ ).then(({ snapshot }) => {
2581
+ if (controller.signal.aborted) return;
2582
+ deps2.emit?.({
2583
+ type: "techstack.snapshot.updated",
2584
+ payload: { snapshot, stale: false }
2585
+ });
2586
+ }).catch((error) => {
2587
+ if (controller.signal.aborted) {
2588
+ deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
2589
+ return;
2590
+ }
2591
+ deps2.emit?.({
2592
+ type: "techstack.job.failed",
2593
+ payload: { jobId, error: errorMessage(error) }
2594
+ });
2595
+ }).finally(() => {
2596
+ deps2.runningJobs?.delete(jobId);
2597
+ });
2598
+ }
2599
+ function handleTechStackInventory(res, deps2) {
2600
+ startJob(res, deps2, "inventory");
2601
+ }
2602
+ function handleTechStackAnalyze(res, deps2) {
2603
+ startJob(res, deps2, "analyze");
2604
+ }
2605
+ function handleTechStackCancel(res, deps2, jobId) {
2606
+ const controller = deps2.runningJobs?.get(jobId);
2607
+ if (controller && !controller.signal.aborted) controller.abort();
2608
+ deps2.store.updateJobStatus(jobId, "cancelled");
2609
+ deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
2610
+ sendJson2(res, 200, { jobId, status: "cancelled" });
2611
+ }
2612
+ async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
2613
+ const snapshot = deps2.store.getSnapshot(deps2.projectId);
2614
+ const dependency = snapshot?.dependencies.find((dep) => dep.id === dependencyId);
2615
+ if (!dependency) {
2616
+ sendJson2(res, 404, { error: "Dependency not found in the current snapshot" });
2617
+ return;
2618
+ }
2619
+ let researcher;
2620
+ try {
2621
+ researcher = await buildResearcher(deps2, "analyze");
2622
+ } catch (error) {
2623
+ sendJson2(res, 503, { error: "Research unavailable", detail: errorMessage(error) });
2624
+ return;
2625
+ }
2626
+ if (!researcher) {
2627
+ sendJson2(res, 503, {
2628
+ error: "No model configured \u2014 connect a provider to run LLM analysis."
2629
+ });
2630
+ return;
2631
+ }
2632
+ const controller = new AbortController();
2633
+ const timeout = setTimeout(() => {
2634
+ controller.abort(new Error("research timeout"));
2635
+ }, DEEP_DIVE_TIMEOUT_MS);
2636
+ timeout.unref?.();
2637
+ try {
2638
+ const { triageCandidates } = await import("@wrongstack/techstack");
2639
+ const [triaged] = triageCandidates([dependency], { limit: 1 });
2640
+ const findings = await researcher.research(
2641
+ [triaged ?? { dependency, cluster: "breaking_change", priority: 0 }],
2642
+ { signal: controller.signal }
2643
+ );
2644
+ sendJson2(res, 200, { dependencyId, findings });
2645
+ } catch (error) {
2646
+ sendJson2(res, 500, { error: "Research failed", detail: errorMessage(error) });
2647
+ } finally {
2648
+ clearTimeout(timeout);
2649
+ controller.abort();
2650
+ }
2651
+ }
2652
+ function handleTechStackJobStatus(res, deps2, jobId) {
2653
+ const job = deps2.store.getJob(jobId);
2654
+ if (!job) {
2655
+ sendJson2(res, 404, { error: "Job not found" });
2656
+ return;
2657
+ }
2658
+ sendJson2(res, 200, { job });
2659
+ }
2660
+ function handleTechStackReport(res, deps2, reportId, format) {
2661
+ const snapshot = deps2.store.getSnapshotById(reportId);
2662
+ if (!snapshot) {
2663
+ sendJson2(res, 404, { error: "Report not found" });
2664
+ return;
2665
+ }
2666
+ if (deps2.engine) {
2667
+ const report = deps2.engine.generateReport(snapshot, format);
2668
+ res.writeHead(200, {
2669
+ "Content-Type": format === "json" ? "application/json" : "text/markdown",
2670
+ "Content-Disposition": `attachment; filename="techstack-report.${format}"`
2671
+ });
2672
+ res.end(report);
2673
+ } else {
2674
+ sendJson2(res, 200, snapshot);
2675
+ }
2676
+ }
2677
+
2678
+ // src/server/projects-manifest.ts
2679
+ import * as fs5 from "node:fs/promises";
2680
+ import * as path6 from "node:path";
2681
+ import { projectSlug } from "@wrongstack/core";
2682
+ function projectsJsonPath(globalConfigPath) {
2683
+ const base = path6.dirname(globalConfigPath);
2684
+ return path6.join(base, "projects.json");
2685
+ }
2686
+ async function loadManifest(globalConfigPath) {
2687
+ try {
2688
+ const raw = await fs5.readFile(projectsJsonPath(globalConfigPath), "utf8");
2689
+ const parsed = JSON.parse(raw);
2690
+ return { projects: parsed.projects ?? [] };
2691
+ } catch {
2692
+ return { projects: [] };
2693
+ }
2694
+ }
2695
+ async function saveManifest(manifest, globalConfigPath) {
2696
+ const file = projectsJsonPath(globalConfigPath);
2697
+ await fs5.mkdir(path6.dirname(file), { recursive: true });
2698
+ await fs5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
2699
+ }
2700
+ function generateProjectSlug(rootPath) {
2701
+ return projectSlug(rootPath);
2702
+ }
2703
+ async function ensureProjectDataDir(slug, globalConfigPath) {
2704
+ const base = path6.dirname(globalConfigPath);
2705
+ const dir = path6.join(base, "projects", slug);
2706
+ await fs5.mkdir(dir, { recursive: true });
2707
+ return dir;
2708
+ }
2709
+
2465
2710
  // src/server/ws-auth.ts
2466
2711
  import { Buffer as Buffer2 } from "node:buffer";
2467
2712
  import { timingSafeEqual } from "node:crypto";
@@ -2647,9 +2892,9 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2647
2892
  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'`;
2648
2893
  }
2649
2894
  function isInsideDist(candidate, distDir) {
2650
- const root = path6.resolve(distDir);
2651
- const resolved = path6.resolve(candidate);
2652
- return resolved === root || resolved.startsWith(root + path6.sep);
2895
+ const root = path7.resolve(distDir);
2896
+ const resolved = path7.resolve(candidate);
2897
+ return resolved === root || resolved.startsWith(root + path7.sep);
2653
2898
  }
2654
2899
  function decodeSessionId(segment) {
2655
2900
  try {
@@ -2660,10 +2905,19 @@ function decodeSessionId(segment) {
2660
2905
  }
2661
2906
  function createHttpServer(opts) {
2662
2907
  const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
2663
- const distDir = path6.resolve(opts.distDir);
2908
+ const distDir = path7.resolve(opts.distDir);
2664
2909
  const wsPort = opts.wsPort;
2665
2910
  const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
2666
- return http.createServer(async (req, res) => {
2911
+ let techStackRuntime = null;
2912
+ const getTechStackRuntime = async () => {
2913
+ if (!opts.projectRoot) throw new Error("Project root not configured");
2914
+ techStackRuntime ??= import("@wrongstack/techstack").then(({ TechStackEngine, TechStackStore }) => {
2915
+ const store = new TechStackStore({ projectSlug: generateProjectSlug(opts.projectRoot) });
2916
+ return { store, engine: new TechStackEngine(store), runningJobs: /* @__PURE__ */ new Map() };
2917
+ });
2918
+ return techStackRuntime;
2919
+ };
2920
+ const server = http.createServer(async (req, res) => {
2667
2921
  try {
2668
2922
  const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
2669
2923
  const providedAccessToken = requestToken(req, url);
@@ -2814,6 +3068,127 @@ function createHttpServer(opts) {
2814
3068
  await handleApiAnalyticsSummary(res);
2815
3069
  return;
2816
3070
  }
3071
+ if (url.pathname === "/api/codemap/packages" && req.method === "GET") {
3072
+ if (requireAccessToken && !accessTokenOk) {
3073
+ res.writeHead(401, { "Content-Type": "application/json" });
3074
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3075
+ return;
3076
+ }
3077
+ if (!opts.projectRoot) {
3078
+ res.writeHead(503, { "Content-Type": "application/json" });
3079
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3080
+ return;
3081
+ }
3082
+ handleCodemapPackages(res, {
3083
+ projectRoot: opts.projectRoot,
3084
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3085
+ });
3086
+ return;
3087
+ }
3088
+ if (url.pathname === "/api/codemap/files" && req.method === "GET") {
3089
+ if (requireAccessToken && !accessTokenOk) {
3090
+ res.writeHead(401, { "Content-Type": "application/json" });
3091
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3092
+ return;
3093
+ }
3094
+ if (!opts.projectRoot) {
3095
+ res.writeHead(503, { "Content-Type": "application/json" });
3096
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3097
+ return;
3098
+ }
3099
+ const pkg = url.searchParams.get("package") ?? "";
3100
+ handleCodemapFiles(res, {
3101
+ projectRoot: opts.projectRoot,
3102
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3103
+ }, pkg);
3104
+ return;
3105
+ }
3106
+ if (url.pathname === "/api/codemap/symbols" && req.method === "GET") {
3107
+ if (requireAccessToken && !accessTokenOk) {
3108
+ res.writeHead(401, { "Content-Type": "application/json" });
3109
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3110
+ return;
3111
+ }
3112
+ if (!opts.projectRoot) {
3113
+ res.writeHead(503, { "Content-Type": "application/json" });
3114
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3115
+ return;
3116
+ }
3117
+ const file = url.searchParams.get("file") ?? "";
3118
+ handleCodemapSymbols(res, {
3119
+ projectRoot: opts.projectRoot,
3120
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3121
+ }, file);
3122
+ return;
3123
+ }
3124
+ if (url.pathname.startsWith("/api/techstack/")) {
3125
+ if (requireAccessToken && !accessTokenOk) {
3126
+ res.writeHead(401, { "Content-Type": "application/json" });
3127
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3128
+ return;
3129
+ }
3130
+ if (!opts.projectRoot) {
3131
+ res.writeHead(503, { "Content-Type": "application/json" });
3132
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3133
+ return;
3134
+ }
3135
+ try {
3136
+ const runtime = await getTechStackRuntime();
3137
+ const deps2 = {
3138
+ projectId: opts.projectRoot,
3139
+ projectRoot: opts.projectRoot,
3140
+ store: runtime.store,
3141
+ engine: runtime.engine,
3142
+ runningJobs: runtime.runningJobs,
3143
+ emit: opts.onTechStackEvent,
3144
+ getLlm: opts.getLlm
3145
+ };
3146
+ if (url.pathname === "/api/techstack/snapshot" && req.method === "GET") {
3147
+ handleTechStackSnapshot(res, deps2);
3148
+ return;
3149
+ }
3150
+ if (url.pathname === "/api/techstack/inventory" && req.method === "POST") {
3151
+ handleTechStackInventory(res, deps2);
3152
+ return;
3153
+ }
3154
+ if (url.pathname === "/api/techstack/analyze" && req.method === "POST") {
3155
+ handleTechStackAnalyze(res, deps2);
3156
+ return;
3157
+ }
3158
+ const cancelMatch = /^\/api\/techstack\/jobs\/([^/]+)\/cancel$/.exec(url.pathname);
3159
+ if (cancelMatch && req.method === "POST") {
3160
+ handleTechStackCancel(res, deps2, decodeURIComponent(cancelMatch[1]));
3161
+ return;
3162
+ }
3163
+ const jobMatch = /^\/api\/techstack\/jobs\/([^/]+)$/.exec(url.pathname);
3164
+ if (jobMatch && req.method === "GET") {
3165
+ handleTechStackJobStatus(res, deps2, decodeURIComponent(jobMatch[1]));
3166
+ return;
3167
+ }
3168
+ const reportMatch = /^\/api\/techstack\/reports\/([^/]+)$/.exec(url.pathname);
3169
+ if (reportMatch && req.method === "GET") {
3170
+ const fmt = url.searchParams.get("format") === "json" ? "json" : "md";
3171
+ handleTechStackReport(res, deps2, decodeURIComponent(reportMatch[1]), fmt);
3172
+ return;
3173
+ }
3174
+ const researchMatch = /^\/api\/techstack\/deps\/([^/]+)\/research$/.exec(url.pathname);
3175
+ if (researchMatch && req.method === "POST") {
3176
+ await handleTechStackDependencyResearch(
3177
+ res,
3178
+ deps2,
3179
+ decodeURIComponent(researchMatch[1])
3180
+ );
3181
+ return;
3182
+ }
3183
+ } catch (error) {
3184
+ res.writeHead(503, { "Content-Type": "application/json" });
3185
+ res.end(JSON.stringify({
3186
+ error: "TechStack store unavailable",
3187
+ detail: error instanceof Error ? error.message : String(error)
3188
+ }));
3189
+ return;
3190
+ }
3191
+ }
2817
3192
  if (url.pathname === "/debug/watcher-metrics" && req.method === "GET") {
2818
3193
  if (requireAccessToken && !accessTokenOk) {
2819
3194
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -2837,21 +3212,21 @@ function createHttpServer(opts) {
2837
3212
  }
2838
3213
  let filePath;
2839
3214
  if (url.pathname === "/" || url.pathname === "") {
2840
- filePath = path6.join(distDir, "index.html");
3215
+ filePath = path7.join(distDir, "index.html");
2841
3216
  } else if (url.pathname.startsWith("/assets/")) {
2842
- filePath = path6.join(distDir, url.pathname);
3217
+ filePath = path7.join(distDir, url.pathname);
2843
3218
  } else if (url.pathname.startsWith("/")) {
2844
- filePath = path6.join(distDir, url.pathname);
3219
+ filePath = path7.join(distDir, url.pathname);
2845
3220
  } else {
2846
- filePath = path6.join(distDir, "index.html");
3221
+ filePath = path7.join(distDir, "index.html");
2847
3222
  }
2848
- const resolvedPath = path6.resolve(filePath);
3223
+ const resolvedPath = path7.resolve(filePath);
2849
3224
  if (!isInsideDist(resolvedPath, distDir)) {
2850
3225
  res.writeHead(403, { "Content-Type": "text/plain" });
2851
3226
  res.end("Forbidden");
2852
3227
  return;
2853
3228
  }
2854
- const ext = path6.extname(resolvedPath);
3229
+ const ext = path7.extname(resolvedPath);
2855
3230
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
2856
3231
  res.setHeader("Content-Type", contentType);
2857
3232
  res.setHeader("X-Content-Type-Options", "nosniff");
@@ -2863,18 +3238,18 @@ function createHttpServer(opts) {
2863
3238
  "Content-Security-Policy",
2864
3239
  buildCspHeader(wsPort, requestHostForCsp(req.headers.host), opts.publicWsUrl)
2865
3240
  );
2866
- const html = await fs5.readFile(resolvedPath, "utf8");
3241
+ const html = await fs6.readFile(resolvedPath, "utf8");
2867
3242
  res.writeHead(200);
2868
3243
  res.end(injectWsConfig(html, { wsPort, publicWsUrl: opts.publicWsUrl }));
2869
3244
  return;
2870
3245
  }
2871
- const fileContent = await fs5.readFile(resolvedPath);
3246
+ const fileContent = await fs6.readFile(resolvedPath);
2872
3247
  res.writeHead(200);
2873
3248
  res.end(fileContent);
2874
3249
  } catch (err) {
2875
3250
  if (err.code === "ENOENT") {
2876
3251
  try {
2877
- const html = await fs5.readFile(path6.join(distDir, "index.html"), "utf8");
3252
+ const html = await fs6.readFile(path7.join(distDir, "index.html"), "utf8");
2878
3253
  res.writeHead(200, {
2879
3254
  "Content-Type": "text/html",
2880
3255
  "X-Content-Type-Options": "nosniff",
@@ -2897,18 +3272,26 @@ function createHttpServer(opts) {
2897
3272
  }
2898
3273
  }
2899
3274
  });
3275
+ server.once("close", () => {
3276
+ void techStackRuntime?.then(({ store, runningJobs }) => {
3277
+ for (const controller of runningJobs.values()) controller.abort();
3278
+ runningJobs.clear();
3279
+ store.close();
3280
+ }).catch(() => void 0);
3281
+ });
3282
+ return server;
2900
3283
  }
2901
3284
 
2902
3285
  // src/server/instance-registry.ts
2903
3286
  import * as os from "node:os";
2904
- import * as path7 from "node:path";
2905
- import * as fs6 from "node:fs/promises";
3287
+ import * as path8 from "node:path";
3288
+ import * as fs7 from "node:fs/promises";
2906
3289
  import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
2907
3290
  function defaultBaseDir() {
2908
- return path7.join(os.homedir(), ".wrongstack");
3291
+ return path8.join(os.homedir(), ".wrongstack");
2909
3292
  }
2910
3293
  function registryPath(baseDir = defaultBaseDir()) {
2911
- return path7.join(baseDir, "webui-instances.json");
3294
+ return path8.join(baseDir, "webui-instances.json");
2912
3295
  }
2913
3296
  function isPidAlive(pid) {
2914
3297
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -2921,7 +3304,7 @@ function isPidAlive(pid) {
2921
3304
  }
2922
3305
  async function load(file) {
2923
3306
  try {
2924
- const raw = await fs6.readFile(file, "utf8");
3307
+ const raw = await fs7.readFile(file, "utf8");
2925
3308
  const parsed = JSON.parse(raw);
2926
3309
  if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
2927
3310
  return parsed;
@@ -3214,7 +3597,7 @@ async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
3214
3597
  payload: { name: serverName, resources, resourceTemplates }
3215
3598
  });
3216
3599
  } catch (err) {
3217
- sendContentError(ws, "resources", serverName, errorMessage(err));
3600
+ sendContentError(ws, "resources", serverName, errorMessage2(err));
3218
3601
  }
3219
3602
  }
3220
3603
  async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3228,7 +3611,7 @@ async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
3228
3611
  });
3229
3612
  send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
3230
3613
  } catch (err) {
3231
- sendContentError(ws, "prompts", serverName, errorMessage(err));
3614
+ sendContentError(ws, "prompts", serverName, errorMessage2(err));
3232
3615
  }
3233
3616
  }
3234
3617
  async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3244,7 +3627,7 @@ async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
3244
3627
  );
3245
3628
  send(ws, { type: "mcp.content.selected", payload: insertion });
3246
3629
  } catch (err) {
3247
- sendContentError(ws, "resource.read", serverName, errorMessage(err));
3630
+ sendContentError(ws, "resource.read", serverName, errorMessage2(err));
3248
3631
  }
3249
3632
  }
3250
3633
  async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3260,7 +3643,7 @@ async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
3260
3643
  );
3261
3644
  send(ws, { type: "mcp.content.selected", payload: insertion });
3262
3645
  } catch (err) {
3263
- sendContentError(ws, "prompt.get", serverName, errorMessage(err));
3646
+ sendContentError(ws, "prompt.get", serverName, errorMessage2(err));
3264
3647
  }
3265
3648
  }
3266
3649
  function payloadRecord(msg) {
@@ -3288,14 +3671,14 @@ function promptArguments(value) {
3288
3671
  function sendContentError(ws, action, name2, error) {
3289
3672
  send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
3290
3673
  }
3291
- function errorMessage(err) {
3674
+ function errorMessage2(err) {
3292
3675
  return err instanceof Error ? err.message : String(err);
3293
3676
  }
3294
3677
 
3295
3678
  // src/server/memory-handlers.ts
3296
3679
  function isSuperMemoryStore(store) {
3297
3680
  const s = store;
3298
- return typeof s.stats === "function" && typeof s.listSuper === "function" && typeof s.getSuperMemory === "function" && typeof s.updateSuperMemory === "function" && typeof s.deleteSuperMemory === "function";
3681
+ 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";
3299
3682
  }
3300
3683
  function requiresSuperMemory(command) {
3301
3684
  return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
@@ -3419,6 +3802,7 @@ async function handleSuperMemoryRemember(ws, msg, memoryStore) {
3419
3802
  importance: payload["importance"],
3420
3803
  confidence: payload["confidence"],
3421
3804
  freshness: payload["freshness"],
3805
+ audience: payload["audience"],
3422
3806
  supersedes: payload["supersedes"],
3423
3807
  contradicts: payload["contradicts"]
3424
3808
  });
@@ -3432,18 +3816,172 @@ async function handleSuperMemoryDelete(ws, msg, memoryStore) {
3432
3816
  send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
3433
3817
  return;
3434
3818
  }
3435
- const { id, reason } = msg.payload;
3819
+ const { id, reason, neverInject } = msg.payload;
3436
3820
  if (!id) {
3437
3821
  send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
3438
3822
  return;
3439
3823
  }
3440
3824
  try {
3441
- await memoryStore.deleteSuperMemory(id, reason);
3825
+ if (neverInject === true) await memoryStore.deleteSuperMemory(id, reason, { neverInject: true });
3826
+ else await memoryStore.deleteSuperMemory(id, reason);
3442
3827
  send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
3443
3828
  } catch (err) {
3444
3829
  send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
3445
3830
  }
3446
3831
  }
3832
+ async function handleSuperMemoryRecover(ws, msg, memoryStore) {
3833
+ if (!isSuperMemoryStore(memoryStore)) {
3834
+ send(ws, { type: "memory.super.recover", payload: { error: requiresSuperMemory("memory.super.recover") } });
3835
+ return;
3836
+ }
3837
+ const payload = msg.payload;
3838
+ const id = payload["id"];
3839
+ if (!id) {
3840
+ send(ws, { type: "memory.super.recover", payload: { error: "id is required" } });
3841
+ return;
3842
+ }
3843
+ const reason = payload["reason"];
3844
+ try {
3845
+ const preExisting = await memoryStore.getSuperMemory(id);
3846
+ if (!preExisting) {
3847
+ send(ws, { type: "memory.super.recover", payload: { error: `Super Memory "${id}" not found.` } });
3848
+ return;
3849
+ }
3850
+ if (preExisting.status === "active") {
3851
+ send(ws, { type: "memory.super.recover", payload: { recovered: true, memory: preExisting, noop: true } });
3852
+ return;
3853
+ }
3854
+ const memory = await memoryStore.recoverSuperMemory(id, reason);
3855
+ const noop = memory.id !== id;
3856
+ const response = { recovered: true, memory };
3857
+ if (noop) {
3858
+ response["activeId"] = memory.id;
3859
+ response["noop"] = true;
3860
+ }
3861
+ send(ws, { type: "memory.super.recover", payload: response });
3862
+ } catch (err) {
3863
+ send(ws, { type: "memory.super.recover", payload: { error: errMessage(err) } });
3864
+ }
3865
+ }
3866
+ async function handleSuperMemoryCandidateResolve(ws, msg, memoryStore) {
3867
+ if (!isSuperMemoryStore(memoryStore)) {
3868
+ send(ws, {
3869
+ type: "memory.super.candidateResolve",
3870
+ payload: { error: requiresSuperMemory("memory.super.candidateResolve") }
3871
+ });
3872
+ return;
3873
+ }
3874
+ const payload = msg.payload;
3875
+ const candidateId = payload["candidateId"];
3876
+ const action = payload["action"];
3877
+ if (!candidateId) {
3878
+ send(ws, {
3879
+ type: "memory.super.candidateResolve",
3880
+ payload: { error: "candidateId is required" }
3881
+ });
3882
+ return;
3883
+ }
3884
+ if (action !== "accept" && action !== "reject") {
3885
+ send(ws, {
3886
+ type: "memory.super.candidateResolve",
3887
+ payload: { error: 'action must be "accept" or "reject"' }
3888
+ });
3889
+ return;
3890
+ }
3891
+ const reason = payload["reason"];
3892
+ try {
3893
+ let candidate;
3894
+ if (action === "accept") {
3895
+ const accepted = await memoryStore.acceptCandidate(candidateId);
3896
+ candidate = accepted ? { id: accepted.id, status: accepted.status ?? "active" } : void 0;
3897
+ } else {
3898
+ const rejected = await memoryStore.rejectCandidate(
3899
+ candidateId,
3900
+ reason ?? "Rejected via WebUI"
3901
+ );
3902
+ candidate = rejected ? { id: candidateId, status: "rejected" } : void 0;
3903
+ }
3904
+ if (!candidate) {
3905
+ send(ws, {
3906
+ type: "memory.super.candidateResolve",
3907
+ payload: { error: `Candidate "${candidateId}" not found` }
3908
+ });
3909
+ return;
3910
+ }
3911
+ send(ws, {
3912
+ type: "memory.super.candidateResolve",
3913
+ payload: { candidate, resolvedAction: action }
3914
+ });
3915
+ } catch (err) {
3916
+ send(ws, {
3917
+ type: "memory.super.candidateResolve",
3918
+ payload: { error: errMessage(err) }
3919
+ });
3920
+ }
3921
+ }
3922
+ async function handleSuperMemoryBackfillRecoverable(ws, msg, memoryStore) {
3923
+ if (!isSuperMemoryStore(memoryStore)) {
3924
+ send(ws, {
3925
+ type: "memory.super.backfillRecoverable",
3926
+ payload: { error: requiresSuperMemory("memory.super.backfillRecoverable") }
3927
+ });
3928
+ return;
3929
+ }
3930
+ const payload = msg.payload ?? {};
3931
+ const apply = payload["apply"] === true;
3932
+ const rawFilter = payload["filter"] ?? {};
3933
+ const filter = {};
3934
+ if (Array.isArray(rawFilter["kinds"])) filter.kinds = rawFilter["kinds"];
3935
+ if (Array.isArray(rawFilter["scopes"])) filter.scopes = rawFilter["scopes"];
3936
+ if (typeof rawFilter["updatedAfter"] === "string") filter.updatedAfter = rawFilter["updatedAfter"];
3937
+ if (typeof rawFilter["updatedBefore"] === "string") filter.updatedBefore = rawFilter["updatedBefore"];
3938
+ try {
3939
+ const report = await memoryStore.backfillRecoverable({
3940
+ apply,
3941
+ ...Object.keys(filter).length > 0 ? { filter } : {}
3942
+ });
3943
+ send(ws, {
3944
+ type: "memory.super.backfillRecoverable",
3945
+ payload: {
3946
+ examined: report.examined,
3947
+ recovered: report.recovered,
3948
+ recoverable: report.recoverable,
3949
+ dryRun: !apply
3950
+ }
3951
+ });
3952
+ } catch (err) {
3953
+ send(ws, {
3954
+ type: "memory.super.backfillRecoverable",
3955
+ payload: { error: errMessage(err) }
3956
+ });
3957
+ }
3958
+ }
3959
+ async function handleSuperMemoryForFile(ws, msg, memoryStore) {
3960
+ if (!isSuperMemoryStore(memoryStore)) {
3961
+ send(ws, {
3962
+ type: "memory.super.forFile",
3963
+ payload: { error: requiresSuperMemory("memory.super.forFile") }
3964
+ });
3965
+ return;
3966
+ }
3967
+ const payload = msg.payload ?? {};
3968
+ const filePath = payload["filePath"];
3969
+ if (!filePath) {
3970
+ send(ws, { type: "memory.super.forFile", payload: { error: "filePath is required" } });
3971
+ return;
3972
+ }
3973
+ try {
3974
+ const response = await memoryStore.findMemoriesForFile(filePath, {
3975
+ ...typeof payload["lineStart"] === "number" ? { lineStart: payload["lineStart"] } : {},
3976
+ ...typeof payload["lineEnd"] === "number" ? { lineEnd: payload["lineEnd"] } : {},
3977
+ ...typeof payload["limit"] === "number" ? { limit: payload["limit"] } : {},
3978
+ ...payload["includeDeleted"] === true ? { includeDeleted: true } : {}
3979
+ });
3980
+ send(ws, { type: "memory.super.forFile", payload: response });
3981
+ } catch (err) {
3982
+ send(ws, { type: "memory.super.forFile", payload: { error: errMessage(err) } });
3983
+ }
3984
+ }
3447
3985
 
3448
3986
  // src/server/open-browser.ts
3449
3987
  import { spawn } from "node:child_process";
@@ -3502,16 +4040,16 @@ function getSurfaceDefaultPorts(surface) {
3502
4040
  return { ...SURFACE_DEFAULT_PORTS[surface] };
3503
4041
  }
3504
4042
  function isPortFree(host, port) {
3505
- return new Promise((resolve10) => {
4043
+ return new Promise((resolve12) => {
3506
4044
  const srv = net.createServer();
3507
- srv.once("error", () => resolve10(false));
4045
+ srv.once("error", () => resolve12(false));
3508
4046
  srv.once("listening", () => {
3509
- srv.close(() => resolve10(true));
4047
+ srv.close(() => resolve12(true));
3510
4048
  });
3511
4049
  try {
3512
4050
  srv.listen(port, host);
3513
4051
  } catch {
3514
- resolve10(false);
4052
+ resolve12(false);
3515
4053
  }
3516
4054
  });
3517
4055
  }
@@ -3738,17 +4276,17 @@ async function handlePromptsRecent(ws, ctx) {
3738
4276
  }
3739
4277
 
3740
4278
  // src/server/provider-config-standalone.ts
3741
- import * as path8 from "node:path";
4279
+ import * as path9 from "node:path";
3742
4280
  import { DefaultSecretVault } from "@wrongstack/core";
3743
4281
 
3744
4282
  // src/server/provider-config-io.ts
3745
- import * as fs7 from "node:fs/promises";
4283
+ import * as fs8 from "node:fs/promises";
3746
4284
  import { ConfigError, atomicWrite as atomicWrite4 } from "@wrongstack/core";
3747
4285
  import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
3748
4286
  async function loadSavedProviders(configPath, vault) {
3749
4287
  let raw;
3750
4288
  try {
3751
- raw = await fs7.readFile(configPath, "utf8");
4289
+ raw = await fs8.readFile(configPath, "utf8");
3752
4290
  } catch {
3753
4291
  return {};
3754
4292
  }
@@ -3765,7 +4303,7 @@ async function saveProviders(configPath, vault, providers) {
3765
4303
  let raw;
3766
4304
  let fileExists = true;
3767
4305
  try {
3768
- raw = await fs7.readFile(configPath, "utf8");
4306
+ raw = await fs8.readFile(configPath, "utf8");
3769
4307
  } catch (err) {
3770
4308
  if (err.code !== "ENOENT") {
3771
4309
  throw new ConfigError({
@@ -3799,7 +4337,7 @@ async function saveProviders(configPath, vault, providers) {
3799
4337
 
3800
4338
  // src/server/provider-config-standalone.ts
3801
4339
  function createProviderConfigIO(configPath) {
3802
- const keyFile = path8.join(path8.dirname(configPath), ".key");
4340
+ const keyFile = path9.join(path9.dirname(configPath), ".key");
3803
4341
  const vault = new DefaultSecretVault({ keyFile });
3804
4342
  return {
3805
4343
  load: () => loadSavedProviders(configPath, vault),
@@ -3809,6 +4347,10 @@ function createProviderConfigIO(configPath) {
3809
4347
 
3810
4348
  // src/server/provider-keys.ts
3811
4349
  import { expectDefined } from "@wrongstack/core";
4350
+ import {
4351
+ buildProviderConfigFromPreset,
4352
+ resolvePresetForAlias
4353
+ } from "@wrongstack/providers";
3812
4354
  function normalizeKeys(cfg) {
3813
4355
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
3814
4356
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -3837,8 +4379,28 @@ function maskedKey(key) {
3837
4379
  if (key.length <= 8) return "\u2022".repeat(key.length);
3838
4380
  return `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
3839
4381
  }
4382
+ function hydratePresetConfig(providerId, dest) {
4383
+ const preset = resolvePresetForAlias(providerId);
4384
+ if (!preset) return void 0;
4385
+ const template = buildProviderConfigFromPreset(preset);
4386
+ if (!dest.type) dest.type = preset.id;
4387
+ if (!dest.family) dest.family = preset.family;
4388
+ if (dest.baseUrl === void 0) dest.baseUrl = template.baseUrl;
4389
+ if (!dest.envVars || dest.envVars.length === 0) dest.envVars = template.envVars;
4390
+ if (!dest.models || dest.models.length === 0) dest.models = template.models;
4391
+ if (template.customModels && (!dest.customModels || Object.keys(dest.customModels).length === 0)) {
4392
+ dest.customModels = template.customModels;
4393
+ }
4394
+ if (template.quirks && dest.quirks === void 0) dest.quirks = template.quirks;
4395
+ return preset.id;
4396
+ }
3840
4397
  function upsertKey(providers, providerId, label, apiKey, nowIso) {
3841
- const existing = providers[providerId] ?? { type: providerId };
4398
+ let existing = providers[providerId];
4399
+ if (!existing) {
4400
+ existing = { type: providerId };
4401
+ const presetId = hydratePresetConfig(providerId, existing);
4402
+ if (presetId) existing.type = presetId;
4403
+ }
3842
4404
  const keys = normalizeKeys(existing);
3843
4405
  const idx = keys.findIndex((k) => k.label === label);
3844
4406
  if (idx >= 0) {
@@ -3888,6 +4450,8 @@ function addProvider(providers, payload, nowIso) {
3888
4450
  family: payload.family,
3889
4451
  baseUrl: payload.baseUrl
3890
4452
  };
4453
+ const presetId = hydratePresetConfig(payload.id, newProv);
4454
+ if (presetId) newProv.type = presetId;
3891
4455
  if (payload.apiKey) {
3892
4456
  newProv.apiKeys = [{ label: "default", apiKey: payload.apiKey, createdAt: nowIso }];
3893
4457
  newProv.activeKey = "default";
@@ -4054,7 +4618,7 @@ var SddBoardWebSocketHandler = class {
4054
4618
  };
4055
4619
 
4056
4620
  // src/server/sdd-wizard-wiring.ts
4057
- import * as path9 from "node:path";
4621
+ import * as path10 from "node:path";
4058
4622
  import { spawnSync as spawnSync2 } from "node:child_process";
4059
4623
  import {
4060
4624
  DefaultTaskStore,
@@ -4154,7 +4718,7 @@ function buildSddWizardDeps(opts) {
4154
4718
  makeDriver: () => new SddInterviewDriver({
4155
4719
  specStore: new SpecStore({ baseDir: opts.paths.projectSpecs }),
4156
4720
  graphStore: new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs }),
4157
- sessionPath: path9.join(opts.paths.projectDir, "sdd-wizard-session.json")
4721
+ sessionPath: path10.join(opts.paths.projectDir, "sdd-wizard-session.json")
4158
4722
  }),
4159
4723
  runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
4160
4724
  startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels, worktrees: useWorktrees }) => {
@@ -4356,8 +4920,8 @@ function toSessionHistoryEntries(summaries, currentSessionId) {
4356
4920
  }
4357
4921
 
4358
4922
  // src/server/shell-open.ts
4359
- import * as fs8 from "node:fs/promises";
4360
- import * as path10 from "node:path";
4923
+ import * as fs9 from "node:fs/promises";
4924
+ import * as path11 from "node:path";
4361
4925
  import { spawn as spawn2 } from "node:child_process";
4362
4926
  var METACHAR_REGEX = /[&|<>^"'`'\n\r]/;
4363
4927
  function shellQuote(s) {
@@ -4365,8 +4929,8 @@ function shellQuote(s) {
4365
4929
  }
4366
4930
  async function handleShellOpen(req, logger) {
4367
4931
  try {
4368
- const resolved = path10.resolve(req.path);
4369
- await fs8.access(resolved);
4932
+ const resolved = path11.resolve(req.path);
4933
+ await fs9.access(resolved);
4370
4934
  if (METACHAR_REGEX.test(resolved)) {
4371
4935
  return { success: false, message: "Path contains unsupported characters." };
4372
4936
  }
@@ -4417,12 +4981,13 @@ async function handleShellOpen(req, logger) {
4417
4981
  }
4418
4982
 
4419
4983
  // src/server/skills-handlers.ts
4420
- import { promises as fs9 } from "node:fs";
4421
- import path11 from "node:path";
4984
+ import { promises as fs10 } from "node:fs";
4985
+ import path12 from "node:path";
4422
4986
  import { atomicWrite as atomicWrite5 } from "@wrongstack/core";
4423
4987
  import { wstackGlobalRoot } from "@wrongstack/core/utils";
4424
4988
 
4425
4989
  // src/server/ws-payload-validation.ts
4990
+ import { FORBIDDEN_PROTO_KEYS } from "@wrongstack/core/utils";
4426
4991
  function isRecord(value) {
4427
4992
  return typeof value === "object" && value !== null && !Array.isArray(value);
4428
4993
  }
@@ -4628,11 +5193,22 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
4628
5193
  "fallbackAuto",
4629
5194
  "favoriteModelsOnly",
4630
5195
  "breakerEnabled",
4631
- "debugStream"
5196
+ "debugStream",
5197
+ // Chimera + auto-review master toggles
5198
+ "chimeraEnabled",
5199
+ "autoReviewEnabled",
5200
+ "showModelReasoning"
5201
+ ]);
5202
+ var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set([
5203
+ "fallbackModels",
5204
+ "favoriteModels",
5205
+ // Auto-review explicit fallback chain (derived when fallbackProfile is unset;
5206
+ // surfaced for visibility/override).
5207
+ "autoReviewFallbackModels"
4632
5208
  ]);
4633
- var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
4634
5209
  var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
4635
5210
  var MODEL_MATRIX_PREF_KEYS = /* @__PURE__ */ new Set(["modelMatrix"]);
5211
+ var BOOLEAN_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["pluginsEnabled"]);
4636
5212
  var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4637
5213
  "autonomyDelayMs",
4638
5214
  "autoProceedMaxIterations",
@@ -4640,7 +5216,12 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4640
5216
  "maxConcurrent",
4641
5217
  "enhanceDelayMs",
4642
5218
  "tgLongToolMs",
4643
- "breakerAutoKillResetMs"
5219
+ "breakerAutoKillResetMs",
5220
+ // Chimera + auto-review numeric knobs
5221
+ "chimeraMaxFiles",
5222
+ "autoReviewDebounceMs",
5223
+ "autoReviewMaxFilesPerBatch",
5224
+ "autoReviewMaxConcurrentReviews"
4644
5225
  ]);
4645
5226
  var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4646
5227
  "hqUrl",
@@ -4649,7 +5230,13 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4649
5230
  "thinkingWord",
4650
5231
  "refinerProvider",
4651
5232
  "refinerModel",
4652
- "refinerFallbackProfile"
5233
+ "refinerFallbackProfile",
5234
+ // Chimera + auto-review override strings
5235
+ "chimeraProvider",
5236
+ "chimeraModel",
5237
+ "autoReviewProvider",
5238
+ "autoReviewModel",
5239
+ "autoReviewFallbackProfile"
4653
5240
  ]);
4654
5241
  var ENUM_PREF_KEYS = {
4655
5242
  autonomy: AUTONOMY_VALUES,
@@ -4664,36 +5251,39 @@ var ENUM_PREF_KEYS = {
4664
5251
  cacheTtl: CACHE_TTL_VALUES,
4665
5252
  statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
4666
5253
  animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
4667
- fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
5254
+ fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"]),
5255
+ // Chimera autoFix + auto-review cascade threshold
5256
+ chimeraAutoFix: /* @__PURE__ */ new Set(["off", "ask", "auto"]),
5257
+ autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"])
4668
5258
  };
4669
- function validateModelRuntimeValue(modelRuntime, path23) {
5259
+ function validateModelRuntimeValue(modelRuntime, path24) {
4670
5260
  const reasoning = modelRuntime["reasoning"];
4671
5261
  if (reasoning !== void 0) {
4672
- if (!isRecord(reasoning)) return `${path23}.reasoning must be an object when provided`;
5262
+ if (!isRecord(reasoning)) return `${path24}.reasoning must be an object when provided`;
4673
5263
  const mode = reasoning["mode"];
4674
5264
  const effort = reasoning["effort"];
4675
5265
  const preserve = reasoning["preserve"];
4676
5266
  if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
4677
- return `${path23}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
5267
+ return `${path24}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
4678
5268
  }
4679
5269
  if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
4680
- return `${path23}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
5270
+ return `${path24}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
4681
5271
  }
4682
5272
  if (preserve !== void 0 && typeof preserve !== "boolean") {
4683
- return `${path23}.reasoning.preserve must be a boolean when provided`;
5273
+ return `${path24}.reasoning.preserve must be a boolean when provided`;
4684
5274
  }
4685
5275
  }
4686
5276
  const cache = modelRuntime["cache"];
4687
5277
  if (cache !== void 0) {
4688
- if (!isRecord(cache)) return `${path23}.cache must be an object when provided`;
5278
+ if (!isRecord(cache)) return `${path24}.cache must be an object when provided`;
4689
5279
  const ttl = cache["ttl"];
4690
5280
  if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
4691
- return `${path23}.cache.ttl must be one of: 5m, 1h`;
5281
+ return `${path24}.cache.ttl must be one of: 5m, 1h`;
4692
5282
  }
4693
5283
  }
4694
5284
  const parameters = modelRuntime["parameters"];
4695
5285
  if (parameters !== void 0 && !isRecord(parameters)) {
4696
- return `${path23}.parameters must be an object when provided`;
5286
+ return `${path24}.parameters must be an object when provided`;
4697
5287
  }
4698
5288
  return null;
4699
5289
  }
@@ -4715,6 +5305,16 @@ function validatePreferenceValue(key, value) {
4715
5305
  (v) => Array.isArray(v) && v.every((item) => typeof item === "string")
4716
5306
  ) ? null : `prefs.update payload.${key} must be an object of string arrays`;
4717
5307
  }
5308
+ if (BOOLEAN_RECORD_PREF_KEYS.has(key)) {
5309
+ if (!isRecord(value) || !Object.values(value).every((v) => typeof v === "boolean")) {
5310
+ return `prefs.update payload.${key} must be an object of booleans`;
5311
+ }
5312
+ const badKey = Object.keys(value).find((k) => FORBIDDEN_PROTO_KEYS.has(k));
5313
+ if (badKey) {
5314
+ return `prefs.update payload.${key} contains a forbidden key: ${badKey}`;
5315
+ }
5316
+ return null;
5317
+ }
4718
5318
  if (MODEL_MATRIX_PREF_KEYS.has(key)) {
4719
5319
  if (!isRecord(value)) return `prefs.update payload.${key} must be an object`;
4720
5320
  for (const entry of Object.values(value)) {
@@ -4973,8 +5573,8 @@ function validateShellOpenPayload(payload) {
4973
5573
  if (!isRecord(payload)) {
4974
5574
  return { ok: false, message: "shell.open payload must be an object with string path" };
4975
5575
  }
4976
- const path23 = payload["path"];
4977
- if (typeof path23 !== "string" || path23.trim().length === 0) {
5576
+ const path24 = payload["path"];
5577
+ if (typeof path24 !== "string" || path24.trim().length === 0) {
4978
5578
  return { ok: false, message: "shell.open payload.path must be a non-empty string" };
4979
5579
  }
4980
5580
  const target = payload["target"];
@@ -4987,7 +5587,7 @@ function validateShellOpenPayload(payload) {
4987
5587
  return {
4988
5588
  ok: true,
4989
5589
  value: {
4990
- path: path23,
5590
+ path: path24,
4991
5591
  ...target !== void 0 ? { target } : {}
4992
5592
  }
4993
5593
  };
@@ -4996,14 +5596,14 @@ function validateGitDiffPayload(payload) {
4996
5596
  if (!isRecord(payload)) {
4997
5597
  return { ok: false, message: "git.diff payload must be an object" };
4998
5598
  }
4999
- const path23 = payload["path"];
5000
- if (path23 === void 0 || path23 === null) {
5599
+ const path24 = payload["path"];
5600
+ if (path24 === void 0 || path24 === null) {
5001
5601
  return { ok: true, value: { path: "" } };
5002
5602
  }
5003
- if (typeof path23 !== "string") {
5603
+ if (typeof path24 !== "string") {
5004
5604
  return { ok: false, message: "git.diff payload.path must be a string when provided" };
5005
5605
  }
5006
- return { ok: true, value: { path: path23 } };
5606
+ return { ok: true, value: { path: path24 } };
5007
5607
  }
5008
5608
  function validateProjectsAddPayload(payload) {
5009
5609
  if (!isRecord(payload)) {
@@ -5225,19 +5825,19 @@ async function handleSkillsContent(ws, ctx, msg) {
5225
5825
  send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
5226
5826
  return;
5227
5827
  }
5228
- const body = await fs9.readFile(entry.path, "utf8");
5229
- const skillDir = path11.dirname(entry.path);
5828
+ const body = await fs10.readFile(entry.path, "utf8");
5829
+ const skillDir = path12.dirname(entry.path);
5230
5830
  let relatedFiles = [];
5231
5831
  try {
5232
- const files = await fs9.readdir(skillDir);
5233
- relatedFiles = files.filter((f) => f !== path11.basename(entry.path)).map((f) => path11.join(skillDir, f));
5832
+ const files = await fs10.readdir(skillDir);
5833
+ relatedFiles = files.filter((f) => f !== path12.basename(entry.path)).map((f) => path12.join(skillDir, f));
5234
5834
  } catch {
5235
5835
  }
5236
5836
  const nameLower = name2.toLowerCase();
5237
5837
  const refResults = await Promise.all(
5238
5838
  entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
5239
5839
  try {
5240
- const content = await fs9.readFile(e.path, "utf8");
5840
+ const content = await fs10.readFile(e.path, "utf8");
5241
5841
  return [e.name, content.toLowerCase().includes(nameLower)];
5242
5842
  } catch {
5243
5843
  return [e.name, false];
@@ -5327,20 +5927,20 @@ async function handleSkillsCreate(ws, ctx, msg) {
5327
5927
  }
5328
5928
  const createPayload = parsed.value;
5329
5929
  try {
5330
- const targetDir = createPayload.scope === "global" ? path11.join(
5331
- ctx.globalSkillsDir ?? path11.join(wstackGlobalRoot(), "skills"),
5930
+ const targetDir = createPayload.scope === "global" ? path12.join(
5931
+ ctx.globalSkillsDir ?? path12.join(wstackGlobalRoot(), "skills"),
5332
5932
  createPayload.name.trim()
5333
- ) : path11.join(
5334
- ctx.projectSkillsDir ?? path11.join(ctx.projectRoot, ".wrongstack", "skills"),
5933
+ ) : path12.join(
5934
+ ctx.projectSkillsDir ?? path12.join(ctx.projectRoot, ".wrongstack", "skills"),
5335
5935
  createPayload.name.trim()
5336
5936
  );
5337
5937
  try {
5338
- await fs9.access(targetDir);
5938
+ await fs10.access(targetDir);
5339
5939
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
5340
5940
  return;
5341
5941
  } catch {
5342
5942
  }
5343
- await fs9.mkdir(targetDir, { recursive: true });
5943
+ await fs10.mkdir(targetDir, { recursive: true });
5344
5944
  const lines = createPayload.description.trim().split("\n");
5345
5945
  const firstLine = (lines[0] ?? "").trim();
5346
5946
  const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
@@ -5388,13 +5988,13 @@ ${trigger}
5388
5988
  "- `bug-hunter` \u2014 for systematic bug detection patterns",
5389
5989
  "- `output-standards` \u2014 for standardized `<nextsteps>` formatting"
5390
5990
  ].join("\n");
5391
- await atomicWrite5(path11.join(targetDir, "SKILL.md"), skillContent);
5991
+ await atomicWrite5(path12.join(targetDir, "SKILL.md"), skillContent);
5392
5992
  send(ws, {
5393
5993
  type: "skills.created",
5394
5994
  payload: {
5395
5995
  success: true,
5396
5996
  error: null,
5397
- skill: { name: createPayload.name.trim(), path: path11.join(targetDir, "SKILL.md"), scope: createPayload.scope }
5997
+ skill: { name: createPayload.name.trim(), path: path12.join(targetDir, "SKILL.md"), scope: createPayload.scope }
5398
5998
  }
5399
5999
  });
5400
6000
  } catch (err) {
@@ -5671,7 +6271,7 @@ function estimateContextBreakdown(input) {
5671
6271
  }
5672
6272
 
5673
6273
  // src/server/worktree-ws-handler.ts
5674
- import { join as join8, resolve as resolve6, sep as sep3 } from "node:path";
6274
+ import { join as join9, resolve as resolve6, sep as sep3 } from "node:path";
5675
6275
  import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core";
5676
6276
  import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
5677
6277
  import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
@@ -5732,7 +6332,7 @@ var WorktreeWebSocketHandler = class {
5732
6332
  // ── orphan management ─────────────────────────────────────────────────────
5733
6333
  /** Absolute managed-worktrees root for this project. */
5734
6334
  worktreesRoot() {
5735
- return resolve6(join8(this.management.projectRoot, ".wrongstack", "worktrees"));
6335
+ return resolve6(join9(this.management.projectRoot, ".wrongstack", "worktrees"));
5736
6336
  }
5737
6337
  /** True iff `dir` resolves strictly inside the managed worktrees root. */
5738
6338
  underRoot(dir) {
@@ -6003,7 +6603,7 @@ var WorktreeWebSocketHandler = class {
6003
6603
  };
6004
6604
 
6005
6605
  // src/server/server-runtime.ts
6006
- import * as path13 from "node:path";
6606
+ import * as path15 from "node:path";
6007
6607
  import { createRequire } from "node:module";
6008
6608
  import { fileURLToPath } from "node:url";
6009
6609
  import { WebSocketServer } from "ws";
@@ -6045,12 +6645,103 @@ function registerShutdownHandlers(res) {
6045
6645
  }
6046
6646
 
6047
6647
  // src/server/setup-events.ts
6048
- import * as fs10 from "node:fs/promises";
6049
6648
  import { watch as fsWatch } from "node:fs";
6050
- import * as path12 from "node:path";
6649
+ import * as fs11 from "node:fs/promises";
6650
+ import * as path14 from "node:path";
6651
+ import { getBoard, getKanbanDir } from "@wrongstack/kanban";
6652
+
6653
+ // src/server/codemap-telemetry.ts
6654
+ import * as path13 from "node:path";
6655
+ var TOOL_OPERATION = {
6656
+ read: "read",
6657
+ read_file: "read",
6658
+ view: "read",
6659
+ write: "write",
6660
+ write_file: "write",
6661
+ create_file: "write",
6662
+ edit: "edit",
6663
+ replace: "edit",
6664
+ patch: "edit",
6665
+ apply_patch: "edit",
6666
+ delete: "delete",
6667
+ delete_file: "delete",
6668
+ remove: "delete",
6669
+ unlink: "delete",
6670
+ grep: "search",
6671
+ search: "search",
6672
+ codebase_search: "search",
6673
+ "codebase-search": "search"
6674
+ };
6675
+ function numberField(input, names) {
6676
+ for (const name2 of names) {
6677
+ const value = input[name2];
6678
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.floor(value);
6679
+ }
6680
+ return void 0;
6681
+ }
6682
+ function normalizeTarget(projectRoot, filePath) {
6683
+ return path13.normalize(path13.isAbsolute(filePath) ? filePath : path13.resolve(projectRoot, filePath));
6684
+ }
6685
+ function normalizeCodeMapFileTarget(projectRoot, filePath, operation = "edit", line, endLine) {
6686
+ return {
6687
+ filePath: normalizeTarget(projectRoot, filePath),
6688
+ operation: operation === "rename" ? "edit" : operation,
6689
+ ...line ? { line } : {},
6690
+ ...endLine ? { endLine } : {}
6691
+ };
6692
+ }
6693
+ function patchTargets(patch) {
6694
+ const targets = [];
6695
+ for (const line of patch.split(/\r?\n/)) {
6696
+ const match = /^\+\+\+\s+(?:b\/)?(.+?)(?:\t.*)?$/.exec(line);
6697
+ const target = match?.[1]?.trim();
6698
+ if (target && target !== "/dev/null") targets.push(target);
6699
+ }
6700
+ return targets;
6701
+ }
6702
+ function extractCodeMapFileTargets(projectRoot, toolName, rawInput) {
6703
+ const operation = TOOL_OPERATION[toolName.toLowerCase()];
6704
+ if (!operation || !rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) return [];
6705
+ const input = rawInput;
6706
+ const rawPaths = [];
6707
+ for (const key of ["path", "file", "filePath", "target"]) {
6708
+ const value = input[key];
6709
+ if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
6710
+ }
6711
+ const files = input["files"];
6712
+ if (Array.isArray(files)) {
6713
+ for (const value of files)
6714
+ if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
6715
+ } else if (typeof files === "string" && files.trim() && !/[?*{}[\]]/.test(files)) {
6716
+ rawPaths.push(files.trim());
6717
+ }
6718
+ if ((toolName === "patch" || toolName === "apply_patch") && typeof input["patch"] === "string") {
6719
+ rawPaths.push(...patchTargets(input["patch"]));
6720
+ }
6721
+ const line = numberField(input, ["line", "offset", "startLine", "start_line", "line_start"]);
6722
+ const explicitEnd = numberField(input, ["endLine", "end_line", "line_end"]);
6723
+ const limit = numberField(input, ["limit"]);
6724
+ const endLine = explicitEnd ?? (line && limit ? line + limit - 1 : void 0);
6725
+ const seen = /* @__PURE__ */ new Set();
6726
+ const targets = [];
6727
+ for (const rawPath of rawPaths) {
6728
+ const filePath = normalizeTarget(projectRoot, rawPath);
6729
+ if (seen.has(filePath)) continue;
6730
+ seen.add(filePath);
6731
+ targets.push({
6732
+ filePath,
6733
+ operation,
6734
+ ...line ? { line } : {},
6735
+ ...endLine ? { endLine } : {}
6736
+ });
6737
+ }
6738
+ return targets;
6739
+ }
6740
+
6741
+ // src/server/setup-events.ts
6051
6742
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
6052
6743
  const raw = String(filename);
6053
- const relative4 = path12.isAbsolute(raw) ? path12.relative(projectsDir, raw) : raw;
6744
+ const relative4 = path14.isAbsolute(raw) ? path14.relative(projectsDir, raw) : raw;
6054
6745
  const parts = relative4.split(/[\\/]+/).filter(Boolean);
6055
6746
  if (parts.length < 2) return null;
6056
6747
  if (parts[parts.length - 1] !== "status.json") return null;
@@ -6061,12 +6752,76 @@ function shouldLogWatcherStats() {
6061
6752
  return value === "1" || value === "true" || value === "yes" || value === "on";
6062
6753
  }
6063
6754
  function setupEvents(deps2) {
6064
- const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
6755
+ const {
6756
+ events,
6757
+ broadcast: broadcast2,
6758
+ clients,
6759
+ config,
6760
+ context,
6761
+ pendingConfirms,
6762
+ globalConfigPath,
6763
+ sessionBridge,
6764
+ wpaths,
6765
+ watcherMetrics,
6766
+ onFleetBroadcaster
6767
+ } = deps2;
6065
6768
  const disposers = [];
6066
6769
  let disposed = false;
6067
6770
  const on = (event, listener) => {
6068
6771
  disposers.push(events.on(event, listener));
6069
6772
  };
6773
+ const conversationState = context.state;
6774
+ if (typeof conversationState?.onChange === "function") {
6775
+ disposers.push(
6776
+ conversationState.onChange((change) => {
6777
+ if (change.kind !== "todos_replaced") return;
6778
+ broadcast2(clients, {
6779
+ type: "todos.updated",
6780
+ payload: {
6781
+ sessionId: context.session?.id ?? "",
6782
+ todos: [...change.todos],
6783
+ revision: conversationState.revision
6784
+ }
6785
+ });
6786
+ })
6787
+ );
6788
+ }
6789
+ let kanbanWatcher = null;
6790
+ let kanbanDebounce = null;
6791
+ const projectRoot = context.projectRoot;
6792
+ if (projectRoot) {
6793
+ try {
6794
+ const kanbanDir = getKanbanDir(projectRoot);
6795
+ kanbanWatcher = fsWatch(kanbanDir, { persistent: false }, (_eventType, filename) => {
6796
+ const name2 = filename?.toString();
6797
+ if (!name2?.endsWith(".json")) return;
6798
+ const boardId = name2.slice(0, -5);
6799
+ if (kanbanDebounce) clearTimeout(kanbanDebounce);
6800
+ kanbanDebounce = setTimeout(async () => {
6801
+ try {
6802
+ const board = await getBoard(projectRoot, boardId);
6803
+ if (board) {
6804
+ broadcast2(clients, {
6805
+ type: "kanban.get",
6806
+ // Wrap in the { board } envelope like every other kanban
6807
+ // broadcast so the client's isBoardEnvelope path handles it
6808
+ // without hijacking another tab's activeBoardId.
6809
+ payload: { success: true, data: { board } }
6810
+ });
6811
+ }
6812
+ } catch {
6813
+ }
6814
+ }, 60);
6815
+ });
6816
+ kanbanWatcher.on("error", () => kanbanWatcher?.close());
6817
+ disposers.push(() => {
6818
+ if (kanbanDebounce) clearTimeout(kanbanDebounce);
6819
+ kanbanWatcher?.close();
6820
+ kanbanWatcher = null;
6821
+ });
6822
+ } catch {
6823
+ }
6824
+ }
6070
6825
  const currentSessionId = () => context.session?.id ?? "";
6071
6826
  const sessionPayload2 = (payload) => {
6072
6827
  const provided = payload["sessionId"];
@@ -6092,7 +6847,11 @@ function setupEvents(deps2) {
6092
6847
  on("iteration.completed", (e) => {
6093
6848
  broadcast2(clients, {
6094
6849
  type: "iteration.completed",
6095
- payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
6850
+ payload: sessionPayload2({
6851
+ sessionId: e.sessionId,
6852
+ index: e.index,
6853
+ totalIterations: e.index + 1
6854
+ })
6096
6855
  });
6097
6856
  });
6098
6857
  on("iteration.limit_reached", (e) => {
@@ -6106,10 +6865,16 @@ function setupEvents(deps2) {
6106
6865
  });
6107
6866
  });
6108
6867
  on("provider.text_delta", (e) => {
6109
- broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
6868
+ broadcast2(clients, {
6869
+ type: "provider.text_delta",
6870
+ payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" })
6871
+ });
6110
6872
  });
6111
6873
  on("provider.thinking_delta", (e) => {
6112
- broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
6874
+ broadcast2(clients, {
6875
+ type: "provider.thinking_delta",
6876
+ payload: sessionPayload2({ sessionId: e.sessionId, text: e.text })
6877
+ });
6113
6878
  });
6114
6879
  on("provider.stream_error", (e) => {
6115
6880
  broadcast2(clients, {
@@ -6120,7 +6885,17 @@ function setupEvents(deps2) {
6120
6885
  on("tool.started", (e) => {
6121
6886
  broadcast2(clients, {
6122
6887
  type: "tool.started",
6123
- payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
6888
+ payload: sessionPayload2({
6889
+ sessionId: e.sessionId,
6890
+ traceId: e.traceId,
6891
+ agentId: e.agentId,
6892
+ agentName: e.agentName,
6893
+ id: e.id,
6894
+ name: e.name,
6895
+ input: e.input,
6896
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
6897
+ messageId: `tool_${e.id}`
6898
+ })
6124
6899
  });
6125
6900
  appendForCurrentSession(e.sessionId, {
6126
6901
  type: "tool_call_start",
@@ -6131,13 +6906,37 @@ function setupEvents(deps2) {
6131
6906
  });
6132
6907
  });
6133
6908
  on("tool.progress", (e) => {
6909
+ const rawProgressPath = e.event.path ?? (typeof e.event.data?.["path"] === "string" ? e.event.data["path"] : void 0);
6910
+ const progressTarget = rawProgressPath ? normalizeCodeMapFileTarget(
6911
+ context.projectRoot,
6912
+ rawProgressPath,
6913
+ e.event.operation ?? "edit",
6914
+ e.event.line,
6915
+ e.event.endLine
6916
+ ) : void 0;
6134
6917
  broadcast2(clients, {
6135
6918
  type: "tool.progress",
6136
6919
  // Nested `event` shape — the client handler reads `payload.event?.text`
6137
6920
  // and early-returns on a falsy text, so a flat { eventType, text } payload
6138
6921
  // makes live tool progress (bash streaming, partial_output, warnings)
6139
6922
  // never render. Must match WSToolProgress and the CLI server.
6140
- payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, event: { type: e.event.type, text: e.event.text, data: e.event.data } })
6923
+ payload: sessionPayload2({
6924
+ sessionId: e.sessionId,
6925
+ traceId: e.traceId,
6926
+ agentId: e.agentId,
6927
+ agentName: e.agentName,
6928
+ id: e.id,
6929
+ name: e.name,
6930
+ event: {
6931
+ type: e.event.type,
6932
+ text: e.event.text,
6933
+ data: e.event.data,
6934
+ path: progressTarget?.filePath,
6935
+ operation: e.event.operation,
6936
+ line: progressTarget?.line,
6937
+ endLine: progressTarget?.endLine
6938
+ }
6939
+ })
6141
6940
  });
6142
6941
  appendForCurrentSession(e.sessionId, {
6143
6942
  type: "tool_progress",
@@ -6154,7 +6953,23 @@ function setupEvents(deps2) {
6154
6953
  on("tool.executed", (e) => {
6155
6954
  broadcast2(clients, {
6156
6955
  type: "tool.executed",
6157
- payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
6956
+ payload: sessionPayload2({
6957
+ sessionId: e.sessionId,
6958
+ traceId: e.traceId,
6959
+ agentId: e.agentId,
6960
+ agentName: e.agentName,
6961
+ id: e.id,
6962
+ name: e.name,
6963
+ durationMs: e.durationMs,
6964
+ ok: e.ok,
6965
+ input: e.input,
6966
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
6967
+ output: e.output,
6968
+ outputBytes: e.outputBytes,
6969
+ outputTokens: e.outputTokens,
6970
+ outputLines: e.outputLines,
6971
+ metadata: e.metadata
6972
+ })
6158
6973
  });
6159
6974
  appendForCurrentSession(e.sessionId, {
6160
6975
  type: "tool_call_end",
@@ -6168,7 +6983,10 @@ function setupEvents(deps2) {
6168
6983
  outputTokens: e.outputTokens,
6169
6984
  outputLines: e.outputLines
6170
6985
  });
6171
- broadcast2(clients, { type: "todos.updated", payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] }) });
6986
+ broadcast2(clients, {
6987
+ type: "todos.updated",
6988
+ payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] })
6989
+ });
6172
6990
  const sideEffects = context.sideEffects ?? [];
6173
6991
  if (sideEffects.length > 0) {
6174
6992
  broadcast2(clients, {
@@ -6193,7 +7011,10 @@ function setupEvents(deps2) {
6193
7011
  if (typeof taskPath === "string" && taskPath) {
6194
7012
  const { loadTasks } = await import("@wrongstack/core");
6195
7013
  const file = await loadTasks(taskPath);
6196
- broadcast2(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
7014
+ broadcast2(clients, {
7015
+ type: "tasks.updated",
7016
+ payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] })
7017
+ });
6197
7018
  }
6198
7019
  } catch {
6199
7020
  }
@@ -6202,13 +7023,27 @@ function setupEvents(deps2) {
6202
7023
  if (typeof planPath === "string" && planPath) {
6203
7024
  const { loadPlan } = await import("@wrongstack/core");
6204
7025
  const plan = await loadPlan(planPath);
6205
- 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: [] } }) });
7026
+ broadcast2(clients, {
7027
+ type: "plan.updated",
7028
+ payload: sessionPayload2({
7029
+ sessionId: e.sessionId,
7030
+ plan: plan ?? {
7031
+ version: 1,
7032
+ sessionId: e.sessionId ?? context.session?.id ?? "",
7033
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7034
+ items: []
7035
+ }
7036
+ })
7037
+ });
6206
7038
  }
6207
7039
  } catch {
6208
7040
  }
6209
7041
  })();
6210
7042
  }
6211
7043
  });
7044
+ on("file.activity", (e) => {
7045
+ broadcast2(clients, { type: "codemap.file_event", payload: e });
7046
+ });
6212
7047
  on("tool.loop_detected", (e) => {
6213
7048
  broadcast2(clients, {
6214
7049
  type: "tool.loop_detected",
@@ -6224,7 +7059,12 @@ function setupEvents(deps2) {
6224
7059
  on("trust.persisted", (e) => {
6225
7060
  broadcast2(clients, {
6226
7061
  type: "trust.persisted",
6227
- payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
7062
+ payload: sessionPayload2({
7063
+ sessionId: e.sessionId,
7064
+ tool: e.tool,
7065
+ pattern: e.pattern,
7066
+ decision: e.decision
7067
+ })
6228
7068
  });
6229
7069
  });
6230
7070
  on("delegate.started", (e) => {
@@ -6266,7 +7106,12 @@ function setupEvents(deps2) {
6266
7106
  on("ctx.pct", (e) => {
6267
7107
  broadcast2(clients, {
6268
7108
  type: "ctx.pct",
6269
- payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
7109
+ payload: sessionPayload2({
7110
+ sessionId: e.sessionId,
7111
+ load: e.load,
7112
+ tokens: e.tokens,
7113
+ maxContext: e.maxContext
7114
+ })
6270
7115
  });
6271
7116
  broadcast2(clients, {
6272
7117
  type: "subagent.event",
@@ -6283,7 +7128,12 @@ function setupEvents(deps2) {
6283
7128
  on("ctx.max_context", (e) => {
6284
7129
  broadcast2(clients, {
6285
7130
  type: "ctx.max_context",
6286
- payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
7131
+ payload: sessionPayload2({
7132
+ sessionId: e.sessionId,
7133
+ providerId: e.providerId,
7134
+ modelId: e.modelId,
7135
+ maxContext: e.maxContext
7136
+ })
6287
7137
  });
6288
7138
  });
6289
7139
  on("token.threshold", (e) => {
@@ -6299,11 +7149,27 @@ function setupEvents(deps2) {
6299
7149
  });
6300
7150
  });
6301
7151
  on("context.repaired", (e) => {
6302
- broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
7152
+ broadcast2(clients, {
7153
+ type: "context.repaired",
7154
+ payload: sessionPayload2({
7155
+ sessionId: e.sessionId,
7156
+ removedToolUses: e.removedToolUses,
7157
+ removedToolResults: e.removedToolResults,
7158
+ removedMessages: e.removedMessages
7159
+ })
7160
+ });
6303
7161
  });
6304
7162
  on("tool.confirm_needed", (e) => {
6305
7163
  const id = e.toolUseId ?? `confirm_${Date.now()}`;
6306
- const payload = sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern, decisionSource: e.decisionSource, riskTier: e.riskTier });
7164
+ const payload = sessionPayload2({
7165
+ sessionId: e.sessionId,
7166
+ id,
7167
+ toolName: e.tool?.name ?? "unknown",
7168
+ input: e.input,
7169
+ suggestedPattern: e.suggestedPattern,
7170
+ decisionSource: e.decisionSource,
7171
+ riskTier: e.riskTier
7172
+ });
6307
7173
  pendingConfirms.set(id, {
6308
7174
  resolve: e.resolve,
6309
7175
  decisionSource: e.decisionSource,
@@ -6313,7 +7179,14 @@ function setupEvents(deps2) {
6313
7179
  broadcast2(clients, { type: "tool.confirm_needed", payload });
6314
7180
  });
6315
7181
  on("error", (e) => {
6316
- broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
7182
+ broadcast2(clients, {
7183
+ type: "error",
7184
+ payload: sessionPayload2({
7185
+ sessionId: e.sessionId,
7186
+ phase: e.phase,
7187
+ message: e.err instanceof Error ? e.err.message : String(e.err)
7188
+ })
7189
+ });
6317
7190
  appendForCurrentSession(e.sessionId, {
6318
7191
  type: "error",
6319
7192
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6384,6 +7257,34 @@ function setupEvents(deps2) {
6384
7257
  description: e.description
6385
7258
  });
6386
7259
  });
7260
+ on("provider.status_changed", (e) => {
7261
+ broadcast2(clients, {
7262
+ type: "provider.status_changed",
7263
+ payload: sessionPayload2({
7264
+ providerId: e.providerId,
7265
+ model: e.model,
7266
+ oldState: e.oldState,
7267
+ newState: e.newState,
7268
+ reason: e.reason,
7269
+ timestamp: e.timestamp
7270
+ })
7271
+ });
7272
+ });
7273
+ on("provider.active_blocked", (e) => {
7274
+ broadcast2(clients, {
7275
+ type: "provider.active_blocked",
7276
+ payload: sessionPayload2({
7277
+ sessionId: e.sessionId,
7278
+ providerId: e.providerId,
7279
+ model: e.model,
7280
+ state: e.state,
7281
+ fallbackProviderId: e.fallbackProviderId,
7282
+ fallbackModel: e.fallbackModel,
7283
+ lastError: e.lastError,
7284
+ timestamp: e.timestamp
7285
+ })
7286
+ });
7287
+ });
6387
7288
  on("provider.error", (e) => {
6388
7289
  broadcast2(clients, {
6389
7290
  type: "provider.error",
@@ -6489,16 +7390,137 @@ function setupEvents(deps2) {
6489
7390
  broadcast2(clients, { type: "mailbox.agent_registered", payload });
6490
7391
  });
6491
7392
  const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
6492
- 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 }));
6493
- on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
6494
- on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
6495
- 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 }));
6496
- on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
6497
- on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
6498
- on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
6499
- 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 }));
6500
- on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
7393
+ on(
7394
+ "subagent.spawned",
7395
+ (e) => forwardSubagent("spawned", {
7396
+ sessionId: e.sessionId,
7397
+ subagentId: e.subagentId,
7398
+ taskId: e.taskId,
7399
+ name: e.name,
7400
+ provider: e.provider,
7401
+ model: e.model,
7402
+ description: e.description
7403
+ })
7404
+ );
7405
+ on(
7406
+ "subagent.task_started",
7407
+ (e) => forwardSubagent("task_started", {
7408
+ sessionId: e.sessionId,
7409
+ subagentId: e.subagentId,
7410
+ taskId: e.taskId,
7411
+ description: e.description
7412
+ })
7413
+ );
7414
+ on("subagent.tool_started", (e) => {
7415
+ broadcast2(clients, {
7416
+ type: "codemap.tool_started",
7417
+ payload: {
7418
+ sessionId: e.agentSessionId ?? e.sessionId ?? "",
7419
+ parentSessionId: e.sessionId,
7420
+ traceId: e.traceId,
7421
+ agentId: e.subagentId,
7422
+ agentName: e.agentName ?? e.subagentId,
7423
+ id: e.id,
7424
+ name: e.name,
7425
+ input: e.input,
7426
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input)
7427
+ }
7428
+ });
7429
+ });
7430
+ on("subagent.tool_executed", (e) => {
7431
+ broadcast2(clients, {
7432
+ type: "codemap.tool_executed",
7433
+ payload: {
7434
+ sessionId: e.agentSessionId ?? e.sessionId ?? "",
7435
+ parentSessionId: e.sessionId,
7436
+ traceId: e.traceId,
7437
+ agentId: e.subagentId,
7438
+ agentName: e.agentName ?? e.subagentId,
7439
+ id: e.id,
7440
+ name: e.name,
7441
+ durationMs: e.durationMs,
7442
+ ok: e.ok,
7443
+ input: e.input,
7444
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
7445
+ output: e.output,
7446
+ outputBytes: e.outputBytes,
7447
+ outputTokens: e.outputTokens,
7448
+ outputLines: e.outputLines
7449
+ }
7450
+ });
7451
+ forwardSubagent("tool_executed", {
7452
+ sessionId: e.sessionId,
7453
+ subagentId: e.subagentId,
7454
+ toolName: e.name,
7455
+ durationMs: e.durationMs,
7456
+ ok: e.ok
7457
+ });
7458
+ });
7459
+ on(
7460
+ "subagent.iteration_summary",
7461
+ (e) => forwardSubagent("iteration_summary", {
7462
+ sessionId: e.sessionId,
7463
+ subagentId: e.subagentId,
7464
+ iteration: e.iteration,
7465
+ toolCalls: e.toolCalls,
7466
+ costUsd: e.costUsd,
7467
+ currentTool: e.currentTool,
7468
+ partialText: e.partialText
7469
+ })
7470
+ );
7471
+ on(
7472
+ "subagent.budget_warning",
7473
+ (e) => forwardSubagent("budget_warning", {
7474
+ sessionId: e.sessionId,
7475
+ subagentId: e.subagentId,
7476
+ budgetKind: e.kind,
7477
+ used: e.used,
7478
+ limit: e.limit
7479
+ })
7480
+ );
7481
+ on(
7482
+ "subagent.budget_extended",
7483
+ (e) => forwardSubagent("budget_extended", {
7484
+ sessionId: e.sessionId,
7485
+ subagentId: e.subagentId,
7486
+ budgetKind: e.kind,
7487
+ newLimit: e.newLimit,
7488
+ totalExtensions: e.totalExtensions
7489
+ })
7490
+ );
7491
+ on(
7492
+ "subagent.ctx_pct",
7493
+ (e) => forwardSubagent("ctx_pct", {
7494
+ sessionId: e.sessionId,
7495
+ subagentId: e.subagentId,
7496
+ load: e.load,
7497
+ tokens: e.tokens,
7498
+ maxContext: e.maxContext
7499
+ })
7500
+ );
7501
+ on(
7502
+ "subagent.task_completed",
7503
+ (e) => forwardSubagent("task_completed", {
7504
+ sessionId: e.sessionId,
7505
+ subagentId: e.subagentId,
7506
+ status: e.status,
7507
+ iterations: e.iterations,
7508
+ toolCalls: e.toolCalls,
7509
+ finalText: e.finalText,
7510
+ failureReason: e.error?.kind,
7511
+ error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0
7512
+ })
7513
+ );
7514
+ on(
7515
+ "subagent.removed",
7516
+ (e) => forwardSubagent("removed", {
7517
+ sessionId: e.sessionId,
7518
+ subagentId: e.subagentId,
7519
+ reason: e.reason
7520
+ })
7521
+ );
6501
7522
  on("agent.timeline.message", (e) => {
7523
+ const timeline = e;
6502
7524
  broadcast2(clients, {
6503
7525
  type: "agent.timeline.message",
6504
7526
  payload: sessionPayload2({
@@ -6510,6 +7532,7 @@ function setupEvents(deps2) {
6510
7532
  iteration: e.iteration,
6511
7533
  ts: e.ts,
6512
7534
  toolName: e.toolName,
7535
+ ...typeof timeline.toolOk === "boolean" ? { toolOk: timeline.toolOk } : {},
6513
7536
  costUsd: e.costUsd
6514
7537
  })
6515
7538
  });
@@ -6618,9 +7641,9 @@ function setupEvents(deps2) {
6618
7641
  if (wpaths?.projectStatus) {
6619
7642
  try {
6620
7643
  const statusFile = wpaths.projectStatus(e.projectHash);
6621
- const dir = path12.dirname(statusFile);
6622
- await fs10.mkdir(dir, { recursive: true });
6623
- await fs10.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
7644
+ const dir = path14.dirname(statusFile);
7645
+ await fs11.mkdir(dir, { recursive: true });
7646
+ await fs11.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
6624
7647
  } catch (err) {
6625
7648
  console.error(
6626
7649
  JSON.stringify({
@@ -6634,7 +7657,7 @@ function setupEvents(deps2) {
6634
7657
  }
6635
7658
  });
6636
7659
  if (wpaths?.projectStatus && wpaths.configDir) {
6637
- const projectsDir = path12.join(wpaths.configDir, "projects");
7660
+ const projectsDir = path14.join(wpaths.configDir, "projects");
6638
7661
  const knownProjectHashes = /* @__PURE__ */ new Set();
6639
7662
  const debounceTimers = /* @__PURE__ */ new Map();
6640
7663
  const DEBOUNCE_MS = 150;
@@ -6698,26 +7721,32 @@ function setupEvents(deps2) {
6698
7721
  let watcher;
6699
7722
  const startWatcher = async () => {
6700
7723
  try {
6701
- await fs10.mkdir(projectsDir, { recursive: true });
7724
+ await fs11.mkdir(projectsDir, { recursive: true });
6702
7725
  if (disposed) return;
6703
- watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
6704
- if (eventType !== "change" && eventType !== "rename") return;
6705
- if (filename == null) return;
6706
- const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
6707
- if (!projectHash) return;
6708
- if (watcherMetrics) watcherMetrics.fileChangesDetected++;
6709
- if (!knownProjectHashes.has(projectHash)) return;
6710
- if (watcherMetrics) watcherMetrics.filesProcessed++;
6711
- try {
6712
- const targetFile = path12.join(projectsDir, projectHash, "status.json");
6713
- const content = await fs10.readFile(targetFile, "utf-8");
6714
- const statusData = JSON.parse(content);
6715
- scheduleBroadcast(projectHash, statusData);
6716
- } catch {
7726
+ watcher = fsWatch(
7727
+ projectsDir,
7728
+ { persistent: true, recursive: true },
7729
+ async (eventType, filename) => {
7730
+ if (eventType !== "change" && eventType !== "rename") return;
7731
+ if (filename == null) return;
7732
+ const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
7733
+ if (!projectHash) return;
7734
+ if (watcherMetrics) watcherMetrics.fileChangesDetected++;
7735
+ if (!knownProjectHashes.has(projectHash)) return;
7736
+ if (watcherMetrics) watcherMetrics.filesProcessed++;
7737
+ try {
7738
+ const targetFile = path14.join(projectsDir, projectHash, "status.json");
7739
+ const content = await fs11.readFile(targetFile, "utf-8");
7740
+ const statusData = JSON.parse(content);
7741
+ scheduleBroadcast(projectHash, statusData);
7742
+ } catch {
7743
+ }
6717
7744
  }
6718
- });
7745
+ );
6719
7746
  if (logWatcherMetricsEnabled) {
6720
- console.log(`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`);
7747
+ console.log(
7748
+ `[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`
7749
+ );
6721
7750
  }
6722
7751
  } catch (err) {
6723
7752
  console.error(
@@ -6762,17 +7791,19 @@ function setupEvents(deps2) {
6762
7791
  }
6763
7792
  });
6764
7793
  }
6765
- const globalRoot = globalConfigPath ? path12.dirname(globalConfigPath) : void 0;
7794
+ const globalRoot = globalConfigPath ? path14.dirname(globalConfigPath) : void 0;
6766
7795
  if (globalRoot) {
6767
7796
  const broadcastSessions = async () => {
6768
7797
  try {
6769
7798
  const { SessionRegistry } = await import("@wrongstack/core");
6770
7799
  const registry = new SessionRegistry(globalRoot);
6771
7800
  const sessions = await registry.list();
6772
- const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
6773
- const live = sessions.filter(
6774
- (s) => s.status === "active" || s.status === "idle"
6775
- ).filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
7801
+ const ownEntry = sessions.find((s) => s.pid === process.pid);
7802
+ const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
7803
+ const myRoot = path14.resolve(context.projectRoot);
7804
+ const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter(
7805
+ (s) => mySlug ? s.projectSlug === mySlug : path14.resolve(s.projectRoot) === myRoot
7806
+ ).map((s) => ({
6776
7807
  sessionId: s.sessionId,
6777
7808
  projectName: s.projectName,
6778
7809
  projectSlug: s.projectSlug,
@@ -6784,12 +7815,15 @@ function setupEvents(deps2) {
6784
7815
  status: s.status,
6785
7816
  pid: s.pid,
6786
7817
  startedAt: s.startedAt,
7818
+ lastHeartbeatAt: s.lastHeartbeatAt,
6787
7819
  agentCount: s.agentCount,
6788
7820
  agents: (s.agents ?? []).map((a) => ({
6789
7821
  id: a.id,
6790
7822
  name: a.name,
6791
7823
  status: a.status,
6792
7824
  currentTool: a.currentTool,
7825
+ currentTask: a.currentTask,
7826
+ taskId: a.taskId,
6793
7827
  iterations: a.iterations,
6794
7828
  toolCalls: a.toolCalls,
6795
7829
  costUsd: a.costUsd,
@@ -6798,6 +7832,12 @@ function setupEvents(deps2) {
6798
7832
  ctxPct: a.ctxPct,
6799
7833
  model: a.model,
6800
7834
  partialText: a.partialText,
7835
+ recentTools: a.recentTools,
7836
+ recentMail: a.recentMail,
7837
+ todos: a.todos,
7838
+ latestPrompt: a.latestPrompt,
7839
+ latestPromptAt: a.latestPromptAt,
7840
+ activity: a.activity,
6801
7841
  lastActivityAt: a.lastActivityAt
6802
7842
  }))
6803
7843
  }));
@@ -7023,7 +8063,7 @@ function createSessionStartPayload(g) {
7023
8063
  inputCost,
7024
8064
  outputCost,
7025
8065
  cacheReadCost,
7026
- projectName: path13.basename(projectRoot) || projectRoot,
8066
+ projectName: path15.basename(projectRoot) || projectRoot,
7027
8067
  projectRoot,
7028
8068
  cwd: g.getWorkingDir(),
7029
8069
  mode: g.getModeId(),
@@ -7121,13 +8161,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
7121
8161
  };
7122
8162
  }
7123
8163
  function resolveWebuiDistDir(fromUrl, explicitDistDir) {
7124
- if (explicitDistDir) return path13.resolve(explicitDistDir);
8164
+ if (explicitDistDir) return path15.resolve(explicitDistDir);
7125
8165
  try {
7126
8166
  const requireFromHere2 = createRequire(fromUrl);
7127
8167
  const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
7128
- return path13.dirname(serverEntry);
8168
+ return path15.dirname(serverEntry);
7129
8169
  } catch {
7130
- return path13.resolve(path13.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
8170
+ return path15.resolve(path15.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
7131
8171
  }
7132
8172
  }
7133
8173
  function startHttpServer(opts) {
@@ -7140,15 +8180,18 @@ function startHttpServer(opts) {
7140
8180
  apiToken: opts.wsToken,
7141
8181
  requireToken: opts.requireToken,
7142
8182
  watcherMetrics: opts.watcherMetrics,
7143
- onFleetPing: opts.onFleetPing
8183
+ onFleetPing: opts.onFleetPing,
8184
+ onTechStackEvent: opts.onTechStackEvent,
8185
+ getLlm: opts.getLlm,
8186
+ projectRoot: opts.projectRoot
7144
8187
  });
7145
- const registryBaseDir = path13.dirname(opts.globalConfigPath);
8188
+ const registryBaseDir = path15.dirname(opts.globalConfigPath);
7146
8189
  httpServer.listen(opts.httpPort, opts.wsHost, () => {
7147
8190
  const openUrl = buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, token: opts.wsToken, publicUrl: opts.publicUrl });
7148
8191
  console.log(`[WebUI] HTTP server running on ${openUrl}`);
7149
8192
  if (opts.openBrowser) openBrowser(openUrl);
7150
8193
  void registerInstance(
7151
- { pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path13.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
8194
+ { pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path15.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
7152
8195
  registryBaseDir
7153
8196
  ).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
7154
8197
  });
@@ -7164,7 +8207,7 @@ function registerShutdown(deps2) {
7164
8207
  }
7165
8208
 
7166
8209
  // src/server/pre-context-services.ts
7167
- import * as path16 from "node:path";
8210
+ import * as path18 from "node:path";
7168
8211
  import { createRequire as createRequire2 } from "node:module";
7169
8212
  import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
7170
8213
  import {
@@ -7310,6 +8353,7 @@ function resolveSetupProvider(opts) {
7310
8353
  }
7311
8354
 
7312
8355
  // src/server/context-meta.ts
8356
+ import { FallbackProfileManager } from "@wrongstack/core";
7313
8357
  function seedContextMeta(config, context) {
7314
8358
  const meta = context.meta;
7315
8359
  const autonomyCfg = config.autonomy ?? {};
@@ -7363,6 +8407,7 @@ function seedContextMeta(config, context) {
7363
8407
  meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
7364
8408
  meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
7365
8409
  meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
8410
+ meta["showModelReasoning"] = autonomyCfg["showModelReasoning"] !== false;
7366
8411
  meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
7367
8412
  meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
7368
8413
  {
@@ -7383,11 +8428,42 @@ function seedContextMeta(config, context) {
7383
8428
  meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
7384
8429
  const tgMs = tgExt?.["longToolThresholdMs"];
7385
8430
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
8431
+ const chimeraExt = config.extensions?.["wstack-chimera"];
8432
+ meta["chimeraEnabled"] = chimeraExt?.["enabled"] !== false;
8433
+ meta["chimeraProvider"] = chimeraExt?.["provider"] ?? "";
8434
+ meta["chimeraModel"] = chimeraExt?.["model"] ?? "";
8435
+ meta["chimeraMaxFiles"] = typeof chimeraExt?.["maxFiles"] === "number" && chimeraExt["maxFiles"] >= 1 ? chimeraExt["maxFiles"] : 15;
8436
+ const autoFix = chimeraExt?.["autoFix"];
8437
+ meta["chimeraAutoFix"] = autoFix === "off" || autoFix === "ask" || autoFix === "auto" ? autoFix : "off";
8438
+ const autoReviewExt = config.extensions?.["wstack-auto-review"];
8439
+ meta["autoReviewEnabled"] = autoReviewExt?.["enabled"] === true;
8440
+ meta["autoReviewProvider"] = autoReviewExt?.["provider"] ?? "";
8441
+ meta["autoReviewModel"] = autoReviewExt?.["model"] ?? "";
8442
+ meta["autoReviewFallbackProfile"] = autoReviewExt?.["fallbackProfile"] ?? "";
8443
+ meta["autoReviewFallbackModels"] = Array.isArray(autoReviewExt?.["fallbackModels"]) ? autoReviewExt?.["fallbackModels"] : [];
8444
+ meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] : 5e3;
8445
+ meta["autoReviewMaxFilesPerBatch"] = typeof autoReviewExt?.["maxFilesPerBatch"] === "number" && autoReviewExt["maxFilesPerBatch"] >= 1 ? autoReviewExt["maxFilesPerBatch"] : 15;
8446
+ meta["autoReviewMaxConcurrentReviews"] = typeof autoReviewExt?.["maxConcurrentReviews"] === "number" && autoReviewExt["maxConcurrentReviews"] >= 1 ? autoReviewExt["maxConcurrentReviews"] : 2;
8447
+ const cascade = autoReviewExt?.["cascadeOn"];
8448
+ meta["autoReviewCascadeOn"] = cascade === "critical" || cascade === "high" ? cascade : "off";
8449
+ {
8450
+ let resolvedChain = [];
8451
+ try {
8452
+ const mgr = new FallbackProfileManager(config);
8453
+ const named = autoReviewExt?.["fallbackProfile"];
8454
+ resolvedChain = typeof named === "string" && named.length > 0 ? mgr.resolve(named) : mgr.resolveEffective({ fallbackAuto: true });
8455
+ } catch {
8456
+ resolvedChain = [];
8457
+ }
8458
+ meta["autoReviewFallbackModels"] = resolvedChain.map(
8459
+ (e) => `${e.providerId}/${e.model}`
8460
+ );
8461
+ }
7386
8462
  }
7387
8463
 
7388
8464
  // src/server/model-auto-discovery.ts
7389
- import * as fs11 from "node:fs/promises";
7390
- import * as path14 from "node:path";
8465
+ import * as fs12 from "node:fs/promises";
8466
+ import * as path16 from "node:path";
7391
8467
  import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
7392
8468
  function isOverlayRegistry(value) {
7393
8469
  return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
@@ -7413,7 +8489,7 @@ function eligibleProviders(config) {
7413
8489
  }
7414
8490
  async function readCache(file) {
7415
8491
  try {
7416
- return JSON.parse(await fs11.readFile(file, "utf8"));
8492
+ return JSON.parse(await fs12.readFile(file, "utf8"));
7417
8493
  } catch {
7418
8494
  return {};
7419
8495
  }
@@ -7423,7 +8499,7 @@ async function discoverAndMergeWebuiProviders(opts) {
7423
8499
  if (!isOverlayRegistry(registry)) return;
7424
8500
  const targets = eligibleProviders(opts.config);
7425
8501
  if (targets.length === 0) return;
7426
- const cacheFile = path14.join(opts.cacheDir, "discovered-models-cache.json");
8502
+ const cacheFile = path16.join(opts.cacheDir, "discovered-models-cache.json");
7427
8503
  const cache = await readCache(cacheFile);
7428
8504
  let cacheDirty = false;
7429
8505
  await Promise.all(
@@ -7460,8 +8536,8 @@ async function discoverAndMergeWebuiProviders(opts) {
7460
8536
  );
7461
8537
  if (cacheDirty) {
7462
8538
  try {
7463
- await fs11.mkdir(path14.dirname(cacheFile), { recursive: true });
7464
- await fs11.writeFile(cacheFile, JSON.stringify(cache), "utf8");
8539
+ await fs12.mkdir(path16.dirname(cacheFile), { recursive: true });
8540
+ await fs12.writeFile(cacheFile, JSON.stringify(cache), "utf8");
7465
8541
  } catch {
7466
8542
  opts.logger?.debug?.("provider auto-discovery cache write failed");
7467
8543
  }
@@ -7469,7 +8545,7 @@ async function discoverAndMergeWebuiProviders(opts) {
7469
8545
  }
7470
8546
 
7471
8547
  // src/server/standalone-session-identity.ts
7472
- import * as path15 from "node:path";
8548
+ import * as path17 from "node:path";
7473
8549
  import {
7474
8550
  AgentStatusTracker,
7475
8551
  FleetNotifier,
@@ -7500,7 +8576,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7500
8576
  sessionId,
7501
8577
  projectSlug: paths.projectSlug,
7502
8578
  projectRoot: paths.projectRoot,
7503
- projectName: path15.basename(paths.projectRoot),
8579
+ projectName: path17.basename(paths.projectRoot),
7504
8580
  workingDir: opts.workingDir,
7505
8581
  clientType: "webui",
7506
8582
  pid: process.pid,
@@ -7509,7 +8585,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7509
8585
  });
7510
8586
  fleetNotifier.notify();
7511
8587
  } catch (err) {
7512
- logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
8588
+ logger.debug?.(`WebUI session registry update failed: ${errorMessage3(err)}`);
7513
8589
  }
7514
8590
  };
7515
8591
  await register(activeSessionId);
@@ -7535,7 +8611,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7535
8611
  const publisher = core.createHqPublisherFromEnv({
7536
8612
  clientKind: "webui",
7537
8613
  projectRoot: paths.projectRoot,
7538
- projectName: path15.basename(paths.projectRoot),
8614
+ projectName: path17.basename(paths.projectRoot),
7539
8615
  appConfig: opts.config,
7540
8616
  socketFactory: (url) => new WebSocket2(url)
7541
8617
  });
@@ -7557,7 +8633,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7557
8633
  events,
7558
8634
  sessionId,
7559
8635
  projectRoot: paths.projectRoot,
7560
- projectName: path15.basename(paths.projectRoot),
8636
+ projectName: path17.basename(paths.projectRoot),
7561
8637
  globalRoot: paths.globalRoot,
7562
8638
  initialAgents: statusTracker.getAgents(),
7563
8639
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7594,7 +8670,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7594
8670
  restartHqBridges(activeSessionId);
7595
8671
  }
7596
8672
  } catch (err) {
7597
- logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
8673
+ logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage3(err)}`);
7598
8674
  }
7599
8675
  }
7600
8676
  const repointRecovery = async (sessionId) => {
@@ -7617,7 +8693,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7617
8693
  try {
7618
8694
  restartHqBridges(sessionId);
7619
8695
  } catch (err) {
7620
- logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
8696
+ logger.debug?.(`WebUI HQ session swap failed: ${errorMessage3(err)}`);
7621
8697
  }
7622
8698
  });
7623
8699
  await transition;
@@ -7642,7 +8718,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7642
8718
  };
7643
8719
  return { statusTracker, activate, stop };
7644
8720
  }
7645
- function errorMessage2(err) {
8721
+ function errorMessage3(err) {
7646
8722
  return err instanceof Error ? err.message : String(err);
7647
8723
  }
7648
8724
 
@@ -7668,7 +8744,7 @@ async function createPreContextServices(input) {
7668
8744
  await discoverAndMergeWebuiProviders({
7669
8745
  config,
7670
8746
  registry: modelsRegistry,
7671
- cacheDir: path16.dirname(wpaths.modelsCache),
8747
+ cacheDir: path18.dirname(wpaths.modelsCache),
7672
8748
  logger
7673
8749
  });
7674
8750
  } catch (err) {
@@ -7713,7 +8789,7 @@ async function createPreContextServices(input) {
7713
8789
  configureChildEnvGitIdentity(config.git?.identity ?? null);
7714
8790
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
7715
8791
  const mcpTokenStore = new MCPVaultTokenStore(
7716
- path16.join(wpaths.projectDir, "mcp-auth.json"),
8792
+ path18.join(wpaths.projectDir, "mcp-auth.json"),
7717
8793
  vault
7718
8794
  );
7719
8795
  const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
@@ -7808,7 +8884,7 @@ async function createPreContextServices(input) {
7808
8884
  const modelCapabilitiesRef = { current: modelCapabilities };
7809
8885
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
7810
8886
  const skillInstaller = config.features.skills ? new SkillInstaller({
7811
- manifestPath: path16.join(wpaths.globalRoot, "installed-skills.json"),
8887
+ manifestPath: path18.join(wpaths.globalRoot, "installed-skills.json"),
7812
8888
  projectSkillsDir: wpaths.inProjectSkills,
7813
8889
  globalSkillsDir: wpaths.globalSkills,
7814
8890
  projectHash: wpaths.projectHash,
@@ -7818,7 +8894,7 @@ async function createPreContextServices(input) {
7818
8894
  const bundledPromptsDir = promptsEnabled ? (() => {
7819
8895
  try {
7820
8896
  const req = createRequire2(import.meta.url);
7821
- return path16.join(path16.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
8897
+ return path18.join(path18.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7822
8898
  } catch {
7823
8899
  return void 0;
7824
8900
  }
@@ -7919,7 +8995,7 @@ function isSuperMemoryService(memoryStore) {
7919
8995
  }
7920
8996
 
7921
8997
  // src/server/start-webui.ts
7922
- import * as path22 from "node:path";
8998
+ import * as path23 from "node:path";
7923
8999
  import {
7924
9000
  createDefaultPipelines,
7925
9001
  createSessionEventBridge,
@@ -7949,7 +9025,7 @@ function patchConfig(config, updates) {
7949
9025
  }
7950
9026
 
7951
9027
  // src/server/backend-services.ts
7952
- import { join as join13 } from "node:path";
9028
+ import { join as join14 } from "node:path";
7953
9029
  import {
7954
9030
  Agent,
7955
9031
  AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
@@ -7975,7 +9051,7 @@ import {
7975
9051
  } from "@wrongstack/core";
7976
9052
 
7977
9053
  // src/server/collaboration-ws-handler.ts
7978
- import { randomUUID } from "node:crypto";
9054
+ import { randomUUID as randomUUID2 } from "node:crypto";
7979
9055
  import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
7980
9056
  var REPLAY_LIMIT = 50;
7981
9057
  var PAUSE_TIMEOUT_MS = 6e4;
@@ -8107,7 +9183,7 @@ var CollaborationWebSocketHandler = class {
8107
9183
  return;
8108
9184
  }
8109
9185
  const participant = {
8110
- participantId: randomUUID(),
9186
+ participantId: randomUUID2(),
8111
9187
  ws,
8112
9188
  sessionId,
8113
9189
  role,
@@ -8727,8 +9803,8 @@ var CollaborationWebSocketHandler = class {
8727
9803
  };
8728
9804
 
8729
9805
  // src/server/codebase-indexing.ts
8730
- import * as fs12 from "node:fs";
8731
- import * as path17 from "node:path";
9806
+ import * as fs13 from "node:fs";
9807
+ import * as path19 from "node:path";
8732
9808
  import {
8733
9809
  cancelPendingReindexes,
8734
9810
  enqueueReindex,
@@ -8748,17 +9824,15 @@ var IGNORE_DIRS = /* @__PURE__ */ new Set([
8748
9824
  ".nyc_output"
8749
9825
  ]);
8750
9826
  function setupWebUICodebaseIndexing(deps2) {
8751
- const indexing = deps2.config.indexing;
8752
- if (!indexing) return noopIndexing();
8753
- const idx = indexing;
9827
+ const idx = deps2.config.indexing;
8754
9828
  const indexDir = typeof deps2.context.meta["codebaseIndexDir"] === "string" ? deps2.context.meta["codebaseIndexDir"] : void 0;
8755
- const debounceMs = idx.debounceMs ?? 400;
9829
+ const debounceMs = idx?.debounceMs ?? 400;
8756
9830
  const onError = (err) => {
8757
9831
  deps2.logger.debug(
8758
9832
  `webui codebase auto-index failed: ${err instanceof Error ? err.message : String(err)}`
8759
9833
  );
8760
9834
  };
8761
- if (idx.onSessionStart) {
9835
+ if (idx?.onSessionStart) {
8762
9836
  void runStartupIndex({
8763
9837
  projectRoot: deps2.projectRoot,
8764
9838
  indexDir,
@@ -8775,14 +9849,27 @@ function setupWebUICodebaseIndexing(deps2) {
8775
9849
  });
8776
9850
  }
8777
9851
  let watcher;
8778
- if (idx.watchExternal) {
9852
+ const lastWatcherEvent = /* @__PURE__ */ new Map();
9853
+ if (idx?.watchExternal || deps2.events) {
8779
9854
  try {
8780
- watcher = fs12.watch(deps2.projectRoot, { recursive: true }, (_event, filename) => {
9855
+ watcher = fs13.watch(deps2.projectRoot, { recursive: true }, (eventType, filename) => {
8781
9856
  if (!filename) return;
8782
9857
  const rel = filename.toString();
8783
9858
  if (isIgnored(rel)) return;
8784
- const abs = path17.resolve(deps2.projectRoot, rel);
8785
- enqueueFile(abs);
9859
+ const abs = path19.resolve(deps2.projectRoot, rel);
9860
+ if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
9861
+ const now = Date.now();
9862
+ if (now - (lastWatcherEvent.get(abs) ?? 0) > 75) {
9863
+ lastWatcherEvent.set(abs, now);
9864
+ deps2.events?.emit("file.activity", {
9865
+ filePath: path19.normalize(abs),
9866
+ operation: eventType === "rename" && !fs13.existsSync(abs) ? "delete" : "edit",
9867
+ phase: "changed",
9868
+ source: "watcher",
9869
+ at: now
9870
+ });
9871
+ }
9872
+ if (idx?.watchExternal) enqueueFile(abs);
8786
9873
  });
8787
9874
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
8788
9875
  watcher.unref?.();
@@ -8793,8 +9880,8 @@ function setupWebUICodebaseIndexing(deps2) {
8793
9880
  }
8794
9881
  }
8795
9882
  function enqueueFile(filePath) {
8796
- if (!idx.onEdit && !idx.watchExternal) return;
8797
- const abs = path17.isAbsolute(filePath) ? path17.normalize(filePath) : path17.resolve(deps2.projectRoot, filePath);
9883
+ if (!idx || !idx.onEdit && !idx.watchExternal) return;
9884
+ const abs = path19.isAbsolute(filePath) ? path19.normalize(filePath) : path19.resolve(deps2.projectRoot, filePath);
8798
9885
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
8799
9886
  enqueueReindex({
8800
9887
  projectRoot: deps2.projectRoot,
@@ -8807,23 +9894,28 @@ function setupWebUICodebaseIndexing(deps2) {
8807
9894
  }
8808
9895
  return {
8809
9896
  onFileWritten(filePath) {
8810
- if (idx.onEdit) enqueueFile(filePath);
9897
+ const abs = path19.isAbsolute(filePath) ? path19.normalize(filePath) : path19.resolve(deps2.projectRoot, filePath);
9898
+ deps2.events?.emit("file.activity", {
9899
+ filePath: abs,
9900
+ operation: "write",
9901
+ phase: "completed",
9902
+ source: "editor",
9903
+ at: Date.now(),
9904
+ sessionId: deps2.context.session?.id,
9905
+ agentId: "webui-editor",
9906
+ agentName: "WebUI Editor"
9907
+ });
9908
+ if (idx?.onEdit) enqueueFile(abs);
8811
9909
  },
8812
9910
  dispose() {
8813
9911
  try {
8814
9912
  watcher?.close();
8815
9913
  } catch {
8816
9914
  }
8817
- cancelPendingReindexes();
8818
- shutdownCodebaseIndexHost();
8819
- }
8820
- };
8821
- }
8822
- function noopIndexing() {
8823
- return {
8824
- onFileWritten() {
8825
- },
8826
- dispose() {
9915
+ if (idx) {
9916
+ cancelPendingReindexes();
9917
+ shutdownCodebaseIndexHost();
9918
+ }
8827
9919
  }
8828
9920
  };
8829
9921
  }
@@ -8831,16 +9923,16 @@ function isIgnored(rel) {
8831
9923
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
8832
9924
  }
8833
9925
  function isInside2(root, target) {
8834
- const normalizedRoot = path17.resolve(root);
8835
- const normalizedTarget = path17.resolve(target);
8836
- return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path17.sep);
9926
+ const normalizedRoot = path19.resolve(root);
9927
+ const normalizedTarget = path19.resolve(target);
9928
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path19.sep);
8837
9929
  }
8838
9930
 
8839
9931
  // src/server/discover-mailbox-bridge.ts
8840
9932
  import { spawn as spawn3 } from "node:child_process";
8841
9933
  import { createRequire as createRequire3 } from "node:module";
8842
- import { existsSync } from "node:fs";
8843
- import { dirname as dirname8, join as join12 } from "node:path";
9934
+ import { existsSync as existsSync2 } from "node:fs";
9935
+ import { dirname as dirname9, join as join13 } from "node:path";
8844
9936
  import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
8845
9937
  import { readLiveLock } from "@wrongstack/core/coordination";
8846
9938
  var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
@@ -8968,16 +10060,16 @@ function mailboxServeInvocation(projectRoot) {
8968
10060
  function findWorkspaceCliEntry(projectRoot) {
8969
10061
  let dir = projectRoot;
8970
10062
  for (let i = 0; i < 6; i++) {
8971
- const candidate = join12(dir, "packages", "cli", "dist", "index.js");
8972
- if (existsSync(candidate)) return candidate;
8973
- const parent = dirname8(dir);
10063
+ const candidate = join13(dir, "packages", "cli", "dist", "index.js");
10064
+ if (existsSync2(candidate)) return candidate;
10065
+ const parent = dirname9(dir);
8974
10066
  if (parent === dir) return null;
8975
10067
  dir = parent;
8976
10068
  }
8977
10069
  return null;
8978
10070
  }
8979
10071
  function sleep(ms) {
8980
- return new Promise((resolve10) => setTimeout(resolve10, ms));
10072
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
8981
10073
  }
8982
10074
 
8983
10075
  // src/server/terminal-ws-handler.ts
@@ -9232,7 +10324,8 @@ async function createAgentServices(input) {
9232
10324
  config,
9233
10325
  context,
9234
10326
  projectRoot,
9235
- logger
10327
+ logger,
10328
+ events
9236
10329
  });
9237
10330
  const compactor = createStrategyCompactor({
9238
10331
  strategy: config.context?.strategy,
@@ -9369,7 +10462,7 @@ async function createAgentServices(input) {
9369
10462
  const brainCfg = resolveBrainConfigDefaults(config.brain, {
9370
10463
  fallbackModels: config.fallbackModels
9371
10464
  });
9372
- const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
10465
+ const brainLedgerPath = join14(wpaths.projectDir, "brain-ledger.jsonl");
9373
10466
  let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
9374
10467
  let brainLedger;
9375
10468
  const startBrainLedger = () => {
@@ -9479,7 +10572,7 @@ async function createAgentServices(input) {
9479
10572
  });
9480
10573
  brainMonitor.start();
9481
10574
  console.log("[WebUI] Brain initialized (tiered policy \u2192 LLM, monitor active)");
9482
- const autoPhaseHandler = new AutoPhaseWebSocketHandler(
10575
+ const goalHandler = new GoalWebSocketHandler(
9483
10576
  agent,
9484
10577
  context,
9485
10578
  logger,
@@ -9551,7 +10644,7 @@ async function createAgentServices(input) {
9551
10644
  return brainLedger;
9552
10645
  },
9553
10646
  codebaseIndexing,
9554
- autoPhaseHandler,
10647
+ goalHandler,
9555
10648
  specsHandler,
9556
10649
  sddBoardHandler,
9557
10650
  sddWizardHandler,
@@ -9624,6 +10717,9 @@ function createConnectionHandler(opts) {
9624
10717
  }
9625
10718
  void opts.sessionStartPayload().then(async (payload) => {
9626
10719
  const enriched = { ...payload };
10720
+ if (typeof opts.context.lastRequestTokens === "number" && opts.context.lastRequestTokens > 0) {
10721
+ enriched.lastInputTokens = opts.context.lastRequestTokens;
10722
+ }
9627
10723
  try {
9628
10724
  const replay = await opts.loadReplay?.();
9629
10725
  const live = replay?.messages ?? opts.context.messages ?? [];
@@ -9653,7 +10749,7 @@ function createConnectionHandler(opts) {
9653
10749
  })
9654
10750
  );
9655
10751
  });
9656
- opts.autoPhaseHandler.addClient(ws);
10752
+ opts.goalHandler.addClient(ws);
9657
10753
  opts.specsHandler.addClient(ws);
9658
10754
  opts.sddBoardHandler.addClient(ws);
9659
10755
  opts.sddWizardHandler.addClient(ws);
@@ -9671,8 +10767,21 @@ function createConnectionHandler(opts) {
9671
10767
  });
9672
10768
  return;
9673
10769
  }
10770
+ let rawObj;
10771
+ try {
10772
+ rawObj = JSON.parse(data.toString());
10773
+ } catch (err) {
10774
+ console.error(
10775
+ JSON.stringify({
10776
+ level: "error",
10777
+ event: "webui.ws_message_parse_failed",
10778
+ message: err instanceof Error ? err.message : String(err),
10779
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10780
+ })
10781
+ );
10782
+ return;
10783
+ }
9674
10784
  try {
9675
- const rawObj = JSON.parse(data.toString());
9676
10785
  if (typeof rawObj === "object" && rawObj !== null) {
9677
10786
  const obj = rawObj;
9678
10787
  if (Object.hasOwn(obj, "__proto__") || Object.hasOwn(obj, "constructor") || Object.hasOwn(obj, "prototype")) {
@@ -9690,7 +10799,7 @@ function createConnectionHandler(opts) {
9690
10799
  console.error(
9691
10800
  JSON.stringify({
9692
10801
  level: "error",
9693
- event: "webui.ws_message_parse_failed",
10802
+ event: "webui.ws_message_handler_failed",
9694
10803
  message: err instanceof Error ? err.message : String(err),
9695
10804
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
9696
10805
  })
@@ -9725,7 +10834,12 @@ function createConnectionHandler(opts) {
9725
10834
  }
9726
10835
 
9727
10836
  // src/server/message-dispatcher.ts
9728
- import path18 from "node:path";
10837
+ import path20 from "node:path";
10838
+ import {
10839
+ ChronicleQueryEngine,
10840
+ resolveWstackPaths as resolveWstackPaths2
10841
+ } from "@wrongstack/core";
10842
+ import * as os2 from "node:os";
9729
10843
  import {
9730
10844
  buildUserContentBlocks,
9731
10845
  IncomingImageError,
@@ -9738,9 +10852,9 @@ import {
9738
10852
  VisionUrlBlockedError
9739
10853
  } from "@wrongstack/runtime/vision";
9740
10854
 
9741
- // src/server/autophase-routes.ts
9742
- async function handleAutoPhaseRoute(_ws, msg, handlers) {
9743
- if (!msg.type.startsWith("autophase.")) return false;
10855
+ // src/server/goal-routes.ts
10856
+ async function handleGoalRoute(_ws, msg, handlers) {
10857
+ if (!msg.type.startsWith("goal.")) return false;
9744
10858
  await handlers.handleMessage(msg);
9745
10859
  return true;
9746
10860
  }
@@ -9776,9 +10890,9 @@ async function handleGoalGet(projectRoot, broadcast2) {
9776
10890
  const { readFile: readFile11 } = await import("node:fs/promises");
9777
10891
  const raw = await readFile11(goalPath, "utf8");
9778
10892
  const goal = JSON.parse(raw);
9779
- broadcast2({ type: "goal.updated", payload: goal });
10893
+ broadcast2({ type: "goal-state.updated", payload: goal });
9780
10894
  } catch {
9781
- broadcast2({ type: "goal.updated", payload: null });
10895
+ broadcast2({ type: "goal-state.updated", payload: null });
9782
10896
  }
9783
10897
  }
9784
10898
 
@@ -10028,17 +11142,19 @@ import {
10028
11142
  duplicateBoard,
10029
11143
  exportBoardToTaskGraph,
10030
11144
  generateBoardFromDescription,
10031
- getBoard,
11145
+ getBoard as getBoard2,
10032
11146
  getKanbanOrchestrationSnapshot,
10033
11147
  getKanbanQueueHealth,
10034
11148
  getTask,
10035
11149
  getTaskChain,
10036
11150
  listBoards,
10037
11151
  listReadyTasks,
11152
+ listTaskActivity,
10038
11153
  mergeTasks,
10039
11154
  moveTask,
10040
11155
  parseLinesIntoTasks,
10041
11156
  reconcileKanbanBoard,
11157
+ recordTaskActivity,
10042
11158
  recoverStaleTaskAssignments,
10043
11159
  releaseTaskClaim,
10044
11160
  removeBoard,
@@ -10047,7 +11163,9 @@ import {
10047
11163
  setTaskChain,
10048
11164
  splitTask,
10049
11165
  syncBoardFromTaskGraph,
11166
+ touchKanbanPresence,
10050
11167
  transferTaskToBoard,
11168
+ transitionTask,
10051
11169
  updateBoard,
10052
11170
  updateCheckOnTask,
10053
11171
  updateGoalMetricOnTask,
@@ -10074,6 +11192,29 @@ function fail(ws, type, message) {
10074
11192
  function has(payload, key) {
10075
11193
  return payload !== void 0 && Object.hasOwn(payload, key);
10076
11194
  }
11195
+ function activityContext(ctx, actor, note) {
11196
+ const sessionId = ctx.context?.session?.id;
11197
+ return {
11198
+ ...sessionId ? { sessionId } : {},
11199
+ ...actor ? { actor } : {},
11200
+ ...note?.trim() ? { note: note.trim() } : {}
11201
+ };
11202
+ }
11203
+ async function touchTaskPresence(ctx, boardId, taskId) {
11204
+ const context = ctx.context;
11205
+ const sessionId = context?.session?.id;
11206
+ if (!context || !sessionId) return null;
11207
+ try {
11208
+ return await touchKanbanPresence(ctx.projectRoot, boardId, {
11209
+ sessionId,
11210
+ agentId: context.agentId || "webui",
11211
+ agentName: context.agentName || context.agentId || "WebUI",
11212
+ taskId
11213
+ });
11214
+ } catch {
11215
+ return null;
11216
+ }
11217
+ }
10077
11218
  async function handleKanbanRoute(ws, msg, ctx) {
10078
11219
  if (!msg.type.startsWith("kanban.")) return false;
10079
11220
  const payload = msg.payload;
@@ -10089,7 +11230,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10089
11230
  fail(ws, type, "boardId required");
10090
11231
  return true;
10091
11232
  }
10092
- const board = await getBoard(ctx.projectRoot, boardId);
11233
+ const board = await getBoard2(ctx.projectRoot, boardId);
10093
11234
  board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
10094
11235
  return true;
10095
11236
  }
@@ -10109,7 +11250,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10109
11250
  fail(ws, type, "boardId required");
10110
11251
  return true;
10111
11252
  }
10112
- const board = await getBoard(ctx.projectRoot, boardId);
11253
+ const board = await getBoard2(ctx.projectRoot, boardId);
10113
11254
  if (!board) {
10114
11255
  fail(ws, type, `Board not found: ${boardId}`);
10115
11256
  return true;
@@ -10147,7 +11288,8 @@ async function handleKanbanRoute(ws, msg, ctx) {
10147
11288
  title,
10148
11289
  ...payload?.description ? { description: payload.description } : {},
10149
11290
  ...payload?.tags ? { tags: payload.tags } : {},
10150
- ...payload?.columns ? { columns: payload.columns } : {}
11291
+ ...payload?.columns ? { columns: payload.columns } : {},
11292
+ ...has(payload, "lifecycle") ? { lifecycle: payload?.lifecycle } : {}
10151
11293
  })
10152
11294
  );
10153
11295
  return true;
@@ -10163,6 +11305,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
10163
11305
  ...payload?.description ? { description: payload.description } : {},
10164
11306
  ...payload?.tags ? { tags: payload.tags } : {},
10165
11307
  ...payload?.columns ? { columns: payload.columns } : {},
11308
+ ...has(payload, "lifecycle") ? {
11309
+ lifecycle: payload?.lifecycle ?? null
11310
+ } : {},
10166
11311
  ...has(payload, "supervisor") ? {
10167
11312
  supervisor: payload?.supervisor ?? null
10168
11313
  } : {}
@@ -10191,7 +11336,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10191
11336
  fail(ws, type, "boardId required");
10192
11337
  return true;
10193
11338
  }
10194
- const board = await getBoard(ctx.projectRoot, boardId);
11339
+ const board = await getBoard2(ctx.projectRoot, boardId);
10195
11340
  const activeSessionId = ctx.context?.session?.id;
10196
11341
  if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
10197
11342
  fail(ws, type, "The active session Kanban board cannot be deleted.");
@@ -10223,7 +11368,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10223
11368
  )) {
10224
11369
  await addTask(ctx.projectRoot, board.id, taskInput);
10225
11370
  }
10226
- ok(ws, type, await getBoard(ctx.projectRoot, board.id) ?? board);
11371
+ ok(ws, type, await getBoard2(ctx.projectRoot, board.id) ?? board);
10227
11372
  return true;
10228
11373
  }
10229
11374
  case "kanban.task.ready": {
@@ -10313,14 +11458,20 @@ async function handleKanbanRoute(ws, msg, ctx) {
10313
11458
  fail(ws, type, "boardId and title required");
10314
11459
  return true;
10315
11460
  }
10316
- const result = await addTask(ctx.projectRoot, boardId, {
10317
- title,
10318
- columnId: payload?.columnId ?? "backlog",
10319
- ...payload?.description ? { description: payload.description } : {},
10320
- ...payload?.priority ? { priority: payload.priority } : {},
10321
- ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
10322
- ...payload?.labels ? { labels: payload.labels } : {}
10323
- });
11461
+ const result = await addTask(
11462
+ ctx.projectRoot,
11463
+ boardId,
11464
+ {
11465
+ title,
11466
+ columnId: payload?.columnId ?? "backlog",
11467
+ ...payload?.description ? { description: payload.description } : {},
11468
+ ...payload?.dueDate ? { dueDate: payload.dueDate } : {},
11469
+ ...payload?.priority ? { priority: payload.priority } : {},
11470
+ ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
11471
+ ...payload?.labels ? { labels: payload.labels } : {}
11472
+ },
11473
+ activityContext(ctx, "webui", payload?.activityNote)
11474
+ );
10324
11475
  result ? ok(ws, type, result.task) : fail(ws, type, `Board not found: ${boardId}`);
10325
11476
  return true;
10326
11477
  }
@@ -10376,25 +11527,32 @@ async function handleKanbanRoute(ws, msg, ctx) {
10376
11527
  fail(ws, type, "boardId and taskId required");
10377
11528
  return true;
10378
11529
  }
10379
- const board = await updateTask(ctx.projectRoot, boardId, taskId, {
10380
- ...has(payload, "title") ? { title: payload?.title } : {},
10381
- ...has(payload, "description") ? { description: payload?.description ?? "" } : {},
10382
- ...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
10383
- ...has(payload, "priority") ? { priority: payload?.priority } : {},
10384
- ...has(payload, "type") ? { type: payload?.type } : {},
10385
- ...has(payload, "status") ? { status: payload?.status } : {},
10386
- ...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
10387
- ...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
10388
- ...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
10389
- ...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
10390
- ...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
10391
- ...has(payload, "retryPolicy") ? {
10392
- retryPolicy: payload?.retryPolicy ?? null
10393
- } : {},
10394
- ...has(payload, "costCeilingUsd") ? {
10395
- costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
10396
- } : {}
10397
- });
11530
+ const board = await updateTask(
11531
+ ctx.projectRoot,
11532
+ boardId,
11533
+ taskId,
11534
+ {
11535
+ ...has(payload, "title") ? { title: payload?.title } : {},
11536
+ ...has(payload, "description") ? { description: payload?.description ?? "" } : {},
11537
+ ...has(payload, "dueDate") ? { dueDate: payload?.dueDate ?? null } : {},
11538
+ ...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
11539
+ ...has(payload, "priority") ? { priority: payload?.priority } : {},
11540
+ ...has(payload, "type") ? { type: payload?.type } : {},
11541
+ ...has(payload, "status") ? { status: payload?.status } : {},
11542
+ ...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
11543
+ ...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
11544
+ ...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
11545
+ ...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
11546
+ ...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
11547
+ ...has(payload, "retryPolicy") ? {
11548
+ retryPolicy: payload?.retryPolicy ?? null
11549
+ } : {},
11550
+ ...has(payload, "costCeilingUsd") ? {
11551
+ costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
11552
+ } : {}
11553
+ },
11554
+ activityContext(ctx, "webui", payload?.activityNote)
11555
+ );
10398
11556
  if (!board) {
10399
11557
  fail(ws, type, "Board or task not found");
10400
11558
  return true;
@@ -10404,6 +11562,32 @@ async function handleKanbanRoute(ws, msg, ctx) {
10404
11562
  ok(ws, type, task);
10405
11563
  return true;
10406
11564
  }
11565
+ case "kanban.task.transition": {
11566
+ const boardId = payload?.boardId;
11567
+ const taskId = payload?.taskId;
11568
+ const to = payload?.to;
11569
+ const actor = payload?.actor;
11570
+ const comment = payload?.comment;
11571
+ if (!boardId || !taskId || !to || !actor || !comment) {
11572
+ fail(ws, type, "boardId, taskId, to, actor, and comment required");
11573
+ return true;
11574
+ }
11575
+ const result = await transitionTask(ctx.projectRoot, boardId, taskId, {
11576
+ to,
11577
+ actor,
11578
+ comment,
11579
+ ...payload?.action ? { action: payload.action } : {},
11580
+ ...payload?.attachment ? { attachment: payload.attachment } : {},
11581
+ ...payload?.patch ? { patch: payload.patch } : {}
11582
+ });
11583
+ if (!result) {
11584
+ fail(ws, type, "Board or task not found");
11585
+ return true;
11586
+ }
11587
+ await syncSessionSource(ctx, result.task);
11588
+ ok(ws, type, result);
11589
+ return true;
11590
+ }
10407
11591
  case "kanban.task.move": {
10408
11592
  const boardId = payload?.boardId;
10409
11593
  const taskId = payload?.taskId;
@@ -10417,7 +11601,8 @@ async function handleKanbanRoute(ws, msg, ctx) {
10417
11601
  boardId,
10418
11602
  taskId,
10419
11603
  columnId,
10420
- payload?.order
11604
+ payload?.order,
11605
+ activityContext(ctx, "webui", payload?.activityNote)
10421
11606
  );
10422
11607
  if (!board) {
10423
11608
  fail(ws, type, "Move failed");
@@ -10522,14 +11707,24 @@ async function handleKanbanRoute(ws, msg, ctx) {
10522
11707
  fail(ws, type, "boardId, taskId, and name required");
10523
11708
  return true;
10524
11709
  }
10525
- const board = await addGoalMetricToTask(ctx.projectRoot, boardId, taskId, {
10526
- name: name2,
10527
- ...payload?.status ? { status: payload.status } : {},
10528
- ...payload?.target !== void 0 ? { target: payload.target } : {},
10529
- ...payload?.current !== void 0 ? { current: payload.current } : {},
10530
- ...payload?.unit ? { unit: payload.unit } : {},
10531
- ...payload?.notes ? { notes: payload.notes } : {}
10532
- });
11710
+ const board = await addGoalMetricToTask(
11711
+ ctx.projectRoot,
11712
+ boardId,
11713
+ taskId,
11714
+ {
11715
+ name: name2,
11716
+ ...payload?.status ? { status: payload.status } : {},
11717
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
11718
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
11719
+ ...payload?.unit ? { unit: payload.unit } : {},
11720
+ ...payload?.notes ? { notes: payload.notes } : {}
11721
+ },
11722
+ activityContext(
11723
+ ctx,
11724
+ "webui",
11725
+ payload?.activityNote ?? `Goal metric added: ${name2}.`
11726
+ )
11727
+ );
10533
11728
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10534
11729
  return true;
10535
11730
  }
@@ -10541,14 +11736,25 @@ async function handleKanbanRoute(ws, msg, ctx) {
10541
11736
  fail(ws, type, "boardId, taskId, and metricId required");
10542
11737
  return true;
10543
11738
  }
10544
- const board = await updateGoalMetricOnTask(ctx.projectRoot, boardId, taskId, metricId, {
10545
- ...payload?.name ? { name: payload.name } : {},
10546
- ...payload?.status ? { status: payload.status } : {},
10547
- ...payload?.target !== void 0 ? { target: payload.target } : {},
10548
- ...payload?.current !== void 0 ? { current: payload.current } : {},
10549
- ...payload?.unit ? { unit: payload.unit } : {},
10550
- ...payload?.notes ? { notes: payload.notes } : {}
10551
- });
11739
+ const board = await updateGoalMetricOnTask(
11740
+ ctx.projectRoot,
11741
+ boardId,
11742
+ taskId,
11743
+ metricId,
11744
+ {
11745
+ ...payload?.name ? { name: payload.name } : {},
11746
+ ...payload?.status ? { status: payload.status } : {},
11747
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
11748
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
11749
+ ...payload?.unit ? { unit: payload.unit } : {},
11750
+ ...payload?.notes ? { notes: payload.notes } : {}
11751
+ },
11752
+ activityContext(
11753
+ ctx,
11754
+ "webui",
11755
+ payload?.activityNote ?? "Goal metric updated in WebUI."
11756
+ )
11757
+ );
10552
11758
  board ? ok(ws, type, board) : fail(ws, type, "Metric not found");
10553
11759
  return true;
10554
11760
  }
@@ -10559,23 +11765,29 @@ async function handleKanbanRoute(ws, msg, ctx) {
10559
11765
  fail(ws, type, "boardId and taskId required");
10560
11766
  return true;
10561
11767
  }
10562
- const board = await assignTask(ctx.projectRoot, boardId, taskId, {
10563
- ...payload?.agentId ? { agentId: payload.agentId } : {},
10564
- ...payload?.name ? { name: payload.name } : {},
10565
- ...payload?.role ? { role: payload.role } : {},
10566
- ...payload?.provider ? { provider: payload.provider } : {},
10567
- ...payload?.model ? { model: payload.model } : {},
10568
- ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
10569
- ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
10570
- ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
10571
- ...payload?.skills ? { skills: payload.skills } : {},
10572
- ...payload?.tools ? { tools: payload.tools } : {},
10573
- ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
10574
- ...payload?.assignee ? { assignee: payload.assignee } : {},
10575
- ...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
10576
- ...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
10577
- ...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
10578
- });
11768
+ const board = await assignTask(
11769
+ ctx.projectRoot,
11770
+ boardId,
11771
+ taskId,
11772
+ {
11773
+ ...payload?.agentId ? { agentId: payload.agentId } : {},
11774
+ ...payload?.name ? { name: payload.name } : {},
11775
+ ...payload?.role ? { role: payload.role } : {},
11776
+ ...payload?.provider ? { provider: payload.provider } : {},
11777
+ ...payload?.model ? { model: payload.model } : {},
11778
+ ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
11779
+ ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
11780
+ ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
11781
+ ...payload?.skills ? { skills: payload.skills } : {},
11782
+ ...payload?.tools ? { tools: payload.tools } : {},
11783
+ ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
11784
+ ...payload?.assignee ? { assignee: payload.assignee } : {},
11785
+ ...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
11786
+ ...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
11787
+ ...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
11788
+ },
11789
+ activityContext(ctx, void 0, payload?.activityNote)
11790
+ );
10579
11791
  board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
10580
11792
  return true;
10581
11793
  }
@@ -10587,11 +11799,21 @@ async function handleKanbanRoute(ws, msg, ctx) {
10587
11799
  fail(ws, type, "boardId, taskId, and description required");
10588
11800
  return true;
10589
11801
  }
10590
- const board = await addCheckToTask(ctx.projectRoot, boardId, taskId, {
10591
- description,
10592
- type: payload?.checkType ?? "manual",
10593
- status: payload?.status ?? "pending"
10594
- });
11802
+ const board = await addCheckToTask(
11803
+ ctx.projectRoot,
11804
+ boardId,
11805
+ taskId,
11806
+ {
11807
+ description,
11808
+ type: payload?.checkType ?? "manual",
11809
+ status: payload?.status ?? "pending"
11810
+ },
11811
+ activityContext(
11812
+ ctx,
11813
+ "webui",
11814
+ payload?.activityNote ?? `Acceptance check added: ${description}.`
11815
+ )
11816
+ );
10595
11817
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10596
11818
  return true;
10597
11819
  }
@@ -10603,9 +11825,20 @@ async function handleKanbanRoute(ws, msg, ctx) {
10603
11825
  fail(ws, type, "boardId, taskId, and checkId required");
10604
11826
  return true;
10605
11827
  }
10606
- const board = await updateCheckOnTask(ctx.projectRoot, boardId, taskId, checkId, {
10607
- ...has(payload, "status") ? { status: payload?.status } : {}
10608
- });
11828
+ const board = await updateCheckOnTask(
11829
+ ctx.projectRoot,
11830
+ boardId,
11831
+ taskId,
11832
+ checkId,
11833
+ {
11834
+ ...has(payload, "status") ? { status: payload?.status } : {}
11835
+ },
11836
+ activityContext(
11837
+ ctx,
11838
+ "webui",
11839
+ payload?.activityNote ?? `Acceptance check updated${payload?.status ? ` to ${String(payload.status)}` : ""}.`
11840
+ )
11841
+ );
10609
11842
  if (!board) fail(ws, type, "Check not found");
10610
11843
  else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
10611
11844
  return true;
@@ -10618,10 +11851,17 @@ async function handleKanbanRoute(ws, msg, ctx) {
10618
11851
  fail(ws, type, "boardId, taskId, and content required");
10619
11852
  return true;
10620
11853
  }
10621
- const board = await addNoteToTask(ctx.projectRoot, boardId, taskId, {
10622
- author: payload?.author ?? "webui",
10623
- content
10624
- });
11854
+ const author = payload?.author ?? "webui";
11855
+ const board = await addNoteToTask(
11856
+ ctx.projectRoot,
11857
+ boardId,
11858
+ taskId,
11859
+ {
11860
+ author,
11861
+ content
11862
+ },
11863
+ activityContext(ctx, author)
11864
+ );
10625
11865
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10626
11866
  return true;
10627
11867
  }
@@ -10664,7 +11904,62 @@ async function handleKanbanRoute(ws, msg, ctx) {
10664
11904
  return true;
10665
11905
  }
10666
11906
  const task = await getTask(ctx.projectRoot, boardId, taskId);
10667
- task ? ok(ws, type, task) : fail(ws, type, "Task not found");
11907
+ if (task) {
11908
+ await touchTaskPresence(ctx, boardId, task.id);
11909
+ ok(ws, type, task);
11910
+ } else {
11911
+ fail(ws, type, "Task not found");
11912
+ }
11913
+ return true;
11914
+ }
11915
+ case "kanban.task.activity": {
11916
+ const boardId = payload?.boardId;
11917
+ const taskId = payload?.taskId;
11918
+ if (!boardId || !taskId) {
11919
+ fail(ws, type, "boardId and taskId required");
11920
+ return true;
11921
+ }
11922
+ const presenceBoard = await touchTaskPresence(ctx, boardId, taskId);
11923
+ const events = await listTaskActivity(ctx.projectRoot, boardId, taskId, {
11924
+ ...typeof payload?.limit === "number" ? { limit: payload.limit } : {}
11925
+ });
11926
+ ok(ws, type, {
11927
+ boardId,
11928
+ taskId,
11929
+ events,
11930
+ presence: presenceBoard?.presence?.filter((entry) => entry.taskId === taskId) ?? []
11931
+ });
11932
+ return true;
11933
+ }
11934
+ case "kanban.task.activity.add": {
11935
+ const boardId = payload?.boardId;
11936
+ const taskId = payload?.taskId;
11937
+ const kind = payload?.kind;
11938
+ const summary = payload?.summary;
11939
+ const allowedKinds = ["decision", "attempt", "result", "blocker", "observation"];
11940
+ const allowedOutcomes = ["succeeded", "failed", "partial", "skipped", "unknown"];
11941
+ if (!boardId || !taskId || !summary?.trim() || !allowedKinds.includes(kind)) {
11942
+ fail(ws, type, "boardId, taskId, summary, and a valid activity kind required");
11943
+ return true;
11944
+ }
11945
+ const requestedOutcome = payload?.outcome;
11946
+ const outcome = allowedOutcomes.includes(requestedOutcome) ? requestedOutcome : "unknown";
11947
+ const board = await recordTaskActivity(
11948
+ ctx.projectRoot,
11949
+ boardId,
11950
+ taskId,
11951
+ {
11952
+ kind,
11953
+ summary: summary.trim(),
11954
+ outcome,
11955
+ ...typeof payload?.details === "string" && payload.details.trim() ? { details: payload.details.trim() } : {}
11956
+ },
11957
+ activityContext(
11958
+ ctx,
11959
+ payload?.actor ?? ctx.context?.agentId ?? "webui"
11960
+ )
11961
+ );
11962
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10668
11963
  return true;
10669
11964
  }
10670
11965
  case "kanban.column.add": {
@@ -11142,6 +12437,18 @@ async function handleSpecsRoute(_ws, msg, handlers) {
11142
12437
  }
11143
12438
 
11144
12439
  // src/server/message-dispatcher.ts
12440
+ var chronicleCache = /* @__PURE__ */ new Map();
12441
+ async function chronicleEngine(projectRoot) {
12442
+ const now = Date.now();
12443
+ const cached = chronicleCache.get(projectRoot);
12444
+ if (cached && now - cached.loadedAt < 1e3) return cached.engine;
12445
+ const paths = resolveWstackPaths2({ projectRoot, userHome: os2.homedir() });
12446
+ const engine = await ChronicleQueryEngine.fromDirectory(
12447
+ path20.join(paths.projectDir, "chronicle")
12448
+ );
12449
+ chronicleCache.set(projectRoot, { loadedAt: now, engine });
12450
+ return engine;
12451
+ }
11145
12452
  function createMessageDispatcher(opts) {
11146
12453
  const { state, deps: deps2, cb, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
11147
12454
  function makeWorklistContext() {
@@ -11153,7 +12460,8 @@ function createMessageDispatcher(opts) {
11153
12460
  state: deps2.context.state
11154
12461
  },
11155
12462
  send: (w, m) => send(w, m),
11156
- broadcast: (m) => broadcast(state.getClients(), m)
12463
+ broadcast: (m) => broadcast(state.getClients(), m),
12464
+ replaceTodos: (todos) => deps2.context.state.replaceTodos(todos)
11157
12465
  };
11158
12466
  }
11159
12467
  function makeSkillsContext() {
@@ -11162,7 +12470,7 @@ function createMessageDispatcher(opts) {
11162
12470
  skillLoader: deps2.skillLoader,
11163
12471
  skillInstaller: deps2.skillInstaller,
11164
12472
  projectRoot,
11165
- projectSkillsDir: path18.join(projectRoot, ".wrongstack", "skills"),
12473
+ projectSkillsDir: path20.join(projectRoot, ".wrongstack", "skills"),
11166
12474
  globalSkillsDir: deps2.wpaths.globalSkills
11167
12475
  };
11168
12476
  }
@@ -11200,7 +12508,7 @@ function createMessageDispatcher(opts) {
11200
12508
  if (await handleMailboxRoute(ws, msg, routes.mailboxRoutes)) return;
11201
12509
  if (await handleMcpRoute(ws, msg, routes.mcpRoutes)) return;
11202
12510
  if (await handleBrainRoute(ws, msg, routes.brainRoutes)) return;
11203
- if (await handleAutoPhaseRoute(ws, msg, routes.autoPhaseRoutes)) return;
12511
+ if (await handleGoalRoute(ws, msg, routes.goalRoutes)) return;
11204
12512
  if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
11205
12513
  if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
11206
12514
  if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
@@ -11404,6 +12712,14 @@ function createMessageDispatcher(opts) {
11404
12712
  return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
11405
12713
  case "memory.super.remember":
11406
12714
  return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
12715
+ case "memory.super.recover":
12716
+ return handleSuperMemoryRecover(ws, msg, deps2.memoryStore);
12717
+ case "memory.super.candidateResolve":
12718
+ return handleSuperMemoryCandidateResolve(ws, msg, deps2.memoryStore);
12719
+ case "memory.super.backfillRecoverable":
12720
+ return handleSuperMemoryBackfillRecoverable(ws, msg, deps2.memoryStore);
12721
+ case "memory.super.forFile":
12722
+ return handleSuperMemoryForFile(ws, msg, deps2.memoryStore);
11407
12723
  // ── MCP tripwires — handleMcpRoute claims these upstream. ──
11408
12724
  case "mcp.list":
11409
12725
  throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
@@ -11634,6 +12950,59 @@ function createMessageDispatcher(opts) {
11634
12950
  });
11635
12951
  break;
11636
12952
  }
12953
+ // ── Chronicle journal queries (parity with embedded webui-server) ──
12954
+ // Mirrors packages/cli/src/webui-server/message-router.ts:645-664.
12955
+ // The engine is cached for 1s to avoid re-reading the journal on every
12956
+ // query; the cache is module-scoped so it survives across messages on
12957
+ // the same connection.
12958
+ case "chronicle.query": {
12959
+ const payload = msg.payload ?? {};
12960
+ const engine = await chronicleEngine(state.getProjectRoot());
12961
+ send(ws, { type: "chronicle.query_result", payload: engine.query(payload.query ?? {}) });
12962
+ break;
12963
+ }
12964
+ case "chronicle.facet": {
12965
+ const payload = msg.payload ?? {};
12966
+ const allowed = /* @__PURE__ */ new Set([
12967
+ "eventType",
12968
+ "outcome",
12969
+ "projectId",
12970
+ "sessionId",
12971
+ "agentId",
12972
+ "taskId",
12973
+ "providerId",
12974
+ "modelId",
12975
+ "resourceKind",
12976
+ "resourcePath",
12977
+ "toolCallId"
12978
+ ]);
12979
+ if (!payload.field || !allowed.has(payload.field)) {
12980
+ send(ws, {
12981
+ type: "chronicle.error",
12982
+ payload: { message: "Invalid Chronicle facet field." }
12983
+ });
12984
+ break;
12985
+ }
12986
+ const engine = await chronicleEngine(state.getProjectRoot());
12987
+ send(ws, {
12988
+ type: "chronicle.facet_result",
12989
+ payload: {
12990
+ field: payload.field,
12991
+ values: engine.facet(payload.field, payload.query ?? {}, payload.limit),
12992
+ diagnostics: engine.diagnostics
12993
+ }
12994
+ });
12995
+ break;
12996
+ }
12997
+ case "chronicle.graph": {
12998
+ const payload = msg.payload ?? {};
12999
+ const engine = await chronicleEngine(state.getProjectRoot());
13000
+ send(ws, {
13001
+ type: "chronicle.graph_result",
13002
+ payload: engine.graph(payload.seed ?? {}, payload.hops, payload.maxNodes)
13003
+ });
13004
+ break;
13005
+ }
11637
13006
  case "process.list": {
11638
13007
  await handleProcessList(ws);
11639
13008
  break;
@@ -11651,7 +13020,7 @@ function createMessageDispatcher(opts) {
11651
13020
  process.kill(process.pid, "SIGINT");
11652
13021
  break;
11653
13022
  }
11654
- case "goal.get": {
13023
+ case "goal-state.get": {
11655
13024
  await handleGoalGet(state.getProjectRoot(), (m) => broadcast(state.getClients(), m));
11656
13025
  break;
11657
13026
  }
@@ -11685,9 +13054,9 @@ function createMessageDispatcher(opts) {
11685
13054
  }
11686
13055
 
11687
13056
  // src/server/pref-helpers.ts
11688
- import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
13057
+ import { atomicWrite as atomicWrite6, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
11689
13058
  import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
11690
- import * as fs13 from "node:fs/promises";
13059
+ import * as fs14 from "node:fs/promises";
11691
13060
  var PREF_KEYS = [
11692
13061
  "autonomy",
11693
13062
  "autonomyDelayMs",
@@ -11742,11 +13111,31 @@ var PREF_KEYS = [
11742
13111
  "thinkingWord",
11743
13112
  "statuslineMode",
11744
13113
  "animationStyle",
13114
+ "showModelReasoning",
11745
13115
  // Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
11746
13116
  "breakerEnabled",
11747
13117
  "breakerAutoKillResetMs",
11748
13118
  "fsAccess",
11749
- "debugStream"
13119
+ "debugStream",
13120
+ // Chimera (post-session) + auto-review (mid-session) settings.
13121
+ // Persisted to config.extensions['wstack-chimera'] / ['wstack-auto-review']
13122
+ // so the running plugins pick up changes after a session restart.
13123
+ "chimeraEnabled",
13124
+ "chimeraProvider",
13125
+ "chimeraModel",
13126
+ "chimeraMaxFiles",
13127
+ "chimeraAutoFix",
13128
+ "autoReviewEnabled",
13129
+ "autoReviewProvider",
13130
+ "autoReviewModel",
13131
+ "autoReviewFallbackProfile",
13132
+ "autoReviewFallbackModels",
13133
+ "autoReviewDebounceMs",
13134
+ "autoReviewMaxFilesPerBatch",
13135
+ "autoReviewMaxConcurrentReviews",
13136
+ "autoReviewCascadeOn",
13137
+ // Per-plugin enable/disable map (parity with the embedded server).
13138
+ "pluginsEnabled"
11750
13139
  ];
11751
13140
  function prefSnapshot(contextMeta) {
11752
13141
  const snapshot = {};
@@ -11760,7 +13149,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
11760
13149
  const write = async () => {
11761
13150
  let raw;
11762
13151
  try {
11763
- raw = await fs13.readFile(globalConfigPath, "utf8");
13152
+ raw = await fs14.readFile(globalConfigPath, "utf8");
11764
13153
  } catch {
11765
13154
  raw = "{}";
11766
13155
  }
@@ -11829,6 +13218,8 @@ async function persistPrefsToConfig(deps2, holder, payload) {
11829
13218
  setAutonomy("statuslineMode", payload["statuslineMode"]);
11830
13219
  if (typeof payload["animationStyle"] === "string")
11831
13220
  setAutonomy("animationStyle", payload["animationStyle"]);
13221
+ if (typeof payload["showModelReasoning"] === "boolean")
13222
+ setAutonomy("showModelReasoning", payload["showModelReasoning"]);
11832
13223
  if (autonomyTouched) decrypted.autonomy = autonomyCfg;
11833
13224
  if (typeof payload["nextPrediction"] === "boolean")
11834
13225
  decrypted.nextPrediction = payload["nextPrediction"];
@@ -11964,41 +13355,76 @@ async function persistPrefsToConfig(deps2, holder, payload) {
11964
13355
  }
11965
13356
  if (typeof payload["debugStream"] === "boolean")
11966
13357
  decrypted.debugStream = payload["debugStream"];
13358
+ if (typeof payload["pluginsEnabled"] === "object" && payload["pluginsEnabled"] !== null) {
13359
+ const ext = decrypted.extensions ?? {};
13360
+ for (const [pluginName, enabled] of Object.entries(
13361
+ payload["pluginsEnabled"]
13362
+ )) {
13363
+ if (FORBIDDEN_PROTO_KEYS2.has(pluginName)) continue;
13364
+ const pExt = ext[pluginName] ?? {};
13365
+ pExt["enabled"] = enabled;
13366
+ ext[pluginName] = pExt;
13367
+ }
13368
+ decrypted.extensions = ext;
13369
+ }
13370
+ const chimeraTouched = typeof payload["chimeraEnabled"] === "boolean" || typeof payload["chimeraProvider"] === "string" || typeof payload["chimeraModel"] === "string" || typeof payload["chimeraMaxFiles"] === "number" || typeof payload["chimeraAutoFix"] === "string";
13371
+ if (chimeraTouched) {
13372
+ const ext = decrypted.extensions ?? {};
13373
+ const chimera = ext["wstack-chimera"] ?? {};
13374
+ if (typeof payload["chimeraEnabled"] === "boolean")
13375
+ chimera["enabled"] = payload["chimeraEnabled"];
13376
+ if (typeof payload["chimeraProvider"] === "string")
13377
+ chimera["provider"] = payload["chimeraProvider"];
13378
+ if (typeof payload["chimeraModel"] === "string")
13379
+ chimera["model"] = payload["chimeraModel"];
13380
+ if (typeof payload["chimeraMaxFiles"] === "number" && payload["chimeraMaxFiles"] >= 1) {
13381
+ chimera["maxFiles"] = payload["chimeraMaxFiles"];
13382
+ }
13383
+ if (typeof payload["chimeraAutoFix"] === "string") {
13384
+ if (payload["chimeraAutoFix"] === "off" || payload["chimeraAutoFix"] === "ask" || payload["chimeraAutoFix"] === "auto") {
13385
+ chimera["autoFix"] = payload["chimeraAutoFix"];
13386
+ }
13387
+ }
13388
+ ext["wstack-chimera"] = chimera;
13389
+ decrypted.extensions = ext;
13390
+ }
13391
+ 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";
13392
+ if (autoReviewTouched) {
13393
+ const ext = decrypted.extensions ?? {};
13394
+ const ar = ext["wstack-auto-review"] ?? {};
13395
+ if (typeof payload["autoReviewEnabled"] === "boolean")
13396
+ ar["enabled"] = payload["autoReviewEnabled"];
13397
+ if (typeof payload["autoReviewProvider"] === "string")
13398
+ ar["provider"] = payload["autoReviewProvider"];
13399
+ if (typeof payload["autoReviewModel"] === "string")
13400
+ ar["model"] = payload["autoReviewModel"];
13401
+ if (typeof payload["autoReviewFallbackProfile"] === "string") {
13402
+ if (payload["autoReviewFallbackProfile"] === "") {
13403
+ delete ar["fallbackProfile"];
13404
+ } else {
13405
+ ar["fallbackProfile"] = payload["autoReviewFallbackProfile"];
13406
+ }
13407
+ }
13408
+ if (typeof payload["autoReviewDebounceMs"] === "number" && payload["autoReviewDebounceMs"] >= 0) {
13409
+ ar["debounceMs"] = payload["autoReviewDebounceMs"];
13410
+ }
13411
+ if (typeof payload["autoReviewMaxFilesPerBatch"] === "number" && payload["autoReviewMaxFilesPerBatch"] >= 1) {
13412
+ ar["maxFilesPerBatch"] = payload["autoReviewMaxFilesPerBatch"];
13413
+ }
13414
+ if (typeof payload["autoReviewMaxConcurrentReviews"] === "number" && payload["autoReviewMaxConcurrentReviews"] >= 1) {
13415
+ ar["maxConcurrentReviews"] = payload["autoReviewMaxConcurrentReviews"];
13416
+ }
13417
+ if (typeof payload["autoReviewCascadeOn"] === "string") {
13418
+ if (payload["autoReviewCascadeOn"] === "off" || payload["autoReviewCascadeOn"] === "critical" || payload["autoReviewCascadeOn"] === "high") {
13419
+ ar["cascadeOn"] = payload["autoReviewCascadeOn"];
13420
+ }
13421
+ }
13422
+ ext["wstack-auto-review"] = ar;
13423
+ decrypted.extensions = ext;
13424
+ }
11967
13425
  }, "prefs");
11968
13426
  }
11969
13427
 
11970
- // src/server/projects-manifest.ts
11971
- import * as fs14 from "node:fs/promises";
11972
- import * as path19 from "node:path";
11973
- import { projectSlug } from "@wrongstack/core";
11974
- function projectsJsonPath(globalConfigPath) {
11975
- const base = path19.dirname(globalConfigPath);
11976
- return path19.join(base, "projects.json");
11977
- }
11978
- async function loadManifest(globalConfigPath) {
11979
- try {
11980
- const raw = await fs14.readFile(projectsJsonPath(globalConfigPath), "utf8");
11981
- const parsed = JSON.parse(raw);
11982
- return { projects: parsed.projects ?? [] };
11983
- } catch {
11984
- return { projects: [] };
11985
- }
11986
- }
11987
- async function saveManifest(manifest, globalConfigPath) {
11988
- const file = projectsJsonPath(globalConfigPath);
11989
- await fs14.mkdir(path19.dirname(file), { recursive: true });
11990
- await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
11991
- }
11992
- function generateProjectSlug(rootPath) {
11993
- return projectSlug(rootPath);
11994
- }
11995
- async function ensureProjectDataDir(slug, globalConfigPath) {
11996
- const base = path19.dirname(globalConfigPath);
11997
- const dir = path19.join(base, "projects", slug);
11998
- await fs14.mkdir(dir, { recursive: true });
11999
- return dir;
12000
- }
12001
-
12002
13428
  // src/server/provider-handlers.ts
12003
13429
  import { DefaultSecretScrubber } from "@wrongstack/core";
12004
13430
  import {
@@ -12327,8 +13753,9 @@ function createProviderHandlers(deps2) {
12327
13753
  }
12328
13754
 
12329
13755
  // src/server/routes.ts
12330
- import path21 from "node:path";
13756
+ import path22 from "node:path";
12331
13757
  import {
13758
+ buildRefinerContextSections,
12332
13759
  enhanceUserPrompt,
12333
13760
  gatedEnhancerReasoning,
12334
13761
  nextEnhanceTimeout,
@@ -12444,7 +13871,7 @@ async function handleMailboxCompact(ws, deps2, opts) {
12444
13871
  // src/server/mode-handlers.ts
12445
13872
  import {
12446
13873
  DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
12447
- resolveWstackPaths as resolveWstackPaths2,
13874
+ resolveWstackPaths as resolveWstackPaths3,
12448
13875
  ToolValidationError as ToolValidationError5
12449
13876
  } from "@wrongstack/core";
12450
13877
  function createModeHandlers(ctx) {
@@ -12484,6 +13911,7 @@ function createModeHandlers(ctx) {
12484
13911
  }
12485
13912
  const { id } = parsed.value;
12486
13913
  try {
13914
+ const prev = await ctx.modeStore.getActiveMode();
12487
13915
  if (id === "default") {
12488
13916
  await ctx.modeStore.setActiveMode(null);
12489
13917
  } else {
@@ -12494,8 +13922,13 @@ function createModeHandlers(ctx) {
12494
13922
  await ctx.modeStore.setActiveMode(id);
12495
13923
  }
12496
13924
  ctx.setModeId(id);
13925
+ const fromMode = prev?.id ?? "default";
13926
+ if (ctx.context.session && fromMode !== id) {
13927
+ void ctx.context.session.append({ type: "mode_changed", ts: (/* @__PURE__ */ new Date()).toISOString(), from: fromMode, to: id }).catch(() => {
13928
+ });
13929
+ }
12497
13930
  const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
12498
- const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
13931
+ const paths = resolveWstackPaths3({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
12499
13932
  const freshBuilder = new DefaultSystemPromptBuilder2({
12500
13933
  memoryStore: ctx.memoryStore,
12501
13934
  // Single injection channel: Super Memory turn middleware, not a static section.
@@ -12530,7 +13963,7 @@ function createModeHandlers(ctx) {
12530
13963
  }
12531
13964
 
12532
13965
  // src/server/project-handlers.ts
12533
- import * as path20 from "node:path";
13966
+ import * as path21 from "node:path";
12534
13967
  function createProjectHandlers(ctx) {
12535
13968
  return {
12536
13969
  listProjects: async (ws) => {
@@ -12556,7 +13989,7 @@ function createProjectHandlers(ctx) {
12556
13989
  selectProject: async (ws, msg) => {
12557
13990
  const payload = msg.payload;
12558
13991
  const root = typeof payload?.root === "string" ? payload.root : "";
12559
- const name2 = typeof payload?.name === "string" ? payload.name : root ? path20.basename(root) : "";
13992
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path21.basename(root) : "";
12560
13993
  send(ws, {
12561
13994
  type: "projects.selected",
12562
13995
  payload: {
@@ -12593,6 +14026,7 @@ function createProjectHandlers(ctx) {
12593
14026
  // src/server/session-handlers.ts
12594
14027
  import {
12595
14028
  DEFAULT_CONTEXT_WINDOW_MODE_ID,
14029
+ loadTodosCheckpoint,
12596
14030
  repairToolUseAdjacency,
12597
14031
  resolveContextWindowPolicy as resolveContextWindowPolicy3
12598
14032
  } from "@wrongstack/core";
@@ -12630,13 +14064,14 @@ function createSessionHandlers(ctx) {
12630
14064
  }).catch(() => void 0);
12631
14065
  await writer.close().catch(() => void 0);
12632
14066
  };
12633
- const activateSession = async (next, messages, usage) => {
14067
+ const activateSession = async (next, messages, usage, todos = []) => {
12634
14068
  const current = ctx.getSession();
12635
14069
  if (current !== next) await finalizeSession(current);
12636
14070
  ctx.setSession(next);
12637
14071
  ctx.context.session = next;
12638
14072
  ctx.context.state.replaceMessages(messages);
12639
- ctx.context.state.replaceTodos([]);
14073
+ await ctx.context.flushConversationJournal?.();
14074
+ ctx.context.state.replaceTodos(todos);
12640
14075
  ctx.context.readFiles.clear();
12641
14076
  ctx.context.fileMtimes.clear();
12642
14077
  ctx.context.state.setMeta(
@@ -12920,7 +14355,15 @@ function createSessionHandlers(ctx) {
12920
14355
  return;
12921
14356
  }
12922
14357
  const resumed = await ctx.getSessionStore().resume(id);
12923
- await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
14358
+ const restoredTodos = await loadTodosCheckpoint(
14359
+ sessionScopedPath2(ctx.sessionsDir, resumed.writer.id, ".todos.json")
14360
+ ).catch(() => null) ?? [];
14361
+ await activateSession(
14362
+ resumed.writer,
14363
+ resumed.data.messages,
14364
+ resumed.data.usage,
14365
+ restoredTodos
14366
+ );
12924
14367
  broadcast(ctx.clients, {
12925
14368
  type: "session.start",
12926
14369
  payload: {
@@ -12930,6 +14373,10 @@ function createSessionHandlers(ctx) {
12930
14373
  replayUsage: resumed.data.usage
12931
14374
  }
12932
14375
  });
14376
+ broadcast(ctx.clients, {
14377
+ type: "todos.updated",
14378
+ payload: { sessionId: resumed.writer.id, todos: restoredTodos }
14379
+ });
12933
14380
  sendResult(ws, true, `Resumed session ${id}`);
12934
14381
  } catch (err) {
12935
14382
  sendResult(ws, false, errMessage(err));
@@ -12955,11 +14402,17 @@ function createSessionHandlers(ctx) {
12955
14402
  if (!ensureCurrentSession(ws, msg, "session.rewind")) return;
12956
14403
  const { checkpointIndex } = msg.payload;
12957
14404
  try {
12958
- const { DefaultSessionRewinder } = await import("@wrongstack/core");
14405
+ const { applyRewindToConversation, DefaultSessionRewinder } = await import("@wrongstack/core");
12959
14406
  const projectRoot = ctx.getProjectRoot();
12960
14407
  const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
12961
- await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
12962
- await ctx.context.session.truncateToCheckpoint(checkpointIndex);
14408
+ const reverted = await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
14409
+ await applyRewindToConversation({
14410
+ session: ctx.context.session,
14411
+ state: ctx.context.state,
14412
+ sessionsDir: ctx.sessionsDir,
14413
+ promptIndex: checkpointIndex,
14414
+ revertedFiles: reverted.revertedFiles
14415
+ });
12963
14416
  sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
12964
14417
  broadcast(ctx.clients, {
12965
14418
  type: "session.start",
@@ -13174,6 +14627,11 @@ function buildRoutes(state, deps2, cb) {
13174
14627
  const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
13175
14628
  try {
13176
14629
  const history = recentTextTurns(deps2.context.messages);
14630
+ const contextSections = await buildRefinerContextSections({
14631
+ text,
14632
+ memoryStore: deps2.memoryStore,
14633
+ context: deps2.context
14634
+ });
13177
14635
  const resolved = await resolveProviderModelMetadata(
13178
14636
  deps2.modelsRegistry,
13179
14637
  providerId,
@@ -13187,6 +14645,14 @@ function buildRoutes(state, deps2, cb) {
13187
14645
  model,
13188
14646
  text,
13189
14647
  history,
14648
+ contextSections,
14649
+ ...payload.previousRefined ? {
14650
+ previousRefinement: {
14651
+ refined: payload.previousRefined,
14652
+ english: payload.previousEnglish || payload.previousRefined
14653
+ }
14654
+ } : {},
14655
+ ...payload.retryFeedback ? { retryFeedback: payload.retryFeedback } : {},
13190
14656
  timeoutMs,
13191
14657
  ...reasoning ? { reasoning } : {},
13192
14658
  onError: (reason, kind) => {
@@ -13346,6 +14812,17 @@ function buildRoutes(state, deps2, cb) {
13346
14812
  cfg.modelMatrix = payload["modelMatrix"];
13347
14813
  }
13348
14814
  if (typeof payload["fallbackAuto"] === "boolean") cfg.fallbackAuto = payload["fallbackAuto"];
14815
+ const routingPatch = {};
14816
+ if (Array.isArray(payload["fallbackModels"])) routingPatch.fallbackModels = payload["fallbackModels"];
14817
+ if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"]))
14818
+ routingPatch.fallbackProfiles = payload["fallbackProfiles"];
14819
+ if (Array.isArray(payload["favoriteModels"])) routingPatch.favoriteModels = payload["favoriteModels"];
14820
+ if (typeof payload["favoriteModelsOnly"] === "boolean") routingPatch.favoriteModelsOnly = payload["favoriteModelsOnly"];
14821
+ if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"]))
14822
+ routingPatch.modelMatrix = payload["modelMatrix"];
14823
+ if (typeof payload["fallbackAuto"] === "boolean") routingPatch.fallbackAuto = payload["fallbackAuto"];
14824
+ if (Object.keys(routingPatch).length > 0)
14825
+ deps2.configStore.update(routingPatch);
13349
14826
  if (typeof payload["contextAutoCompact"] === "boolean") {
13350
14827
  if (payload["contextAutoCompact"] && deps2.autoCompactor) {
13351
14828
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
@@ -13414,7 +14891,7 @@ function buildRoutes(state, deps2, cb) {
13414
14891
  }
13415
14892
  return handleMailboxMessages(
13416
14893
  ws,
13417
- { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
14894
+ { projectRoot: state.getProjectRoot(), globalRoot: path22.dirname(deps2.globalConfigPath) },
13418
14895
  parsed.value
13419
14896
  );
13420
14897
  },
@@ -13426,13 +14903,13 @@ function buildRoutes(state, deps2, cb) {
13426
14903
  }
13427
14904
  return handleMailboxAgents(
13428
14905
  ws,
13429
- { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
14906
+ { projectRoot: state.getProjectRoot(), globalRoot: path22.dirname(deps2.globalConfigPath) },
13430
14907
  parsed.value
13431
14908
  );
13432
14909
  },
13433
14910
  clear: (ws) => handleMailboxClear(ws, {
13434
14911
  projectRoot: state.getProjectRoot(),
13435
- globalRoot: path21.dirname(deps2.globalConfigPath)
14912
+ globalRoot: path22.dirname(deps2.globalConfigPath)
13436
14913
  }),
13437
14914
  purge: (ws, msg) => {
13438
14915
  const parsed = validateMailboxPurgePayload(msg.payload);
@@ -13442,14 +14919,14 @@ function buildRoutes(state, deps2, cb) {
13442
14919
  }
13443
14920
  return handleMailboxPurge(
13444
14921
  ws,
13445
- { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
14922
+ { projectRoot: state.getProjectRoot(), globalRoot: path22.dirname(deps2.globalConfigPath) },
13446
14923
  parsed.value
13447
14924
  );
13448
14925
  },
13449
14926
  compact: (ws, msg) => {
13450
14927
  return handleMailboxCompact(
13451
14928
  ws,
13452
- { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
14929
+ { projectRoot: state.getProjectRoot(), globalRoot: path22.dirname(deps2.globalConfigPath) },
13453
14930
  msg.payload ?? {}
13454
14931
  );
13455
14932
  }
@@ -13558,8 +15035,8 @@ function buildRoutes(state, deps2, cb) {
13558
15035
  }
13559
15036
  }
13560
15037
  };
13561
- const autoPhaseRoutes = {
13562
- handleMessage: (msg) => deps2.autoPhaseHandler.handleMessage(msg)
15038
+ const goalRoutes = {
15039
+ handleMessage: (msg) => deps2.goalHandler.handleMessage(msg)
13563
15040
  };
13564
15041
  const specsRoutes = {
13565
15042
  handleMessage: (msg) => deps2.specsHandler.handleMessage(msg)
@@ -13580,7 +15057,7 @@ function buildRoutes(state, deps2, cb) {
13580
15057
  mailboxRoutes,
13581
15058
  mcpRoutes,
13582
15059
  brainRoutes,
13583
- autoPhaseRoutes,
15060
+ goalRoutes,
13584
15061
  specsRoutes,
13585
15062
  sddBoardRoutes,
13586
15063
  sddWizardRoutes
@@ -13701,7 +15178,7 @@ async function startWebUI(opts = {}) {
13701
15178
  brainLog,
13702
15179
  brainMonitor,
13703
15180
  codebaseIndexing,
13704
- autoPhaseHandler,
15181
+ goalHandler,
13705
15182
  specsHandler,
13706
15183
  sddBoardHandler,
13707
15184
  sddWizardHandler,
@@ -13762,21 +15239,21 @@ async function startWebUI(opts = {}) {
13762
15239
  wpaths
13763
15240
  }, watcherMetricsRef);
13764
15241
  async function touchProjectEntry(root, workDir) {
13765
- const resolved = path22.resolve(root);
15242
+ const resolved = path23.resolve(root);
13766
15243
  const manifest = await loadManifest(globalConfigPath);
13767
15244
  const now = (/* @__PURE__ */ new Date()).toISOString();
13768
- const existing = manifest.projects.find((p) => path22.resolve(p.root) === resolved);
15245
+ const existing = manifest.projects.find((p) => path23.resolve(p.root) === resolved);
13769
15246
  if (existing) {
13770
15247
  existing.lastSeen = now;
13771
- if (workDir) existing.lastWorkingDir = path22.resolve(workDir);
15248
+ if (workDir) existing.lastWorkingDir = path23.resolve(workDir);
13772
15249
  } else {
13773
15250
  manifest.projects.push({
13774
- name: path22.basename(resolved),
15251
+ name: path23.basename(resolved),
13775
15252
  root: resolved,
13776
15253
  slug: generateProjectSlug(resolved),
13777
15254
  createdAt: now,
13778
15255
  lastSeen: now,
13779
- lastWorkingDir: workDir ? path22.resolve(workDir) : void 0
15256
+ lastWorkingDir: workDir ? path23.resolve(workDir) : void 0
13780
15257
  });
13781
15258
  }
13782
15259
  await saveManifest(manifest, globalConfigPath);
@@ -13859,7 +15336,7 @@ async function startWebUI(opts = {}) {
13859
15336
  httpPort,
13860
15337
  wssPrimary,
13861
15338
  wssSecondary,
13862
- autoPhaseHandler,
15339
+ goalHandler,
13863
15340
  specsHandler,
13864
15341
  sddBoardHandler,
13865
15342
  sddWizardHandler,
@@ -13903,7 +15380,13 @@ async function startWebUI(opts = {}) {
13903
15380
  deps2.configStore.update({
13904
15381
  providers: snapshot.providers,
13905
15382
  ...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
13906
- ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
15383
+ ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {},
15384
+ ...snapshot.fallbackModels !== void 0 ? { fallbackModels: snapshot.fallbackModels } : {},
15385
+ ...snapshot.fallbackProfiles !== void 0 ? { fallbackProfiles: snapshot.fallbackProfiles } : {},
15386
+ ...snapshot.favoriteModels !== void 0 ? { favoriteModels: snapshot.favoriteModels } : {},
15387
+ ...snapshot.favoriteModelsOnly !== void 0 ? { favoriteModelsOnly: snapshot.favoriteModelsOnly } : {},
15388
+ ...snapshot.modelMatrix !== void 0 ? { modelMatrix: snapshot.modelMatrix } : {},
15389
+ ...snapshot.fallbackAuto !== void 0 ? { fallbackAuto: snapshot.fallbackAuto } : {}
13907
15390
  });
13908
15391
  broadcast(clients, {
13909
15392
  type: "providers.saved",
@@ -13964,7 +15447,7 @@ async function startWebUI(opts = {}) {
13964
15447
  },
13965
15448
  clients,
13966
15449
  pendingConfirms,
13967
- autoPhaseHandler,
15450
+ goalHandler,
13968
15451
  specsHandler,
13969
15452
  sddBoardHandler,
13970
15453
  sddWizardHandler,
@@ -13991,6 +15474,11 @@ async function startWebUI(opts = {}) {
13991
15474
  onFleetPing: () => {
13992
15475
  void eventArming.getFleetBroadcast()?.();
13993
15476
  },
15477
+ onTechStackEvent: (event) => broadcast(clients, event),
15478
+ // Read through `context` on every call rather than capturing: the running
15479
+ // loop swaps provider/model when the user switches (same live source the
15480
+ // completion handler reads).
15481
+ getLlm: () => context.provider && context.model ? { provider: context.provider, model: context.model } : void 0,
13994
15482
  distDir: opts.distDir
13995
15483
  });
13996
15484
  registerShutdown({
@@ -14020,7 +15508,7 @@ async function startWebUI(opts = {}) {
14020
15508
  archiveLowConfidenceAfterDays: config.superMemory?.hygiene?.archiveLowConfidenceAfterDays
14021
15509
  }).catch((err) => logger.warn(`super-memory session hygiene failed: ${toErrorMessage10(err)}`));
14022
15510
  }
14023
- await unregisterInstance(process.pid, path22.dirname(globalConfigPath));
15511
+ await unregisterInstance(process.pid, path23.dirname(globalConfigPath));
14024
15512
  }
14025
15513
  });
14026
15514
  }
@@ -14039,8 +15527,8 @@ function createConfigWriteLock() {
14039
15527
  acquire() {
14040
15528
  const prev = lock;
14041
15529
  let release = () => void 0;
14042
- lock = new Promise((resolve10) => {
14043
- release = resolve10;
15530
+ lock = new Promise((resolve12) => {
15531
+ release = resolve12;
14044
15532
  });
14045
15533
  return { prev, release };
14046
15534
  }
@@ -14114,8 +15602,8 @@ function createProviderStore(deps2) {
14114
15602
  };
14115
15603
  }
14116
15604
  export {
14117
- AutoPhaseWebSocketHandler,
14118
15605
  CollaborationWebSocketHandler,
15606
+ GoalWebSocketHandler,
14119
15607
  SKIP_DIRS,
14120
15608
  SURFACE_DEFAULT_PORTS,
14121
15609
  SddBoardWebSocketHandler,
@@ -14150,6 +15638,7 @@ export {
14150
15638
  errMessage,
14151
15639
  estimateContextBreakdown,
14152
15640
  estimateTokens,
15641
+ extractCodeMapFileTargets,
14153
15642
  extractToken,
14154
15643
  extractTokenFromCookie,
14155
15644
  findFreePort,
@@ -14161,7 +15650,6 @@ export {
14161
15650
  handleApiAnalyticsGet,
14162
15651
  handleApiAnalyticsPost,
14163
15652
  handleApiAnalyticsSummary,
14164
- handleAutoPhaseRoute,
14165
15653
  handleBrainRoute,
14166
15654
  handleCompletionRequest,
14167
15655
  handleDesignList,
@@ -14178,6 +15666,7 @@ export {
14178
15666
  handleGitDiff,
14179
15667
  handleGitInfo,
14180
15668
  handleGoalGet,
15669
+ handleGoalRoute,
14181
15670
  handleMailboxMessages,
14182
15671
  handleMailboxRoute,
14183
15672
  handleMcpAdd,
@@ -14224,9 +15713,13 @@ export {
14224
15713
  handleSkillsUninstall,
14225
15714
  handleSkillsUpdate,
14226
15715
  handleSpecsRoute,
15716
+ handleSuperMemoryBackfillRecoverable,
15717
+ handleSuperMemoryCandidateResolve,
14227
15718
  handleSuperMemoryDelete,
15719
+ handleSuperMemoryForFile,
14228
15720
  handleSuperMemoryGet,
14229
15721
  handleSuperMemoryList,
15722
+ handleSuperMemoryRecover,
14230
15723
  handleSuperMemoryRemember,
14231
15724
  handleSuperMemoryUpdate,
14232
15725
  handleWorklistMessage,
@@ -14248,6 +15741,7 @@ export {
14248
15741
  maskedKey,
14249
15742
  messagePreview,
14250
15743
  messageTokens,
15744
+ normalizeCodeMapFileTarget,
14251
15745
  normalizeKeys,
14252
15746
  openBrowser,
14253
15747
  patchConfig,