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