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