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