@wrongstack/webui-server 0.287.0 → 0.291.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/index.js +2270 -642
  2. package/dist/index.js.map +4 -4
  3. package/dist/server/backend-services.d.ts +4 -4
  4. package/dist/server/backend-services.d.ts.map +1 -1
  5. package/dist/server/codebase-indexing.d.ts +2 -1
  6. package/dist/server/codebase-indexing.d.ts.map +1 -1
  7. package/dist/server/codemap-handlers.d.ts +26 -0
  8. package/dist/server/codemap-handlers.d.ts.map +1 -0
  9. package/dist/server/codemap-telemetry.d.ts +15 -0
  10. package/dist/server/codemap-telemetry.d.ts.map +1 -0
  11. package/dist/server/collaboration-ws-handler.d.ts +1 -1
  12. package/dist/server/connection-handler.d.ts +2 -2
  13. package/dist/server/connection-handler.d.ts.map +1 -1
  14. package/dist/server/context-meta.d.ts.map +1 -1
  15. package/dist/server/entry.js +2258 -637
  16. package/dist/server/entry.js.map +4 -4
  17. package/dist/server/goal-handlers.d.ts +1 -1
  18. package/dist/server/goal-routes.d.ts +10 -0
  19. package/dist/server/goal-routes.d.ts.map +1 -0
  20. package/dist/server/{autophase-ws-handler.d.ts → goal-ws-handler.d.ts} +18 -18
  21. package/dist/server/goal-ws-handler.d.ts.map +1 -0
  22. package/dist/server/handlers.js +1 -0
  23. package/dist/server/handlers.js.map +2 -2
  24. package/dist/server/http-server.d.ts +20 -0
  25. package/dist/server/http-server.d.ts.map +1 -1
  26. package/dist/server/index.d.ts +11 -8
  27. package/dist/server/index.d.ts.map +1 -1
  28. package/dist/server/instance-registry.d.ts +2 -2
  29. package/dist/server/instance-registry.d.ts.map +1 -1
  30. package/dist/server/kanban-routes.d.ts +15 -0
  31. package/dist/server/kanban-routes.d.ts.map +1 -1
  32. package/dist/server/mcp-handlers.d.ts +1 -1
  33. package/dist/server/memory-handlers.d.ts +37 -0
  34. package/dist/server/memory-handlers.d.ts.map +1 -1
  35. package/dist/server/message-dispatcher.d.ts +3 -2
  36. package/dist/server/message-dispatcher.d.ts.map +1 -1
  37. package/dist/server/mode-handlers.d.ts.map +1 -1
  38. package/dist/server/pending-confirms.d.ts +1 -0
  39. package/dist/server/pending-confirms.d.ts.map +1 -1
  40. package/dist/server/pre-context-services.d.ts +3 -2
  41. package/dist/server/pre-context-services.d.ts.map +1 -1
  42. package/dist/server/pref-helpers.d.ts +18 -1
  43. package/dist/server/pref-helpers.d.ts.map +1 -1
  44. package/dist/server/provider-keys.d.ts.map +1 -1
  45. package/dist/server/routes.d.ts +5 -5
  46. package/dist/server/routes.d.ts.map +1 -1
  47. package/dist/server/server-runtime.d.ts +11 -20
  48. package/dist/server/server-runtime.d.ts.map +1 -1
  49. package/dist/server/session-handlers.d.ts.map +1 -1
  50. package/dist/server/setup-events.d.ts +1 -1
  51. package/dist/server/setup-events.d.ts.map +1 -1
  52. package/dist/server/start-webui.d.ts.map +1 -1
  53. package/dist/server/techstack-handlers.d.ts +101 -0
  54. package/dist/server/techstack-handlers.d.ts.map +1 -0
  55. package/dist/server/terminal-ws-handler.d.ts +2 -1
  56. package/dist/server/terminal-ws-handler.d.ts.map +1 -1
  57. package/dist/server/worktree-ws-handler.d.ts +1 -1
  58. package/dist/server/ws-payload-validation.d.ts +24 -23
  59. package/dist/server/ws-payload-validation.d.ts.map +1 -1
  60. package/package.json +12 -14
  61. package/dist/server/autophase-routes.d.ts +0 -10
  62. package/dist/server/autophase-routes.d.ts.map +0 -1
  63. package/dist/server/autophase-ws-handler.d.ts.map +0 -1
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,10 @@ 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
+ import * as v8 from "node:v8";
1907
1908
 
1908
1909
  // src/server/http-server/api-handlers.ts
1909
1910
  async function handleApiSessions(res, globalRoot) {
@@ -2091,7 +2092,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2091
2092
  return;
2092
2093
  }
2093
2094
  try {
2094
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2095
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
2095
2096
  const registry = new SessionRegistry(globalRoot);
2096
2097
  const entry = await registry.get(sessionId);
2097
2098
  if (!entry) {
@@ -2099,7 +2100,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2099
2100
  res.end(JSON.stringify({ error: "Session not found" }));
2100
2101
  return;
2101
2102
  }
2102
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2103
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2103
2104
  const store = new DefaultSessionStore2({ dir: paths.projectSessions });
2104
2105
  const reader = new DefaultSessionReader2({ store });
2105
2106
  const rawEntries = [];
@@ -2126,7 +2127,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
2126
2127
  }
2127
2128
  }
2128
2129
  function readJsonBody(req) {
2129
- return new Promise((resolve10, reject) => {
2130
+ return new Promise((resolve12, reject) => {
2130
2131
  let data = "";
2131
2132
  req.on("data", (chunk) => {
2132
2133
  data += chunk;
@@ -2137,7 +2138,7 @@ function readJsonBody(req) {
2137
2138
  });
2138
2139
  req.on("end", () => {
2139
2140
  try {
2140
- resolve10(data ? JSON.parse(data) : {});
2141
+ resolve12(data ? JSON.parse(data) : {});
2141
2142
  } catch (err) {
2142
2143
  reject(err instanceof Error ? err : new Error(String(err)));
2143
2144
  }
@@ -2173,7 +2174,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2173
2174
  const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
2174
2175
  const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
2175
2176
  try {
2176
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2177
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2177
2178
  const registry = new SessionRegistry(globalRoot);
2178
2179
  const entry = await registry.get(sessionId);
2179
2180
  if (!entry) {
@@ -2181,7 +2182,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
2181
2182
  res.end(JSON.stringify({ error: "Session not found" }));
2182
2183
  return;
2183
2184
  }
2184
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2185
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2185
2186
  const mailbox = new GlobalMailbox4(paths.projectDir);
2186
2187
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
2187
2188
  const sent = await mailbox.send({ from, to, type, subject, body: text, priority });
@@ -2199,7 +2200,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2199
2200
  return;
2200
2201
  }
2201
2202
  try {
2202
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2203
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2203
2204
  const registry = new SessionRegistry(globalRoot);
2204
2205
  const entry = await registry.get(sessionId);
2205
2206
  if (!entry) {
@@ -2207,7 +2208,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
2207
2208
  res.end(JSON.stringify({ error: "Session not found" }));
2208
2209
  return;
2209
2210
  }
2210
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2211
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2211
2212
  const mailbox = new GlobalMailbox4(paths.projectDir);
2212
2213
  const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
2213
2214
  const [inbound, outbound] = await Promise.all([
@@ -2257,7 +2258,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2257
2258
  const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
2258
2259
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2259
2260
  try {
2260
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2261
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2261
2262
  const registry = new SessionRegistry(globalRoot);
2262
2263
  const entry = await registry.get(sessionId);
2263
2264
  if (!entry) {
@@ -2265,7 +2266,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
2265
2266
  res.end(JSON.stringify({ error: "Session not found" }));
2266
2267
  return;
2267
2268
  }
2268
- const paths = resolveWstackPaths3({ projectRoot: entry.projectRoot, globalRoot });
2269
+ const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
2269
2270
  const mailbox = new GlobalMailbox4(paths.projectDir);
2270
2271
  const to = `leader@${mailboxSessionTag2(sessionId)}`;
2271
2272
  const sent = await mailbox.send({
@@ -2305,7 +2306,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2305
2306
  }
2306
2307
  const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
2307
2308
  try {
2308
- const { SessionRegistry, resolveWstackPaths: resolveWstackPaths3, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2309
+ const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
2309
2310
  const registry = new SessionRegistry(globalRoot);
2310
2311
  const all = await registry.list();
2311
2312
  const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
@@ -2317,7 +2318,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
2317
2318
  }
2318
2319
  const mbByDir = /* @__PURE__ */ new Map();
2319
2320
  const mailboxFor = (projectRoot) => {
2320
- const dir = resolveWstackPaths3({ projectRoot, globalRoot }).projectDir;
2321
+ const dir = resolveWstackPaths4({ projectRoot, globalRoot }).projectDir;
2321
2322
  let mb = mbByDir.get(dir);
2322
2323
  if (!mb) {
2323
2324
  mb = new GlobalMailbox4(dir);
@@ -2380,14 +2381,14 @@ function pushEvent(event) {
2380
2381
  }
2381
2382
  }
2382
2383
  function parseBody(req) {
2383
- return new Promise((resolve10, reject) => {
2384
+ return new Promise((resolve12, reject) => {
2384
2385
  let body = "";
2385
2386
  req.on("data", (chunk) => {
2386
2387
  body += chunk.toString("utf-8");
2387
2388
  });
2388
2389
  req.on("end", () => {
2389
2390
  try {
2390
- resolve10(JSON.parse(body));
2391
+ resolve12(JSON.parse(body));
2391
2392
  } catch {
2392
2393
  reject(new Error("Invalid JSON"));
2393
2394
  }
@@ -2462,6 +2463,251 @@ function getAnalyticsBuffer() {
2462
2463
  return [...EVENT_BUFFER];
2463
2464
  }
2464
2465
 
2466
+ // src/server/codemap-handlers.ts
2467
+ import { packageGraphService, fileGraphService, symbolGraphService } from "@wrongstack/tools";
2468
+ function sendJson(res, status, data) {
2469
+ res.writeHead(status, { "Content-Type": "application/json" });
2470
+ res.end(JSON.stringify(data));
2471
+ }
2472
+ function handleCodemapPackages(res, deps2) {
2473
+ try {
2474
+ const graph = packageGraphService({
2475
+ projectRoot: deps2.projectRoot,
2476
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2477
+ });
2478
+ sendJson(res, 200, graph);
2479
+ } catch (err) {
2480
+ const msg = err instanceof Error ? err.message : String(err);
2481
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2482
+ }
2483
+ }
2484
+ function handleCodemapFiles(res, deps2, pkg) {
2485
+ if (!pkg) {
2486
+ sendJson(res, 400, { error: 'Missing "package" query parameter' });
2487
+ return;
2488
+ }
2489
+ try {
2490
+ const graph = fileGraphService({
2491
+ projectRoot: deps2.projectRoot,
2492
+ packageFilter: pkg,
2493
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2494
+ });
2495
+ sendJson(res, 200, graph);
2496
+ } catch (err) {
2497
+ const msg = err instanceof Error ? err.message : String(err);
2498
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2499
+ }
2500
+ }
2501
+ function handleCodemapSymbols(res, deps2, file) {
2502
+ if (!file) {
2503
+ sendJson(res, 400, { error: 'Missing "file" query parameter' });
2504
+ return;
2505
+ }
2506
+ try {
2507
+ const graph = symbolGraphService({
2508
+ projectRoot: deps2.projectRoot,
2509
+ fileFilter: file,
2510
+ ...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
2511
+ });
2512
+ sendJson(res, 200, graph);
2513
+ } catch (err) {
2514
+ const msg = err instanceof Error ? err.message : String(err);
2515
+ sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
2516
+ }
2517
+ }
2518
+
2519
+ // src/server/techstack-handlers.ts
2520
+ import { randomUUID } from "node:crypto";
2521
+ var DEEP_DIVE_TIMEOUT_MS = 6e4;
2522
+ function sendJson2(res, status, data) {
2523
+ res.writeHead(status, { "Content-Type": "application/json" });
2524
+ res.end(JSON.stringify(data));
2525
+ }
2526
+ async function buildResearcher(deps2, kind) {
2527
+ if (kind !== "analyze" || !deps2.getLlm) return void 0;
2528
+ const { createProviderLlm, createResearcher, createToolSearch } = await import("@wrongstack/techstack");
2529
+ const llm = createProviderLlm(deps2.getLlm);
2530
+ if (!llm) return void 0;
2531
+ return createResearcher({ llm, search: createToolSearch() });
2532
+ }
2533
+ function handleTechStackSnapshot(res, deps2) {
2534
+ try {
2535
+ const snapshot = deps2.store.getSnapshot(deps2.projectId);
2536
+ if (!snapshot) {
2537
+ sendJson2(res, 404, { snapshot: null, stale: false });
2538
+ return;
2539
+ }
2540
+ const ageMs = Date.now() - new Date(snapshot.createdAt).getTime();
2541
+ sendJson2(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
2542
+ } catch (error) {
2543
+ sendJson2(res, 500, {
2544
+ error: "TechStack store unavailable",
2545
+ detail: errorMessage(error)
2546
+ });
2547
+ }
2548
+ }
2549
+ function errorMessage(error) {
2550
+ return error instanceof Error ? error.message : String(error);
2551
+ }
2552
+ function requireJobDeps(res, deps2) {
2553
+ if (!deps2.projectRoot || !deps2.engine) {
2554
+ sendJson2(res, 503, { error: "TechStack engine unavailable" });
2555
+ return false;
2556
+ }
2557
+ return true;
2558
+ }
2559
+ function startJob(res, deps2, kind) {
2560
+ if (!requireJobDeps(res, deps2)) return;
2561
+ const jobId = randomUUID();
2562
+ const controller = new AbortController();
2563
+ deps2.runningJobs?.set(jobId, controller);
2564
+ deps2.emit?.({ type: "techstack.job.started", payload: { jobId, kind } });
2565
+ sendJson2(res, 202, { jobId, kind, status: "queued" });
2566
+ void buildResearcher(deps2, kind).catch(() => void 0).then(
2567
+ (researcher) => deps2.engine.analyze(deps2.projectId, {
2568
+ targetRoot: deps2.projectRoot,
2569
+ requestedBy: "webui",
2570
+ online: kind === "analyze",
2571
+ jobId,
2572
+ signal: controller.signal,
2573
+ researcher,
2574
+ onProgress: (phase, completed, total) => {
2575
+ deps2.emit?.({
2576
+ type: "techstack.job.progress",
2577
+ payload: { jobId, phase, completed, total }
2578
+ });
2579
+ }
2580
+ })
2581
+ ).then(({ snapshot }) => {
2582
+ if (controller.signal.aborted) return;
2583
+ deps2.emit?.({
2584
+ type: "techstack.snapshot.updated",
2585
+ payload: { snapshot, stale: false }
2586
+ });
2587
+ }).catch((error) => {
2588
+ if (controller.signal.aborted) {
2589
+ deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
2590
+ return;
2591
+ }
2592
+ deps2.emit?.({
2593
+ type: "techstack.job.failed",
2594
+ payload: { jobId, error: errorMessage(error) }
2595
+ });
2596
+ }).finally(() => {
2597
+ deps2.runningJobs?.delete(jobId);
2598
+ });
2599
+ }
2600
+ function handleTechStackInventory(res, deps2) {
2601
+ startJob(res, deps2, "inventory");
2602
+ }
2603
+ function handleTechStackAnalyze(res, deps2) {
2604
+ startJob(res, deps2, "analyze");
2605
+ }
2606
+ function handleTechStackCancel(res, deps2, jobId) {
2607
+ const controller = deps2.runningJobs?.get(jobId);
2608
+ if (controller && !controller.signal.aborted) controller.abort();
2609
+ deps2.store.updateJobStatus(jobId, "cancelled");
2610
+ deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
2611
+ sendJson2(res, 200, { jobId, status: "cancelled" });
2612
+ }
2613
+ async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
2614
+ const snapshot = deps2.store.getSnapshot(deps2.projectId);
2615
+ const dependency = snapshot?.dependencies.find((dep) => dep.id === dependencyId);
2616
+ if (!dependency) {
2617
+ sendJson2(res, 404, { error: "Dependency not found in the current snapshot" });
2618
+ return;
2619
+ }
2620
+ let researcher;
2621
+ try {
2622
+ researcher = await buildResearcher(deps2, "analyze");
2623
+ } catch (error) {
2624
+ sendJson2(res, 503, { error: "Research unavailable", detail: errorMessage(error) });
2625
+ return;
2626
+ }
2627
+ if (!researcher) {
2628
+ sendJson2(res, 503, {
2629
+ error: "No model configured \u2014 connect a provider to run LLM analysis."
2630
+ });
2631
+ return;
2632
+ }
2633
+ const controller = new AbortController();
2634
+ const timeout = setTimeout(() => {
2635
+ controller.abort(new Error("research timeout"));
2636
+ }, DEEP_DIVE_TIMEOUT_MS);
2637
+ timeout.unref?.();
2638
+ try {
2639
+ const { triageCandidates } = await import("@wrongstack/techstack");
2640
+ const [triaged] = triageCandidates([dependency], { limit: 1 });
2641
+ const findings = await researcher.research(
2642
+ [triaged ?? { dependency, cluster: "breaking_change", priority: 0 }],
2643
+ { signal: controller.signal }
2644
+ );
2645
+ sendJson2(res, 200, { dependencyId, findings });
2646
+ } catch (error) {
2647
+ sendJson2(res, 500, { error: "Research failed", detail: errorMessage(error) });
2648
+ } finally {
2649
+ clearTimeout(timeout);
2650
+ controller.abort();
2651
+ }
2652
+ }
2653
+ function handleTechStackJobStatus(res, deps2, jobId) {
2654
+ const job = deps2.store.getJob(jobId);
2655
+ if (!job) {
2656
+ sendJson2(res, 404, { error: "Job not found" });
2657
+ return;
2658
+ }
2659
+ sendJson2(res, 200, { job });
2660
+ }
2661
+ function handleTechStackReport(res, deps2, reportId, format) {
2662
+ const snapshot = deps2.store.getSnapshotById(reportId);
2663
+ if (!snapshot) {
2664
+ sendJson2(res, 404, { error: "Report not found" });
2665
+ return;
2666
+ }
2667
+ if (deps2.engine) {
2668
+ const report = deps2.engine.generateReport(snapshot, format);
2669
+ res.writeHead(200, {
2670
+ "Content-Type": format === "json" ? "application/json" : "text/markdown",
2671
+ "Content-Disposition": `attachment; filename="techstack-report.${format}"`
2672
+ });
2673
+ res.end(report);
2674
+ } else {
2675
+ sendJson2(res, 200, snapshot);
2676
+ }
2677
+ }
2678
+
2679
+ // src/server/projects-manifest.ts
2680
+ import * as fs5 from "node:fs/promises";
2681
+ import * as path6 from "node:path";
2682
+ import { projectSlug } from "@wrongstack/core";
2683
+ function projectsJsonPath(globalConfigPath) {
2684
+ const base = path6.dirname(globalConfigPath);
2685
+ return path6.join(base, "projects.json");
2686
+ }
2687
+ async function loadManifest(globalConfigPath) {
2688
+ try {
2689
+ const raw = await fs5.readFile(projectsJsonPath(globalConfigPath), "utf8");
2690
+ const parsed = JSON.parse(raw);
2691
+ return { projects: parsed.projects ?? [] };
2692
+ } catch {
2693
+ return { projects: [] };
2694
+ }
2695
+ }
2696
+ async function saveManifest(manifest, globalConfigPath) {
2697
+ const file = projectsJsonPath(globalConfigPath);
2698
+ await fs5.mkdir(path6.dirname(file), { recursive: true });
2699
+ await fs5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
2700
+ }
2701
+ function generateProjectSlug(rootPath) {
2702
+ return projectSlug(rootPath);
2703
+ }
2704
+ async function ensureProjectDataDir(slug, globalConfigPath) {
2705
+ const base = path6.dirname(globalConfigPath);
2706
+ const dir = path6.join(base, "projects", slug);
2707
+ await fs5.mkdir(dir, { recursive: true });
2708
+ return dir;
2709
+ }
2710
+
2465
2711
  // src/server/ws-auth.ts
2466
2712
  import { Buffer as Buffer2 } from "node:buffer";
2467
2713
  import { timingSafeEqual } from "node:crypto";
@@ -2647,9 +2893,9 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
2647
2893
  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
2894
  }
2649
2895
  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);
2896
+ const root = path7.resolve(distDir);
2897
+ const resolved = path7.resolve(candidate);
2898
+ return resolved === root || resolved.startsWith(root + path7.sep);
2653
2899
  }
2654
2900
  function decodeSessionId(segment) {
2655
2901
  try {
@@ -2660,10 +2906,19 @@ function decodeSessionId(segment) {
2660
2906
  }
2661
2907
  function createHttpServer(opts) {
2662
2908
  const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
2663
- const distDir = path6.resolve(opts.distDir);
2909
+ const distDir = path7.resolve(opts.distDir);
2664
2910
  const wsPort = opts.wsPort;
2665
2911
  const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
2666
- return http.createServer(async (req, res) => {
2912
+ let techStackRuntime = null;
2913
+ const getTechStackRuntime = async () => {
2914
+ if (!opts.projectRoot) throw new Error("Project root not configured");
2915
+ techStackRuntime ??= import("@wrongstack/techstack").then(({ TechStackEngine, TechStackStore }) => {
2916
+ const store = new TechStackStore({ projectSlug: generateProjectSlug(opts.projectRoot) });
2917
+ return { store, engine: new TechStackEngine(store), runningJobs: /* @__PURE__ */ new Map() };
2918
+ });
2919
+ return techStackRuntime;
2920
+ };
2921
+ const server = http.createServer(async (req, res) => {
2667
2922
  try {
2668
2923
  const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
2669
2924
  const providedAccessToken = requestToken(req, url);
@@ -2814,6 +3069,127 @@ function createHttpServer(opts) {
2814
3069
  await handleApiAnalyticsSummary(res);
2815
3070
  return;
2816
3071
  }
3072
+ if (url.pathname === "/api/codemap/packages" && req.method === "GET") {
3073
+ if (requireAccessToken && !accessTokenOk) {
3074
+ res.writeHead(401, { "Content-Type": "application/json" });
3075
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3076
+ return;
3077
+ }
3078
+ if (!opts.projectRoot) {
3079
+ res.writeHead(503, { "Content-Type": "application/json" });
3080
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3081
+ return;
3082
+ }
3083
+ handleCodemapPackages(res, {
3084
+ projectRoot: opts.projectRoot,
3085
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3086
+ });
3087
+ return;
3088
+ }
3089
+ if (url.pathname === "/api/codemap/files" && req.method === "GET") {
3090
+ if (requireAccessToken && !accessTokenOk) {
3091
+ res.writeHead(401, { "Content-Type": "application/json" });
3092
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3093
+ return;
3094
+ }
3095
+ if (!opts.projectRoot) {
3096
+ res.writeHead(503, { "Content-Type": "application/json" });
3097
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3098
+ return;
3099
+ }
3100
+ const pkg = url.searchParams.get("package") ?? "";
3101
+ handleCodemapFiles(res, {
3102
+ projectRoot: opts.projectRoot,
3103
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3104
+ }, pkg);
3105
+ return;
3106
+ }
3107
+ if (url.pathname === "/api/codemap/symbols" && req.method === "GET") {
3108
+ if (requireAccessToken && !accessTokenOk) {
3109
+ res.writeHead(401, { "Content-Type": "application/json" });
3110
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3111
+ return;
3112
+ }
3113
+ if (!opts.projectRoot) {
3114
+ res.writeHead(503, { "Content-Type": "application/json" });
3115
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3116
+ return;
3117
+ }
3118
+ const file = url.searchParams.get("file") ?? "";
3119
+ handleCodemapSymbols(res, {
3120
+ projectRoot: opts.projectRoot,
3121
+ ...opts.indexDir ? { indexDir: opts.indexDir } : {}
3122
+ }, file);
3123
+ return;
3124
+ }
3125
+ if (url.pathname.startsWith("/api/techstack/")) {
3126
+ if (requireAccessToken && !accessTokenOk) {
3127
+ res.writeHead(401, { "Content-Type": "application/json" });
3128
+ res.end(JSON.stringify({ error: "Unauthorized" }));
3129
+ return;
3130
+ }
3131
+ if (!opts.projectRoot) {
3132
+ res.writeHead(503, { "Content-Type": "application/json" });
3133
+ res.end(JSON.stringify({ error: "Project root not configured" }));
3134
+ return;
3135
+ }
3136
+ try {
3137
+ const runtime = await getTechStackRuntime();
3138
+ const deps2 = {
3139
+ projectId: opts.projectRoot,
3140
+ projectRoot: opts.projectRoot,
3141
+ store: runtime.store,
3142
+ engine: runtime.engine,
3143
+ runningJobs: runtime.runningJobs,
3144
+ emit: opts.onTechStackEvent,
3145
+ getLlm: opts.getLlm
3146
+ };
3147
+ if (url.pathname === "/api/techstack/snapshot" && req.method === "GET") {
3148
+ handleTechStackSnapshot(res, deps2);
3149
+ return;
3150
+ }
3151
+ if (url.pathname === "/api/techstack/inventory" && req.method === "POST") {
3152
+ handleTechStackInventory(res, deps2);
3153
+ return;
3154
+ }
3155
+ if (url.pathname === "/api/techstack/analyze" && req.method === "POST") {
3156
+ handleTechStackAnalyze(res, deps2);
3157
+ return;
3158
+ }
3159
+ const cancelMatch = /^\/api\/techstack\/jobs\/([^/]+)\/cancel$/.exec(url.pathname);
3160
+ if (cancelMatch && req.method === "POST") {
3161
+ handleTechStackCancel(res, deps2, decodeURIComponent(cancelMatch[1]));
3162
+ return;
3163
+ }
3164
+ const jobMatch = /^\/api\/techstack\/jobs\/([^/]+)$/.exec(url.pathname);
3165
+ if (jobMatch && req.method === "GET") {
3166
+ handleTechStackJobStatus(res, deps2, decodeURIComponent(jobMatch[1]));
3167
+ return;
3168
+ }
3169
+ const reportMatch = /^\/api\/techstack\/reports\/([^/]+)$/.exec(url.pathname);
3170
+ if (reportMatch && req.method === "GET") {
3171
+ const fmt = url.searchParams.get("format") === "json" ? "json" : "md";
3172
+ handleTechStackReport(res, deps2, decodeURIComponent(reportMatch[1]), fmt);
3173
+ return;
3174
+ }
3175
+ const researchMatch = /^\/api\/techstack\/deps\/([^/]+)\/research$/.exec(url.pathname);
3176
+ if (researchMatch && req.method === "POST") {
3177
+ await handleTechStackDependencyResearch(
3178
+ res,
3179
+ deps2,
3180
+ decodeURIComponent(researchMatch[1])
3181
+ );
3182
+ return;
3183
+ }
3184
+ } catch (error) {
3185
+ res.writeHead(503, { "Content-Type": "application/json" });
3186
+ res.end(JSON.stringify({
3187
+ error: "TechStack store unavailable",
3188
+ detail: error instanceof Error ? error.message : String(error)
3189
+ }));
3190
+ return;
3191
+ }
3192
+ }
2817
3193
  if (url.pathname === "/debug/watcher-metrics" && req.method === "GET") {
2818
3194
  if (requireAccessToken && !accessTokenOk) {
2819
3195
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -2835,23 +3211,40 @@ function createHttpServer(opts) {
2835
3211
  }
2836
3212
  return;
2837
3213
  }
3214
+ if (url.pathname === "/debug/system" && req.method === "GET") {
3215
+ res.writeHead(200, {
3216
+ "Content-Type": "application/json",
3217
+ "Cache-Control": "no-store"
3218
+ });
3219
+ res.end(
3220
+ JSON.stringify({
3221
+ pid: process.pid,
3222
+ memoryUsage: process.memoryUsage(),
3223
+ heapLimit: v8.getHeapStatistics().heap_size_limit,
3224
+ uptime: process.uptime(),
3225
+ cpuUsage: process.cpuUsage(),
3226
+ timestamp: Date.now()
3227
+ })
3228
+ );
3229
+ return;
3230
+ }
2838
3231
  let filePath;
2839
3232
  if (url.pathname === "/" || url.pathname === "") {
2840
- filePath = path6.join(distDir, "index.html");
3233
+ filePath = path7.join(distDir, "index.html");
2841
3234
  } else if (url.pathname.startsWith("/assets/")) {
2842
- filePath = path6.join(distDir, url.pathname);
3235
+ filePath = path7.join(distDir, url.pathname);
2843
3236
  } else if (url.pathname.startsWith("/")) {
2844
- filePath = path6.join(distDir, url.pathname);
3237
+ filePath = path7.join(distDir, url.pathname);
2845
3238
  } else {
2846
- filePath = path6.join(distDir, "index.html");
3239
+ filePath = path7.join(distDir, "index.html");
2847
3240
  }
2848
- const resolvedPath = path6.resolve(filePath);
3241
+ const resolvedPath = path7.resolve(filePath);
2849
3242
  if (!isInsideDist(resolvedPath, distDir)) {
2850
3243
  res.writeHead(403, { "Content-Type": "text/plain" });
2851
3244
  res.end("Forbidden");
2852
3245
  return;
2853
3246
  }
2854
- const ext = path6.extname(resolvedPath);
3247
+ const ext = path7.extname(resolvedPath);
2855
3248
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
2856
3249
  res.setHeader("Content-Type", contentType);
2857
3250
  res.setHeader("X-Content-Type-Options", "nosniff");
@@ -2863,18 +3256,18 @@ function createHttpServer(opts) {
2863
3256
  "Content-Security-Policy",
2864
3257
  buildCspHeader(wsPort, requestHostForCsp(req.headers.host), opts.publicWsUrl)
2865
3258
  );
2866
- const html = await fs5.readFile(resolvedPath, "utf8");
3259
+ const html = await fs6.readFile(resolvedPath, "utf8");
2867
3260
  res.writeHead(200);
2868
3261
  res.end(injectWsConfig(html, { wsPort, publicWsUrl: opts.publicWsUrl }));
2869
3262
  return;
2870
3263
  }
2871
- const fileContent = await fs5.readFile(resolvedPath);
3264
+ const fileContent = await fs6.readFile(resolvedPath);
2872
3265
  res.writeHead(200);
2873
3266
  res.end(fileContent);
2874
3267
  } catch (err) {
2875
3268
  if (err.code === "ENOENT") {
2876
3269
  try {
2877
- const html = await fs5.readFile(path6.join(distDir, "index.html"), "utf8");
3270
+ const html = await fs6.readFile(path7.join(distDir, "index.html"), "utf8");
2878
3271
  res.writeHead(200, {
2879
3272
  "Content-Type": "text/html",
2880
3273
  "X-Content-Type-Options": "nosniff",
@@ -2897,18 +3290,26 @@ function createHttpServer(opts) {
2897
3290
  }
2898
3291
  }
2899
3292
  });
3293
+ server.once("close", () => {
3294
+ void techStackRuntime?.then(({ store, runningJobs }) => {
3295
+ for (const controller of runningJobs.values()) controller.abort();
3296
+ runningJobs.clear();
3297
+ store.close();
3298
+ }).catch(() => void 0);
3299
+ });
3300
+ return server;
2900
3301
  }
2901
3302
 
2902
3303
  // src/server/instance-registry.ts
2903
3304
  import * as os from "node:os";
2904
- import * as path7 from "node:path";
2905
- import * as fs6 from "node:fs/promises";
3305
+ import * as path8 from "node:path";
3306
+ import * as fs7 from "node:fs/promises";
2906
3307
  import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
2907
3308
  function defaultBaseDir() {
2908
- return path7.join(os.homedir(), ".wrongstack");
3309
+ return path8.join(os.homedir(), ".wrongstack");
2909
3310
  }
2910
3311
  function registryPath(baseDir = defaultBaseDir()) {
2911
- return path7.join(baseDir, "webui-instances.json");
3312
+ return path8.join(baseDir, "webui-instances.json");
2912
3313
  }
2913
3314
  function isPidAlive(pid) {
2914
3315
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -2921,7 +3322,7 @@ function isPidAlive(pid) {
2921
3322
  }
2922
3323
  async function load(file) {
2923
3324
  try {
2924
- const raw = await fs6.readFile(file, "utf8");
3325
+ const raw = await fs7.readFile(file, "utf8");
2925
3326
  const parsed = JSON.parse(raw);
2926
3327
  if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
2927
3328
  return parsed;
@@ -3214,7 +3615,7 @@ async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
3214
3615
  payload: { name: serverName, resources, resourceTemplates }
3215
3616
  });
3216
3617
  } catch (err) {
3217
- sendContentError(ws, "resources", serverName, errorMessage(err));
3618
+ sendContentError(ws, "resources", serverName, errorMessage2(err));
3218
3619
  }
3219
3620
  }
3220
3621
  async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3228,7 +3629,7 @@ async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
3228
3629
  });
3229
3630
  send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
3230
3631
  } catch (err) {
3231
- sendContentError(ws, "prompts", serverName, errorMessage(err));
3632
+ sendContentError(ws, "prompts", serverName, errorMessage2(err));
3232
3633
  }
3233
3634
  }
3234
3635
  async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3244,7 +3645,7 @@ async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
3244
3645
  );
3245
3646
  send(ws, { type: "mcp.content.selected", payload: insertion });
3246
3647
  } catch (err) {
3247
- sendContentError(ws, "resource.read", serverName, errorMessage(err));
3648
+ sendContentError(ws, "resource.read", serverName, errorMessage2(err));
3248
3649
  }
3249
3650
  }
3250
3651
  async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
@@ -3260,7 +3661,7 @@ async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
3260
3661
  );
3261
3662
  send(ws, { type: "mcp.content.selected", payload: insertion });
3262
3663
  } catch (err) {
3263
- sendContentError(ws, "prompt.get", serverName, errorMessage(err));
3664
+ sendContentError(ws, "prompt.get", serverName, errorMessage2(err));
3264
3665
  }
3265
3666
  }
3266
3667
  function payloadRecord(msg) {
@@ -3288,14 +3689,14 @@ function promptArguments(value) {
3288
3689
  function sendContentError(ws, action, name2, error) {
3289
3690
  send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
3290
3691
  }
3291
- function errorMessage(err) {
3692
+ function errorMessage2(err) {
3292
3693
  return err instanceof Error ? err.message : String(err);
3293
3694
  }
3294
3695
 
3295
3696
  // src/server/memory-handlers.ts
3296
3697
  function isSuperMemoryStore(store) {
3297
3698
  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";
3699
+ 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
3700
  }
3300
3701
  function requiresSuperMemory(command) {
3301
3702
  return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
@@ -3419,6 +3820,7 @@ async function handleSuperMemoryRemember(ws, msg, memoryStore) {
3419
3820
  importance: payload["importance"],
3420
3821
  confidence: payload["confidence"],
3421
3822
  freshness: payload["freshness"],
3823
+ audience: payload["audience"],
3422
3824
  supersedes: payload["supersedes"],
3423
3825
  contradicts: payload["contradicts"]
3424
3826
  });
@@ -3432,18 +3834,172 @@ async function handleSuperMemoryDelete(ws, msg, memoryStore) {
3432
3834
  send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
3433
3835
  return;
3434
3836
  }
3435
- const { id, reason } = msg.payload;
3837
+ const { id, reason, neverInject } = msg.payload;
3436
3838
  if (!id) {
3437
3839
  send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
3438
3840
  return;
3439
3841
  }
3440
3842
  try {
3441
- await memoryStore.deleteSuperMemory(id, reason);
3843
+ if (neverInject === true) await memoryStore.deleteSuperMemory(id, reason, { neverInject: true });
3844
+ else await memoryStore.deleteSuperMemory(id, reason);
3442
3845
  send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
3443
3846
  } catch (err) {
3444
3847
  send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
3445
3848
  }
3446
3849
  }
3850
+ async function handleSuperMemoryRecover(ws, msg, memoryStore) {
3851
+ if (!isSuperMemoryStore(memoryStore)) {
3852
+ send(ws, { type: "memory.super.recover", payload: { error: requiresSuperMemory("memory.super.recover") } });
3853
+ return;
3854
+ }
3855
+ const payload = msg.payload;
3856
+ const id = payload["id"];
3857
+ if (!id) {
3858
+ send(ws, { type: "memory.super.recover", payload: { error: "id is required" } });
3859
+ return;
3860
+ }
3861
+ const reason = payload["reason"];
3862
+ try {
3863
+ const preExisting = await memoryStore.getSuperMemory(id);
3864
+ if (!preExisting) {
3865
+ send(ws, { type: "memory.super.recover", payload: { error: `Super Memory "${id}" not found.` } });
3866
+ return;
3867
+ }
3868
+ if (preExisting.status === "active") {
3869
+ send(ws, { type: "memory.super.recover", payload: { recovered: true, memory: preExisting, noop: true } });
3870
+ return;
3871
+ }
3872
+ const memory = await memoryStore.recoverSuperMemory(id, reason);
3873
+ const noop = memory.id !== id;
3874
+ const response = { recovered: true, memory };
3875
+ if (noop) {
3876
+ response["activeId"] = memory.id;
3877
+ response["noop"] = true;
3878
+ }
3879
+ send(ws, { type: "memory.super.recover", payload: response });
3880
+ } catch (err) {
3881
+ send(ws, { type: "memory.super.recover", payload: { error: errMessage(err) } });
3882
+ }
3883
+ }
3884
+ async function handleSuperMemoryCandidateResolve(ws, msg, memoryStore) {
3885
+ if (!isSuperMemoryStore(memoryStore)) {
3886
+ send(ws, {
3887
+ type: "memory.super.candidateResolve",
3888
+ payload: { error: requiresSuperMemory("memory.super.candidateResolve") }
3889
+ });
3890
+ return;
3891
+ }
3892
+ const payload = msg.payload;
3893
+ const candidateId = payload["candidateId"];
3894
+ const action = payload["action"];
3895
+ if (!candidateId) {
3896
+ send(ws, {
3897
+ type: "memory.super.candidateResolve",
3898
+ payload: { error: "candidateId is required" }
3899
+ });
3900
+ return;
3901
+ }
3902
+ if (action !== "accept" && action !== "reject") {
3903
+ send(ws, {
3904
+ type: "memory.super.candidateResolve",
3905
+ payload: { error: 'action must be "accept" or "reject"' }
3906
+ });
3907
+ return;
3908
+ }
3909
+ const reason = payload["reason"];
3910
+ try {
3911
+ let candidate;
3912
+ if (action === "accept") {
3913
+ const accepted = await memoryStore.acceptCandidate(candidateId);
3914
+ candidate = accepted ? { id: accepted.id, status: accepted.status ?? "active" } : void 0;
3915
+ } else {
3916
+ const rejected = await memoryStore.rejectCandidate(
3917
+ candidateId,
3918
+ reason ?? "Rejected via WebUI"
3919
+ );
3920
+ candidate = rejected ? { id: candidateId, status: "rejected" } : void 0;
3921
+ }
3922
+ if (!candidate) {
3923
+ send(ws, {
3924
+ type: "memory.super.candidateResolve",
3925
+ payload: { error: `Candidate "${candidateId}" not found` }
3926
+ });
3927
+ return;
3928
+ }
3929
+ send(ws, {
3930
+ type: "memory.super.candidateResolve",
3931
+ payload: { candidate, resolvedAction: action }
3932
+ });
3933
+ } catch (err) {
3934
+ send(ws, {
3935
+ type: "memory.super.candidateResolve",
3936
+ payload: { error: errMessage(err) }
3937
+ });
3938
+ }
3939
+ }
3940
+ async function handleSuperMemoryBackfillRecoverable(ws, msg, memoryStore) {
3941
+ if (!isSuperMemoryStore(memoryStore)) {
3942
+ send(ws, {
3943
+ type: "memory.super.backfillRecoverable",
3944
+ payload: { error: requiresSuperMemory("memory.super.backfillRecoverable") }
3945
+ });
3946
+ return;
3947
+ }
3948
+ const payload = msg.payload ?? {};
3949
+ const apply = payload["apply"] === true;
3950
+ const rawFilter = payload["filter"] ?? {};
3951
+ const filter = {};
3952
+ if (Array.isArray(rawFilter["kinds"])) filter.kinds = rawFilter["kinds"];
3953
+ if (Array.isArray(rawFilter["scopes"])) filter.scopes = rawFilter["scopes"];
3954
+ if (typeof rawFilter["updatedAfter"] === "string") filter.updatedAfter = rawFilter["updatedAfter"];
3955
+ if (typeof rawFilter["updatedBefore"] === "string") filter.updatedBefore = rawFilter["updatedBefore"];
3956
+ try {
3957
+ const report = await memoryStore.backfillRecoverable({
3958
+ apply,
3959
+ ...Object.keys(filter).length > 0 ? { filter } : {}
3960
+ });
3961
+ send(ws, {
3962
+ type: "memory.super.backfillRecoverable",
3963
+ payload: {
3964
+ examined: report.examined,
3965
+ recovered: report.recovered,
3966
+ recoverable: report.recoverable,
3967
+ dryRun: !apply
3968
+ }
3969
+ });
3970
+ } catch (err) {
3971
+ send(ws, {
3972
+ type: "memory.super.backfillRecoverable",
3973
+ payload: { error: errMessage(err) }
3974
+ });
3975
+ }
3976
+ }
3977
+ async function handleSuperMemoryForFile(ws, msg, memoryStore) {
3978
+ if (!isSuperMemoryStore(memoryStore)) {
3979
+ send(ws, {
3980
+ type: "memory.super.forFile",
3981
+ payload: { error: requiresSuperMemory("memory.super.forFile") }
3982
+ });
3983
+ return;
3984
+ }
3985
+ const payload = msg.payload ?? {};
3986
+ const filePath = payload["filePath"];
3987
+ if (!filePath) {
3988
+ send(ws, { type: "memory.super.forFile", payload: { error: "filePath is required" } });
3989
+ return;
3990
+ }
3991
+ try {
3992
+ const response = await memoryStore.findMemoriesForFile(filePath, {
3993
+ ...typeof payload["lineStart"] === "number" ? { lineStart: payload["lineStart"] } : {},
3994
+ ...typeof payload["lineEnd"] === "number" ? { lineEnd: payload["lineEnd"] } : {},
3995
+ ...typeof payload["limit"] === "number" ? { limit: payload["limit"] } : {},
3996
+ ...payload["includeDeleted"] === true ? { includeDeleted: true } : {}
3997
+ });
3998
+ send(ws, { type: "memory.super.forFile", payload: response });
3999
+ } catch (err) {
4000
+ send(ws, { type: "memory.super.forFile", payload: { error: errMessage(err) } });
4001
+ }
4002
+ }
3447
4003
 
3448
4004
  // src/server/open-browser.ts
3449
4005
  import { spawn } from "node:child_process";
@@ -3502,16 +4058,16 @@ function getSurfaceDefaultPorts(surface) {
3502
4058
  return { ...SURFACE_DEFAULT_PORTS[surface] };
3503
4059
  }
3504
4060
  function isPortFree(host, port) {
3505
- return new Promise((resolve10) => {
4061
+ return new Promise((resolve12) => {
3506
4062
  const srv = net.createServer();
3507
- srv.once("error", () => resolve10(false));
4063
+ srv.once("error", () => resolve12(false));
3508
4064
  srv.once("listening", () => {
3509
- srv.close(() => resolve10(true));
4065
+ srv.close(() => resolve12(true));
3510
4066
  });
3511
4067
  try {
3512
4068
  srv.listen(port, host);
3513
4069
  } catch {
3514
- resolve10(false);
4070
+ resolve12(false);
3515
4071
  }
3516
4072
  });
3517
4073
  }
@@ -3738,17 +4294,17 @@ async function handlePromptsRecent(ws, ctx) {
3738
4294
  }
3739
4295
 
3740
4296
  // src/server/provider-config-standalone.ts
3741
- import * as path8 from "node:path";
4297
+ import * as path9 from "node:path";
3742
4298
  import { DefaultSecretVault } from "@wrongstack/core";
3743
4299
 
3744
4300
  // src/server/provider-config-io.ts
3745
- import * as fs7 from "node:fs/promises";
4301
+ import * as fs8 from "node:fs/promises";
3746
4302
  import { ConfigError, atomicWrite as atomicWrite4 } from "@wrongstack/core";
3747
4303
  import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
3748
4304
  async function loadSavedProviders(configPath, vault) {
3749
4305
  let raw;
3750
4306
  try {
3751
- raw = await fs7.readFile(configPath, "utf8");
4307
+ raw = await fs8.readFile(configPath, "utf8");
3752
4308
  } catch {
3753
4309
  return {};
3754
4310
  }
@@ -3765,7 +4321,7 @@ async function saveProviders(configPath, vault, providers) {
3765
4321
  let raw;
3766
4322
  let fileExists = true;
3767
4323
  try {
3768
- raw = await fs7.readFile(configPath, "utf8");
4324
+ raw = await fs8.readFile(configPath, "utf8");
3769
4325
  } catch (err) {
3770
4326
  if (err.code !== "ENOENT") {
3771
4327
  throw new ConfigError({
@@ -3799,7 +4355,7 @@ async function saveProviders(configPath, vault, providers) {
3799
4355
 
3800
4356
  // src/server/provider-config-standalone.ts
3801
4357
  function createProviderConfigIO(configPath) {
3802
- const keyFile = path8.join(path8.dirname(configPath), ".key");
4358
+ const keyFile = path9.join(path9.dirname(configPath), ".key");
3803
4359
  const vault = new DefaultSecretVault({ keyFile });
3804
4360
  return {
3805
4361
  load: () => loadSavedProviders(configPath, vault),
@@ -3809,6 +4365,10 @@ function createProviderConfigIO(configPath) {
3809
4365
 
3810
4366
  // src/server/provider-keys.ts
3811
4367
  import { expectDefined } from "@wrongstack/core";
4368
+ import {
4369
+ buildProviderConfigFromPreset,
4370
+ resolvePresetForAlias
4371
+ } from "@wrongstack/providers";
3812
4372
  function normalizeKeys(cfg) {
3813
4373
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
3814
4374
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -3837,8 +4397,28 @@ function maskedKey(key) {
3837
4397
  if (key.length <= 8) return "\u2022".repeat(key.length);
3838
4398
  return `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
3839
4399
  }
4400
+ function hydratePresetConfig(providerId, dest) {
4401
+ const preset = resolvePresetForAlias(providerId);
4402
+ if (!preset) return void 0;
4403
+ const template = buildProviderConfigFromPreset(preset);
4404
+ if (!dest.type) dest.type = preset.id;
4405
+ if (!dest.family) dest.family = preset.family;
4406
+ if (dest.baseUrl === void 0) dest.baseUrl = template.baseUrl;
4407
+ if (!dest.envVars || dest.envVars.length === 0) dest.envVars = template.envVars;
4408
+ if (!dest.models || dest.models.length === 0) dest.models = template.models;
4409
+ if (template.customModels && (!dest.customModels || Object.keys(dest.customModels).length === 0)) {
4410
+ dest.customModels = template.customModels;
4411
+ }
4412
+ if (template.quirks && dest.quirks === void 0) dest.quirks = template.quirks;
4413
+ return preset.id;
4414
+ }
3840
4415
  function upsertKey(providers, providerId, label, apiKey, nowIso) {
3841
- const existing = providers[providerId] ?? { type: providerId };
4416
+ let existing = providers[providerId];
4417
+ if (!existing) {
4418
+ existing = { type: providerId };
4419
+ const presetId = hydratePresetConfig(providerId, existing);
4420
+ if (presetId) existing.type = presetId;
4421
+ }
3842
4422
  const keys = normalizeKeys(existing);
3843
4423
  const idx = keys.findIndex((k) => k.label === label);
3844
4424
  if (idx >= 0) {
@@ -3888,6 +4468,8 @@ function addProvider(providers, payload, nowIso) {
3888
4468
  family: payload.family,
3889
4469
  baseUrl: payload.baseUrl
3890
4470
  };
4471
+ const presetId = hydratePresetConfig(payload.id, newProv);
4472
+ if (presetId) newProv.type = presetId;
3891
4473
  if (payload.apiKey) {
3892
4474
  newProv.apiKeys = [{ label: "default", apiKey: payload.apiKey, createdAt: nowIso }];
3893
4475
  newProv.activeKey = "default";
@@ -4054,7 +4636,7 @@ var SddBoardWebSocketHandler = class {
4054
4636
  };
4055
4637
 
4056
4638
  // src/server/sdd-wizard-wiring.ts
4057
- import * as path9 from "node:path";
4639
+ import * as path10 from "node:path";
4058
4640
  import { spawnSync as spawnSync2 } from "node:child_process";
4059
4641
  import {
4060
4642
  DefaultTaskStore,
@@ -4154,7 +4736,7 @@ function buildSddWizardDeps(opts) {
4154
4736
  makeDriver: () => new SddInterviewDriver({
4155
4737
  specStore: new SpecStore({ baseDir: opts.paths.projectSpecs }),
4156
4738
  graphStore: new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs }),
4157
- sessionPath: path9.join(opts.paths.projectDir, "sdd-wizard-session.json")
4739
+ sessionPath: path10.join(opts.paths.projectDir, "sdd-wizard-session.json")
4158
4740
  }),
4159
4741
  runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
4160
4742
  startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels, worktrees: useWorktrees }) => {
@@ -4356,8 +4938,8 @@ function toSessionHistoryEntries(summaries, currentSessionId) {
4356
4938
  }
4357
4939
 
4358
4940
  // src/server/shell-open.ts
4359
- import * as fs8 from "node:fs/promises";
4360
- import * as path10 from "node:path";
4941
+ import * as fs9 from "node:fs/promises";
4942
+ import * as path11 from "node:path";
4361
4943
  import { spawn as spawn2 } from "node:child_process";
4362
4944
  var METACHAR_REGEX = /[&|<>^"'`'\n\r]/;
4363
4945
  function shellQuote(s) {
@@ -4365,8 +4947,8 @@ function shellQuote(s) {
4365
4947
  }
4366
4948
  async function handleShellOpen(req, logger) {
4367
4949
  try {
4368
- const resolved = path10.resolve(req.path);
4369
- await fs8.access(resolved);
4950
+ const resolved = path11.resolve(req.path);
4951
+ await fs9.access(resolved);
4370
4952
  if (METACHAR_REGEX.test(resolved)) {
4371
4953
  return { success: false, message: "Path contains unsupported characters." };
4372
4954
  }
@@ -4417,12 +4999,13 @@ async function handleShellOpen(req, logger) {
4417
4999
  }
4418
5000
 
4419
5001
  // src/server/skills-handlers.ts
4420
- import { promises as fs9 } from "node:fs";
4421
- import path11 from "node:path";
5002
+ import { promises as fs10 } from "node:fs";
5003
+ import path12 from "node:path";
4422
5004
  import { atomicWrite as atomicWrite5 } from "@wrongstack/core";
4423
5005
  import { wstackGlobalRoot } from "@wrongstack/core/utils";
4424
5006
 
4425
5007
  // src/server/ws-payload-validation.ts
5008
+ import { FORBIDDEN_PROTO_KEYS } from "@wrongstack/core/utils";
4426
5009
  function isRecord(value) {
4427
5010
  return typeof value === "object" && value !== null && !Array.isArray(value);
4428
5011
  }
@@ -4627,12 +5210,24 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
4627
5210
  "hqRawContent",
4628
5211
  "fallbackAuto",
4629
5212
  "favoriteModelsOnly",
5213
+ "modelAvailabilitySchedule",
4630
5214
  "breakerEnabled",
4631
- "debugStream"
5215
+ "debugStream",
5216
+ // Chimera + auto-review master toggles
5217
+ "chimeraEnabled",
5218
+ "autoReviewEnabled",
5219
+ "showModelReasoning"
5220
+ ]);
5221
+ var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set([
5222
+ "fallbackModels",
5223
+ "favoriteModels",
5224
+ // Auto-review explicit fallback chain (derived when fallbackProfile is unset;
5225
+ // surfaced for visibility/override).
5226
+ "autoReviewFallbackModels"
4632
5227
  ]);
4633
- var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
4634
5228
  var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
4635
5229
  var MODEL_MATRIX_PREF_KEYS = /* @__PURE__ */ new Set(["modelMatrix"]);
5230
+ var BOOLEAN_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["pluginsEnabled"]);
4636
5231
  var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4637
5232
  "autonomyDelayMs",
4638
5233
  "autoProceedMaxIterations",
@@ -4640,7 +5235,12 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
4640
5235
  "maxConcurrent",
4641
5236
  "enhanceDelayMs",
4642
5237
  "tgLongToolMs",
4643
- "breakerAutoKillResetMs"
5238
+ "breakerAutoKillResetMs",
5239
+ // Chimera + auto-review numeric knobs
5240
+ "chimeraMaxFiles",
5241
+ "autoReviewDebounceMs",
5242
+ "autoReviewMaxFilesPerBatch",
5243
+ "autoReviewMaxConcurrentReviews"
4644
5244
  ]);
4645
5245
  var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4646
5246
  "hqUrl",
@@ -4649,7 +5249,13 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
4649
5249
  "thinkingWord",
4650
5250
  "refinerProvider",
4651
5251
  "refinerModel",
4652
- "refinerFallbackProfile"
5252
+ "refinerFallbackProfile",
5253
+ // Chimera + auto-review override strings
5254
+ "chimeraProvider",
5255
+ "chimeraModel",
5256
+ "autoReviewProvider",
5257
+ "autoReviewModel",
5258
+ "autoReviewFallbackProfile"
4653
5259
  ]);
4654
5260
  var ENUM_PREF_KEYS = {
4655
5261
  autonomy: AUTONOMY_VALUES,
@@ -4664,36 +5270,39 @@ var ENUM_PREF_KEYS = {
4664
5270
  cacheTtl: CACHE_TTL_VALUES,
4665
5271
  statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
4666
5272
  animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
4667
- fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
5273
+ fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"]),
5274
+ // Chimera autoFix + auto-review cascade threshold
5275
+ chimeraAutoFix: /* @__PURE__ */ new Set(["off", "ask", "auto"]),
5276
+ autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"])
4668
5277
  };
4669
- function validateModelRuntimeValue(modelRuntime, path23) {
5278
+ function validateModelRuntimeValue(modelRuntime, path24) {
4670
5279
  const reasoning = modelRuntime["reasoning"];
4671
5280
  if (reasoning !== void 0) {
4672
- if (!isRecord(reasoning)) return `${path23}.reasoning must be an object when provided`;
5281
+ if (!isRecord(reasoning)) return `${path24}.reasoning must be an object when provided`;
4673
5282
  const mode = reasoning["mode"];
4674
5283
  const effort = reasoning["effort"];
4675
5284
  const preserve = reasoning["preserve"];
4676
5285
  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(", ")}`;
5286
+ return `${path24}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
4678
5287
  }
4679
5288
  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(", ")}`;
5289
+ return `${path24}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
4681
5290
  }
4682
5291
  if (preserve !== void 0 && typeof preserve !== "boolean") {
4683
- return `${path23}.reasoning.preserve must be a boolean when provided`;
5292
+ return `${path24}.reasoning.preserve must be a boolean when provided`;
4684
5293
  }
4685
5294
  }
4686
5295
  const cache = modelRuntime["cache"];
4687
5296
  if (cache !== void 0) {
4688
- if (!isRecord(cache)) return `${path23}.cache must be an object when provided`;
5297
+ if (!isRecord(cache)) return `${path24}.cache must be an object when provided`;
4689
5298
  const ttl = cache["ttl"];
4690
5299
  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`;
5300
+ return `${path24}.cache.ttl must be one of: 5m, 1h`;
4692
5301
  }
4693
5302
  }
4694
5303
  const parameters = modelRuntime["parameters"];
4695
5304
  if (parameters !== void 0 && !isRecord(parameters)) {
4696
- return `${path23}.parameters must be an object when provided`;
5305
+ return `${path24}.parameters must be an object when provided`;
4697
5306
  }
4698
5307
  return null;
4699
5308
  }
@@ -4715,6 +5324,16 @@ function validatePreferenceValue(key, value) {
4715
5324
  (v) => Array.isArray(v) && v.every((item) => typeof item === "string")
4716
5325
  ) ? null : `prefs.update payload.${key} must be an object of string arrays`;
4717
5326
  }
5327
+ if (BOOLEAN_RECORD_PREF_KEYS.has(key)) {
5328
+ if (!isRecord(value) || !Object.values(value).every((v) => typeof v === "boolean")) {
5329
+ return `prefs.update payload.${key} must be an object of booleans`;
5330
+ }
5331
+ const badKey = Object.keys(value).find((k) => FORBIDDEN_PROTO_KEYS.has(k));
5332
+ if (badKey) {
5333
+ return `prefs.update payload.${key} contains a forbidden key: ${badKey}`;
5334
+ }
5335
+ return null;
5336
+ }
4718
5337
  if (MODEL_MATRIX_PREF_KEYS.has(key)) {
4719
5338
  if (!isRecord(value)) return `prefs.update payload.${key} must be an object`;
4720
5339
  for (const entry of Object.values(value)) {
@@ -4736,7 +5355,10 @@ function validatePreferenceValue(key, value) {
4736
5355
  return `prefs.update payload.${key}.modelRuntime must be an object when provided`;
4737
5356
  }
4738
5357
  if (isRecord(modelRuntime)) {
4739
- const runtimeError = validateModelRuntimeValue(modelRuntime, `prefs.update payload.${key}.modelRuntime`);
5358
+ const runtimeError = validateModelRuntimeValue(
5359
+ modelRuntime,
5360
+ `prefs.update payload.${key}.modelRuntime`
5361
+ );
4740
5362
  if (runtimeError) return runtimeError;
4741
5363
  }
4742
5364
  if (model === void 0 && fallbackProfile === void 0 && modelRuntime === void 0) {
@@ -4973,8 +5595,8 @@ function validateShellOpenPayload(payload) {
4973
5595
  if (!isRecord(payload)) {
4974
5596
  return { ok: false, message: "shell.open payload must be an object with string path" };
4975
5597
  }
4976
- const path23 = payload["path"];
4977
- if (typeof path23 !== "string" || path23.trim().length === 0) {
5598
+ const path24 = payload["path"];
5599
+ if (typeof path24 !== "string" || path24.trim().length === 0) {
4978
5600
  return { ok: false, message: "shell.open payload.path must be a non-empty string" };
4979
5601
  }
4980
5602
  const target = payload["target"];
@@ -4987,7 +5609,7 @@ function validateShellOpenPayload(payload) {
4987
5609
  return {
4988
5610
  ok: true,
4989
5611
  value: {
4990
- path: path23,
5612
+ path: path24,
4991
5613
  ...target !== void 0 ? { target } : {}
4992
5614
  }
4993
5615
  };
@@ -4996,14 +5618,14 @@ function validateGitDiffPayload(payload) {
4996
5618
  if (!isRecord(payload)) {
4997
5619
  return { ok: false, message: "git.diff payload must be an object" };
4998
5620
  }
4999
- const path23 = payload["path"];
5000
- if (path23 === void 0 || path23 === null) {
5621
+ const path24 = payload["path"];
5622
+ if (path24 === void 0 || path24 === null) {
5001
5623
  return { ok: true, value: { path: "" } };
5002
5624
  }
5003
- if (typeof path23 !== "string") {
5625
+ if (typeof path24 !== "string") {
5004
5626
  return { ok: false, message: "git.diff payload.path must be a string when provided" };
5005
5627
  }
5006
- return { ok: true, value: { path: path23 } };
5628
+ return { ok: true, value: { path: path24 } };
5007
5629
  }
5008
5630
  function validateProjectsAddPayload(payload) {
5009
5631
  if (!isRecord(payload)) {
@@ -5225,19 +5847,19 @@ async function handleSkillsContent(ws, ctx, msg) {
5225
5847
  send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
5226
5848
  return;
5227
5849
  }
5228
- const body = await fs9.readFile(entry.path, "utf8");
5229
- const skillDir = path11.dirname(entry.path);
5850
+ const body = await fs10.readFile(entry.path, "utf8");
5851
+ const skillDir = path12.dirname(entry.path);
5230
5852
  let relatedFiles = [];
5231
5853
  try {
5232
- const files = await fs9.readdir(skillDir);
5233
- relatedFiles = files.filter((f) => f !== path11.basename(entry.path)).map((f) => path11.join(skillDir, f));
5854
+ const files = await fs10.readdir(skillDir);
5855
+ relatedFiles = files.filter((f) => f !== path12.basename(entry.path)).map((f) => path12.join(skillDir, f));
5234
5856
  } catch {
5235
5857
  }
5236
5858
  const nameLower = name2.toLowerCase();
5237
5859
  const refResults = await Promise.all(
5238
5860
  entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
5239
5861
  try {
5240
- const content = await fs9.readFile(e.path, "utf8");
5862
+ const content = await fs10.readFile(e.path, "utf8");
5241
5863
  return [e.name, content.toLowerCase().includes(nameLower)];
5242
5864
  } catch {
5243
5865
  return [e.name, false];
@@ -5327,20 +5949,20 @@ async function handleSkillsCreate(ws, ctx, msg) {
5327
5949
  }
5328
5950
  const createPayload = parsed.value;
5329
5951
  try {
5330
- const targetDir = createPayload.scope === "global" ? path11.join(
5331
- ctx.globalSkillsDir ?? path11.join(wstackGlobalRoot(), "skills"),
5952
+ const targetDir = createPayload.scope === "global" ? path12.join(
5953
+ ctx.globalSkillsDir ?? path12.join(wstackGlobalRoot(), "skills"),
5332
5954
  createPayload.name.trim()
5333
- ) : path11.join(
5334
- ctx.projectSkillsDir ?? path11.join(ctx.projectRoot, ".wrongstack", "skills"),
5955
+ ) : path12.join(
5956
+ ctx.projectSkillsDir ?? path12.join(ctx.projectRoot, ".wrongstack", "skills"),
5335
5957
  createPayload.name.trim()
5336
5958
  );
5337
5959
  try {
5338
- await fs9.access(targetDir);
5960
+ await fs10.access(targetDir);
5339
5961
  send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
5340
5962
  return;
5341
5963
  } catch {
5342
5964
  }
5343
- await fs9.mkdir(targetDir, { recursive: true });
5965
+ await fs10.mkdir(targetDir, { recursive: true });
5344
5966
  const lines = createPayload.description.trim().split("\n");
5345
5967
  const firstLine = (lines[0] ?? "").trim();
5346
5968
  const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
@@ -5388,13 +6010,13 @@ ${trigger}
5388
6010
  "- `bug-hunter` \u2014 for systematic bug detection patterns",
5389
6011
  "- `output-standards` \u2014 for standardized `<nextsteps>` formatting"
5390
6012
  ].join("\n");
5391
- await atomicWrite5(path11.join(targetDir, "SKILL.md"), skillContent);
6013
+ await atomicWrite5(path12.join(targetDir, "SKILL.md"), skillContent);
5392
6014
  send(ws, {
5393
6015
  type: "skills.created",
5394
6016
  payload: {
5395
6017
  success: true,
5396
6018
  error: null,
5397
- skill: { name: createPayload.name.trim(), path: path11.join(targetDir, "SKILL.md"), scope: createPayload.scope }
6019
+ skill: { name: createPayload.name.trim(), path: path12.join(targetDir, "SKILL.md"), scope: createPayload.scope }
5398
6020
  }
5399
6021
  });
5400
6022
  } catch (err) {
@@ -5671,7 +6293,7 @@ function estimateContextBreakdown(input) {
5671
6293
  }
5672
6294
 
5673
6295
  // src/server/worktree-ws-handler.ts
5674
- import { join as join8, resolve as resolve6, sep as sep3 } from "node:path";
6296
+ import { join as join9, resolve as resolve6, sep as sep3 } from "node:path";
5675
6297
  import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core";
5676
6298
  import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
5677
6299
  import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
@@ -5732,7 +6354,7 @@ var WorktreeWebSocketHandler = class {
5732
6354
  // ── orphan management ─────────────────────────────────────────────────────
5733
6355
  /** Absolute managed-worktrees root for this project. */
5734
6356
  worktreesRoot() {
5735
- return resolve6(join8(this.management.projectRoot, ".wrongstack", "worktrees"));
6357
+ return resolve6(join9(this.management.projectRoot, ".wrongstack", "worktrees"));
5736
6358
  }
5737
6359
  /** True iff `dir` resolves strictly inside the managed worktrees root. */
5738
6360
  underRoot(dir) {
@@ -6003,7 +6625,7 @@ var WorktreeWebSocketHandler = class {
6003
6625
  };
6004
6626
 
6005
6627
  // src/server/server-runtime.ts
6006
- import * as path13 from "node:path";
6628
+ import * as path15 from "node:path";
6007
6629
  import { createRequire } from "node:module";
6008
6630
  import { fileURLToPath } from "node:url";
6009
6631
  import { WebSocketServer } from "ws";
@@ -6045,12 +6667,103 @@ function registerShutdownHandlers(res) {
6045
6667
  }
6046
6668
 
6047
6669
  // src/server/setup-events.ts
6048
- import * as fs10 from "node:fs/promises";
6049
6670
  import { watch as fsWatch } from "node:fs";
6050
- import * as path12 from "node:path";
6671
+ import * as fs11 from "node:fs/promises";
6672
+ import * as path14 from "node:path";
6673
+ import { getBoard, getKanbanDir, recordTaskFileActivity } from "@wrongstack/kanban";
6674
+
6675
+ // src/server/codemap-telemetry.ts
6676
+ import * as path13 from "node:path";
6677
+ var TOOL_OPERATION = {
6678
+ read: "read",
6679
+ read_file: "read",
6680
+ view: "read",
6681
+ write: "write",
6682
+ write_file: "write",
6683
+ create_file: "write",
6684
+ edit: "edit",
6685
+ replace: "edit",
6686
+ patch: "edit",
6687
+ apply_patch: "edit",
6688
+ delete: "delete",
6689
+ delete_file: "delete",
6690
+ remove: "delete",
6691
+ unlink: "delete",
6692
+ grep: "search",
6693
+ search: "search",
6694
+ codebase_search: "search",
6695
+ "codebase-search": "search"
6696
+ };
6697
+ function numberField(input, names) {
6698
+ for (const name2 of names) {
6699
+ const value = input[name2];
6700
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.floor(value);
6701
+ }
6702
+ return void 0;
6703
+ }
6704
+ function normalizeTarget(projectRoot, filePath) {
6705
+ return path13.normalize(path13.isAbsolute(filePath) ? filePath : path13.resolve(projectRoot, filePath));
6706
+ }
6707
+ function normalizeCodeMapFileTarget(projectRoot, filePath, operation = "edit", line, endLine) {
6708
+ return {
6709
+ filePath: normalizeTarget(projectRoot, filePath),
6710
+ operation: operation === "rename" ? "edit" : operation,
6711
+ ...line ? { line } : {},
6712
+ ...endLine ? { endLine } : {}
6713
+ };
6714
+ }
6715
+ function patchTargets(patch) {
6716
+ const targets = [];
6717
+ for (const line of patch.split(/\r?\n/)) {
6718
+ const match = /^\+\+\+\s+(?:b\/)?(.+?)(?:\t.*)?$/.exec(line);
6719
+ const target = match?.[1]?.trim();
6720
+ if (target && target !== "/dev/null") targets.push(target);
6721
+ }
6722
+ return targets;
6723
+ }
6724
+ function extractCodeMapFileTargets(projectRoot, toolName, rawInput) {
6725
+ const operation = TOOL_OPERATION[toolName.toLowerCase()];
6726
+ if (!operation || !rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) return [];
6727
+ const input = rawInput;
6728
+ const rawPaths = [];
6729
+ for (const key of ["path", "file", "filePath", "target"]) {
6730
+ const value = input[key];
6731
+ if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
6732
+ }
6733
+ const files = input["files"];
6734
+ if (Array.isArray(files)) {
6735
+ for (const value of files)
6736
+ if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
6737
+ } else if (typeof files === "string" && files.trim() && !/[?*{}[\]]/.test(files)) {
6738
+ rawPaths.push(files.trim());
6739
+ }
6740
+ if ((toolName === "patch" || toolName === "apply_patch") && typeof input["patch"] === "string") {
6741
+ rawPaths.push(...patchTargets(input["patch"]));
6742
+ }
6743
+ const line = numberField(input, ["line", "offset", "startLine", "start_line", "line_start"]);
6744
+ const explicitEnd = numberField(input, ["endLine", "end_line", "line_end"]);
6745
+ const limit = numberField(input, ["limit"]);
6746
+ const endLine = explicitEnd ?? (line && limit ? line + limit - 1 : void 0);
6747
+ const seen = /* @__PURE__ */ new Set();
6748
+ const targets = [];
6749
+ for (const rawPath of rawPaths) {
6750
+ const filePath = normalizeTarget(projectRoot, rawPath);
6751
+ if (seen.has(filePath)) continue;
6752
+ seen.add(filePath);
6753
+ targets.push({
6754
+ filePath,
6755
+ operation,
6756
+ ...line ? { line } : {},
6757
+ ...endLine ? { endLine } : {}
6758
+ });
6759
+ }
6760
+ return targets;
6761
+ }
6762
+
6763
+ // src/server/setup-events.ts
6051
6764
  function statusProjectHashFromWatchFilename(projectsDir, filename) {
6052
6765
  const raw = String(filename);
6053
- const relative4 = path12.isAbsolute(raw) ? path12.relative(projectsDir, raw) : raw;
6766
+ const relative4 = path14.isAbsolute(raw) ? path14.relative(projectsDir, raw) : raw;
6054
6767
  const parts = relative4.split(/[\\/]+/).filter(Boolean);
6055
6768
  if (parts.length < 2) return null;
6056
6769
  if (parts[parts.length - 1] !== "status.json") return null;
@@ -6061,12 +6774,76 @@ function shouldLogWatcherStats() {
6061
6774
  return value === "1" || value === "true" || value === "yes" || value === "on";
6062
6775
  }
6063
6776
  function setupEvents(deps2) {
6064
- const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
6777
+ const {
6778
+ events,
6779
+ broadcast: broadcast2,
6780
+ clients,
6781
+ config,
6782
+ context,
6783
+ pendingConfirms,
6784
+ globalConfigPath,
6785
+ sessionBridge,
6786
+ wpaths,
6787
+ watcherMetrics,
6788
+ onFleetBroadcaster
6789
+ } = deps2;
6065
6790
  const disposers = [];
6066
6791
  let disposed = false;
6067
6792
  const on = (event, listener) => {
6068
6793
  disposers.push(events.on(event, listener));
6069
6794
  };
6795
+ const conversationState = context.state;
6796
+ if (typeof conversationState?.onChange === "function") {
6797
+ disposers.push(
6798
+ conversationState.onChange((change) => {
6799
+ if (change.kind !== "todos_replaced") return;
6800
+ broadcast2(clients, {
6801
+ type: "todos.updated",
6802
+ payload: {
6803
+ sessionId: context.session?.id ?? "",
6804
+ todos: [...change.todos],
6805
+ revision: conversationState.revision
6806
+ }
6807
+ });
6808
+ })
6809
+ );
6810
+ }
6811
+ let kanbanWatcher = null;
6812
+ let kanbanDebounce = null;
6813
+ const projectRoot = context.projectRoot;
6814
+ if (projectRoot) {
6815
+ try {
6816
+ const kanbanDir = getKanbanDir(projectRoot);
6817
+ kanbanWatcher = fsWatch(kanbanDir, { persistent: false }, (_eventType, filename) => {
6818
+ const name2 = filename?.toString();
6819
+ if (!name2?.endsWith(".json")) return;
6820
+ const boardId = name2.slice(0, -5);
6821
+ if (kanbanDebounce) clearTimeout(kanbanDebounce);
6822
+ kanbanDebounce = setTimeout(async () => {
6823
+ try {
6824
+ const board = await getBoard(projectRoot, boardId);
6825
+ if (board) {
6826
+ broadcast2(clients, {
6827
+ type: "kanban.get",
6828
+ // Wrap in the { board } envelope like every other kanban
6829
+ // broadcast so the client's isBoardEnvelope path handles it
6830
+ // without hijacking another tab's activeBoardId.
6831
+ payload: { success: true, data: { board } }
6832
+ });
6833
+ }
6834
+ } catch {
6835
+ }
6836
+ }, 60);
6837
+ });
6838
+ kanbanWatcher.on("error", () => kanbanWatcher?.close());
6839
+ disposers.push(() => {
6840
+ if (kanbanDebounce) clearTimeout(kanbanDebounce);
6841
+ kanbanWatcher?.close();
6842
+ kanbanWatcher = null;
6843
+ });
6844
+ } catch {
6845
+ }
6846
+ }
6070
6847
  const currentSessionId = () => context.session?.id ?? "";
6071
6848
  const sessionPayload2 = (payload) => {
6072
6849
  const provided = payload["sessionId"];
@@ -6092,7 +6869,11 @@ function setupEvents(deps2) {
6092
6869
  on("iteration.completed", (e) => {
6093
6870
  broadcast2(clients, {
6094
6871
  type: "iteration.completed",
6095
- payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
6872
+ payload: sessionPayload2({
6873
+ sessionId: e.sessionId,
6874
+ index: e.index,
6875
+ totalIterations: e.index + 1
6876
+ })
6096
6877
  });
6097
6878
  });
6098
6879
  on("iteration.limit_reached", (e) => {
@@ -6106,10 +6887,16 @@ function setupEvents(deps2) {
6106
6887
  });
6107
6888
  });
6108
6889
  on("provider.text_delta", (e) => {
6109
- broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
6890
+ broadcast2(clients, {
6891
+ type: "provider.text_delta",
6892
+ payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" })
6893
+ });
6110
6894
  });
6111
6895
  on("provider.thinking_delta", (e) => {
6112
- broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
6896
+ broadcast2(clients, {
6897
+ type: "provider.thinking_delta",
6898
+ payload: sessionPayload2({ sessionId: e.sessionId, text: e.text })
6899
+ });
6113
6900
  });
6114
6901
  on("provider.stream_error", (e) => {
6115
6902
  broadcast2(clients, {
@@ -6120,7 +6907,17 @@ function setupEvents(deps2) {
6120
6907
  on("tool.started", (e) => {
6121
6908
  broadcast2(clients, {
6122
6909
  type: "tool.started",
6123
- payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
6910
+ payload: sessionPayload2({
6911
+ sessionId: e.sessionId,
6912
+ traceId: e.traceId,
6913
+ agentId: e.agentId,
6914
+ agentName: e.agentName,
6915
+ id: e.id,
6916
+ name: e.name,
6917
+ input: e.input,
6918
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
6919
+ messageId: `tool_${e.id}`
6920
+ })
6124
6921
  });
6125
6922
  appendForCurrentSession(e.sessionId, {
6126
6923
  type: "tool_call_start",
@@ -6131,13 +6928,37 @@ function setupEvents(deps2) {
6131
6928
  });
6132
6929
  });
6133
6930
  on("tool.progress", (e) => {
6931
+ const rawProgressPath = e.event.path ?? (typeof e.event.data?.["path"] === "string" ? e.event.data["path"] : void 0);
6932
+ const progressTarget = rawProgressPath ? normalizeCodeMapFileTarget(
6933
+ context.projectRoot,
6934
+ rawProgressPath,
6935
+ e.event.operation ?? "edit",
6936
+ e.event.line,
6937
+ e.event.endLine
6938
+ ) : void 0;
6134
6939
  broadcast2(clients, {
6135
6940
  type: "tool.progress",
6136
6941
  // Nested `event` shape — the client handler reads `payload.event?.text`
6137
6942
  // and early-returns on a falsy text, so a flat { eventType, text } payload
6138
6943
  // makes live tool progress (bash streaming, partial_output, warnings)
6139
6944
  // 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 } })
6945
+ payload: sessionPayload2({
6946
+ sessionId: e.sessionId,
6947
+ traceId: e.traceId,
6948
+ agentId: e.agentId,
6949
+ agentName: e.agentName,
6950
+ id: e.id,
6951
+ name: e.name,
6952
+ event: {
6953
+ type: e.event.type,
6954
+ text: e.event.text,
6955
+ data: e.event.data,
6956
+ path: progressTarget?.filePath,
6957
+ operation: e.event.operation,
6958
+ line: progressTarget?.line,
6959
+ endLine: progressTarget?.endLine
6960
+ }
6961
+ })
6141
6962
  });
6142
6963
  appendForCurrentSession(e.sessionId, {
6143
6964
  type: "tool_progress",
@@ -6154,7 +6975,23 @@ function setupEvents(deps2) {
6154
6975
  on("tool.executed", (e) => {
6155
6976
  broadcast2(clients, {
6156
6977
  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 })
6978
+ payload: sessionPayload2({
6979
+ sessionId: e.sessionId,
6980
+ traceId: e.traceId,
6981
+ agentId: e.agentId,
6982
+ agentName: e.agentName,
6983
+ id: e.id,
6984
+ name: e.name,
6985
+ durationMs: e.durationMs,
6986
+ ok: e.ok,
6987
+ input: e.input,
6988
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
6989
+ output: e.output,
6990
+ outputBytes: e.outputBytes,
6991
+ outputTokens: e.outputTokens,
6992
+ outputLines: e.outputLines,
6993
+ metadata: e.metadata
6994
+ })
6158
6995
  });
6159
6996
  appendForCurrentSession(e.sessionId, {
6160
6997
  type: "tool_call_end",
@@ -6168,7 +7005,10 @@ function setupEvents(deps2) {
6168
7005
  outputTokens: e.outputTokens,
6169
7006
  outputLines: e.outputLines
6170
7007
  });
6171
- broadcast2(clients, { type: "todos.updated", payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] }) });
7008
+ broadcast2(clients, {
7009
+ type: "todos.updated",
7010
+ payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] })
7011
+ });
6172
7012
  const sideEffects = context.sideEffects ?? [];
6173
7013
  if (sideEffects.length > 0) {
6174
7014
  broadcast2(clients, {
@@ -6193,7 +7033,10 @@ function setupEvents(deps2) {
6193
7033
  if (typeof taskPath === "string" && taskPath) {
6194
7034
  const { loadTasks } = await import("@wrongstack/core");
6195
7035
  const file = await loadTasks(taskPath);
6196
- broadcast2(clients, { type: "tasks.updated", payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] }) });
7036
+ broadcast2(clients, {
7037
+ type: "tasks.updated",
7038
+ payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] })
7039
+ });
6197
7040
  }
6198
7041
  } catch {
6199
7042
  }
@@ -6202,13 +7045,39 @@ function setupEvents(deps2) {
6202
7045
  if (typeof planPath === "string" && planPath) {
6203
7046
  const { loadPlan } = await import("@wrongstack/core");
6204
7047
  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: [] } }) });
7048
+ broadcast2(clients, {
7049
+ type: "plan.updated",
7050
+ payload: sessionPayload2({
7051
+ sessionId: e.sessionId,
7052
+ plan: plan ?? {
7053
+ version: 1,
7054
+ sessionId: e.sessionId ?? context.session?.id ?? "",
7055
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7056
+ items: []
7057
+ }
7058
+ })
7059
+ });
6206
7060
  }
6207
7061
  } catch {
6208
7062
  }
6209
7063
  })();
6210
7064
  }
6211
7065
  });
7066
+ on("file.activity", (e) => {
7067
+ broadcast2(clients, { type: "codemap.file_event", payload: e });
7068
+ });
7069
+ on("file.event", (e) => {
7070
+ if (e.scope !== "task" || !e.boardId || !e.taskId) return;
7071
+ void recordTaskFileActivity(context.projectRoot, e.boardId, e.taskId, e).then((recorded) => {
7072
+ if (recorded) {
7073
+ broadcast2(clients, {
7074
+ type: "kanban.task.activity.changed",
7075
+ payload: { boardId: e.boardId, taskId: e.taskId }
7076
+ });
7077
+ }
7078
+ }).catch(() => {
7079
+ });
7080
+ });
6212
7081
  on("tool.loop_detected", (e) => {
6213
7082
  broadcast2(clients, {
6214
7083
  type: "tool.loop_detected",
@@ -6224,7 +7093,12 @@ function setupEvents(deps2) {
6224
7093
  on("trust.persisted", (e) => {
6225
7094
  broadcast2(clients, {
6226
7095
  type: "trust.persisted",
6227
- payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
7096
+ payload: sessionPayload2({
7097
+ sessionId: e.sessionId,
7098
+ tool: e.tool,
7099
+ pattern: e.pattern,
7100
+ decision: e.decision
7101
+ })
6228
7102
  });
6229
7103
  });
6230
7104
  on("delegate.started", (e) => {
@@ -6266,7 +7140,12 @@ function setupEvents(deps2) {
6266
7140
  on("ctx.pct", (e) => {
6267
7141
  broadcast2(clients, {
6268
7142
  type: "ctx.pct",
6269
- payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
7143
+ payload: sessionPayload2({
7144
+ sessionId: e.sessionId,
7145
+ load: e.load,
7146
+ tokens: e.tokens,
7147
+ maxContext: e.maxContext
7148
+ })
6270
7149
  });
6271
7150
  broadcast2(clients, {
6272
7151
  type: "subagent.event",
@@ -6283,7 +7162,12 @@ function setupEvents(deps2) {
6283
7162
  on("ctx.max_context", (e) => {
6284
7163
  broadcast2(clients, {
6285
7164
  type: "ctx.max_context",
6286
- payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
7165
+ payload: sessionPayload2({
7166
+ sessionId: e.sessionId,
7167
+ providerId: e.providerId,
7168
+ modelId: e.modelId,
7169
+ maxContext: e.maxContext
7170
+ })
6287
7171
  });
6288
7172
  });
6289
7173
  on("token.threshold", (e) => {
@@ -6299,21 +7183,46 @@ function setupEvents(deps2) {
6299
7183
  });
6300
7184
  });
6301
7185
  on("context.repaired", (e) => {
6302
- broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
7186
+ broadcast2(clients, {
7187
+ type: "context.repaired",
7188
+ payload: sessionPayload2({
7189
+ sessionId: e.sessionId,
7190
+ removedToolUses: e.removedToolUses,
7191
+ removedToolResults: e.removedToolResults,
7192
+ removedMessages: e.removedMessages
7193
+ })
7194
+ });
6303
7195
  });
6304
7196
  on("tool.confirm_needed", (e) => {
6305
7197
  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 });
7198
+ const payload = sessionPayload2({
7199
+ sessionId: e.sessionId,
7200
+ id,
7201
+ toolName: e.tool?.name ?? "unknown",
7202
+ input: e.input,
7203
+ suggestedPattern: e.suggestedPattern,
7204
+ decisionSource: e.decisionSource,
7205
+ riskTier: e.riskTier,
7206
+ boundaryReason: e.boundaryReason
7207
+ });
6307
7208
  pendingConfirms.set(id, {
6308
7209
  resolve: e.resolve,
6309
7210
  decisionSource: e.decisionSource,
6310
7211
  riskTier: e.riskTier,
7212
+ boundaryReason: e.boundaryReason,
6311
7213
  payload
6312
7214
  });
6313
7215
  broadcast2(clients, { type: "tool.confirm_needed", payload });
6314
7216
  });
6315
7217
  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) }) });
7218
+ broadcast2(clients, {
7219
+ type: "error",
7220
+ payload: sessionPayload2({
7221
+ sessionId: e.sessionId,
7222
+ phase: e.phase,
7223
+ message: e.err instanceof Error ? e.err.message : String(e.err)
7224
+ })
7225
+ });
6317
7226
  appendForCurrentSession(e.sessionId, {
6318
7227
  type: "error",
6319
7228
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6384,6 +7293,35 @@ function setupEvents(deps2) {
6384
7293
  description: e.description
6385
7294
  });
6386
7295
  });
7296
+ on("provider.status_changed", (e) => {
7297
+ broadcast2(clients, {
7298
+ type: "provider.status_changed",
7299
+ payload: sessionPayload2({
7300
+ providerId: e.providerId,
7301
+ model: e.model,
7302
+ oldState: e.oldState,
7303
+ newState: e.newState,
7304
+ reason: e.reason,
7305
+ timestamp: e.timestamp,
7306
+ stateExpiresAt: e.stateExpiresAt
7307
+ })
7308
+ });
7309
+ });
7310
+ on("provider.active_blocked", (e) => {
7311
+ broadcast2(clients, {
7312
+ type: "provider.active_blocked",
7313
+ payload: sessionPayload2({
7314
+ sessionId: e.sessionId,
7315
+ providerId: e.providerId,
7316
+ model: e.model,
7317
+ state: e.state,
7318
+ fallbackProviderId: e.fallbackProviderId,
7319
+ fallbackModel: e.fallbackModel,
7320
+ lastError: e.lastError,
7321
+ timestamp: e.timestamp
7322
+ })
7323
+ });
7324
+ });
6387
7325
  on("provider.error", (e) => {
6388
7326
  broadcast2(clients, {
6389
7327
  type: "provider.error",
@@ -6489,16 +7427,137 @@ function setupEvents(deps2) {
6489
7427
  broadcast2(clients, { type: "mailbox.agent_registered", payload });
6490
7428
  });
6491
7429
  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 }));
7430
+ on(
7431
+ "subagent.spawned",
7432
+ (e) => forwardSubagent("spawned", {
7433
+ sessionId: e.sessionId,
7434
+ subagentId: e.subagentId,
7435
+ taskId: e.taskId,
7436
+ name: e.name,
7437
+ provider: e.provider,
7438
+ model: e.model,
7439
+ description: e.description
7440
+ })
7441
+ );
7442
+ on(
7443
+ "subagent.task_started",
7444
+ (e) => forwardSubagent("task_started", {
7445
+ sessionId: e.sessionId,
7446
+ subagentId: e.subagentId,
7447
+ taskId: e.taskId,
7448
+ description: e.description
7449
+ })
7450
+ );
7451
+ on("subagent.tool_started", (e) => {
7452
+ broadcast2(clients, {
7453
+ type: "codemap.tool_started",
7454
+ payload: {
7455
+ sessionId: e.agentSessionId ?? e.sessionId ?? "",
7456
+ parentSessionId: e.sessionId,
7457
+ traceId: e.traceId,
7458
+ agentId: e.subagentId,
7459
+ agentName: e.agentName ?? e.subagentId,
7460
+ id: e.id,
7461
+ name: e.name,
7462
+ input: e.input,
7463
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input)
7464
+ }
7465
+ });
7466
+ });
7467
+ on("subagent.tool_executed", (e) => {
7468
+ broadcast2(clients, {
7469
+ type: "codemap.tool_executed",
7470
+ payload: {
7471
+ sessionId: e.agentSessionId ?? e.sessionId ?? "",
7472
+ parentSessionId: e.sessionId,
7473
+ traceId: e.traceId,
7474
+ agentId: e.subagentId,
7475
+ agentName: e.agentName ?? e.subagentId,
7476
+ id: e.id,
7477
+ name: e.name,
7478
+ durationMs: e.durationMs,
7479
+ ok: e.ok,
7480
+ input: e.input,
7481
+ fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
7482
+ output: e.output,
7483
+ outputBytes: e.outputBytes,
7484
+ outputTokens: e.outputTokens,
7485
+ outputLines: e.outputLines
7486
+ }
7487
+ });
7488
+ forwardSubagent("tool_executed", {
7489
+ sessionId: e.sessionId,
7490
+ subagentId: e.subagentId,
7491
+ toolName: e.name,
7492
+ durationMs: e.durationMs,
7493
+ ok: e.ok
7494
+ });
7495
+ });
7496
+ on(
7497
+ "subagent.iteration_summary",
7498
+ (e) => forwardSubagent("iteration_summary", {
7499
+ sessionId: e.sessionId,
7500
+ subagentId: e.subagentId,
7501
+ iteration: e.iteration,
7502
+ toolCalls: e.toolCalls,
7503
+ costUsd: e.costUsd,
7504
+ currentTool: e.currentTool,
7505
+ partialText: e.partialText
7506
+ })
7507
+ );
7508
+ on(
7509
+ "subagent.budget_warning",
7510
+ (e) => forwardSubagent("budget_warning", {
7511
+ sessionId: e.sessionId,
7512
+ subagentId: e.subagentId,
7513
+ budgetKind: e.kind,
7514
+ used: e.used,
7515
+ limit: e.limit
7516
+ })
7517
+ );
7518
+ on(
7519
+ "subagent.budget_extended",
7520
+ (e) => forwardSubagent("budget_extended", {
7521
+ sessionId: e.sessionId,
7522
+ subagentId: e.subagentId,
7523
+ budgetKind: e.kind,
7524
+ newLimit: e.newLimit,
7525
+ totalExtensions: e.totalExtensions
7526
+ })
7527
+ );
7528
+ on(
7529
+ "subagent.ctx_pct",
7530
+ (e) => forwardSubagent("ctx_pct", {
7531
+ sessionId: e.sessionId,
7532
+ subagentId: e.subagentId,
7533
+ load: e.load,
7534
+ tokens: e.tokens,
7535
+ maxContext: e.maxContext
7536
+ })
7537
+ );
7538
+ on(
7539
+ "subagent.task_completed",
7540
+ (e) => forwardSubagent("task_completed", {
7541
+ sessionId: e.sessionId,
7542
+ subagentId: e.subagentId,
7543
+ status: e.status,
7544
+ iterations: e.iterations,
7545
+ toolCalls: e.toolCalls,
7546
+ finalText: e.finalText,
7547
+ failureReason: e.error?.kind,
7548
+ error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0
7549
+ })
7550
+ );
7551
+ on(
7552
+ "subagent.removed",
7553
+ (e) => forwardSubagent("removed", {
7554
+ sessionId: e.sessionId,
7555
+ subagentId: e.subagentId,
7556
+ reason: e.reason
7557
+ })
7558
+ );
6501
7559
  on("agent.timeline.message", (e) => {
7560
+ const timeline = e;
6502
7561
  broadcast2(clients, {
6503
7562
  type: "agent.timeline.message",
6504
7563
  payload: sessionPayload2({
@@ -6510,6 +7569,7 @@ function setupEvents(deps2) {
6510
7569
  iteration: e.iteration,
6511
7570
  ts: e.ts,
6512
7571
  toolName: e.toolName,
7572
+ ...typeof timeline.toolOk === "boolean" ? { toolOk: timeline.toolOk } : {},
6513
7573
  costUsd: e.costUsd
6514
7574
  })
6515
7575
  });
@@ -6618,9 +7678,9 @@ function setupEvents(deps2) {
6618
7678
  if (wpaths?.projectStatus) {
6619
7679
  try {
6620
7680
  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");
7681
+ const dir = path14.dirname(statusFile);
7682
+ await fs11.mkdir(dir, { recursive: true });
7683
+ await fs11.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
6624
7684
  } catch (err) {
6625
7685
  console.error(
6626
7686
  JSON.stringify({
@@ -6634,7 +7694,7 @@ function setupEvents(deps2) {
6634
7694
  }
6635
7695
  });
6636
7696
  if (wpaths?.projectStatus && wpaths.configDir) {
6637
- const projectsDir = path12.join(wpaths.configDir, "projects");
7697
+ const projectsDir = path14.join(wpaths.configDir, "projects");
6638
7698
  const knownProjectHashes = /* @__PURE__ */ new Set();
6639
7699
  const debounceTimers = /* @__PURE__ */ new Map();
6640
7700
  const DEBOUNCE_MS = 150;
@@ -6698,26 +7758,32 @@ function setupEvents(deps2) {
6698
7758
  let watcher;
6699
7759
  const startWatcher = async () => {
6700
7760
  try {
6701
- await fs10.mkdir(projectsDir, { recursive: true });
7761
+ await fs11.mkdir(projectsDir, { recursive: true });
6702
7762
  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 {
7763
+ watcher = fsWatch(
7764
+ projectsDir,
7765
+ { persistent: true, recursive: true },
7766
+ async (eventType, filename) => {
7767
+ if (eventType !== "change" && eventType !== "rename") return;
7768
+ if (filename == null) return;
7769
+ const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
7770
+ if (!projectHash) return;
7771
+ if (watcherMetrics) watcherMetrics.fileChangesDetected++;
7772
+ if (!knownProjectHashes.has(projectHash)) return;
7773
+ if (watcherMetrics) watcherMetrics.filesProcessed++;
7774
+ try {
7775
+ const targetFile = path14.join(projectsDir, projectHash, "status.json");
7776
+ const content = await fs11.readFile(targetFile, "utf-8");
7777
+ const statusData = JSON.parse(content);
7778
+ scheduleBroadcast(projectHash, statusData);
7779
+ } catch {
7780
+ }
6717
7781
  }
6718
- });
7782
+ );
6719
7783
  if (logWatcherMetricsEnabled) {
6720
- console.log(`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`);
7784
+ console.log(
7785
+ `[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`
7786
+ );
6721
7787
  }
6722
7788
  } catch (err) {
6723
7789
  console.error(
@@ -6762,17 +7828,19 @@ function setupEvents(deps2) {
6762
7828
  }
6763
7829
  });
6764
7830
  }
6765
- const globalRoot = globalConfigPath ? path12.dirname(globalConfigPath) : void 0;
7831
+ const globalRoot = globalConfigPath ? path14.dirname(globalConfigPath) : void 0;
6766
7832
  if (globalRoot) {
6767
7833
  const broadcastSessions = async () => {
6768
7834
  try {
6769
7835
  const { SessionRegistry } = await import("@wrongstack/core");
6770
7836
  const registry = new SessionRegistry(globalRoot);
6771
7837
  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) => ({
7838
+ const ownEntry = sessions.find((s) => s.pid === process.pid);
7839
+ const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
7840
+ const myRoot = path14.resolve(context.projectRoot);
7841
+ const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter(
7842
+ (s) => mySlug ? s.projectSlug === mySlug : path14.resolve(s.projectRoot) === myRoot
7843
+ ).map((s) => ({
6776
7844
  sessionId: s.sessionId,
6777
7845
  projectName: s.projectName,
6778
7846
  projectSlug: s.projectSlug,
@@ -6784,12 +7852,15 @@ function setupEvents(deps2) {
6784
7852
  status: s.status,
6785
7853
  pid: s.pid,
6786
7854
  startedAt: s.startedAt,
7855
+ lastHeartbeatAt: s.lastHeartbeatAt,
6787
7856
  agentCount: s.agentCount,
6788
7857
  agents: (s.agents ?? []).map((a) => ({
6789
7858
  id: a.id,
6790
7859
  name: a.name,
6791
7860
  status: a.status,
6792
7861
  currentTool: a.currentTool,
7862
+ currentTask: a.currentTask,
7863
+ taskId: a.taskId,
6793
7864
  iterations: a.iterations,
6794
7865
  toolCalls: a.toolCalls,
6795
7866
  costUsd: a.costUsd,
@@ -6798,6 +7869,12 @@ function setupEvents(deps2) {
6798
7869
  ctxPct: a.ctxPct,
6799
7870
  model: a.model,
6800
7871
  partialText: a.partialText,
7872
+ recentTools: a.recentTools,
7873
+ recentMail: a.recentMail,
7874
+ todos: a.todos,
7875
+ latestPrompt: a.latestPrompt,
7876
+ latestPromptAt: a.latestPromptAt,
7877
+ activity: a.activity,
6801
7878
  lastActivityAt: a.lastActivityAt
6802
7879
  }))
6803
7880
  }));
@@ -7023,7 +8100,7 @@ function createSessionStartPayload(g) {
7023
8100
  inputCost,
7024
8101
  outputCost,
7025
8102
  cacheReadCost,
7026
- projectName: path13.basename(projectRoot) || projectRoot,
8103
+ projectName: path15.basename(projectRoot) || projectRoot,
7027
8104
  projectRoot,
7028
8105
  cwd: g.getWorkingDir(),
7029
8106
  mode: g.getModeId(),
@@ -7121,13 +8198,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
7121
8198
  };
7122
8199
  }
7123
8200
  function resolveWebuiDistDir(fromUrl, explicitDistDir) {
7124
- if (explicitDistDir) return path13.resolve(explicitDistDir);
8201
+ if (explicitDistDir) return path15.resolve(explicitDistDir);
7125
8202
  try {
7126
8203
  const requireFromHere2 = createRequire(fromUrl);
7127
8204
  const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
7128
- return path13.dirname(serverEntry);
8205
+ return path15.dirname(serverEntry);
7129
8206
  } catch {
7130
- return path13.resolve(path13.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
8207
+ return path15.resolve(path15.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
7131
8208
  }
7132
8209
  }
7133
8210
  function startHttpServer(opts) {
@@ -7140,15 +8217,18 @@ function startHttpServer(opts) {
7140
8217
  apiToken: opts.wsToken,
7141
8218
  requireToken: opts.requireToken,
7142
8219
  watcherMetrics: opts.watcherMetrics,
7143
- onFleetPing: opts.onFleetPing
8220
+ onFleetPing: opts.onFleetPing,
8221
+ onTechStackEvent: opts.onTechStackEvent,
8222
+ getLlm: opts.getLlm,
8223
+ projectRoot: opts.projectRoot
7144
8224
  });
7145
- const registryBaseDir = path13.dirname(opts.globalConfigPath);
8225
+ const registryBaseDir = path15.dirname(opts.globalConfigPath);
7146
8226
  httpServer.listen(opts.httpPort, opts.wsHost, () => {
7147
8227
  const openUrl = buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, token: opts.wsToken, publicUrl: opts.publicUrl });
7148
8228
  console.log(`[WebUI] HTTP server running on ${openUrl}`);
7149
8229
  if (opts.openBrowser) openBrowser(openUrl);
7150
8230
  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 }) },
8231
+ { 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
8232
  registryBaseDir
7153
8233
  ).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
7154
8234
  });
@@ -7164,7 +8244,7 @@ function registerShutdown(deps2) {
7164
8244
  }
7165
8245
 
7166
8246
  // src/server/pre-context-services.ts
7167
- import * as path16 from "node:path";
8247
+ import * as path18 from "node:path";
7168
8248
  import { createRequire as createRequire2 } from "node:module";
7169
8249
  import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
7170
8250
  import {
@@ -7310,6 +8390,7 @@ function resolveSetupProvider(opts) {
7310
8390
  }
7311
8391
 
7312
8392
  // src/server/context-meta.ts
8393
+ import { FallbackProfileManager } from "@wrongstack/core";
7313
8394
  function seedContextMeta(config, context) {
7314
8395
  const meta = context.meta;
7315
8396
  const autonomyCfg = config.autonomy ?? {};
@@ -7329,6 +8410,7 @@ function seedContextMeta(config, context) {
7329
8410
  meta["fallbackProfiles"] = config.fallbackProfiles ?? {};
7330
8411
  meta["favoriteModels"] = config.favoriteModels ?? [];
7331
8412
  meta["favoriteModelsOnly"] = config.favoriteModelsOnly === true;
8413
+ meta["modelAvailabilitySchedule"] = config.modelAvailabilitySchedule ?? [];
7332
8414
  meta["modelMatrix"] = config.modelMatrix ?? {};
7333
8415
  meta["fallbackAuto"] = config.fallbackAuto !== false;
7334
8416
  if (typeof config.uiLocale === "string" && config.uiLocale) meta["uiLocale"] = config.uiLocale;
@@ -7363,6 +8445,7 @@ function seedContextMeta(config, context) {
7363
8445
  meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
7364
8446
  meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
7365
8447
  meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
8448
+ meta["showModelReasoning"] = autonomyCfg["showModelReasoning"] !== false;
7366
8449
  meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
7367
8450
  meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
7368
8451
  {
@@ -7383,11 +8466,40 @@ function seedContextMeta(config, context) {
7383
8466
  meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
7384
8467
  const tgMs = tgExt?.["longToolThresholdMs"];
7385
8468
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
8469
+ const chimeraExt = config.extensions?.["wstack-chimera"];
8470
+ meta["chimeraEnabled"] = chimeraExt?.["enabled"] !== false;
8471
+ meta["chimeraProvider"] = chimeraExt?.["provider"] ?? "";
8472
+ meta["chimeraModel"] = chimeraExt?.["model"] ?? "";
8473
+ meta["chimeraMaxFiles"] = typeof chimeraExt?.["maxFiles"] === "number" && chimeraExt["maxFiles"] >= 1 ? chimeraExt["maxFiles"] : 15;
8474
+ const autoFix = chimeraExt?.["autoFix"];
8475
+ meta["chimeraAutoFix"] = autoFix === "off" || autoFix === "ask" || autoFix === "auto" ? autoFix : "off";
8476
+ const autoReviewExt = config.extensions?.["wstack-auto-review"];
8477
+ meta["autoReviewEnabled"] = autoReviewExt?.["enabled"] === true;
8478
+ meta["autoReviewProvider"] = autoReviewExt?.["provider"] ?? "";
8479
+ meta["autoReviewModel"] = autoReviewExt?.["model"] ?? "";
8480
+ meta["autoReviewFallbackProfile"] = autoReviewExt?.["fallbackProfile"] ?? "";
8481
+ meta["autoReviewFallbackModels"] = Array.isArray(autoReviewExt?.["fallbackModels"]) ? autoReviewExt?.["fallbackModels"] : [];
8482
+ meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] : 5e3;
8483
+ meta["autoReviewMaxFilesPerBatch"] = typeof autoReviewExt?.["maxFilesPerBatch"] === "number" && autoReviewExt["maxFilesPerBatch"] >= 1 ? autoReviewExt["maxFilesPerBatch"] : 15;
8484
+ meta["autoReviewMaxConcurrentReviews"] = typeof autoReviewExt?.["maxConcurrentReviews"] === "number" && autoReviewExt["maxConcurrentReviews"] >= 1 ? autoReviewExt["maxConcurrentReviews"] : 2;
8485
+ const cascade = autoReviewExt?.["cascadeOn"];
8486
+ meta["autoReviewCascadeOn"] = cascade === "critical" || cascade === "high" ? cascade : "off";
8487
+ {
8488
+ let resolvedChain = [];
8489
+ try {
8490
+ const mgr = new FallbackProfileManager(config);
8491
+ const named = autoReviewExt?.["fallbackProfile"];
8492
+ resolvedChain = typeof named === "string" && named.length > 0 ? mgr.resolve(named) : mgr.resolveEffective({ fallbackAuto: true });
8493
+ } catch {
8494
+ resolvedChain = [];
8495
+ }
8496
+ meta["autoReviewFallbackModels"] = resolvedChain.map((e) => `${e.providerId}/${e.model}`);
8497
+ }
7386
8498
  }
7387
8499
 
7388
8500
  // src/server/model-auto-discovery.ts
7389
- import * as fs11 from "node:fs/promises";
7390
- import * as path14 from "node:path";
8501
+ import * as fs12 from "node:fs/promises";
8502
+ import * as path16 from "node:path";
7391
8503
  import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
7392
8504
  function isOverlayRegistry(value) {
7393
8505
  return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
@@ -7413,7 +8525,7 @@ function eligibleProviders(config) {
7413
8525
  }
7414
8526
  async function readCache(file) {
7415
8527
  try {
7416
- return JSON.parse(await fs11.readFile(file, "utf8"));
8528
+ return JSON.parse(await fs12.readFile(file, "utf8"));
7417
8529
  } catch {
7418
8530
  return {};
7419
8531
  }
@@ -7423,7 +8535,7 @@ async function discoverAndMergeWebuiProviders(opts) {
7423
8535
  if (!isOverlayRegistry(registry)) return;
7424
8536
  const targets = eligibleProviders(opts.config);
7425
8537
  if (targets.length === 0) return;
7426
- const cacheFile = path14.join(opts.cacheDir, "discovered-models-cache.json");
8538
+ const cacheFile = path16.join(opts.cacheDir, "discovered-models-cache.json");
7427
8539
  const cache = await readCache(cacheFile);
7428
8540
  let cacheDirty = false;
7429
8541
  await Promise.all(
@@ -7460,8 +8572,8 @@ async function discoverAndMergeWebuiProviders(opts) {
7460
8572
  );
7461
8573
  if (cacheDirty) {
7462
8574
  try {
7463
- await fs11.mkdir(path14.dirname(cacheFile), { recursive: true });
7464
- await fs11.writeFile(cacheFile, JSON.stringify(cache), "utf8");
8575
+ await fs12.mkdir(path16.dirname(cacheFile), { recursive: true });
8576
+ await fs12.writeFile(cacheFile, JSON.stringify(cache), "utf8");
7465
8577
  } catch {
7466
8578
  opts.logger?.debug?.("provider auto-discovery cache write failed");
7467
8579
  }
@@ -7469,7 +8581,7 @@ async function discoverAndMergeWebuiProviders(opts) {
7469
8581
  }
7470
8582
 
7471
8583
  // src/server/standalone-session-identity.ts
7472
- import * as path15 from "node:path";
8584
+ import * as path17 from "node:path";
7473
8585
  import {
7474
8586
  AgentStatusTracker,
7475
8587
  FleetNotifier,
@@ -7500,7 +8612,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7500
8612
  sessionId,
7501
8613
  projectSlug: paths.projectSlug,
7502
8614
  projectRoot: paths.projectRoot,
7503
- projectName: path15.basename(paths.projectRoot),
8615
+ projectName: path17.basename(paths.projectRoot),
7504
8616
  workingDir: opts.workingDir,
7505
8617
  clientType: "webui",
7506
8618
  pid: process.pid,
@@ -7509,7 +8621,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7509
8621
  });
7510
8622
  fleetNotifier.notify();
7511
8623
  } catch (err) {
7512
- logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
8624
+ logger.debug?.(`WebUI session registry update failed: ${errorMessage3(err)}`);
7513
8625
  }
7514
8626
  };
7515
8627
  await register(activeSessionId);
@@ -7535,7 +8647,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7535
8647
  const publisher = core.createHqPublisherFromEnv({
7536
8648
  clientKind: "webui",
7537
8649
  projectRoot: paths.projectRoot,
7538
- projectName: path15.basename(paths.projectRoot),
8650
+ projectName: path17.basename(paths.projectRoot),
7539
8651
  appConfig: opts.config,
7540
8652
  socketFactory: (url) => new WebSocket2(url)
7541
8653
  });
@@ -7557,7 +8669,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7557
8669
  events,
7558
8670
  sessionId,
7559
8671
  projectRoot: paths.projectRoot,
7560
- projectName: path15.basename(paths.projectRoot),
8672
+ projectName: path17.basename(paths.projectRoot),
7561
8673
  globalRoot: paths.globalRoot,
7562
8674
  initialAgents: statusTracker.getAgents(),
7563
8675
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7594,7 +8706,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7594
8706
  restartHqBridges(activeSessionId);
7595
8707
  }
7596
8708
  } catch (err) {
7597
- logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
8709
+ logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage3(err)}`);
7598
8710
  }
7599
8711
  }
7600
8712
  const repointRecovery = async (sessionId) => {
@@ -7617,7 +8729,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7617
8729
  try {
7618
8730
  restartHqBridges(sessionId);
7619
8731
  } catch (err) {
7620
- logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
8732
+ logger.debug?.(`WebUI HQ session swap failed: ${errorMessage3(err)}`);
7621
8733
  }
7622
8734
  });
7623
8735
  await transition;
@@ -7642,7 +8754,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
7642
8754
  };
7643
8755
  return { statusTracker, activate, stop };
7644
8756
  }
7645
- function errorMessage2(err) {
8757
+ function errorMessage3(err) {
7646
8758
  return err instanceof Error ? err.message : String(err);
7647
8759
  }
7648
8760
 
@@ -7668,7 +8780,7 @@ async function createPreContextServices(input) {
7668
8780
  await discoverAndMergeWebuiProviders({
7669
8781
  config,
7670
8782
  registry: modelsRegistry,
7671
- cacheDir: path16.dirname(wpaths.modelsCache),
8783
+ cacheDir: path18.dirname(wpaths.modelsCache),
7672
8784
  logger
7673
8785
  });
7674
8786
  } catch (err) {
@@ -7713,7 +8825,7 @@ async function createPreContextServices(input) {
7713
8825
  configureChildEnvGitIdentity(config.git?.identity ?? null);
7714
8826
  console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
7715
8827
  const mcpTokenStore = new MCPVaultTokenStore(
7716
- path16.join(wpaths.projectDir, "mcp-auth.json"),
8828
+ path18.join(wpaths.projectDir, "mcp-auth.json"),
7717
8829
  vault
7718
8830
  );
7719
8831
  const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
@@ -7808,7 +8920,7 @@ async function createPreContextServices(input) {
7808
8920
  const modelCapabilitiesRef = { current: modelCapabilities };
7809
8921
  const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
7810
8922
  const skillInstaller = config.features.skills ? new SkillInstaller({
7811
- manifestPath: path16.join(wpaths.globalRoot, "installed-skills.json"),
8923
+ manifestPath: path18.join(wpaths.globalRoot, "installed-skills.json"),
7812
8924
  projectSkillsDir: wpaths.inProjectSkills,
7813
8925
  globalSkillsDir: wpaths.globalSkills,
7814
8926
  projectHash: wpaths.projectHash,
@@ -7818,7 +8930,7 @@ async function createPreContextServices(input) {
7818
8930
  const bundledPromptsDir = promptsEnabled ? (() => {
7819
8931
  try {
7820
8932
  const req = createRequire2(import.meta.url);
7821
- return path16.join(path16.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
8933
+ return path18.join(path18.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
7822
8934
  } catch {
7823
8935
  return void 0;
7824
8936
  }
@@ -7919,7 +9031,7 @@ function isSuperMemoryService(memoryStore) {
7919
9031
  }
7920
9032
 
7921
9033
  // src/server/start-webui.ts
7922
- import * as path22 from "node:path";
9034
+ import * as path23 from "node:path";
7923
9035
  import {
7924
9036
  createDefaultPipelines,
7925
9037
  createSessionEventBridge,
@@ -7949,7 +9061,7 @@ function patchConfig(config, updates) {
7949
9061
  }
7950
9062
 
7951
9063
  // src/server/backend-services.ts
7952
- import { join as join13 } from "node:path";
9064
+ import { join as join14 } from "node:path";
7953
9065
  import {
7954
9066
  Agent,
7955
9067
  AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
@@ -7975,7 +9087,7 @@ import {
7975
9087
  } from "@wrongstack/core";
7976
9088
 
7977
9089
  // src/server/collaboration-ws-handler.ts
7978
- import { randomUUID } from "node:crypto";
9090
+ import { randomUUID as randomUUID2 } from "node:crypto";
7979
9091
  import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
7980
9092
  var REPLAY_LIMIT = 50;
7981
9093
  var PAUSE_TIMEOUT_MS = 6e4;
@@ -8107,7 +9219,7 @@ var CollaborationWebSocketHandler = class {
8107
9219
  return;
8108
9220
  }
8109
9221
  const participant = {
8110
- participantId: randomUUID(),
9222
+ participantId: randomUUID2(),
8111
9223
  ws,
8112
9224
  sessionId,
8113
9225
  role,
@@ -8727,8 +9839,8 @@ var CollaborationWebSocketHandler = class {
8727
9839
  };
8728
9840
 
8729
9841
  // src/server/codebase-indexing.ts
8730
- import * as fs12 from "node:fs";
8731
- import * as path17 from "node:path";
9842
+ import * as fs13 from "node:fs";
9843
+ import * as path19 from "node:path";
8732
9844
  import {
8733
9845
  cancelPendingReindexes,
8734
9846
  enqueueReindex,
@@ -8748,17 +9860,15 @@ var IGNORE_DIRS = /* @__PURE__ */ new Set([
8748
9860
  ".nyc_output"
8749
9861
  ]);
8750
9862
  function setupWebUICodebaseIndexing(deps2) {
8751
- const indexing = deps2.config.indexing;
8752
- if (!indexing) return noopIndexing();
8753
- const idx = indexing;
9863
+ const idx = deps2.config.indexing;
8754
9864
  const indexDir = typeof deps2.context.meta["codebaseIndexDir"] === "string" ? deps2.context.meta["codebaseIndexDir"] : void 0;
8755
- const debounceMs = idx.debounceMs ?? 400;
9865
+ const debounceMs = idx?.debounceMs ?? 400;
8756
9866
  const onError = (err) => {
8757
9867
  deps2.logger.debug(
8758
9868
  `webui codebase auto-index failed: ${err instanceof Error ? err.message : String(err)}`
8759
9869
  );
8760
9870
  };
8761
- if (idx.onSessionStart) {
9871
+ if (idx?.onSessionStart) {
8762
9872
  void runStartupIndex({
8763
9873
  projectRoot: deps2.projectRoot,
8764
9874
  indexDir,
@@ -8775,14 +9885,27 @@ function setupWebUICodebaseIndexing(deps2) {
8775
9885
  });
8776
9886
  }
8777
9887
  let watcher;
8778
- if (idx.watchExternal) {
9888
+ const lastWatcherEvent = /* @__PURE__ */ new Map();
9889
+ if (idx?.watchExternal || deps2.events) {
8779
9890
  try {
8780
- watcher = fs12.watch(deps2.projectRoot, { recursive: true }, (_event, filename) => {
9891
+ watcher = fs13.watch(deps2.projectRoot, { recursive: true }, (eventType, filename) => {
8781
9892
  if (!filename) return;
8782
9893
  const rel = filename.toString();
8783
9894
  if (isIgnored(rel)) return;
8784
- const abs = path17.resolve(deps2.projectRoot, rel);
8785
- enqueueFile(abs);
9895
+ const abs = path19.resolve(deps2.projectRoot, rel);
9896
+ if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
9897
+ const now = Date.now();
9898
+ if (now - (lastWatcherEvent.get(abs) ?? 0) > 75) {
9899
+ lastWatcherEvent.set(abs, now);
9900
+ deps2.events?.emit("file.activity", {
9901
+ filePath: path19.normalize(abs),
9902
+ operation: eventType === "rename" && !fs13.existsSync(abs) ? "delete" : "edit",
9903
+ phase: "changed",
9904
+ source: "watcher",
9905
+ at: now
9906
+ });
9907
+ }
9908
+ if (idx?.watchExternal) enqueueFile(abs);
8786
9909
  });
8787
9910
  watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
8788
9911
  watcher.unref?.();
@@ -8793,8 +9916,8 @@ function setupWebUICodebaseIndexing(deps2) {
8793
9916
  }
8794
9917
  }
8795
9918
  function enqueueFile(filePath) {
8796
- if (!idx.onEdit && !idx.watchExternal) return;
8797
- const abs = path17.isAbsolute(filePath) ? path17.normalize(filePath) : path17.resolve(deps2.projectRoot, filePath);
9919
+ if (!idx || !idx.onEdit && !idx.watchExternal) return;
9920
+ const abs = path19.isAbsolute(filePath) ? path19.normalize(filePath) : path19.resolve(deps2.projectRoot, filePath);
8798
9921
  if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
8799
9922
  enqueueReindex({
8800
9923
  projectRoot: deps2.projectRoot,
@@ -8807,23 +9930,28 @@ function setupWebUICodebaseIndexing(deps2) {
8807
9930
  }
8808
9931
  return {
8809
9932
  onFileWritten(filePath) {
8810
- if (idx.onEdit) enqueueFile(filePath);
9933
+ const abs = path19.isAbsolute(filePath) ? path19.normalize(filePath) : path19.resolve(deps2.projectRoot, filePath);
9934
+ deps2.events?.emit("file.activity", {
9935
+ filePath: abs,
9936
+ operation: "write",
9937
+ phase: "completed",
9938
+ source: "editor",
9939
+ at: Date.now(),
9940
+ sessionId: deps2.context.session?.id,
9941
+ agentId: "webui-editor",
9942
+ agentName: "WebUI Editor"
9943
+ });
9944
+ if (idx?.onEdit) enqueueFile(abs);
8811
9945
  },
8812
9946
  dispose() {
8813
9947
  try {
8814
9948
  watcher?.close();
8815
9949
  } catch {
8816
9950
  }
8817
- cancelPendingReindexes();
8818
- shutdownCodebaseIndexHost();
8819
- }
8820
- };
8821
- }
8822
- function noopIndexing() {
8823
- return {
8824
- onFileWritten() {
8825
- },
8826
- dispose() {
9951
+ if (idx) {
9952
+ cancelPendingReindexes();
9953
+ void shutdownCodebaseIndexHost();
9954
+ }
8827
9955
  }
8828
9956
  };
8829
9957
  }
@@ -8831,16 +9959,16 @@ function isIgnored(rel) {
8831
9959
  return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
8832
9960
  }
8833
9961
  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);
9962
+ const normalizedRoot = path19.resolve(root);
9963
+ const normalizedTarget = path19.resolve(target);
9964
+ return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path19.sep);
8837
9965
  }
8838
9966
 
8839
9967
  // src/server/discover-mailbox-bridge.ts
8840
9968
  import { spawn as spawn3 } from "node:child_process";
8841
9969
  import { createRequire as createRequire3 } from "node:module";
8842
- import { existsSync } from "node:fs";
8843
- import { dirname as dirname8, join as join12 } from "node:path";
9970
+ import { existsSync as existsSync2 } from "node:fs";
9971
+ import { dirname as dirname9, join as join13 } from "node:path";
8844
9972
  import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
8845
9973
  import { readLiveLock } from "@wrongstack/core/coordination";
8846
9974
  var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
@@ -8968,16 +10096,16 @@ function mailboxServeInvocation(projectRoot) {
8968
10096
  function findWorkspaceCliEntry(projectRoot) {
8969
10097
  let dir = projectRoot;
8970
10098
  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);
10099
+ const candidate = join13(dir, "packages", "cli", "dist", "index.js");
10100
+ if (existsSync2(candidate)) return candidate;
10101
+ const parent = dirname9(dir);
8974
10102
  if (parent === dir) return null;
8975
10103
  dir = parent;
8976
10104
  }
8977
10105
  return null;
8978
10106
  }
8979
10107
  function sleep(ms) {
8980
- return new Promise((resolve10) => setTimeout(resolve10, ms));
10108
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
8981
10109
  }
8982
10110
 
8983
10111
  // src/server/terminal-ws-handler.ts
@@ -8989,6 +10117,9 @@ var DEFAULT_COLS = 80;
8989
10117
  var DEFAULT_ROWS = 24;
8990
10118
  var requireFromHere = createRequire4(import.meta.url);
8991
10119
  var cachedNodePty;
10120
+ function resolveTerminalShell(platform = process.platform, env = process.env) {
10121
+ return platform === "win32" ? env.COMSPEC || "cmd.exe" : env.SHELL || "/bin/sh";
10122
+ }
8992
10123
  var TerminalWebSocketHandler = class {
8993
10124
  constructor(getCwd, logger, loadNodePty = defaultLoadNodePty, killProcessTree = defaultKillProcessTree) {
8994
10125
  this.getCwd = getCwd;
@@ -9044,7 +10175,7 @@ var TerminalWebSocketHandler = class {
9044
10175
  });
9045
10176
  return;
9046
10177
  }
9047
- const shell = process.platform === "win32" ? process.env.COMSPEC || "cmd.exe" : process.env.SHELL || "/bin/bash";
10178
+ const shell = resolveTerminalShell();
9048
10179
  const nodePty = this.loadNodePty();
9049
10180
  if (!nodePty) {
9050
10181
  const msg = "Integrated terminal unavailable: optional dependency node-pty is not installed. Install node-pty to enable WebUI terminal sessions.";
@@ -9232,7 +10363,8 @@ async function createAgentServices(input) {
9232
10363
  config,
9233
10364
  context,
9234
10365
  projectRoot,
9235
- logger
10366
+ logger,
10367
+ events
9236
10368
  });
9237
10369
  const compactor = createStrategyCompactor({
9238
10370
  strategy: config.context?.strategy,
@@ -9363,13 +10495,17 @@ async function createAgentServices(input) {
9363
10495
  toolExecutor
9364
10496
  });
9365
10497
  if (config.features.memory && config.features.memoryConsolidation !== false) {
9366
- agent.extensions.register(new SessionMemoryConsolidator({ memoryStore }));
10498
+ const consSuperMemory = typeof memoryStore["rememberSuper"] === "function" ? memoryStore : void 0;
10499
+ agent.extensions.register(new SessionMemoryConsolidator({
10500
+ memoryStore,
10501
+ ...consSuperMemory ? { superMemory: consSuperMemory } : {}
10502
+ }));
9367
10503
  }
9368
10504
  console.log("[WebUI] Agent initialized");
9369
10505
  const brainCfg = resolveBrainConfigDefaults(config.brain, {
9370
10506
  fallbackModels: config.fallbackModels
9371
10507
  });
9372
- const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
10508
+ const brainLedgerPath = join14(wpaths.projectDir, "brain-ledger.jsonl");
9373
10509
  let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
9374
10510
  let brainLedger;
9375
10511
  const startBrainLedger = () => {
@@ -9479,7 +10615,7 @@ async function createAgentServices(input) {
9479
10615
  });
9480
10616
  brainMonitor.start();
9481
10617
  console.log("[WebUI] Brain initialized (tiered policy \u2192 LLM, monitor active)");
9482
- const autoPhaseHandler = new AutoPhaseWebSocketHandler(
10618
+ const goalHandler = new GoalWebSocketHandler(
9483
10619
  agent,
9484
10620
  context,
9485
10621
  logger,
@@ -9551,7 +10687,7 @@ async function createAgentServices(input) {
9551
10687
  return brainLedger;
9552
10688
  },
9553
10689
  codebaseIndexing,
9554
- autoPhaseHandler,
10690
+ goalHandler,
9555
10691
  specsHandler,
9556
10692
  sddBoardHandler,
9557
10693
  sddWizardHandler,
@@ -9569,6 +10705,7 @@ function isSuperMemoryRetriever(memoryStore) {
9569
10705
  // src/server/pending-confirms.ts
9570
10706
  function resolveYoloEligiblePendingConfirms(pendingConfirms) {
9571
10707
  for (const [id, confirm] of pendingConfirms) {
10708
+ if (confirm.boundaryReason) continue;
9572
10709
  pendingConfirms.delete(id);
9573
10710
  confirm.resolve("yes");
9574
10711
  }
@@ -9624,6 +10761,9 @@ function createConnectionHandler(opts) {
9624
10761
  }
9625
10762
  void opts.sessionStartPayload().then(async (payload) => {
9626
10763
  const enriched = { ...payload };
10764
+ if (typeof opts.context.lastRequestTokens === "number" && opts.context.lastRequestTokens > 0) {
10765
+ enriched.lastInputTokens = opts.context.lastRequestTokens;
10766
+ }
9627
10767
  try {
9628
10768
  const replay = await opts.loadReplay?.();
9629
10769
  const live = replay?.messages ?? opts.context.messages ?? [];
@@ -9653,7 +10793,7 @@ function createConnectionHandler(opts) {
9653
10793
  })
9654
10794
  );
9655
10795
  });
9656
- opts.autoPhaseHandler.addClient(ws);
10796
+ opts.goalHandler.addClient(ws);
9657
10797
  opts.specsHandler.addClient(ws);
9658
10798
  opts.sddBoardHandler.addClient(ws);
9659
10799
  opts.sddWizardHandler.addClient(ws);
@@ -9671,8 +10811,21 @@ function createConnectionHandler(opts) {
9671
10811
  });
9672
10812
  return;
9673
10813
  }
10814
+ let rawObj;
10815
+ try {
10816
+ rawObj = JSON.parse(data.toString());
10817
+ } catch (err) {
10818
+ console.error(
10819
+ JSON.stringify({
10820
+ level: "error",
10821
+ event: "webui.ws_message_parse_failed",
10822
+ message: err instanceof Error ? err.message : String(err),
10823
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10824
+ })
10825
+ );
10826
+ return;
10827
+ }
9674
10828
  try {
9675
- const rawObj = JSON.parse(data.toString());
9676
10829
  if (typeof rawObj === "object" && rawObj !== null) {
9677
10830
  const obj = rawObj;
9678
10831
  if (Object.hasOwn(obj, "__proto__") || Object.hasOwn(obj, "constructor") || Object.hasOwn(obj, "prototype")) {
@@ -9690,7 +10843,7 @@ function createConnectionHandler(opts) {
9690
10843
  console.error(
9691
10844
  JSON.stringify({
9692
10845
  level: "error",
9693
- event: "webui.ws_message_parse_failed",
10846
+ event: "webui.ws_message_handler_failed",
9694
10847
  message: err instanceof Error ? err.message : String(err),
9695
10848
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
9696
10849
  })
@@ -9725,7 +10878,12 @@ function createConnectionHandler(opts) {
9725
10878
  }
9726
10879
 
9727
10880
  // src/server/message-dispatcher.ts
9728
- import path18 from "node:path";
10881
+ import path20 from "node:path";
10882
+ import {
10883
+ ChronicleQueryEngine,
10884
+ resolveWstackPaths as resolveWstackPaths2
10885
+ } from "@wrongstack/core";
10886
+ import * as os2 from "node:os";
9729
10887
  import {
9730
10888
  buildUserContentBlocks,
9731
10889
  IncomingImageError,
@@ -9738,9 +10896,9 @@ import {
9738
10896
  VisionUrlBlockedError
9739
10897
  } from "@wrongstack/runtime/vision";
9740
10898
 
9741
- // src/server/autophase-routes.ts
9742
- async function handleAutoPhaseRoute(_ws, msg, handlers) {
9743
- if (!msg.type.startsWith("autophase.")) return false;
10899
+ // src/server/goal-routes.ts
10900
+ async function handleGoalRoute(_ws, msg, handlers) {
10901
+ if (!msg.type.startsWith("goal.")) return false;
9744
10902
  await handlers.handleMessage(msg);
9745
10903
  return true;
9746
10904
  }
@@ -9776,9 +10934,9 @@ async function handleGoalGet(projectRoot, broadcast2) {
9776
10934
  const { readFile: readFile11 } = await import("node:fs/promises");
9777
10935
  const raw = await readFile11(goalPath, "utf8");
9778
10936
  const goal = JSON.parse(raw);
9779
- broadcast2({ type: "goal.updated", payload: goal });
10937
+ broadcast2({ type: "goal-state.updated", payload: goal });
9780
10938
  } catch {
9781
- broadcast2({ type: "goal.updated", payload: null });
10939
+ broadcast2({ type: "goal-state.updated", payload: null });
9782
10940
  }
9783
10941
  }
9784
10942
 
@@ -10027,18 +11185,20 @@ import {
10027
11185
  createBoard,
10028
11186
  duplicateBoard,
10029
11187
  exportBoardToTaskGraph,
10030
- generateBoardFromDescription,
10031
- getBoard,
11188
+ createBoardFromText,
11189
+ getBoard as getBoard2,
10032
11190
  getKanbanOrchestrationSnapshot,
10033
11191
  getKanbanQueueHealth,
10034
11192
  getTask,
10035
11193
  getTaskChain,
10036
11194
  listBoards,
10037
11195
  listReadyTasks,
11196
+ listTaskActivity,
10038
11197
  mergeTasks,
10039
11198
  moveTask,
10040
11199
  parseLinesIntoTasks,
10041
11200
  reconcileKanbanBoard,
11201
+ recordTaskActivity,
10042
11202
  recoverStaleTaskAssignments,
10043
11203
  releaseTaskClaim,
10044
11204
  removeBoard,
@@ -10047,13 +11207,41 @@ import {
10047
11207
  setTaskChain,
10048
11208
  splitTask,
10049
11209
  syncBoardFromTaskGraph,
11210
+ touchKanbanPresence,
10050
11211
  transferTaskToBoard,
11212
+ transitionTask,
10051
11213
  updateBoard,
10052
11214
  updateCheckOnTask,
10053
11215
  updateGoalMetricOnTask,
10054
11216
  updateTask
10055
11217
  } from "@wrongstack/kanban";
10056
11218
  import { applySessionKanbanTaskToSource } from "@wrongstack/tools/session-kanban";
11219
+ function paginateKanbanBoards(boards, input) {
11220
+ const pageSize = Math.min(100, Math.max(1, Math.floor(input.pageSize)));
11221
+ const activeSessionIds = new Set(input.activeSessionIds ?? []);
11222
+ const isActive = (board) => board.presence?.some((entry) => entry.active) === true || board.tags?.some(
11223
+ (tag) => tag.startsWith("session:") && activeSessionIds.has(tag.slice(8))
11224
+ ) === true;
11225
+ const sorted = [...boards].sort((left, right) => {
11226
+ const activityOrder = Number(isActive(right)) - Number(isActive(left));
11227
+ return activityOrder || right.updatedAt.localeCompare(left.updatedAt);
11228
+ });
11229
+ const activeTotal = sorted.filter(isActive).length;
11230
+ const total = sorted.length;
11231
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
11232
+ const requestedPage = Number.isFinite(input.page) ? Math.floor(input.page) : 1;
11233
+ const page = Math.min(totalPages, Math.max(1, requestedPage));
11234
+ const start = (page - 1) * pageSize;
11235
+ return {
11236
+ items: sorted.slice(start, start + pageSize),
11237
+ total,
11238
+ page,
11239
+ pageSize,
11240
+ totalPages,
11241
+ activeTotal,
11242
+ orphanedTotal: total - activeTotal
11243
+ };
11244
+ }
10057
11245
  async function syncSessionSource(ctx, task, remove = false) {
10058
11246
  if (!ctx.context) return;
10059
11247
  const update = await applySessionKanbanTaskToSource(ctx.context, task, { remove });
@@ -10074,22 +11262,62 @@ function fail(ws, type, message) {
10074
11262
  function has(payload, key) {
10075
11263
  return payload !== void 0 && Object.hasOwn(payload, key);
10076
11264
  }
11265
+ function activityContext(ctx, actor, note) {
11266
+ const sessionId = ctx.context?.session?.id;
11267
+ return {
11268
+ ...sessionId ? { sessionId } : {},
11269
+ ...actor ? { actor } : {},
11270
+ ...note?.trim() ? { note: note.trim() } : {}
11271
+ };
11272
+ }
11273
+ async function touchTaskPresence(ctx, boardId, taskId) {
11274
+ const context = ctx.context;
11275
+ const sessionId = context?.session?.id;
11276
+ if (!context || !sessionId) return null;
11277
+ try {
11278
+ return await touchKanbanPresence(ctx.projectRoot, boardId, {
11279
+ sessionId,
11280
+ agentId: context.agentId || "webui",
11281
+ agentName: context.agentName || context.agentId || "WebUI",
11282
+ taskId
11283
+ });
11284
+ } catch {
11285
+ return null;
11286
+ }
11287
+ }
10077
11288
  async function handleKanbanRoute(ws, msg, ctx) {
10078
11289
  if (!msg.type.startsWith("kanban.")) return false;
10079
11290
  const payload = msg.payload;
10080
11291
  const type = msg.type;
10081
11292
  try {
10082
11293
  switch (type) {
10083
- case "kanban.list":
10084
- ok(ws, type, await listBoards(ctx.projectRoot));
11294
+ case "kanban.list": {
11295
+ const boards = await listBoards(ctx.projectRoot);
11296
+ const requestedPage = Number(payload?.page);
11297
+ const requestedPageSize = Number(payload?.pageSize);
11298
+ if (!Number.isFinite(requestedPage) || !Number.isFinite(requestedPageSize)) {
11299
+ ok(ws, type, boards);
11300
+ return true;
11301
+ }
11302
+ const activeSessionIds = Array.isArray(payload?.activeSessionIds) ? payload.activeSessionIds.filter((id) => typeof id === "string") : [];
11303
+ ok(
11304
+ ws,
11305
+ type,
11306
+ paginateKanbanBoards(boards, {
11307
+ page: requestedPage,
11308
+ pageSize: requestedPageSize,
11309
+ activeSessionIds
11310
+ })
11311
+ );
10085
11312
  return true;
11313
+ }
10086
11314
  case "kanban.get": {
10087
11315
  const boardId = payload?.boardId;
10088
11316
  if (!boardId) {
10089
11317
  fail(ws, type, "boardId required");
10090
11318
  return true;
10091
11319
  }
10092
- const board = await getBoard(ctx.projectRoot, boardId);
11320
+ const board = await getBoard2(ctx.projectRoot, boardId);
10093
11321
  board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
10094
11322
  return true;
10095
11323
  }
@@ -10109,7 +11337,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10109
11337
  fail(ws, type, "boardId required");
10110
11338
  return true;
10111
11339
  }
10112
- const board = await getBoard(ctx.projectRoot, boardId);
11340
+ const board = await getBoard2(ctx.projectRoot, boardId);
10113
11341
  if (!board) {
10114
11342
  fail(ws, type, `Board not found: ${boardId}`);
10115
11343
  return true;
@@ -10147,7 +11375,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
10147
11375
  title,
10148
11376
  ...payload?.description ? { description: payload.description } : {},
10149
11377
  ...payload?.tags ? { tags: payload.tags } : {},
10150
- ...payload?.columns ? { columns: payload.columns } : {}
11378
+ ...payload?.columns ? { columns: payload.columns } : {},
11379
+ ...has(payload, "lifecycle") ? { lifecycle: payload?.lifecycle } : {},
11380
+ ...has(payload, "boundary") ? { boundary: payload?.boundary } : {}
10151
11381
  })
10152
11382
  );
10153
11383
  return true;
@@ -10163,8 +11393,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
10163
11393
  ...payload?.description ? { description: payload.description } : {},
10164
11394
  ...payload?.tags ? { tags: payload.tags } : {},
10165
11395
  ...payload?.columns ? { columns: payload.columns } : {},
11396
+ ...has(payload, "lifecycle") ? {
11397
+ lifecycle: payload?.lifecycle ?? null
11398
+ } : {},
10166
11399
  ...has(payload, "supervisor") ? {
10167
11400
  supervisor: payload?.supervisor ?? null
11401
+ } : {},
11402
+ ...has(payload, "boundary") ? {
11403
+ boundary: payload?.boundary ?? null
10168
11404
  } : {}
10169
11405
  });
10170
11406
  board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
@@ -10191,7 +11427,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10191
11427
  fail(ws, type, "boardId required");
10192
11428
  return true;
10193
11429
  }
10194
- const board = await getBoard(ctx.projectRoot, boardId);
11430
+ const board = await getBoard2(ctx.projectRoot, boardId);
10195
11431
  const activeSessionId = ctx.context?.session?.id;
10196
11432
  if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
10197
11433
  fail(ws, type, "The active session Kanban board cannot be deleted.");
@@ -10211,7 +11447,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10211
11447
  }
10212
11448
  const board = await createBoard(
10213
11449
  ctx.projectRoot,
10214
- generateBoardFromDescription({
11450
+ createBoardFromText({
10215
11451
  description,
10216
11452
  ...payload?.title ? { title: payload.title } : {},
10217
11453
  ...payload?.context ? { context: payload.context } : {}
@@ -10223,7 +11459,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
10223
11459
  )) {
10224
11460
  await addTask(ctx.projectRoot, board.id, taskInput);
10225
11461
  }
10226
- ok(ws, type, await getBoard(ctx.projectRoot, board.id) ?? board);
11462
+ ok(ws, type, await getBoard2(ctx.projectRoot, board.id) ?? board);
10227
11463
  return true;
10228
11464
  }
10229
11465
  case "kanban.task.ready": {
@@ -10313,14 +11549,21 @@ async function handleKanbanRoute(ws, msg, ctx) {
10313
11549
  fail(ws, type, "boardId and title required");
10314
11550
  return true;
10315
11551
  }
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
- });
11552
+ const result = await addTask(
11553
+ ctx.projectRoot,
11554
+ boardId,
11555
+ {
11556
+ title,
11557
+ columnId: payload?.columnId ?? "backlog",
11558
+ ...payload?.description ? { description: payload.description } : {},
11559
+ ...payload?.dueDate ? { dueDate: payload.dueDate } : {},
11560
+ ...payload?.priority ? { priority: payload.priority } : {},
11561
+ ...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
11562
+ ...payload?.labels ? { labels: payload.labels } : {},
11563
+ ...has(payload, "boundary") ? { boundary: payload?.boundary } : {}
11564
+ },
11565
+ activityContext(ctx, "webui", payload?.activityNote)
11566
+ );
10324
11567
  result ? ok(ws, type, result.task) : fail(ws, type, `Board not found: ${boardId}`);
10325
11568
  return true;
10326
11569
  }
@@ -10376,25 +11619,35 @@ async function handleKanbanRoute(ws, msg, ctx) {
10376
11619
  fail(ws, type, "boardId and taskId required");
10377
11620
  return true;
10378
11621
  }
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
- });
11622
+ const board = await updateTask(
11623
+ ctx.projectRoot,
11624
+ boardId,
11625
+ taskId,
11626
+ {
11627
+ ...has(payload, "title") ? { title: payload?.title } : {},
11628
+ ...has(payload, "description") ? { description: payload?.description ?? "" } : {},
11629
+ ...has(payload, "dueDate") ? { dueDate: payload?.dueDate ?? null } : {},
11630
+ ...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
11631
+ ...has(payload, "priority") ? { priority: payload?.priority } : {},
11632
+ ...has(payload, "type") ? { type: payload?.type } : {},
11633
+ ...has(payload, "status") ? { status: payload?.status } : {},
11634
+ ...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
11635
+ ...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
11636
+ ...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
11637
+ ...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
11638
+ ...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
11639
+ ...has(payload, "retryPolicy") ? {
11640
+ retryPolicy: payload?.retryPolicy ?? null
11641
+ } : {},
11642
+ ...has(payload, "costCeilingUsd") ? {
11643
+ costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
11644
+ } : {},
11645
+ ...has(payload, "boundary") ? {
11646
+ boundary: payload?.boundary ?? null
11647
+ } : {}
11648
+ },
11649
+ activityContext(ctx, "webui", payload?.activityNote)
11650
+ );
10398
11651
  if (!board) {
10399
11652
  fail(ws, type, "Board or task not found");
10400
11653
  return true;
@@ -10404,6 +11657,32 @@ async function handleKanbanRoute(ws, msg, ctx) {
10404
11657
  ok(ws, type, task);
10405
11658
  return true;
10406
11659
  }
11660
+ case "kanban.task.transition": {
11661
+ const boardId = payload?.boardId;
11662
+ const taskId = payload?.taskId;
11663
+ const to = payload?.to;
11664
+ const actor = payload?.actor;
11665
+ const comment = payload?.comment;
11666
+ if (!boardId || !taskId || !to || !actor || !comment) {
11667
+ fail(ws, type, "boardId, taskId, to, actor, and comment required");
11668
+ return true;
11669
+ }
11670
+ const result = await transitionTask(ctx.projectRoot, boardId, taskId, {
11671
+ to,
11672
+ actor,
11673
+ comment,
11674
+ ...payload?.action ? { action: payload.action } : {},
11675
+ ...payload?.attachment ? { attachment: payload.attachment } : {},
11676
+ ...payload?.patch ? { patch: payload.patch } : {}
11677
+ });
11678
+ if (!result) {
11679
+ fail(ws, type, "Board or task not found");
11680
+ return true;
11681
+ }
11682
+ await syncSessionSource(ctx, result.task);
11683
+ ok(ws, type, result);
11684
+ return true;
11685
+ }
10407
11686
  case "kanban.task.move": {
10408
11687
  const boardId = payload?.boardId;
10409
11688
  const taskId = payload?.taskId;
@@ -10417,7 +11696,8 @@ async function handleKanbanRoute(ws, msg, ctx) {
10417
11696
  boardId,
10418
11697
  taskId,
10419
11698
  columnId,
10420
- payload?.order
11699
+ payload?.order,
11700
+ activityContext(ctx, "webui", payload?.activityNote)
10421
11701
  );
10422
11702
  if (!board) {
10423
11703
  fail(ws, type, "Move failed");
@@ -10522,14 +11802,24 @@ async function handleKanbanRoute(ws, msg, ctx) {
10522
11802
  fail(ws, type, "boardId, taskId, and name required");
10523
11803
  return true;
10524
11804
  }
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
- });
11805
+ const board = await addGoalMetricToTask(
11806
+ ctx.projectRoot,
11807
+ boardId,
11808
+ taskId,
11809
+ {
11810
+ name: name2,
11811
+ ...payload?.status ? { status: payload.status } : {},
11812
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
11813
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
11814
+ ...payload?.unit ? { unit: payload.unit } : {},
11815
+ ...payload?.notes ? { notes: payload.notes } : {}
11816
+ },
11817
+ activityContext(
11818
+ ctx,
11819
+ "webui",
11820
+ payload?.activityNote ?? `Goal metric added: ${name2}.`
11821
+ )
11822
+ );
10533
11823
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10534
11824
  return true;
10535
11825
  }
@@ -10541,14 +11831,25 @@ async function handleKanbanRoute(ws, msg, ctx) {
10541
11831
  fail(ws, type, "boardId, taskId, and metricId required");
10542
11832
  return true;
10543
11833
  }
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
- });
11834
+ const board = await updateGoalMetricOnTask(
11835
+ ctx.projectRoot,
11836
+ boardId,
11837
+ taskId,
11838
+ metricId,
11839
+ {
11840
+ ...payload?.name ? { name: payload.name } : {},
11841
+ ...payload?.status ? { status: payload.status } : {},
11842
+ ...payload?.target !== void 0 ? { target: payload.target } : {},
11843
+ ...payload?.current !== void 0 ? { current: payload.current } : {},
11844
+ ...payload?.unit ? { unit: payload.unit } : {},
11845
+ ...payload?.notes ? { notes: payload.notes } : {}
11846
+ },
11847
+ activityContext(
11848
+ ctx,
11849
+ "webui",
11850
+ payload?.activityNote ?? "Goal metric updated in WebUI."
11851
+ )
11852
+ );
10552
11853
  board ? ok(ws, type, board) : fail(ws, type, "Metric not found");
10553
11854
  return true;
10554
11855
  }
@@ -10559,23 +11860,29 @@ async function handleKanbanRoute(ws, msg, ctx) {
10559
11860
  fail(ws, type, "boardId and taskId required");
10560
11861
  return true;
10561
11862
  }
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
- });
11863
+ const board = await assignTask(
11864
+ ctx.projectRoot,
11865
+ boardId,
11866
+ taskId,
11867
+ {
11868
+ ...payload?.agentId ? { agentId: payload.agentId } : {},
11869
+ ...payload?.name ? { name: payload.name } : {},
11870
+ ...payload?.role ? { role: payload.role } : {},
11871
+ ...payload?.provider ? { provider: payload.provider } : {},
11872
+ ...payload?.model ? { model: payload.model } : {},
11873
+ ...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
11874
+ ...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
11875
+ ...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
11876
+ ...payload?.skills ? { skills: payload.skills } : {},
11877
+ ...payload?.tools ? { tools: payload.tools } : {},
11878
+ ...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
11879
+ ...payload?.assignee ? { assignee: payload.assignee } : {},
11880
+ ...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
11881
+ ...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
11882
+ ...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
11883
+ },
11884
+ activityContext(ctx, void 0, payload?.activityNote)
11885
+ );
10579
11886
  board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
10580
11887
  return true;
10581
11888
  }
@@ -10587,11 +11894,21 @@ async function handleKanbanRoute(ws, msg, ctx) {
10587
11894
  fail(ws, type, "boardId, taskId, and description required");
10588
11895
  return true;
10589
11896
  }
10590
- const board = await addCheckToTask(ctx.projectRoot, boardId, taskId, {
10591
- description,
10592
- type: payload?.checkType ?? "manual",
10593
- status: payload?.status ?? "pending"
10594
- });
11897
+ const board = await addCheckToTask(
11898
+ ctx.projectRoot,
11899
+ boardId,
11900
+ taskId,
11901
+ {
11902
+ description,
11903
+ type: payload?.checkType ?? "manual",
11904
+ status: payload?.status ?? "pending"
11905
+ },
11906
+ activityContext(
11907
+ ctx,
11908
+ "webui",
11909
+ payload?.activityNote ?? `Acceptance check added: ${description}.`
11910
+ )
11911
+ );
10595
11912
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10596
11913
  return true;
10597
11914
  }
@@ -10603,9 +11920,20 @@ async function handleKanbanRoute(ws, msg, ctx) {
10603
11920
  fail(ws, type, "boardId, taskId, and checkId required");
10604
11921
  return true;
10605
11922
  }
10606
- const board = await updateCheckOnTask(ctx.projectRoot, boardId, taskId, checkId, {
10607
- ...has(payload, "status") ? { status: payload?.status } : {}
10608
- });
11923
+ const board = await updateCheckOnTask(
11924
+ ctx.projectRoot,
11925
+ boardId,
11926
+ taskId,
11927
+ checkId,
11928
+ {
11929
+ ...has(payload, "status") ? { status: payload?.status } : {}
11930
+ },
11931
+ activityContext(
11932
+ ctx,
11933
+ "webui",
11934
+ payload?.activityNote ?? `Acceptance check updated${payload?.status ? ` to ${String(payload.status)}` : ""}.`
11935
+ )
11936
+ );
10609
11937
  if (!board) fail(ws, type, "Check not found");
10610
11938
  else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
10611
11939
  return true;
@@ -10618,10 +11946,17 @@ async function handleKanbanRoute(ws, msg, ctx) {
10618
11946
  fail(ws, type, "boardId, taskId, and content required");
10619
11947
  return true;
10620
11948
  }
10621
- const board = await addNoteToTask(ctx.projectRoot, boardId, taskId, {
10622
- author: payload?.author ?? "webui",
10623
- content
10624
- });
11949
+ const author = payload?.author ?? "webui";
11950
+ const board = await addNoteToTask(
11951
+ ctx.projectRoot,
11952
+ boardId,
11953
+ taskId,
11954
+ {
11955
+ author,
11956
+ content
11957
+ },
11958
+ activityContext(ctx, author)
11959
+ );
10625
11960
  board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10626
11961
  return true;
10627
11962
  }
@@ -10664,7 +11999,62 @@ async function handleKanbanRoute(ws, msg, ctx) {
10664
11999
  return true;
10665
12000
  }
10666
12001
  const task = await getTask(ctx.projectRoot, boardId, taskId);
10667
- task ? ok(ws, type, task) : fail(ws, type, "Task not found");
12002
+ if (task) {
12003
+ await touchTaskPresence(ctx, boardId, task.id);
12004
+ ok(ws, type, task);
12005
+ } else {
12006
+ fail(ws, type, "Task not found");
12007
+ }
12008
+ return true;
12009
+ }
12010
+ case "kanban.task.activity": {
12011
+ const boardId = payload?.boardId;
12012
+ const taskId = payload?.taskId;
12013
+ if (!boardId || !taskId) {
12014
+ fail(ws, type, "boardId and taskId required");
12015
+ return true;
12016
+ }
12017
+ const presenceBoard = await touchTaskPresence(ctx, boardId, taskId);
12018
+ const events = await listTaskActivity(ctx.projectRoot, boardId, taskId, {
12019
+ ...typeof payload?.limit === "number" ? { limit: payload.limit } : {}
12020
+ });
12021
+ ok(ws, type, {
12022
+ boardId,
12023
+ taskId,
12024
+ events,
12025
+ presence: presenceBoard?.presence?.filter((entry) => entry.taskId === taskId) ?? []
12026
+ });
12027
+ return true;
12028
+ }
12029
+ case "kanban.task.activity.add": {
12030
+ const boardId = payload?.boardId;
12031
+ const taskId = payload?.taskId;
12032
+ const kind = payload?.kind;
12033
+ const summary = payload?.summary;
12034
+ const allowedKinds = ["decision", "attempt", "result", "blocker", "observation"];
12035
+ const allowedOutcomes = ["succeeded", "failed", "partial", "skipped", "unknown"];
12036
+ if (!boardId || !taskId || !summary?.trim() || !allowedKinds.includes(kind)) {
12037
+ fail(ws, type, "boardId, taskId, summary, and a valid activity kind required");
12038
+ return true;
12039
+ }
12040
+ const requestedOutcome = payload?.outcome;
12041
+ const outcome = allowedOutcomes.includes(requestedOutcome) ? requestedOutcome : "unknown";
12042
+ const board = await recordTaskActivity(
12043
+ ctx.projectRoot,
12044
+ boardId,
12045
+ taskId,
12046
+ {
12047
+ kind,
12048
+ summary: summary.trim(),
12049
+ outcome,
12050
+ ...typeof payload?.details === "string" && payload.details.trim() ? { details: payload.details.trim() } : {}
12051
+ },
12052
+ activityContext(
12053
+ ctx,
12054
+ payload?.actor ?? ctx.context?.agentId ?? "webui"
12055
+ )
12056
+ );
12057
+ board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
10668
12058
  return true;
10669
12059
  }
10670
12060
  case "kanban.column.add": {
@@ -11142,6 +12532,18 @@ async function handleSpecsRoute(_ws, msg, handlers) {
11142
12532
  }
11143
12533
 
11144
12534
  // src/server/message-dispatcher.ts
12535
+ var chronicleCache = /* @__PURE__ */ new Map();
12536
+ async function chronicleEngine(projectRoot) {
12537
+ const now = Date.now();
12538
+ const cached = chronicleCache.get(projectRoot);
12539
+ if (cached && now - cached.loadedAt < 6e4) return cached.engine;
12540
+ const paths = resolveWstackPaths2({ projectRoot, userHome: os2.homedir() });
12541
+ const engine = await ChronicleQueryEngine.fromDirectory(
12542
+ path20.join(paths.projectDir, "chronicle")
12543
+ );
12544
+ chronicleCache.set(projectRoot, { loadedAt: now, engine });
12545
+ return engine;
12546
+ }
11145
12547
  function createMessageDispatcher(opts) {
11146
12548
  const { state, deps: deps2, cb, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
11147
12549
  function makeWorklistContext() {
@@ -11153,7 +12555,8 @@ function createMessageDispatcher(opts) {
11153
12555
  state: deps2.context.state
11154
12556
  },
11155
12557
  send: (w, m) => send(w, m),
11156
- broadcast: (m) => broadcast(state.getClients(), m)
12558
+ broadcast: (m) => broadcast(state.getClients(), m),
12559
+ replaceTodos: (todos) => deps2.context.state.replaceTodos(todos)
11157
12560
  };
11158
12561
  }
11159
12562
  function makeSkillsContext() {
@@ -11162,7 +12565,7 @@ function createMessageDispatcher(opts) {
11162
12565
  skillLoader: deps2.skillLoader,
11163
12566
  skillInstaller: deps2.skillInstaller,
11164
12567
  projectRoot,
11165
- projectSkillsDir: path18.join(projectRoot, ".wrongstack", "skills"),
12568
+ projectSkillsDir: path20.join(projectRoot, ".wrongstack", "skills"),
11166
12569
  globalSkillsDir: deps2.wpaths.globalSkills
11167
12570
  };
11168
12571
  }
@@ -11200,7 +12603,7 @@ function createMessageDispatcher(opts) {
11200
12603
  if (await handleMailboxRoute(ws, msg, routes.mailboxRoutes)) return;
11201
12604
  if (await handleMcpRoute(ws, msg, routes.mcpRoutes)) return;
11202
12605
  if (await handleBrainRoute(ws, msg, routes.brainRoutes)) return;
11203
- if (await handleAutoPhaseRoute(ws, msg, routes.autoPhaseRoutes)) return;
12606
+ if (await handleGoalRoute(ws, msg, routes.goalRoutes)) return;
11204
12607
  if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
11205
12608
  if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
11206
12609
  if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
@@ -11404,6 +12807,14 @@ function createMessageDispatcher(opts) {
11404
12807
  return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
11405
12808
  case "memory.super.remember":
11406
12809
  return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
12810
+ case "memory.super.recover":
12811
+ return handleSuperMemoryRecover(ws, msg, deps2.memoryStore);
12812
+ case "memory.super.candidateResolve":
12813
+ return handleSuperMemoryCandidateResolve(ws, msg, deps2.memoryStore);
12814
+ case "memory.super.backfillRecoverable":
12815
+ return handleSuperMemoryBackfillRecoverable(ws, msg, deps2.memoryStore);
12816
+ case "memory.super.forFile":
12817
+ return handleSuperMemoryForFile(ws, msg, deps2.memoryStore);
11407
12818
  // ── MCP tripwires — handleMcpRoute claims these upstream. ──
11408
12819
  case "mcp.list":
11409
12820
  throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
@@ -11634,6 +13045,59 @@ function createMessageDispatcher(opts) {
11634
13045
  });
11635
13046
  break;
11636
13047
  }
13048
+ // ── Chronicle journal queries (parity with embedded webui-server) ──
13049
+ // Mirrors packages/cli/src/webui-server/message-router.ts:645-664.
13050
+ // The engine is cached for 1s to avoid re-reading the journal on every
13051
+ // query; the cache is module-scoped so it survives across messages on
13052
+ // the same connection.
13053
+ case "chronicle.query": {
13054
+ const payload = msg.payload ?? {};
13055
+ const engine = await chronicleEngine(state.getProjectRoot());
13056
+ send(ws, { type: "chronicle.query_result", payload: await engine.query(payload.query ?? {}) });
13057
+ break;
13058
+ }
13059
+ case "chronicle.facet": {
13060
+ const payload = msg.payload ?? {};
13061
+ const allowed = /* @__PURE__ */ new Set([
13062
+ "eventType",
13063
+ "outcome",
13064
+ "projectId",
13065
+ "sessionId",
13066
+ "agentId",
13067
+ "taskId",
13068
+ "providerId",
13069
+ "modelId",
13070
+ "resourceKind",
13071
+ "resourcePath",
13072
+ "toolCallId"
13073
+ ]);
13074
+ if (!payload.field || !allowed.has(payload.field)) {
13075
+ send(ws, {
13076
+ type: "chronicle.error",
13077
+ payload: { message: "Invalid Chronicle facet field." }
13078
+ });
13079
+ break;
13080
+ }
13081
+ const engine = await chronicleEngine(state.getProjectRoot());
13082
+ send(ws, {
13083
+ type: "chronicle.facet_result",
13084
+ payload: {
13085
+ field: payload.field,
13086
+ values: await engine.facet(payload.field, payload.query ?? {}, payload.limit),
13087
+ diagnostics: engine.diagnostics
13088
+ }
13089
+ });
13090
+ break;
13091
+ }
13092
+ case "chronicle.graph": {
13093
+ const payload = msg.payload ?? {};
13094
+ const engine = await chronicleEngine(state.getProjectRoot());
13095
+ send(ws, {
13096
+ type: "chronicle.graph_result",
13097
+ payload: await engine.graph(payload.seed ?? {}, payload.hops, payload.maxNodes)
13098
+ });
13099
+ break;
13100
+ }
11637
13101
  case "process.list": {
11638
13102
  await handleProcessList(ws);
11639
13103
  break;
@@ -11651,7 +13115,7 @@ function createMessageDispatcher(opts) {
11651
13115
  process.kill(process.pid, "SIGINT");
11652
13116
  break;
11653
13117
  }
11654
- case "goal.get": {
13118
+ case "goal-state.get": {
11655
13119
  await handleGoalGet(state.getProjectRoot(), (m) => broadcast(state.getClients(), m));
11656
13120
  break;
11657
13121
  }
@@ -11685,9 +13149,9 @@ function createMessageDispatcher(opts) {
11685
13149
  }
11686
13150
 
11687
13151
  // src/server/pref-helpers.ts
11688
- import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
13152
+ import * as fs14 from "node:fs/promises";
11689
13153
  import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
11690
- import * as fs13 from "node:fs/promises";
13154
+ import { atomicWrite as atomicWrite6, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
11691
13155
  var PREF_KEYS = [
11692
13156
  "autonomy",
11693
13157
  "autonomyDelayMs",
@@ -11732,6 +13196,7 @@ var PREF_KEYS = [
11732
13196
  "fallbackProfiles",
11733
13197
  "favoriteModels",
11734
13198
  "favoriteModelsOnly",
13199
+ "modelAvailabilitySchedule",
11735
13200
  "modelMatrix",
11736
13201
  "fallbackAuto",
11737
13202
  // Refiner + TUI visual prefs (parity with the CLI's embedded server —
@@ -11742,11 +13207,31 @@ var PREF_KEYS = [
11742
13207
  "thinkingWord",
11743
13208
  "statuslineMode",
11744
13209
  "animationStyle",
13210
+ "showModelReasoning",
11745
13211
  // Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
11746
13212
  "breakerEnabled",
11747
13213
  "breakerAutoKillResetMs",
11748
13214
  "fsAccess",
11749
- "debugStream"
13215
+ "debugStream",
13216
+ // Chimera (post-session) + auto-review (mid-session) settings.
13217
+ // Persisted to config.extensions['wstack-chimera'] / ['wstack-auto-review']
13218
+ // so the running plugins pick up changes after a session restart.
13219
+ "chimeraEnabled",
13220
+ "chimeraProvider",
13221
+ "chimeraModel",
13222
+ "chimeraMaxFiles",
13223
+ "chimeraAutoFix",
13224
+ "autoReviewEnabled",
13225
+ "autoReviewProvider",
13226
+ "autoReviewModel",
13227
+ "autoReviewFallbackProfile",
13228
+ "autoReviewFallbackModels",
13229
+ "autoReviewDebounceMs",
13230
+ "autoReviewMaxFilesPerBatch",
13231
+ "autoReviewMaxConcurrentReviews",
13232
+ "autoReviewCascadeOn",
13233
+ // Per-plugin enable/disable map (parity with the embedded server).
13234
+ "pluginsEnabled"
11750
13235
  ];
11751
13236
  function prefSnapshot(contextMeta) {
11752
13237
  const snapshot = {};
@@ -11760,7 +13245,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
11760
13245
  const write = async () => {
11761
13246
  let raw;
11762
13247
  try {
11763
- raw = await fs13.readFile(globalConfigPath, "utf8");
13248
+ raw = await fs14.readFile(globalConfigPath, "utf8");
11764
13249
  } catch {
11765
13250
  raw = "{}";
11766
13251
  }
@@ -11784,219 +13269,264 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
11784
13269
  try {
11785
13270
  await next;
11786
13271
  } catch (err) {
11787
- logger.warn(`${errorLabel}: failed to persist to config: ${err instanceof Error ? err.message : String(err)}`);
13272
+ logger.warn(
13273
+ `${errorLabel}: failed to persist to config: ${err instanceof Error ? err.message : String(err)}`
13274
+ );
11788
13275
  }
11789
13276
  }
11790
13277
  async function persistPrefsToConfig(deps2, holder, payload) {
11791
- return updateGlobalConfig(deps2, holder, (decrypted) => {
11792
- const autonomyCfg = decrypted.autonomy ?? {};
11793
- let autonomyTouched = false;
11794
- const setAutonomy = (key, val) => {
11795
- autonomyCfg[key] = val;
11796
- autonomyTouched = true;
11797
- };
11798
- if (typeof payload["autonomy"] === "string" && ["off", "suggest", "auto"].includes(payload["autonomy"])) {
11799
- setAutonomy("defaultMode", payload["autonomy"]);
11800
- }
11801
- if (typeof payload["autonomyDelayMs"] === "number")
11802
- setAutonomy("autoProceedDelayMs", payload["autonomyDelayMs"]);
11803
- if (typeof payload["autoProceedMaxIterations"] === "number")
11804
- setAutonomy("autoProceedMaxIterations", payload["autoProceedMaxIterations"]);
11805
- if (typeof payload["yolo"] === "boolean") {
11806
- setAutonomy("yolo", payload["yolo"]);
11807
- decrypted.yolo = payload["yolo"];
11808
- }
11809
- if (typeof payload["chime"] === "boolean") setAutonomy("chime", payload["chime"]);
11810
- if (typeof payload["confirmExit"] === "boolean")
11811
- setAutonomy("confirmExit", payload["confirmExit"]);
11812
- if (typeof payload["streamFleet"] === "boolean")
11813
- setAutonomy("streamFleet", payload["streamFleet"]);
11814
- if (typeof payload["enhanceEnabled"] === "boolean")
11815
- setAutonomy("enhance", payload["enhanceEnabled"]);
11816
- if (typeof payload["enhanceDelayMs"] === "number")
11817
- setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
11818
- if (typeof payload["enhanceLanguage"] === "string")
11819
- setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
11820
- if (typeof payload["refinerProvider"] === "string")
11821
- setAutonomy("refinerProvider", payload["refinerProvider"]);
11822
- if (typeof payload["refinerModel"] === "string")
11823
- setAutonomy("refinerModel", payload["refinerModel"]);
11824
- if (typeof payload["refinerFallbackProfile"] === "string")
11825
- setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
11826
- if (typeof payload["thinkingWord"] === "string")
11827
- setAutonomy("thinkingWord", payload["thinkingWord"]);
11828
- if (typeof payload["statuslineMode"] === "string")
11829
- setAutonomy("statuslineMode", payload["statuslineMode"]);
11830
- if (typeof payload["animationStyle"] === "string")
11831
- setAutonomy("animationStyle", payload["animationStyle"]);
11832
- if (autonomyTouched) decrypted.autonomy = autonomyCfg;
11833
- if (typeof payload["nextPrediction"] === "boolean")
11834
- decrypted.nextPrediction = payload["nextPrediction"];
11835
- if (typeof payload["uiLocale"] === "string") decrypted.uiLocale = payload["uiLocale"];
11836
- if (Array.isArray(payload["fallbackModels"]))
11837
- decrypted.fallbackModels = payload["fallbackModels"];
11838
- if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"])) {
11839
- decrypted.fallbackProfiles = payload["fallbackProfiles"];
11840
- }
11841
- if (Array.isArray(payload["favoriteModels"]))
11842
- decrypted.favoriteModels = payload["favoriteModels"];
11843
- if (typeof payload["favoriteModelsOnly"] === "boolean")
11844
- decrypted.favoriteModelsOnly = payload["favoriteModelsOnly"];
11845
- if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"])) {
11846
- decrypted.modelMatrix = payload["modelMatrix"];
11847
- }
11848
- if (typeof payload["fallbackAuto"] === "boolean")
11849
- decrypted.fallbackAuto = payload["fallbackAuto"];
11850
- const FEATURE_MAP = {
11851
- featureMcp: "mcp",
11852
- featurePlugins: "plugins",
11853
- featureMemory: "memory",
11854
- featureSkills: "skills",
11855
- featureModelsRegistry: "modelsRegistry"
11856
- };
11857
- for (const [prefKey, cfgKey] of Object.entries(FEATURE_MAP)) {
11858
- if (typeof payload[prefKey] === "boolean") {
11859
- const feats = decrypted.features ?? {};
11860
- feats[cfgKey] = payload[prefKey];
11861
- decrypted.features = feats;
11862
- }
11863
- }
11864
- if (typeof payload["contextAutoCompact"] === "boolean" || typeof payload["contextStrategy"] === "string" || typeof payload["contextMode"] === "string") {
11865
- const ctxCfg = decrypted.context ?? {};
11866
- if (typeof payload["contextAutoCompact"] === "boolean")
11867
- ctxCfg.autoCompact = payload["contextAutoCompact"];
11868
- if (typeof payload["contextStrategy"] === "string")
11869
- ctxCfg.strategy = payload["contextStrategy"];
11870
- if (typeof payload["contextMode"] === "string") ctxCfg.mode = payload["contextMode"];
11871
- decrypted.context = ctxCfg;
11872
- }
11873
- if (typeof payload["tokenSavingTier"] === "string") {
11874
- const featsCfg = decrypted.features ?? {};
11875
- featsCfg.tokenSavingMode = payload["tokenSavingTier"];
11876
- decrypted.features = featsCfg;
11877
- }
11878
- if (typeof payload["maxConcurrent"] === "number") {
11879
- decrypted.maxConcurrent = payload["maxConcurrent"];
11880
- }
11881
- if (typeof payload["titleAnimation"] === "boolean") {
11882
- const autoCfg = decrypted.autonomy ?? {};
11883
- autoCfg.terminalTitleAnimation = payload["titleAnimation"];
11884
- decrypted.autonomy = autoCfg;
11885
- }
11886
- if (typeof payload["logLevel"] === "string") {
11887
- const logCfg = decrypted.log ?? {};
11888
- logCfg.level = payload["logLevel"];
11889
- decrypted.log = logCfg;
11890
- }
11891
- if (typeof payload["auditLevel"] === "string") {
11892
- const sessionCfg = decrypted.session ?? {};
11893
- sessionCfg.auditLevel = payload["auditLevel"];
11894
- decrypted.session = sessionCfg;
11895
- }
11896
- if (typeof payload["indexOnStart"] === "boolean") {
11897
- const indexingCfg = decrypted.indexing ?? {};
11898
- indexingCfg.onSessionStart = payload["indexOnStart"];
11899
- decrypted.indexing = indexingCfg;
11900
- }
11901
- if (typeof payload["maxIterations"] === "number") {
11902
- const toolsCfg = decrypted.tools ?? {};
11903
- toolsCfg.maxIterations = payload["maxIterations"];
11904
- decrypted.tools = toolsCfg;
11905
- }
11906
- const hqTouched = typeof payload["hqEnabled"] === "boolean" || typeof payload["hqUrl"] === "string" || typeof payload["hqToken"] === "string" || typeof payload["hqRawContent"] === "boolean";
11907
- if (hqTouched) {
11908
- const hqCfg = decrypted.hq ?? {};
11909
- if (typeof payload["hqEnabled"] === "boolean") hqCfg.enabled = payload["hqEnabled"];
11910
- if (typeof payload["hqUrl"] === "string") hqCfg.url = payload["hqUrl"];
11911
- if (typeof payload["hqToken"] === "string") hqCfg.token = payload["hqToken"];
11912
- if (typeof payload["hqRawContent"] === "boolean")
11913
- hqCfg.rawContent = payload["hqRawContent"];
11914
- decrypted.hq = hqCfg;
11915
- }
11916
- const tgTouched = typeof payload["tgSessionEnd"] === "boolean" || typeof payload["tgDelegate"] === "boolean" || typeof payload["tgLongToolMs"] === "number";
11917
- if (tgTouched) {
11918
- const ext = decrypted.extensions ?? {};
11919
- const tg = ext["telegram"] ?? {};
11920
- if (typeof payload["tgSessionEnd"] === "boolean") {
11921
- tg["notifyOnSessionEnd"] = payload["tgSessionEnd"];
11922
- }
11923
- if (typeof payload["tgDelegate"] === "boolean") {
11924
- tg["notifyOnDelegate"] = payload["tgDelegate"];
11925
- }
11926
- if (typeof payload["tgLongToolMs"] === "number") {
11927
- tg["longToolThresholdMs"] = payload["tgLongToolMs"];
11928
- }
11929
- ext["telegram"] = tg;
11930
- decrypted.extensions = ext;
11931
- }
11932
- const modelRuntimeTouched = typeof payload["reasoningMode"] === "string" || typeof payload["reasoningEffort"] === "string" || typeof payload["reasoningPreserve"] === "boolean" || typeof payload["cacheTtl"] === "string";
11933
- if (modelRuntimeTouched) {
11934
- const mr = decrypted.modelRuntime ?? {};
11935
- const reasoning = mr.reasoning ?? {};
11936
- if (typeof payload["reasoningMode"] === "string") reasoning.mode = payload["reasoningMode"];
11937
- if (typeof payload["reasoningEffort"] === "string")
11938
- reasoning.effort = payload["reasoningEffort"];
11939
- if (typeof payload["reasoningPreserve"] === "boolean")
11940
- reasoning.preserve = payload["reasoningPreserve"];
11941
- mr.reasoning = reasoning;
11942
- if (typeof payload["cacheTtl"] === "string" && payload["cacheTtl"] !== "default") {
11943
- mr.cache = { ttl: payload["cacheTtl"] };
11944
- } else if (payload["cacheTtl"] === "default") {
11945
- delete mr.cache;
11946
- }
11947
- decrypted.modelRuntime = mr;
11948
- }
11949
- if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
11950
- const cb = decrypted.circuitBreaker ?? {};
11951
- if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
11952
- if (typeof payload["breakerAutoKillResetMs"] === "number")
11953
- cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
11954
- decrypted.circuitBreaker = cb;
11955
- }
11956
- if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
11957
- const restrict = payload["fsAccess"] === "project";
11958
- const toolsCfg = decrypted.tools ?? {};
11959
- toolsCfg.restrictToProjectRoot = restrict;
11960
- decrypted.tools = toolsCfg;
11961
- const featsCfg = decrypted.features ?? {};
11962
- featsCfg.allowOutsideProjectRoot = !restrict;
11963
- decrypted.features = featsCfg;
11964
- }
11965
- if (typeof payload["debugStream"] === "boolean")
11966
- decrypted.debugStream = payload["debugStream"];
11967
- }, "prefs");
11968
- }
11969
-
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;
13278
+ return updateGlobalConfig(
13279
+ deps2,
13280
+ holder,
13281
+ (decrypted) => {
13282
+ const autonomyCfg = decrypted.autonomy ?? {};
13283
+ let autonomyTouched = false;
13284
+ const setAutonomy = (key, val) => {
13285
+ autonomyCfg[key] = val;
13286
+ autonomyTouched = true;
13287
+ };
13288
+ if (typeof payload["autonomy"] === "string" && ["off", "suggest", "auto"].includes(payload["autonomy"])) {
13289
+ setAutonomy("defaultMode", payload["autonomy"]);
13290
+ }
13291
+ if (typeof payload["autonomyDelayMs"] === "number")
13292
+ setAutonomy("autoProceedDelayMs", payload["autonomyDelayMs"]);
13293
+ if (typeof payload["autoProceedMaxIterations"] === "number")
13294
+ setAutonomy("autoProceedMaxIterations", payload["autoProceedMaxIterations"]);
13295
+ if (typeof payload["yolo"] === "boolean") {
13296
+ setAutonomy("yolo", payload["yolo"]);
13297
+ decrypted.yolo = payload["yolo"];
13298
+ }
13299
+ if (typeof payload["chime"] === "boolean") setAutonomy("chime", payload["chime"]);
13300
+ if (typeof payload["confirmExit"] === "boolean")
13301
+ setAutonomy("confirmExit", payload["confirmExit"]);
13302
+ if (typeof payload["streamFleet"] === "boolean")
13303
+ setAutonomy("streamFleet", payload["streamFleet"]);
13304
+ if (typeof payload["enhanceEnabled"] === "boolean")
13305
+ setAutonomy("enhance", payload["enhanceEnabled"]);
13306
+ if (typeof payload["enhanceDelayMs"] === "number")
13307
+ setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
13308
+ if (typeof payload["enhanceLanguage"] === "string")
13309
+ setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
13310
+ if (typeof payload["refinerProvider"] === "string")
13311
+ setAutonomy("refinerProvider", payload["refinerProvider"]);
13312
+ if (typeof payload["refinerModel"] === "string")
13313
+ setAutonomy("refinerModel", payload["refinerModel"]);
13314
+ if (typeof payload["refinerFallbackProfile"] === "string")
13315
+ setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
13316
+ if (typeof payload["thinkingWord"] === "string")
13317
+ setAutonomy("thinkingWord", payload["thinkingWord"]);
13318
+ if (typeof payload["statuslineMode"] === "string")
13319
+ setAutonomy("statuslineMode", payload["statuslineMode"]);
13320
+ if (typeof payload["animationStyle"] === "string")
13321
+ setAutonomy("animationStyle", payload["animationStyle"]);
13322
+ if (typeof payload["showModelReasoning"] === "boolean")
13323
+ setAutonomy("showModelReasoning", payload["showModelReasoning"]);
13324
+ if (autonomyTouched) decrypted.autonomy = autonomyCfg;
13325
+ if (typeof payload["nextPrediction"] === "boolean")
13326
+ decrypted.nextPrediction = payload["nextPrediction"];
13327
+ if (typeof payload["uiLocale"] === "string") decrypted.uiLocale = payload["uiLocale"];
13328
+ if (Array.isArray(payload["fallbackModels"]))
13329
+ decrypted.fallbackModels = payload["fallbackModels"];
13330
+ if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"])) {
13331
+ decrypted.fallbackProfiles = payload["fallbackProfiles"];
13332
+ }
13333
+ if (Array.isArray(payload["favoriteModels"]))
13334
+ decrypted.favoriteModels = payload["favoriteModels"];
13335
+ if (typeof payload["favoriteModelsOnly"] === "boolean")
13336
+ decrypted.favoriteModelsOnly = payload["favoriteModelsOnly"];
13337
+ if (Array.isArray(payload["modelAvailabilitySchedule"]))
13338
+ decrypted.modelAvailabilitySchedule = payload["modelAvailabilitySchedule"];
13339
+ if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"])) {
13340
+ decrypted.modelMatrix = payload["modelMatrix"];
13341
+ }
13342
+ if (typeof payload["fallbackAuto"] === "boolean")
13343
+ decrypted.fallbackAuto = payload["fallbackAuto"];
13344
+ const FEATURE_MAP = {
13345
+ featureMcp: "mcp",
13346
+ featurePlugins: "plugins",
13347
+ featureMemory: "memory",
13348
+ featureSkills: "skills",
13349
+ featureModelsRegistry: "modelsRegistry"
13350
+ };
13351
+ for (const [prefKey, cfgKey] of Object.entries(FEATURE_MAP)) {
13352
+ if (typeof payload[prefKey] === "boolean") {
13353
+ const feats = decrypted.features ?? {};
13354
+ feats[cfgKey] = payload[prefKey];
13355
+ decrypted.features = feats;
13356
+ }
13357
+ }
13358
+ if (typeof payload["contextAutoCompact"] === "boolean" || typeof payload["contextStrategy"] === "string" || typeof payload["contextMode"] === "string") {
13359
+ const ctxCfg = decrypted.context ?? {};
13360
+ if (typeof payload["contextAutoCompact"] === "boolean")
13361
+ ctxCfg.autoCompact = payload["contextAutoCompact"];
13362
+ if (typeof payload["contextStrategy"] === "string")
13363
+ ctxCfg.strategy = payload["contextStrategy"];
13364
+ if (typeof payload["contextMode"] === "string") ctxCfg.mode = payload["contextMode"];
13365
+ decrypted.context = ctxCfg;
13366
+ }
13367
+ if (typeof payload["tokenSavingTier"] === "string") {
13368
+ const featsCfg = decrypted.features ?? {};
13369
+ featsCfg.tokenSavingMode = payload["tokenSavingTier"];
13370
+ decrypted.features = featsCfg;
13371
+ }
13372
+ if (typeof payload["maxConcurrent"] === "number") {
13373
+ decrypted.maxConcurrent = payload["maxConcurrent"];
13374
+ }
13375
+ if (typeof payload["titleAnimation"] === "boolean") {
13376
+ const autoCfg = decrypted.autonomy ?? {};
13377
+ autoCfg.terminalTitleAnimation = payload["titleAnimation"];
13378
+ decrypted.autonomy = autoCfg;
13379
+ }
13380
+ if (typeof payload["logLevel"] === "string") {
13381
+ const logCfg = decrypted.log ?? {};
13382
+ logCfg.level = payload["logLevel"];
13383
+ decrypted.log = logCfg;
13384
+ }
13385
+ if (typeof payload["auditLevel"] === "string") {
13386
+ const sessionCfg = decrypted.session ?? {};
13387
+ sessionCfg.auditLevel = payload["auditLevel"];
13388
+ decrypted.session = sessionCfg;
13389
+ }
13390
+ if (typeof payload["indexOnStart"] === "boolean") {
13391
+ const indexingCfg = decrypted.indexing ?? {};
13392
+ indexingCfg.onSessionStart = payload["indexOnStart"];
13393
+ decrypted.indexing = indexingCfg;
13394
+ }
13395
+ if (typeof payload["maxIterations"] === "number") {
13396
+ const toolsCfg = decrypted.tools ?? {};
13397
+ toolsCfg.maxIterations = payload["maxIterations"];
13398
+ decrypted.tools = toolsCfg;
13399
+ }
13400
+ const hqTouched = typeof payload["hqEnabled"] === "boolean" || typeof payload["hqUrl"] === "string" || typeof payload["hqToken"] === "string" || typeof payload["hqRawContent"] === "boolean";
13401
+ if (hqTouched) {
13402
+ const hqCfg = decrypted.hq ?? {};
13403
+ if (typeof payload["hqEnabled"] === "boolean") hqCfg.enabled = payload["hqEnabled"];
13404
+ if (typeof payload["hqUrl"] === "string") hqCfg.url = payload["hqUrl"];
13405
+ if (typeof payload["hqToken"] === "string") hqCfg.token = payload["hqToken"];
13406
+ if (typeof payload["hqRawContent"] === "boolean")
13407
+ hqCfg.rawContent = payload["hqRawContent"];
13408
+ decrypted.hq = hqCfg;
13409
+ }
13410
+ const tgTouched = typeof payload["tgSessionEnd"] === "boolean" || typeof payload["tgDelegate"] === "boolean" || typeof payload["tgLongToolMs"] === "number";
13411
+ if (tgTouched) {
13412
+ const ext = decrypted.extensions ?? {};
13413
+ const tg = ext["telegram"] ?? {};
13414
+ if (typeof payload["tgSessionEnd"] === "boolean") {
13415
+ tg["notifyOnSessionEnd"] = payload["tgSessionEnd"];
13416
+ }
13417
+ if (typeof payload["tgDelegate"] === "boolean") {
13418
+ tg["notifyOnDelegate"] = payload["tgDelegate"];
13419
+ }
13420
+ if (typeof payload["tgLongToolMs"] === "number") {
13421
+ tg["longToolThresholdMs"] = payload["tgLongToolMs"];
13422
+ }
13423
+ ext["telegram"] = tg;
13424
+ decrypted.extensions = ext;
13425
+ }
13426
+ const modelRuntimeTouched = typeof payload["reasoningMode"] === "string" || typeof payload["reasoningEffort"] === "string" || typeof payload["reasoningPreserve"] === "boolean" || typeof payload["cacheTtl"] === "string";
13427
+ if (modelRuntimeTouched) {
13428
+ const mr = decrypted.modelRuntime ?? {};
13429
+ const reasoning = mr.reasoning ?? {};
13430
+ if (typeof payload["reasoningMode"] === "string") reasoning.mode = payload["reasoningMode"];
13431
+ if (typeof payload["reasoningEffort"] === "string")
13432
+ reasoning.effort = payload["reasoningEffort"];
13433
+ if (typeof payload["reasoningPreserve"] === "boolean")
13434
+ reasoning.preserve = payload["reasoningPreserve"];
13435
+ mr.reasoning = reasoning;
13436
+ if (typeof payload["cacheTtl"] === "string" && payload["cacheTtl"] !== "default") {
13437
+ mr.cache = { ttl: payload["cacheTtl"] };
13438
+ } else if (payload["cacheTtl"] === "default") {
13439
+ delete mr.cache;
13440
+ }
13441
+ decrypted.modelRuntime = mr;
13442
+ }
13443
+ if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
13444
+ const cb = decrypted.circuitBreaker ?? {};
13445
+ if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
13446
+ if (typeof payload["breakerAutoKillResetMs"] === "number")
13447
+ cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
13448
+ decrypted.circuitBreaker = cb;
13449
+ }
13450
+ if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
13451
+ const restrict = payload["fsAccess"] === "project";
13452
+ const toolsCfg = decrypted.tools ?? {};
13453
+ toolsCfg.restrictToProjectRoot = restrict;
13454
+ decrypted.tools = toolsCfg;
13455
+ const featsCfg = decrypted.features ?? {};
13456
+ featsCfg.allowOutsideProjectRoot = !restrict;
13457
+ decrypted.features = featsCfg;
13458
+ }
13459
+ if (typeof payload["debugStream"] === "boolean")
13460
+ decrypted.debugStream = payload["debugStream"];
13461
+ if (typeof payload["pluginsEnabled"] === "object" && payload["pluginsEnabled"] !== null) {
13462
+ const ext = decrypted.extensions ?? {};
13463
+ for (const [pluginName, enabled] of Object.entries(
13464
+ payload["pluginsEnabled"]
13465
+ )) {
13466
+ if (FORBIDDEN_PROTO_KEYS2.has(pluginName)) continue;
13467
+ const pExt = ext[pluginName] ?? {};
13468
+ pExt["enabled"] = enabled;
13469
+ ext[pluginName] = pExt;
13470
+ }
13471
+ decrypted.extensions = ext;
13472
+ }
13473
+ const chimeraTouched = typeof payload["chimeraEnabled"] === "boolean" || typeof payload["chimeraProvider"] === "string" || typeof payload["chimeraModel"] === "string" || typeof payload["chimeraMaxFiles"] === "number" || typeof payload["chimeraAutoFix"] === "string";
13474
+ if (chimeraTouched) {
13475
+ const ext = decrypted.extensions ?? {};
13476
+ const chimera = ext["wstack-chimera"] ?? {};
13477
+ if (typeof payload["chimeraEnabled"] === "boolean")
13478
+ chimera["enabled"] = payload["chimeraEnabled"];
13479
+ if (typeof payload["chimeraProvider"] === "string")
13480
+ chimera["provider"] = payload["chimeraProvider"];
13481
+ if (typeof payload["chimeraModel"] === "string") chimera["model"] = payload["chimeraModel"];
13482
+ if (typeof payload["chimeraMaxFiles"] === "number" && payload["chimeraMaxFiles"] >= 1) {
13483
+ chimera["maxFiles"] = payload["chimeraMaxFiles"];
13484
+ }
13485
+ if (typeof payload["chimeraAutoFix"] === "string") {
13486
+ if (payload["chimeraAutoFix"] === "off" || payload["chimeraAutoFix"] === "ask" || payload["chimeraAutoFix"] === "auto") {
13487
+ chimera["autoFix"] = payload["chimeraAutoFix"];
13488
+ }
13489
+ }
13490
+ ext["wstack-chimera"] = chimera;
13491
+ decrypted.extensions = ext;
13492
+ }
13493
+ 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";
13494
+ if (autoReviewTouched) {
13495
+ const ext = decrypted.extensions ?? {};
13496
+ const ar = ext["wstack-auto-review"] ?? {};
13497
+ if (typeof payload["autoReviewEnabled"] === "boolean")
13498
+ ar["enabled"] = payload["autoReviewEnabled"];
13499
+ if (typeof payload["autoReviewProvider"] === "string")
13500
+ ar["provider"] = payload["autoReviewProvider"];
13501
+ if (typeof payload["autoReviewModel"] === "string")
13502
+ ar["model"] = payload["autoReviewModel"];
13503
+ if (typeof payload["autoReviewFallbackProfile"] === "string") {
13504
+ if (payload["autoReviewFallbackProfile"] === "") {
13505
+ delete ar["fallbackProfile"];
13506
+ } else {
13507
+ ar["fallbackProfile"] = payload["autoReviewFallbackProfile"];
13508
+ }
13509
+ }
13510
+ if (typeof payload["autoReviewDebounceMs"] === "number" && payload["autoReviewDebounceMs"] >= 0) {
13511
+ ar["debounceMs"] = payload["autoReviewDebounceMs"];
13512
+ }
13513
+ if (typeof payload["autoReviewMaxFilesPerBatch"] === "number" && payload["autoReviewMaxFilesPerBatch"] >= 1) {
13514
+ ar["maxFilesPerBatch"] = payload["autoReviewMaxFilesPerBatch"];
13515
+ }
13516
+ if (typeof payload["autoReviewMaxConcurrentReviews"] === "number" && payload["autoReviewMaxConcurrentReviews"] >= 1) {
13517
+ ar["maxConcurrentReviews"] = payload["autoReviewMaxConcurrentReviews"];
13518
+ }
13519
+ if (typeof payload["autoReviewCascadeOn"] === "string") {
13520
+ if (payload["autoReviewCascadeOn"] === "off" || payload["autoReviewCascadeOn"] === "critical" || payload["autoReviewCascadeOn"] === "high") {
13521
+ ar["cascadeOn"] = payload["autoReviewCascadeOn"];
13522
+ }
13523
+ }
13524
+ ext["wstack-auto-review"] = ar;
13525
+ decrypted.extensions = ext;
13526
+ }
13527
+ },
13528
+ "prefs"
13529
+ );
12000
13530
  }
12001
13531
 
12002
13532
  // src/server/provider-handlers.ts
@@ -12327,12 +13857,14 @@ function createProviderHandlers(deps2) {
12327
13857
  }
12328
13858
 
12329
13859
  // src/server/routes.ts
12330
- import path21 from "node:path";
13860
+ import path22 from "node:path";
12331
13861
  import {
13862
+ buildRefinerContextSections,
12332
13863
  enhanceUserPrompt,
12333
13864
  gatedEnhancerReasoning,
12334
13865
  nextEnhanceTimeout,
12335
13866
  recentTextTurns,
13867
+ resolveConfiguredRefinerRef,
12336
13868
  resolveEnhanceFallbackRef,
12337
13869
  resolveProviderModelList
12338
13870
  } from "@wrongstack/core";
@@ -12444,7 +13976,7 @@ async function handleMailboxCompact(ws, deps2, opts) {
12444
13976
  // src/server/mode-handlers.ts
12445
13977
  import {
12446
13978
  DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
12447
- resolveWstackPaths as resolveWstackPaths2,
13979
+ resolveWstackPaths as resolveWstackPaths3,
12448
13980
  ToolValidationError as ToolValidationError5
12449
13981
  } from "@wrongstack/core";
12450
13982
  function createModeHandlers(ctx) {
@@ -12484,6 +14016,7 @@ function createModeHandlers(ctx) {
12484
14016
  }
12485
14017
  const { id } = parsed.value;
12486
14018
  try {
14019
+ const prev = await ctx.modeStore.getActiveMode();
12487
14020
  if (id === "default") {
12488
14021
  await ctx.modeStore.setActiveMode(null);
12489
14022
  } else {
@@ -12494,8 +14027,13 @@ function createModeHandlers(ctx) {
12494
14027
  await ctx.modeStore.setActiveMode(id);
12495
14028
  }
12496
14029
  ctx.setModeId(id);
14030
+ const fromMode = prev?.id ?? "default";
14031
+ if (ctx.context.session && fromMode !== id) {
14032
+ void ctx.context.session.append({ type: "mode_changed", ts: (/* @__PURE__ */ new Date()).toISOString(), from: fromMode, to: id }).catch(() => {
14033
+ });
14034
+ }
12497
14035
  const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
12498
- const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
14036
+ const paths = resolveWstackPaths3({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
12499
14037
  const freshBuilder = new DefaultSystemPromptBuilder2({
12500
14038
  memoryStore: ctx.memoryStore,
12501
14039
  // Single injection channel: Super Memory turn middleware, not a static section.
@@ -12530,7 +14068,7 @@ function createModeHandlers(ctx) {
12530
14068
  }
12531
14069
 
12532
14070
  // src/server/project-handlers.ts
12533
- import * as path20 from "node:path";
14071
+ import * as path21 from "node:path";
12534
14072
  function createProjectHandlers(ctx) {
12535
14073
  return {
12536
14074
  listProjects: async (ws) => {
@@ -12556,7 +14094,7 @@ function createProjectHandlers(ctx) {
12556
14094
  selectProject: async (ws, msg) => {
12557
14095
  const payload = msg.payload;
12558
14096
  const root = typeof payload?.root === "string" ? payload.root : "";
12559
- const name2 = typeof payload?.name === "string" ? payload.name : root ? path20.basename(root) : "";
14097
+ const name2 = typeof payload?.name === "string" ? payload.name : root ? path21.basename(root) : "";
12560
14098
  send(ws, {
12561
14099
  type: "projects.selected",
12562
14100
  payload: {
@@ -12593,6 +14131,7 @@ function createProjectHandlers(ctx) {
12593
14131
  // src/server/session-handlers.ts
12594
14132
  import {
12595
14133
  DEFAULT_CONTEXT_WINDOW_MODE_ID,
14134
+ loadTodosCheckpoint,
12596
14135
  repairToolUseAdjacency,
12597
14136
  resolveContextWindowPolicy as resolveContextWindowPolicy3
12598
14137
  } from "@wrongstack/core";
@@ -12630,13 +14169,14 @@ function createSessionHandlers(ctx) {
12630
14169
  }).catch(() => void 0);
12631
14170
  await writer.close().catch(() => void 0);
12632
14171
  };
12633
- const activateSession = async (next, messages, usage) => {
14172
+ const activateSession = async (next, messages, usage, todos = []) => {
12634
14173
  const current = ctx.getSession();
12635
14174
  if (current !== next) await finalizeSession(current);
12636
14175
  ctx.setSession(next);
12637
14176
  ctx.context.session = next;
12638
14177
  ctx.context.state.replaceMessages(messages);
12639
- ctx.context.state.replaceTodos([]);
14178
+ await ctx.context.flushConversationJournal?.();
14179
+ ctx.context.state.replaceTodos(todos);
12640
14180
  ctx.context.readFiles.clear();
12641
14181
  ctx.context.fileMtimes.clear();
12642
14182
  ctx.context.state.setMeta(
@@ -12920,7 +14460,15 @@ function createSessionHandlers(ctx) {
12920
14460
  return;
12921
14461
  }
12922
14462
  const resumed = await ctx.getSessionStore().resume(id);
12923
- await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
14463
+ const restoredTodos = await loadTodosCheckpoint(
14464
+ sessionScopedPath2(ctx.sessionsDir, resumed.writer.id, ".todos.json")
14465
+ ).catch(() => null) ?? [];
14466
+ await activateSession(
14467
+ resumed.writer,
14468
+ resumed.data.messages,
14469
+ resumed.data.usage,
14470
+ restoredTodos
14471
+ );
12924
14472
  broadcast(ctx.clients, {
12925
14473
  type: "session.start",
12926
14474
  payload: {
@@ -12930,6 +14478,10 @@ function createSessionHandlers(ctx) {
12930
14478
  replayUsage: resumed.data.usage
12931
14479
  }
12932
14480
  });
14481
+ broadcast(ctx.clients, {
14482
+ type: "todos.updated",
14483
+ payload: { sessionId: resumed.writer.id, todos: restoredTodos }
14484
+ });
12933
14485
  sendResult(ws, true, `Resumed session ${id}`);
12934
14486
  } catch (err) {
12935
14487
  sendResult(ws, false, errMessage(err));
@@ -12955,11 +14507,17 @@ function createSessionHandlers(ctx) {
12955
14507
  if (!ensureCurrentSession(ws, msg, "session.rewind")) return;
12956
14508
  const { checkpointIndex } = msg.payload;
12957
14509
  try {
12958
- const { DefaultSessionRewinder } = await import("@wrongstack/core");
14510
+ const { applyRewindToConversation, DefaultSessionRewinder } = await import("@wrongstack/core");
12959
14511
  const projectRoot = ctx.getProjectRoot();
12960
14512
  const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
12961
- await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
12962
- await ctx.context.session.truncateToCheckpoint(checkpointIndex);
14513
+ const reverted = await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
14514
+ await applyRewindToConversation({
14515
+ session: ctx.context.session,
14516
+ state: ctx.context.state,
14517
+ sessionsDir: ctx.sessionsDir,
14518
+ promptIndex: checkpointIndex,
14519
+ revertedFiles: reverted.revertedFiles
14520
+ });
12963
14521
  sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
12964
14522
  broadcast(ctx.clients, {
12965
14523
  type: "session.start",
@@ -13169,11 +14727,36 @@ function buildRoutes(state, deps2, cb) {
13169
14727
  });
13170
14728
  return;
13171
14729
  }
14730
+ } else {
14731
+ const configuredRef = resolveConfiguredRefinerRef({
14732
+ ...cfg,
14733
+ provider: providerId,
14734
+ model
14735
+ });
14736
+ if (configuredRef) {
14737
+ const slash = configuredRef.indexOf("/");
14738
+ const configuredProvider = slash > 0 ? configuredRef.slice(0, slash) : providerId;
14739
+ const configuredModel = slash > 0 ? configuredRef.slice(slash + 1) : configuredRef;
14740
+ try {
14741
+ const providerCfg = cfg.providers?.[configuredProvider] ?? {
14742
+ type: configuredProvider
14743
+ };
14744
+ provider = deps2.providerRegistry.has(configuredProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: configuredProvider }) : makeProviderFromConfig2(configuredProvider, providerCfg);
14745
+ providerId = configuredProvider;
14746
+ model = configuredModel;
14747
+ } catch {
14748
+ }
14749
+ }
13172
14750
  }
13173
14751
  const baseTimeout = 9e4;
13174
14752
  const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
13175
14753
  try {
13176
14754
  const history = recentTextTurns(deps2.context.messages);
14755
+ const contextSections = await buildRefinerContextSections({
14756
+ text,
14757
+ memoryStore: deps2.memoryStore,
14758
+ context: deps2.context
14759
+ });
13177
14760
  const resolved = await resolveProviderModelMetadata(
13178
14761
  deps2.modelsRegistry,
13179
14762
  providerId,
@@ -13187,6 +14770,14 @@ function buildRoutes(state, deps2, cb) {
13187
14770
  model,
13188
14771
  text,
13189
14772
  history,
14773
+ contextSections,
14774
+ ...payload.previousRefined ? {
14775
+ previousRefinement: {
14776
+ refined: payload.previousRefined,
14777
+ english: payload.previousEnglish || payload.previousRefined
14778
+ }
14779
+ } : {},
14780
+ ...payload.retryFeedback ? { retryFeedback: payload.retryFeedback } : {},
13190
14781
  timeoutMs,
13191
14782
  ...reasoning ? { reasoning } : {},
13192
14783
  onError: (reason, kind) => {
@@ -13342,10 +14933,29 @@ function buildRoutes(state, deps2, cb) {
13342
14933
  cfg.favoriteModels = payload["favoriteModels"];
13343
14934
  if (typeof payload["favoriteModelsOnly"] === "boolean")
13344
14935
  cfg.favoriteModelsOnly = payload["favoriteModelsOnly"];
14936
+ if (Array.isArray(payload["modelAvailabilitySchedule"]))
14937
+ cfg.modelAvailabilitySchedule = payload["modelAvailabilitySchedule"];
13345
14938
  if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"])) {
13346
14939
  cfg.modelMatrix = payload["modelMatrix"];
13347
14940
  }
13348
14941
  if (typeof payload["fallbackAuto"] === "boolean") cfg.fallbackAuto = payload["fallbackAuto"];
14942
+ const routingPatch = {};
14943
+ if (Array.isArray(payload["fallbackModels"]))
14944
+ routingPatch.fallbackModels = payload["fallbackModels"];
14945
+ if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"]))
14946
+ routingPatch.fallbackProfiles = payload["fallbackProfiles"];
14947
+ if (Array.isArray(payload["favoriteModels"]))
14948
+ routingPatch.favoriteModels = payload["favoriteModels"];
14949
+ if (typeof payload["favoriteModelsOnly"] === "boolean")
14950
+ routingPatch.favoriteModelsOnly = payload["favoriteModelsOnly"];
14951
+ if (Array.isArray(payload["modelAvailabilitySchedule"]))
14952
+ routingPatch.modelAvailabilitySchedule = payload["modelAvailabilitySchedule"];
14953
+ if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"]))
14954
+ routingPatch.modelMatrix = payload["modelMatrix"];
14955
+ if (typeof payload["fallbackAuto"] === "boolean")
14956
+ routingPatch.fallbackAuto = payload["fallbackAuto"];
14957
+ if (Object.keys(routingPatch).length > 0)
14958
+ deps2.configStore.update(routingPatch);
13349
14959
  if (typeof payload["contextAutoCompact"] === "boolean") {
13350
14960
  if (payload["contextAutoCompact"] && deps2.autoCompactor) {
13351
14961
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
@@ -13414,7 +15024,7 @@ function buildRoutes(state, deps2, cb) {
13414
15024
  }
13415
15025
  return handleMailboxMessages(
13416
15026
  ws,
13417
- { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
15027
+ { projectRoot: state.getProjectRoot(), globalRoot: path22.dirname(deps2.globalConfigPath) },
13418
15028
  parsed.value
13419
15029
  );
13420
15030
  },
@@ -13426,13 +15036,13 @@ function buildRoutes(state, deps2, cb) {
13426
15036
  }
13427
15037
  return handleMailboxAgents(
13428
15038
  ws,
13429
- { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
15039
+ { projectRoot: state.getProjectRoot(), globalRoot: path22.dirname(deps2.globalConfigPath) },
13430
15040
  parsed.value
13431
15041
  );
13432
15042
  },
13433
15043
  clear: (ws) => handleMailboxClear(ws, {
13434
15044
  projectRoot: state.getProjectRoot(),
13435
- globalRoot: path21.dirname(deps2.globalConfigPath)
15045
+ globalRoot: path22.dirname(deps2.globalConfigPath)
13436
15046
  }),
13437
15047
  purge: (ws, msg) => {
13438
15048
  const parsed = validateMailboxPurgePayload(msg.payload);
@@ -13442,14 +15052,14 @@ function buildRoutes(state, deps2, cb) {
13442
15052
  }
13443
15053
  return handleMailboxPurge(
13444
15054
  ws,
13445
- { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
15055
+ { projectRoot: state.getProjectRoot(), globalRoot: path22.dirname(deps2.globalConfigPath) },
13446
15056
  parsed.value
13447
15057
  );
13448
15058
  },
13449
15059
  compact: (ws, msg) => {
13450
15060
  return handleMailboxCompact(
13451
15061
  ws,
13452
- { projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
15062
+ { projectRoot: state.getProjectRoot(), globalRoot: path22.dirname(deps2.globalConfigPath) },
13453
15063
  msg.payload ?? {}
13454
15064
  );
13455
15065
  }
@@ -13558,8 +15168,8 @@ function buildRoutes(state, deps2, cb) {
13558
15168
  }
13559
15169
  }
13560
15170
  };
13561
- const autoPhaseRoutes = {
13562
- handleMessage: (msg) => deps2.autoPhaseHandler.handleMessage(msg)
15171
+ const goalRoutes = {
15172
+ handleMessage: (msg) => deps2.goalHandler.handleMessage(msg)
13563
15173
  };
13564
15174
  const specsRoutes = {
13565
15175
  handleMessage: (msg) => deps2.specsHandler.handleMessage(msg)
@@ -13580,7 +15190,7 @@ function buildRoutes(state, deps2, cb) {
13580
15190
  mailboxRoutes,
13581
15191
  mcpRoutes,
13582
15192
  brainRoutes,
13583
- autoPhaseRoutes,
15193
+ goalRoutes,
13584
15194
  specsRoutes,
13585
15195
  sddBoardRoutes,
13586
15196
  sddWizardRoutes
@@ -13701,7 +15311,7 @@ async function startWebUI(opts = {}) {
13701
15311
  brainLog,
13702
15312
  brainMonitor,
13703
15313
  codebaseIndexing,
13704
- autoPhaseHandler,
15314
+ goalHandler,
13705
15315
  specsHandler,
13706
15316
  sddBoardHandler,
13707
15317
  sddWizardHandler,
@@ -13762,21 +15372,21 @@ async function startWebUI(opts = {}) {
13762
15372
  wpaths
13763
15373
  }, watcherMetricsRef);
13764
15374
  async function touchProjectEntry(root, workDir) {
13765
- const resolved = path22.resolve(root);
15375
+ const resolved = path23.resolve(root);
13766
15376
  const manifest = await loadManifest(globalConfigPath);
13767
15377
  const now = (/* @__PURE__ */ new Date()).toISOString();
13768
- const existing = manifest.projects.find((p) => path22.resolve(p.root) === resolved);
15378
+ const existing = manifest.projects.find((p) => path23.resolve(p.root) === resolved);
13769
15379
  if (existing) {
13770
15380
  existing.lastSeen = now;
13771
- if (workDir) existing.lastWorkingDir = path22.resolve(workDir);
15381
+ if (workDir) existing.lastWorkingDir = path23.resolve(workDir);
13772
15382
  } else {
13773
15383
  manifest.projects.push({
13774
- name: path22.basename(resolved),
15384
+ name: path23.basename(resolved),
13775
15385
  root: resolved,
13776
15386
  slug: generateProjectSlug(resolved),
13777
15387
  createdAt: now,
13778
15388
  lastSeen: now,
13779
- lastWorkingDir: workDir ? path22.resolve(workDir) : void 0
15389
+ lastWorkingDir: workDir ? path23.resolve(workDir) : void 0
13780
15390
  });
13781
15391
  }
13782
15392
  await saveManifest(manifest, globalConfigPath);
@@ -13859,7 +15469,7 @@ async function startWebUI(opts = {}) {
13859
15469
  httpPort,
13860
15470
  wssPrimary,
13861
15471
  wssSecondary,
13862
- autoPhaseHandler,
15472
+ goalHandler,
13863
15473
  specsHandler,
13864
15474
  sddBoardHandler,
13865
15475
  sddWizardHandler,
@@ -13903,7 +15513,13 @@ async function startWebUI(opts = {}) {
13903
15513
  deps2.configStore.update({
13904
15514
  providers: snapshot.providers,
13905
15515
  ...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
13906
- ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
15516
+ ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {},
15517
+ ...snapshot.fallbackModels !== void 0 ? { fallbackModels: snapshot.fallbackModels } : {},
15518
+ ...snapshot.fallbackProfiles !== void 0 ? { fallbackProfiles: snapshot.fallbackProfiles } : {},
15519
+ ...snapshot.favoriteModels !== void 0 ? { favoriteModels: snapshot.favoriteModels } : {},
15520
+ ...snapshot.favoriteModelsOnly !== void 0 ? { favoriteModelsOnly: snapshot.favoriteModelsOnly } : {},
15521
+ ...snapshot.modelMatrix !== void 0 ? { modelMatrix: snapshot.modelMatrix } : {},
15522
+ ...snapshot.fallbackAuto !== void 0 ? { fallbackAuto: snapshot.fallbackAuto } : {}
13907
15523
  });
13908
15524
  broadcast(clients, {
13909
15525
  type: "providers.saved",
@@ -13964,7 +15580,7 @@ async function startWebUI(opts = {}) {
13964
15580
  },
13965
15581
  clients,
13966
15582
  pendingConfirms,
13967
- autoPhaseHandler,
15583
+ goalHandler,
13968
15584
  specsHandler,
13969
15585
  sddBoardHandler,
13970
15586
  sddWizardHandler,
@@ -13991,6 +15607,11 @@ async function startWebUI(opts = {}) {
13991
15607
  onFleetPing: () => {
13992
15608
  void eventArming.getFleetBroadcast()?.();
13993
15609
  },
15610
+ onTechStackEvent: (event) => broadcast(clients, event),
15611
+ // Read through `context` on every call rather than capturing: the running
15612
+ // loop swaps provider/model when the user switches (same live source the
15613
+ // completion handler reads).
15614
+ getLlm: () => context.provider && context.model ? { provider: context.provider, model: context.model } : void 0,
13994
15615
  distDir: opts.distDir
13995
15616
  });
13996
15617
  registerShutdown({
@@ -14020,7 +15641,7 @@ async function startWebUI(opts = {}) {
14020
15641
  archiveLowConfidenceAfterDays: config.superMemory?.hygiene?.archiveLowConfidenceAfterDays
14021
15642
  }).catch((err) => logger.warn(`super-memory session hygiene failed: ${toErrorMessage10(err)}`));
14022
15643
  }
14023
- await unregisterInstance(process.pid, path22.dirname(globalConfigPath));
15644
+ await unregisterInstance(process.pid, path23.dirname(globalConfigPath));
14024
15645
  }
14025
15646
  });
14026
15647
  }
@@ -14039,8 +15660,8 @@ function createConfigWriteLock() {
14039
15660
  acquire() {
14040
15661
  const prev = lock;
14041
15662
  let release = () => void 0;
14042
- lock = new Promise((resolve10) => {
14043
- release = resolve10;
15663
+ lock = new Promise((resolve12) => {
15664
+ release = resolve12;
14044
15665
  });
14045
15666
  return { prev, release };
14046
15667
  }
@@ -14114,8 +15735,8 @@ function createProviderStore(deps2) {
14114
15735
  };
14115
15736
  }
14116
15737
  export {
14117
- AutoPhaseWebSocketHandler,
14118
15738
  CollaborationWebSocketHandler,
15739
+ GoalWebSocketHandler,
14119
15740
  SKIP_DIRS,
14120
15741
  SURFACE_DEFAULT_PORTS,
14121
15742
  SddBoardWebSocketHandler,
@@ -14150,6 +15771,7 @@ export {
14150
15771
  errMessage,
14151
15772
  estimateContextBreakdown,
14152
15773
  estimateTokens,
15774
+ extractCodeMapFileTargets,
14153
15775
  extractToken,
14154
15776
  extractTokenFromCookie,
14155
15777
  findFreePort,
@@ -14161,7 +15783,6 @@ export {
14161
15783
  handleApiAnalyticsGet,
14162
15784
  handleApiAnalyticsPost,
14163
15785
  handleApiAnalyticsSummary,
14164
- handleAutoPhaseRoute,
14165
15786
  handleBrainRoute,
14166
15787
  handleCompletionRequest,
14167
15788
  handleDesignList,
@@ -14178,6 +15799,7 @@ export {
14178
15799
  handleGitDiff,
14179
15800
  handleGitInfo,
14180
15801
  handleGoalGet,
15802
+ handleGoalRoute,
14181
15803
  handleMailboxMessages,
14182
15804
  handleMailboxRoute,
14183
15805
  handleMcpAdd,
@@ -14224,9 +15846,13 @@ export {
14224
15846
  handleSkillsUninstall,
14225
15847
  handleSkillsUpdate,
14226
15848
  handleSpecsRoute,
15849
+ handleSuperMemoryBackfillRecoverable,
15850
+ handleSuperMemoryCandidateResolve,
14227
15851
  handleSuperMemoryDelete,
15852
+ handleSuperMemoryForFile,
14228
15853
  handleSuperMemoryGet,
14229
15854
  handleSuperMemoryList,
15855
+ handleSuperMemoryRecover,
14230
15856
  handleSuperMemoryRemember,
14231
15857
  handleSuperMemoryUpdate,
14232
15858
  handleWorklistMessage,
@@ -14248,8 +15874,10 @@ export {
14248
15874
  maskedKey,
14249
15875
  messagePreview,
14250
15876
  messageTokens,
15877
+ normalizeCodeMapFileTarget,
14251
15878
  normalizeKeys,
14252
15879
  openBrowser,
15880
+ paginateKanbanBoards,
14253
15881
  patchConfig,
14254
15882
  persistPrefsToConfig,
14255
15883
  projectSavedProviders,