@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/server/entry.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// src/server/entry.ts
|
|
3
3
|
import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core";
|
|
4
4
|
|
|
5
|
-
// src/server/
|
|
5
|
+
// src/server/goal-ws-handler.ts
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
8
8
|
import {
|
|
9
9
|
assignNickname,
|
|
10
|
-
|
|
10
|
+
GoalPlanner,
|
|
11
11
|
PhaseGraphBuilder,
|
|
12
12
|
PhaseOrchestrator,
|
|
13
13
|
PhaseStore,
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
} from "@wrongstack/core";
|
|
16
16
|
function deriveTitle(goal) {
|
|
17
17
|
const firstLine = goal.split("\n").map((l) => l.trim()).find(Boolean);
|
|
18
|
-
if (!firstLine) return "
|
|
18
|
+
if (!firstLine) return "Goal";
|
|
19
19
|
const sentence = firstLine.split(/(?<=[.!?])\s/)[0] ?? firstLine;
|
|
20
20
|
const trimmed = sentence.length <= 64 ? sentence : `${sentence.slice(0, 63).trimEnd()}\u2026`;
|
|
21
|
-
return trimmed || "
|
|
21
|
+
return trimmed || "Goal";
|
|
22
22
|
}
|
|
23
23
|
function isGitRepo(cwd) {
|
|
24
24
|
try {
|
|
@@ -41,7 +41,7 @@ function commitsSince(cwd, baseSha, branch) {
|
|
|
41
41
|
return [];
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
var
|
|
44
|
+
var GoalWebSocketHandler = class {
|
|
45
45
|
constructor(agent, context, logger, storeDir, events, projectRoot, onBoardState) {
|
|
46
46
|
this.agent = agent;
|
|
47
47
|
this.context = context;
|
|
@@ -84,94 +84,94 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
84
84
|
}
|
|
85
85
|
async handleMessage(msg) {
|
|
86
86
|
switch (msg.type) {
|
|
87
|
-
case "
|
|
87
|
+
case "goal.start":
|
|
88
88
|
await this.handleStart(msg.payload);
|
|
89
89
|
break;
|
|
90
|
-
case "
|
|
90
|
+
case "goal.pause":
|
|
91
91
|
this.orchestrator?.pause();
|
|
92
|
-
this.broadcast({ type: "
|
|
92
|
+
this.broadcast({ type: "goal.paused", payload: {} });
|
|
93
93
|
break;
|
|
94
|
-
case "
|
|
94
|
+
case "goal.resume":
|
|
95
95
|
this.orchestrator?.resume();
|
|
96
|
-
this.broadcast({ type: "
|
|
96
|
+
this.broadcast({ type: "goal.resumed", payload: {} });
|
|
97
97
|
break;
|
|
98
|
-
case "
|
|
98
|
+
case "goal.stop":
|
|
99
99
|
await this.handleStop();
|
|
100
100
|
break;
|
|
101
|
-
case "
|
|
101
|
+
case "goal.clear":
|
|
102
102
|
await this.handleClear();
|
|
103
103
|
break;
|
|
104
|
-
case "
|
|
104
|
+
case "goal.revert":
|
|
105
105
|
await this.handleRevert();
|
|
106
106
|
break;
|
|
107
|
-
case "
|
|
107
|
+
case "goal.status":
|
|
108
108
|
this.broadcastState();
|
|
109
109
|
break;
|
|
110
|
-
case "
|
|
110
|
+
case "goal.selectPhase": {
|
|
111
111
|
const phaseId = msg.payload?.phaseId;
|
|
112
112
|
if (phaseId && this.graph) {
|
|
113
113
|
this.broadcastState(phaseId);
|
|
114
114
|
}
|
|
115
115
|
break;
|
|
116
116
|
}
|
|
117
|
-
case "
|
|
117
|
+
case "goal.taskStatus": {
|
|
118
118
|
const { taskId, status } = msg.payload;
|
|
119
119
|
await this.handleTaskStatusChange(taskId, status);
|
|
120
120
|
break;
|
|
121
121
|
}
|
|
122
|
-
case "
|
|
122
|
+
case "goal.moveTask": {
|
|
123
123
|
const { taskId, toPhaseId } = msg.payload;
|
|
124
124
|
if (this.orchestrator?.moveTask(taskId, toPhaseId)) this.afterBoardMutation();
|
|
125
125
|
break;
|
|
126
126
|
}
|
|
127
|
-
case "
|
|
127
|
+
case "goal.assignTask": {
|
|
128
128
|
const { taskId, agentId, agentName } = msg.payload;
|
|
129
129
|
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName)) this.afterBoardMutation();
|
|
130
130
|
break;
|
|
131
131
|
}
|
|
132
|
-
case "
|
|
132
|
+
case "goal.addTask": {
|
|
133
133
|
const { phaseId, title, description, type, priority } = msg.payload;
|
|
134
134
|
if (title?.trim() && this.orchestrator?.addTask(phaseId, { title: title.trim(), description, type, priority })) {
|
|
135
135
|
this.afterBoardMutation();
|
|
136
136
|
}
|
|
137
137
|
break;
|
|
138
138
|
}
|
|
139
|
-
case "
|
|
140
|
-
case "
|
|
139
|
+
case "goal.retryTask":
|
|
140
|
+
case "goal.runTask": {
|
|
141
141
|
const { taskId } = msg.payload;
|
|
142
142
|
if (this.orchestrator?.requeueTask(taskId)) this.afterBoardMutation();
|
|
143
143
|
break;
|
|
144
144
|
}
|
|
145
|
-
case "
|
|
145
|
+
case "goal.toggleAutonomous": {
|
|
146
146
|
const autonomous = msg.payload?.autonomous ?? !this.graph?.autonomous;
|
|
147
147
|
if (this.graph) {
|
|
148
148
|
this.graph.autonomous = autonomous;
|
|
149
149
|
await this.store.save(this.graph);
|
|
150
|
-
this.broadcast({ type: "
|
|
150
|
+
this.broadcast({ type: "goal.state", payload: this.buildState() });
|
|
151
151
|
}
|
|
152
152
|
break;
|
|
153
153
|
}
|
|
154
|
-
case "
|
|
154
|
+
case "goal.save": {
|
|
155
155
|
if (this.graph) {
|
|
156
156
|
await this.store.save(this.graph);
|
|
157
|
-
this.broadcast({ type: "
|
|
157
|
+
this.broadcast({ type: "goal.saved", payload: { graphId: this.graph.id } });
|
|
158
158
|
}
|
|
159
159
|
break;
|
|
160
160
|
}
|
|
161
|
-
case "
|
|
161
|
+
case "goal.list": {
|
|
162
162
|
const graphs = await this.store.list();
|
|
163
|
-
this.broadcast({ type: "
|
|
163
|
+
this.broadcast({ type: "goal.list", payload: { graphs } });
|
|
164
164
|
break;
|
|
165
165
|
}
|
|
166
|
-
case "
|
|
166
|
+
case "goal.load": {
|
|
167
167
|
const graphId = msg.payload?.graphId;
|
|
168
168
|
if (graphId) {
|
|
169
169
|
const graph = await this.store.load(graphId);
|
|
170
170
|
if (graph) {
|
|
171
171
|
this.graph = graph;
|
|
172
|
-
this.broadcast({ type: "
|
|
172
|
+
this.broadcast({ type: "goal.state", payload: this.buildState() });
|
|
173
173
|
} else {
|
|
174
|
-
this.broadcast({ type: "
|
|
174
|
+
this.broadcast({ type: "goal.error", payload: { message: `Graph not found: ${graphId}` } });
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
break;
|
|
@@ -186,14 +186,14 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
186
186
|
this.stopping = false;
|
|
187
187
|
const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal, this.abort.signal);
|
|
188
188
|
if (this.stopping || this.abort.signal.aborted) {
|
|
189
|
-
this.broadcast({ type: "
|
|
189
|
+
this.broadcast({ type: "goal.stopped", payload: { title } });
|
|
190
190
|
return;
|
|
191
191
|
}
|
|
192
|
-
this.logger.info(`[
|
|
192
|
+
this.logger.info(`[Goal] Starting: ${title}`);
|
|
193
193
|
const graph = await new PhaseGraphBuilder({ title, description: goal, phases, autonomous }).build();
|
|
194
194
|
this.graph = graph;
|
|
195
195
|
await this.store.save(graph);
|
|
196
|
-
const useWorktrees = payload?.worktrees ?? process.env["
|
|
196
|
+
const useWorktrees = payload?.worktrees ?? process.env["WRONGSTACK_GOAL_WORKTREES"] !== "0";
|
|
197
197
|
if (!this.worktrees && this.events && this.projectRoot && useWorktrees && isGitRepo(this.projectRoot)) {
|
|
198
198
|
this.worktrees = new WorktreeManager({
|
|
199
199
|
projectRoot: this.projectRoot,
|
|
@@ -208,18 +208,18 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
208
208
|
graph,
|
|
209
209
|
ctx: {
|
|
210
210
|
executeTask: async (task, phaseId, env) => {
|
|
211
|
-
this.logger.info(`[
|
|
211
|
+
this.logger.info(`[Goal] [${phaseId}] Executing: ${task.title}`);
|
|
212
212
|
const result = await this.executeTaskWithAgent(task, phaseId, env);
|
|
213
|
-
this.logger.info(`[
|
|
213
|
+
this.logger.info(`[Goal] [${phaseId}] Completed: ${task.title}`);
|
|
214
214
|
return result;
|
|
215
215
|
},
|
|
216
216
|
onPhaseComplete: (phase) => {
|
|
217
|
-
this.logger.info(`[
|
|
217
|
+
this.logger.info(`[Goal] Phase completed: ${phase.name}`);
|
|
218
218
|
void this.store.save(graph);
|
|
219
219
|
this.broadcastState();
|
|
220
220
|
},
|
|
221
221
|
onPhaseFail: (phase, error) => {
|
|
222
|
-
this.logger.error(`[
|
|
222
|
+
this.logger.error(`[Goal] Phase failed: ${phase.name} \u2014 ${error.message}`);
|
|
223
223
|
void this.store.save(graph);
|
|
224
224
|
this.broadcastState();
|
|
225
225
|
}
|
|
@@ -241,20 +241,20 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
241
241
|
this.stopBroadcast();
|
|
242
242
|
const failed = graph.failedPhaseIds.length > 0;
|
|
243
243
|
this.broadcast(
|
|
244
|
-
failed ? { type: "
|
|
244
|
+
failed ? { type: "goal.failed", payload: { title } } : { type: "goal.completed", payload: { title } }
|
|
245
245
|
);
|
|
246
246
|
this.broadcastState();
|
|
247
247
|
}).catch((err) => {
|
|
248
|
-
this.logger.error(`[
|
|
248
|
+
this.logger.error(`[Goal] Aborted: ${toErrorMessage(err)}`);
|
|
249
249
|
this.stopBroadcast();
|
|
250
|
-
this.broadcast({ type: "
|
|
250
|
+
this.broadcast({ type: "goal.failed", payload: { title, error: String(err) } });
|
|
251
251
|
});
|
|
252
252
|
}
|
|
253
253
|
/**
|
|
254
254
|
* Halt the run NOW — at any phase. Sets `stopping` (so a planning turn that
|
|
255
255
|
* resolves afterwards bails), aborts in-flight agents, stops the orchestrator
|
|
256
256
|
* tick, and ends the live broadcast. The board is kept for review; use
|
|
257
|
-
* `
|
|
257
|
+
* `goal.clear` to reset or `goal.revert` to undo the changes.
|
|
258
258
|
*/
|
|
259
259
|
async handleStop() {
|
|
260
260
|
this.stopping = true;
|
|
@@ -262,12 +262,12 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
262
262
|
this.orchestrator?.stop();
|
|
263
263
|
this.stopBroadcast();
|
|
264
264
|
if (this.graph) await this.store.save(this.graph).catch(() => void 0);
|
|
265
|
-
this.broadcast({ type: "
|
|
265
|
+
this.broadcast({ type: "goal.stopped", payload: { title: this.graph?.title } });
|
|
266
266
|
}
|
|
267
267
|
/**
|
|
268
268
|
* Stop + wipe: tear down phase worktrees and reset to an empty board so the UI
|
|
269
269
|
* returns to the start screen ("new one"). Does NOT touch already-merged commits
|
|
270
|
-
* on the base branch — that is `
|
|
270
|
+
* on the base branch — that is `goal.revert`.
|
|
271
271
|
*/
|
|
272
272
|
async handleClear() {
|
|
273
273
|
await this.handleStop();
|
|
@@ -276,8 +276,8 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
276
276
|
this.graph = null;
|
|
277
277
|
this.runBase = null;
|
|
278
278
|
this.usedNicknames.clear();
|
|
279
|
-
this.broadcast({ type: "
|
|
280
|
-
this.broadcast({ type: "
|
|
279
|
+
this.broadcast({ type: "goal.cleared", payload: {} });
|
|
280
|
+
this.broadcast({ type: "goal.state", payload: this.buildState() });
|
|
281
281
|
}
|
|
282
282
|
/**
|
|
283
283
|
* Stop + undo: remove phase worktrees, then history-preservingly `git revert`
|
|
@@ -289,7 +289,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
289
289
|
await this.handleStop();
|
|
290
290
|
if (!this.worktrees || !this.runBase || !this.projectRoot) {
|
|
291
291
|
this.broadcast({
|
|
292
|
-
type: "
|
|
292
|
+
type: "goal.reverted",
|
|
293
293
|
payload: { ok: false, reverted: 0, reason: "no git baseline was captured for this run" }
|
|
294
294
|
});
|
|
295
295
|
return;
|
|
@@ -297,13 +297,13 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
297
297
|
await this.worktrees.cleanupAllManaged().catch(() => void 0);
|
|
298
298
|
const shas = commitsSince(this.projectRoot, this.runBase.sha, this.runBase.branch);
|
|
299
299
|
const res = await this.worktrees.revertCommits(this.runBase.branch, shas);
|
|
300
|
-
this.broadcast({ type: "
|
|
300
|
+
this.broadcast({ type: "goal.reverted", payload: res });
|
|
301
301
|
if (res.ok) {
|
|
302
302
|
this.orchestrator = null;
|
|
303
303
|
this.graph = null;
|
|
304
304
|
this.runBase = null;
|
|
305
|
-
this.broadcast({ type: "
|
|
306
|
-
this.broadcast({ type: "
|
|
305
|
+
this.broadcast({ type: "goal.cleared", payload: {} });
|
|
306
|
+
this.broadcast({ type: "goal.state", payload: this.buildState() });
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
/** Generic fallback phases when the LLM planner produces nothing usable. */
|
|
@@ -322,7 +322,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
322
322
|
* uninterruptible). */
|
|
323
323
|
async planPhases(goal, signal) {
|
|
324
324
|
try {
|
|
325
|
-
const planner = new
|
|
325
|
+
const planner = new GoalPlanner({
|
|
326
326
|
goal,
|
|
327
327
|
runOnce: async (prompt) => {
|
|
328
328
|
const result = await this.agent.run(prompt, {
|
|
@@ -334,12 +334,12 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
334
334
|
const { phases, parseFailed } = await planner.plan();
|
|
335
335
|
if (!parseFailed && phases.length > 0) {
|
|
336
336
|
const todos = phases.reduce((n, p) => n + (p.taskTemplates?.length ?? 0), 0);
|
|
337
|
-
this.logger.info(`[
|
|
337
|
+
this.logger.info(`[Goal] Planned ${phases.length} phases / ${todos} todos for: ${goal}`);
|
|
338
338
|
return phases;
|
|
339
339
|
}
|
|
340
|
-
this.logger.info(`[
|
|
340
|
+
this.logger.info(`[Goal] Planner produced no phases; using defaults for: ${goal}`);
|
|
341
341
|
} catch (err) {
|
|
342
|
-
this.logger.error(`[
|
|
342
|
+
this.logger.error(`[Goal] Planning failed, using defaults: ${toErrorMessage(err)}`);
|
|
343
343
|
}
|
|
344
344
|
return this.defaultPhases();
|
|
345
345
|
}
|
|
@@ -387,7 +387,7 @@ Type: ${task.type}`;
|
|
|
387
387
|
if (this.broadcastInterval) return;
|
|
388
388
|
this.broadcastInterval = setInterval(() => {
|
|
389
389
|
const progress = this.orchestrator?.getProgress();
|
|
390
|
-
if (progress) this.broadcast({ type: "
|
|
390
|
+
if (progress) this.broadcast({ type: "goal.progress", payload: progress });
|
|
391
391
|
this.broadcastState();
|
|
392
392
|
}, 2e3);
|
|
393
393
|
}
|
|
@@ -400,13 +400,13 @@ Type: ${task.type}`;
|
|
|
400
400
|
broadcastState(activePhaseId) {
|
|
401
401
|
if (!this.graph) return;
|
|
402
402
|
const state = this.buildState(activePhaseId);
|
|
403
|
-
this.broadcast({ type: "
|
|
403
|
+
this.broadcast({ type: "goal.state", payload: state });
|
|
404
404
|
if (this.onBoardState) {
|
|
405
405
|
try {
|
|
406
406
|
this.onBoardState(this.graph.id, state);
|
|
407
407
|
} catch (err) {
|
|
408
408
|
this.logger.error(
|
|
409
|
-
`[
|
|
409
|
+
`[Goal] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
|
|
410
410
|
);
|
|
411
411
|
}
|
|
412
412
|
}
|
|
@@ -482,7 +482,7 @@ Type: ${task.type}`;
|
|
|
482
482
|
autonomous: this.graph.autonomous,
|
|
483
483
|
totalTasks,
|
|
484
484
|
completedTasks,
|
|
485
|
-
// Structured progress + lastError consumed by the
|
|
485
|
+
// Structured progress + lastError consumed by the goal store (were
|
|
486
486
|
// defined client-side but never sent, so they stayed null on the board).
|
|
487
487
|
progress: {
|
|
488
488
|
totalPhases: phases.length,
|
|
@@ -498,7 +498,7 @@ Type: ${task.type}`;
|
|
|
498
498
|
sendState(client) {
|
|
499
499
|
if (!this.graph) return;
|
|
500
500
|
const state = this.buildState();
|
|
501
|
-
this.send(client, { type: "
|
|
501
|
+
this.send(client, { type: "goal.state", payload: state });
|
|
502
502
|
}
|
|
503
503
|
broadcast(msg) {
|
|
504
504
|
const data = JSON.stringify(msg);
|
|
@@ -1756,9 +1756,9 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
1756
1756
|
const cwd = projectRoot || void 0;
|
|
1757
1757
|
try {
|
|
1758
1758
|
const { execFile: ef } = await import("node:child_process");
|
|
1759
|
-
const git = (args) => new Promise((
|
|
1759
|
+
const git = (args) => new Promise((resolve12) => {
|
|
1760
1760
|
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
1761
|
-
|
|
1761
|
+
resolve12(err ? "" : stdout.trim());
|
|
1762
1762
|
});
|
|
1763
1763
|
});
|
|
1764
1764
|
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
@@ -1784,12 +1784,12 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
1784
1784
|
function makeGit(cwd) {
|
|
1785
1785
|
return async (args) => {
|
|
1786
1786
|
const { execFile: ef } = await import("node:child_process");
|
|
1787
|
-
return new Promise((
|
|
1787
|
+
return new Promise((resolve12) => {
|
|
1788
1788
|
ef(
|
|
1789
1789
|
"git",
|
|
1790
1790
|
args,
|
|
1791
1791
|
{ cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
|
|
1792
|
-
(err, stdout) =>
|
|
1792
|
+
(err, stdout) => resolve12(err ? "" : stdout)
|
|
1793
1793
|
);
|
|
1794
1794
|
});
|
|
1795
1795
|
};
|
|
@@ -1813,15 +1813,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
1813
1813
|
if (!m) continue;
|
|
1814
1814
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
1815
1815
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
1816
|
-
let
|
|
1817
|
-
if (
|
|
1816
|
+
let path23 = m[3] ?? "";
|
|
1817
|
+
if (path23 === "") {
|
|
1818
1818
|
i += 1;
|
|
1819
|
-
|
|
1819
|
+
path23 = parts[i + 1] ?? parts[i] ?? "";
|
|
1820
1820
|
i += 1;
|
|
1821
1821
|
}
|
|
1822
|
-
if (!
|
|
1823
|
-
const prev = counts.get(
|
|
1824
|
-
counts.set(
|
|
1822
|
+
if (!path23) continue;
|
|
1823
|
+
const prev = counts.get(path23) ?? { added: 0, deleted: 0 };
|
|
1824
|
+
counts.set(path23, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
1825
1825
|
}
|
|
1826
1826
|
};
|
|
1827
1827
|
parseNumstat(unstagedNumstat);
|
|
@@ -1833,7 +1833,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
1833
1833
|
if (!rec || rec.length < 3) continue;
|
|
1834
1834
|
const x = rec[0] ?? " ";
|
|
1835
1835
|
const y = rec[1] ?? " ";
|
|
1836
|
-
const
|
|
1836
|
+
const path23 = rec.slice(3);
|
|
1837
1837
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
1838
1838
|
if (isRename) i += 1;
|
|
1839
1839
|
let status;
|
|
@@ -1845,13 +1845,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
1845
1845
|
else if (x === "D" || y === "D") status = "D";
|
|
1846
1846
|
else status = "M";
|
|
1847
1847
|
const staged = x !== " " && x !== "?";
|
|
1848
|
-
let added = counts.get(
|
|
1849
|
-
let deleted = counts.get(
|
|
1848
|
+
let added = counts.get(path23)?.added ?? 0;
|
|
1849
|
+
let deleted = counts.get(path23)?.deleted ?? 0;
|
|
1850
1850
|
if (status === "?") {
|
|
1851
1851
|
added = 0;
|
|
1852
1852
|
deleted = 0;
|
|
1853
1853
|
}
|
|
1854
|
-
files.push({ path:
|
|
1854
|
+
files.push({ path: path23, status, added, deleted, staged });
|
|
1855
1855
|
}
|
|
1856
1856
|
send(ws, { type: "git.changes", payload: { files } });
|
|
1857
1857
|
} catch (err) {
|
|
@@ -1862,10 +1862,10 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
1862
1862
|
}
|
|
1863
1863
|
}
|
|
1864
1864
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
1865
|
-
async function handleGitDiff(ws, projectRoot,
|
|
1865
|
+
async function handleGitDiff(ws, projectRoot, path23) {
|
|
1866
1866
|
const cwd = projectRoot || void 0;
|
|
1867
|
-
const reply = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
1868
|
-
if (!
|
|
1867
|
+
const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path23, ...extra } });
|
|
1868
|
+
if (!path23 || path23.includes("\0") || path23.includes("..") || nodePath.isAbsolute(path23)) {
|
|
1869
1869
|
reply({ oldText: "", newText: "", error: "invalid path" });
|
|
1870
1870
|
return;
|
|
1871
1871
|
}
|
|
@@ -1873,10 +1873,10 @@ async function handleGitDiff(ws, projectRoot, path22) {
|
|
|
1873
1873
|
const git = makeGit(cwd);
|
|
1874
1874
|
const { readFile: readFile10 } = await import("node:fs/promises");
|
|
1875
1875
|
const { join: join14 } = await import("node:path");
|
|
1876
|
-
const oldText = await git(["show", `HEAD:${
|
|
1876
|
+
const oldText = await git(["show", `HEAD:${path23}`]);
|
|
1877
1877
|
let newText = "";
|
|
1878
1878
|
try {
|
|
1879
|
-
const abs = cwd ? join14(cwd,
|
|
1879
|
+
const abs = cwd ? join14(cwd, path23) : path23;
|
|
1880
1880
|
const buf = await readFile10(abs);
|
|
1881
1881
|
if (buf.includes(0)) {
|
|
1882
1882
|
reply({ oldText: "", newText: "", binary: true });
|
|
@@ -1905,9 +1905,9 @@ async function handleGitDiff(ws, projectRoot, path22) {
|
|
|
1905
1905
|
}
|
|
1906
1906
|
|
|
1907
1907
|
// src/server/http-server.ts
|
|
1908
|
-
import * as
|
|
1908
|
+
import * as fs6 from "node:fs/promises";
|
|
1909
1909
|
import * as http from "node:http";
|
|
1910
|
-
import * as
|
|
1910
|
+
import * as path7 from "node:path";
|
|
1911
1911
|
|
|
1912
1912
|
// src/server/http-server/api-handlers.ts
|
|
1913
1913
|
async function handleApiSessions(res, globalRoot) {
|
|
@@ -2095,7 +2095,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
2095
2095
|
return;
|
|
2096
2096
|
}
|
|
2097
2097
|
try {
|
|
2098
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
2098
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, DefaultSessionStore: DefaultSessionStore2, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core");
|
|
2099
2099
|
const registry = new SessionRegistry(globalRoot);
|
|
2100
2100
|
const entry = await registry.get(sessionId);
|
|
2101
2101
|
if (!entry) {
|
|
@@ -2103,7 +2103,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
2103
2103
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
2104
2104
|
return;
|
|
2105
2105
|
}
|
|
2106
|
-
const paths =
|
|
2106
|
+
const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
|
|
2107
2107
|
const store = new DefaultSessionStore2({ dir: paths.projectSessions });
|
|
2108
2108
|
const reader = new DefaultSessionReader2({ store });
|
|
2109
2109
|
const rawEntries = [];
|
|
@@ -2130,7 +2130,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
2130
2130
|
}
|
|
2131
2131
|
}
|
|
2132
2132
|
function readJsonBody(req) {
|
|
2133
|
-
return new Promise((
|
|
2133
|
+
return new Promise((resolve12, reject) => {
|
|
2134
2134
|
let data = "";
|
|
2135
2135
|
req.on("data", (chunk) => {
|
|
2136
2136
|
data += chunk;
|
|
@@ -2141,7 +2141,7 @@ function readJsonBody(req) {
|
|
|
2141
2141
|
});
|
|
2142
2142
|
req.on("end", () => {
|
|
2143
2143
|
try {
|
|
2144
|
-
|
|
2144
|
+
resolve12(data ? JSON.parse(data) : {});
|
|
2145
2145
|
} catch (err) {
|
|
2146
2146
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
2147
2147
|
}
|
|
@@ -2177,7 +2177,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
2177
2177
|
const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
|
|
2178
2178
|
const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
|
|
2179
2179
|
try {
|
|
2180
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
2180
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
2181
2181
|
const registry = new SessionRegistry(globalRoot);
|
|
2182
2182
|
const entry = await registry.get(sessionId);
|
|
2183
2183
|
if (!entry) {
|
|
@@ -2185,7 +2185,7 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
2185
2185
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
2186
2186
|
return;
|
|
2187
2187
|
}
|
|
2188
|
-
const paths =
|
|
2188
|
+
const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
|
|
2189
2189
|
const mailbox = new GlobalMailbox4(paths.projectDir);
|
|
2190
2190
|
const to = `leader@${mailboxSessionTag2(sessionId)}`;
|
|
2191
2191
|
const sent = await mailbox.send({ from, to, type, subject, body: text, priority });
|
|
@@ -2203,7 +2203,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
|
|
|
2203
2203
|
return;
|
|
2204
2204
|
}
|
|
2205
2205
|
try {
|
|
2206
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
2206
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
2207
2207
|
const registry = new SessionRegistry(globalRoot);
|
|
2208
2208
|
const entry = await registry.get(sessionId);
|
|
2209
2209
|
if (!entry) {
|
|
@@ -2211,7 +2211,7 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
|
|
|
2211
2211
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
2212
2212
|
return;
|
|
2213
2213
|
}
|
|
2214
|
-
const paths =
|
|
2214
|
+
const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
|
|
2215
2215
|
const mailbox = new GlobalMailbox4(paths.projectDir);
|
|
2216
2216
|
const leaderAddr = `leader@${mailboxSessionTag2(sessionId)}`;
|
|
2217
2217
|
const [inbound, outbound] = await Promise.all([
|
|
@@ -2261,7 +2261,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
2261
2261
|
const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
|
|
2262
2262
|
const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
|
|
2263
2263
|
try {
|
|
2264
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
2264
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
2265
2265
|
const registry = new SessionRegistry(globalRoot);
|
|
2266
2266
|
const entry = await registry.get(sessionId);
|
|
2267
2267
|
if (!entry) {
|
|
@@ -2269,7 +2269,7 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
2269
2269
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
2270
2270
|
return;
|
|
2271
2271
|
}
|
|
2272
|
-
const paths =
|
|
2272
|
+
const paths = resolveWstackPaths4({ projectRoot: entry.projectRoot, globalRoot });
|
|
2273
2273
|
const mailbox = new GlobalMailbox4(paths.projectDir);
|
|
2274
2274
|
const to = `leader@${mailboxSessionTag2(sessionId)}`;
|
|
2275
2275
|
const sent = await mailbox.send({
|
|
@@ -2309,7 +2309,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
2309
2309
|
}
|
|
2310
2310
|
const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
|
|
2311
2311
|
try {
|
|
2312
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
2312
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths4, GlobalMailbox: GlobalMailbox4, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core");
|
|
2313
2313
|
const registry = new SessionRegistry(globalRoot);
|
|
2314
2314
|
const all = await registry.list();
|
|
2315
2315
|
const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
|
|
@@ -2321,7 +2321,7 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
2321
2321
|
}
|
|
2322
2322
|
const mbByDir = /* @__PURE__ */ new Map();
|
|
2323
2323
|
const mailboxFor = (projectRoot) => {
|
|
2324
|
-
const dir =
|
|
2324
|
+
const dir = resolveWstackPaths4({ projectRoot, globalRoot }).projectDir;
|
|
2325
2325
|
let mb = mbByDir.get(dir);
|
|
2326
2326
|
if (!mb) {
|
|
2327
2327
|
mb = new GlobalMailbox4(dir);
|
|
@@ -2384,14 +2384,14 @@ function pushEvent(event) {
|
|
|
2384
2384
|
}
|
|
2385
2385
|
}
|
|
2386
2386
|
function parseBody(req) {
|
|
2387
|
-
return new Promise((
|
|
2387
|
+
return new Promise((resolve12, reject) => {
|
|
2388
2388
|
let body = "";
|
|
2389
2389
|
req.on("data", (chunk) => {
|
|
2390
2390
|
body += chunk.toString("utf-8");
|
|
2391
2391
|
});
|
|
2392
2392
|
req.on("end", () => {
|
|
2393
2393
|
try {
|
|
2394
|
-
|
|
2394
|
+
resolve12(JSON.parse(body));
|
|
2395
2395
|
} catch {
|
|
2396
2396
|
reject(new Error("Invalid JSON"));
|
|
2397
2397
|
}
|
|
@@ -2458,6 +2458,251 @@ async function handleApiAnalyticsSummary(res) {
|
|
|
2458
2458
|
);
|
|
2459
2459
|
}
|
|
2460
2460
|
|
|
2461
|
+
// src/server/codemap-handlers.ts
|
|
2462
|
+
import { packageGraphService, fileGraphService, symbolGraphService } from "@wrongstack/tools";
|
|
2463
|
+
function sendJson(res, status, data) {
|
|
2464
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
2465
|
+
res.end(JSON.stringify(data));
|
|
2466
|
+
}
|
|
2467
|
+
function handleCodemapPackages(res, deps2) {
|
|
2468
|
+
try {
|
|
2469
|
+
const graph = packageGraphService({
|
|
2470
|
+
projectRoot: deps2.projectRoot,
|
|
2471
|
+
...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
|
|
2472
|
+
});
|
|
2473
|
+
sendJson(res, 200, graph);
|
|
2474
|
+
} catch (err) {
|
|
2475
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2476
|
+
sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
function handleCodemapFiles(res, deps2, pkg) {
|
|
2480
|
+
if (!pkg) {
|
|
2481
|
+
sendJson(res, 400, { error: 'Missing "package" query parameter' });
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
try {
|
|
2485
|
+
const graph = fileGraphService({
|
|
2486
|
+
projectRoot: deps2.projectRoot,
|
|
2487
|
+
packageFilter: pkg,
|
|
2488
|
+
...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
|
|
2489
|
+
});
|
|
2490
|
+
sendJson(res, 200, graph);
|
|
2491
|
+
} catch (err) {
|
|
2492
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2493
|
+
sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
function handleCodemapSymbols(res, deps2, file) {
|
|
2497
|
+
if (!file) {
|
|
2498
|
+
sendJson(res, 400, { error: 'Missing "file" query parameter' });
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
try {
|
|
2502
|
+
const graph = symbolGraphService({
|
|
2503
|
+
projectRoot: deps2.projectRoot,
|
|
2504
|
+
fileFilter: file,
|
|
2505
|
+
...deps2.indexDir ? { indexDir: deps2.indexDir } : {}
|
|
2506
|
+
});
|
|
2507
|
+
sendJson(res, 200, graph);
|
|
2508
|
+
} catch (err) {
|
|
2509
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2510
|
+
sendJson(res, 503, { error: "CodeMap index unavailable", detail: msg });
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/server/techstack-handlers.ts
|
|
2515
|
+
import { randomUUID } from "node:crypto";
|
|
2516
|
+
var DEEP_DIVE_TIMEOUT_MS = 6e4;
|
|
2517
|
+
function sendJson2(res, status, data) {
|
|
2518
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
2519
|
+
res.end(JSON.stringify(data));
|
|
2520
|
+
}
|
|
2521
|
+
async function buildResearcher(deps2, kind) {
|
|
2522
|
+
if (kind !== "analyze" || !deps2.getLlm) return void 0;
|
|
2523
|
+
const { createProviderLlm, createResearcher, createToolSearch } = await import("@wrongstack/techstack");
|
|
2524
|
+
const llm = createProviderLlm(deps2.getLlm);
|
|
2525
|
+
if (!llm) return void 0;
|
|
2526
|
+
return createResearcher({ llm, search: createToolSearch() });
|
|
2527
|
+
}
|
|
2528
|
+
function handleTechStackSnapshot(res, deps2) {
|
|
2529
|
+
try {
|
|
2530
|
+
const snapshot = deps2.store.getSnapshot(deps2.projectId);
|
|
2531
|
+
if (!snapshot) {
|
|
2532
|
+
sendJson2(res, 404, { snapshot: null, stale: false });
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2535
|
+
const ageMs = Date.now() - new Date(snapshot.createdAt).getTime();
|
|
2536
|
+
sendJson2(res, 200, { snapshot, stale: ageMs > 24 * 60 * 60 * 1e3 });
|
|
2537
|
+
} catch (error) {
|
|
2538
|
+
sendJson2(res, 500, {
|
|
2539
|
+
error: "TechStack store unavailable",
|
|
2540
|
+
detail: errorMessage(error)
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
function errorMessage(error) {
|
|
2545
|
+
return error instanceof Error ? error.message : String(error);
|
|
2546
|
+
}
|
|
2547
|
+
function requireJobDeps(res, deps2) {
|
|
2548
|
+
if (!deps2.projectRoot || !deps2.engine) {
|
|
2549
|
+
sendJson2(res, 503, { error: "TechStack engine unavailable" });
|
|
2550
|
+
return false;
|
|
2551
|
+
}
|
|
2552
|
+
return true;
|
|
2553
|
+
}
|
|
2554
|
+
function startJob(res, deps2, kind) {
|
|
2555
|
+
if (!requireJobDeps(res, deps2)) return;
|
|
2556
|
+
const jobId = randomUUID();
|
|
2557
|
+
const controller = new AbortController();
|
|
2558
|
+
deps2.runningJobs?.set(jobId, controller);
|
|
2559
|
+
deps2.emit?.({ type: "techstack.job.started", payload: { jobId, kind } });
|
|
2560
|
+
sendJson2(res, 202, { jobId, kind, status: "queued" });
|
|
2561
|
+
void buildResearcher(deps2, kind).catch(() => void 0).then(
|
|
2562
|
+
(researcher) => deps2.engine.analyze(deps2.projectId, {
|
|
2563
|
+
targetRoot: deps2.projectRoot,
|
|
2564
|
+
requestedBy: "webui",
|
|
2565
|
+
online: kind === "analyze",
|
|
2566
|
+
jobId,
|
|
2567
|
+
signal: controller.signal,
|
|
2568
|
+
researcher,
|
|
2569
|
+
onProgress: (phase, completed, total) => {
|
|
2570
|
+
deps2.emit?.({
|
|
2571
|
+
type: "techstack.job.progress",
|
|
2572
|
+
payload: { jobId, phase, completed, total }
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
})
|
|
2576
|
+
).then(({ snapshot }) => {
|
|
2577
|
+
if (controller.signal.aborted) return;
|
|
2578
|
+
deps2.emit?.({
|
|
2579
|
+
type: "techstack.snapshot.updated",
|
|
2580
|
+
payload: { snapshot, stale: false }
|
|
2581
|
+
});
|
|
2582
|
+
}).catch((error) => {
|
|
2583
|
+
if (controller.signal.aborted) {
|
|
2584
|
+
deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
deps2.emit?.({
|
|
2588
|
+
type: "techstack.job.failed",
|
|
2589
|
+
payload: { jobId, error: errorMessage(error) }
|
|
2590
|
+
});
|
|
2591
|
+
}).finally(() => {
|
|
2592
|
+
deps2.runningJobs?.delete(jobId);
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
function handleTechStackInventory(res, deps2) {
|
|
2596
|
+
startJob(res, deps2, "inventory");
|
|
2597
|
+
}
|
|
2598
|
+
function handleTechStackAnalyze(res, deps2) {
|
|
2599
|
+
startJob(res, deps2, "analyze");
|
|
2600
|
+
}
|
|
2601
|
+
function handleTechStackCancel(res, deps2, jobId) {
|
|
2602
|
+
const controller = deps2.runningJobs?.get(jobId);
|
|
2603
|
+
if (controller && !controller.signal.aborted) controller.abort();
|
|
2604
|
+
deps2.store.updateJobStatus(jobId, "cancelled");
|
|
2605
|
+
deps2.emit?.({ type: "techstack.job.cancelled", payload: { jobId } });
|
|
2606
|
+
sendJson2(res, 200, { jobId, status: "cancelled" });
|
|
2607
|
+
}
|
|
2608
|
+
async function handleTechStackDependencyResearch(res, deps2, dependencyId) {
|
|
2609
|
+
const snapshot = deps2.store.getSnapshot(deps2.projectId);
|
|
2610
|
+
const dependency = snapshot?.dependencies.find((dep) => dep.id === dependencyId);
|
|
2611
|
+
if (!dependency) {
|
|
2612
|
+
sendJson2(res, 404, { error: "Dependency not found in the current snapshot" });
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
let researcher;
|
|
2616
|
+
try {
|
|
2617
|
+
researcher = await buildResearcher(deps2, "analyze");
|
|
2618
|
+
} catch (error) {
|
|
2619
|
+
sendJson2(res, 503, { error: "Research unavailable", detail: errorMessage(error) });
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
if (!researcher) {
|
|
2623
|
+
sendJson2(res, 503, {
|
|
2624
|
+
error: "No model configured \u2014 connect a provider to run LLM analysis."
|
|
2625
|
+
});
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
const controller = new AbortController();
|
|
2629
|
+
const timeout = setTimeout(() => {
|
|
2630
|
+
controller.abort(new Error("research timeout"));
|
|
2631
|
+
}, DEEP_DIVE_TIMEOUT_MS);
|
|
2632
|
+
timeout.unref?.();
|
|
2633
|
+
try {
|
|
2634
|
+
const { triageCandidates } = await import("@wrongstack/techstack");
|
|
2635
|
+
const [triaged] = triageCandidates([dependency], { limit: 1 });
|
|
2636
|
+
const findings = await researcher.research(
|
|
2637
|
+
[triaged ?? { dependency, cluster: "breaking_change", priority: 0 }],
|
|
2638
|
+
{ signal: controller.signal }
|
|
2639
|
+
);
|
|
2640
|
+
sendJson2(res, 200, { dependencyId, findings });
|
|
2641
|
+
} catch (error) {
|
|
2642
|
+
sendJson2(res, 500, { error: "Research failed", detail: errorMessage(error) });
|
|
2643
|
+
} finally {
|
|
2644
|
+
clearTimeout(timeout);
|
|
2645
|
+
controller.abort();
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
function handleTechStackJobStatus(res, deps2, jobId) {
|
|
2649
|
+
const job = deps2.store.getJob(jobId);
|
|
2650
|
+
if (!job) {
|
|
2651
|
+
sendJson2(res, 404, { error: "Job not found" });
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
sendJson2(res, 200, { job });
|
|
2655
|
+
}
|
|
2656
|
+
function handleTechStackReport(res, deps2, reportId, format) {
|
|
2657
|
+
const snapshot = deps2.store.getSnapshotById(reportId);
|
|
2658
|
+
if (!snapshot) {
|
|
2659
|
+
sendJson2(res, 404, { error: "Report not found" });
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
if (deps2.engine) {
|
|
2663
|
+
const report = deps2.engine.generateReport(snapshot, format);
|
|
2664
|
+
res.writeHead(200, {
|
|
2665
|
+
"Content-Type": format === "json" ? "application/json" : "text/markdown",
|
|
2666
|
+
"Content-Disposition": `attachment; filename="techstack-report.${format}"`
|
|
2667
|
+
});
|
|
2668
|
+
res.end(report);
|
|
2669
|
+
} else {
|
|
2670
|
+
sendJson2(res, 200, snapshot);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
// src/server/projects-manifest.ts
|
|
2675
|
+
import * as fs5 from "node:fs/promises";
|
|
2676
|
+
import * as path6 from "node:path";
|
|
2677
|
+
import { projectSlug } from "@wrongstack/core";
|
|
2678
|
+
function projectsJsonPath(globalConfigPath) {
|
|
2679
|
+
const base = path6.dirname(globalConfigPath);
|
|
2680
|
+
return path6.join(base, "projects.json");
|
|
2681
|
+
}
|
|
2682
|
+
async function loadManifest(globalConfigPath) {
|
|
2683
|
+
try {
|
|
2684
|
+
const raw = await fs5.readFile(projectsJsonPath(globalConfigPath), "utf8");
|
|
2685
|
+
const parsed = JSON.parse(raw);
|
|
2686
|
+
return { projects: parsed.projects ?? [] };
|
|
2687
|
+
} catch {
|
|
2688
|
+
return { projects: [] };
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
async function saveManifest(manifest, globalConfigPath) {
|
|
2692
|
+
const file = projectsJsonPath(globalConfigPath);
|
|
2693
|
+
await fs5.mkdir(path6.dirname(file), { recursive: true });
|
|
2694
|
+
await fs5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
2695
|
+
}
|
|
2696
|
+
function generateProjectSlug(rootPath) {
|
|
2697
|
+
return projectSlug(rootPath);
|
|
2698
|
+
}
|
|
2699
|
+
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
2700
|
+
const base = path6.dirname(globalConfigPath);
|
|
2701
|
+
const dir = path6.join(base, "projects", slug);
|
|
2702
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
2703
|
+
return dir;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2461
2706
|
// src/server/ws-auth.ts
|
|
2462
2707
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
2463
2708
|
import { timingSafeEqual } from "node:crypto";
|
|
@@ -2643,9 +2888,9 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
|
|
|
2643
2888
|
return `default-src 'self'; script-src ${scriptSrc}; style-src 'self' 'unsafe-inline'; connect-src ${Array.from(connect).join(" ")}; img-src 'self' data:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`;
|
|
2644
2889
|
}
|
|
2645
2890
|
function isInsideDist(candidate, distDir) {
|
|
2646
|
-
const root =
|
|
2647
|
-
const resolved =
|
|
2648
|
-
return resolved === root || resolved.startsWith(root +
|
|
2891
|
+
const root = path7.resolve(distDir);
|
|
2892
|
+
const resolved = path7.resolve(candidate);
|
|
2893
|
+
return resolved === root || resolved.startsWith(root + path7.sep);
|
|
2649
2894
|
}
|
|
2650
2895
|
function decodeSessionId(segment) {
|
|
2651
2896
|
try {
|
|
@@ -2656,10 +2901,19 @@ function decodeSessionId(segment) {
|
|
|
2656
2901
|
}
|
|
2657
2902
|
function createHttpServer(opts) {
|
|
2658
2903
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
2659
|
-
const distDir =
|
|
2904
|
+
const distDir = path7.resolve(opts.distDir);
|
|
2660
2905
|
const wsPort = opts.wsPort;
|
|
2661
2906
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
2662
|
-
|
|
2907
|
+
let techStackRuntime = null;
|
|
2908
|
+
const getTechStackRuntime = async () => {
|
|
2909
|
+
if (!opts.projectRoot) throw new Error("Project root not configured");
|
|
2910
|
+
techStackRuntime ??= import("@wrongstack/techstack").then(({ TechStackEngine, TechStackStore }) => {
|
|
2911
|
+
const store = new TechStackStore({ projectSlug: generateProjectSlug(opts.projectRoot) });
|
|
2912
|
+
return { store, engine: new TechStackEngine(store), runningJobs: /* @__PURE__ */ new Map() };
|
|
2913
|
+
});
|
|
2914
|
+
return techStackRuntime;
|
|
2915
|
+
};
|
|
2916
|
+
const server = http.createServer(async (req, res) => {
|
|
2663
2917
|
try {
|
|
2664
2918
|
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
2665
2919
|
const providedAccessToken = requestToken(req, url);
|
|
@@ -2810,6 +3064,127 @@ function createHttpServer(opts) {
|
|
|
2810
3064
|
await handleApiAnalyticsSummary(res);
|
|
2811
3065
|
return;
|
|
2812
3066
|
}
|
|
3067
|
+
if (url.pathname === "/api/codemap/packages" && req.method === "GET") {
|
|
3068
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
3069
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
3070
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
if (!opts.projectRoot) {
|
|
3074
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
3075
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3078
|
+
handleCodemapPackages(res, {
|
|
3079
|
+
projectRoot: opts.projectRoot,
|
|
3080
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
3081
|
+
});
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
if (url.pathname === "/api/codemap/files" && req.method === "GET") {
|
|
3085
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
3086
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
3087
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
if (!opts.projectRoot) {
|
|
3091
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
3092
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
const pkg = url.searchParams.get("package") ?? "";
|
|
3096
|
+
handleCodemapFiles(res, {
|
|
3097
|
+
projectRoot: opts.projectRoot,
|
|
3098
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
3099
|
+
}, pkg);
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
3102
|
+
if (url.pathname === "/api/codemap/symbols" && req.method === "GET") {
|
|
3103
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
3104
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
3105
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
if (!opts.projectRoot) {
|
|
3109
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
3110
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
const file = url.searchParams.get("file") ?? "";
|
|
3114
|
+
handleCodemapSymbols(res, {
|
|
3115
|
+
projectRoot: opts.projectRoot,
|
|
3116
|
+
...opts.indexDir ? { indexDir: opts.indexDir } : {}
|
|
3117
|
+
}, file);
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
if (url.pathname.startsWith("/api/techstack/")) {
|
|
3121
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
3122
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
3123
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
if (!opts.projectRoot) {
|
|
3127
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
3128
|
+
res.end(JSON.stringify({ error: "Project root not configured" }));
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
3131
|
+
try {
|
|
3132
|
+
const runtime = await getTechStackRuntime();
|
|
3133
|
+
const deps2 = {
|
|
3134
|
+
projectId: opts.projectRoot,
|
|
3135
|
+
projectRoot: opts.projectRoot,
|
|
3136
|
+
store: runtime.store,
|
|
3137
|
+
engine: runtime.engine,
|
|
3138
|
+
runningJobs: runtime.runningJobs,
|
|
3139
|
+
emit: opts.onTechStackEvent,
|
|
3140
|
+
getLlm: opts.getLlm
|
|
3141
|
+
};
|
|
3142
|
+
if (url.pathname === "/api/techstack/snapshot" && req.method === "GET") {
|
|
3143
|
+
handleTechStackSnapshot(res, deps2);
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
if (url.pathname === "/api/techstack/inventory" && req.method === "POST") {
|
|
3147
|
+
handleTechStackInventory(res, deps2);
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
3150
|
+
if (url.pathname === "/api/techstack/analyze" && req.method === "POST") {
|
|
3151
|
+
handleTechStackAnalyze(res, deps2);
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
const cancelMatch = /^\/api\/techstack\/jobs\/([^/]+)\/cancel$/.exec(url.pathname);
|
|
3155
|
+
if (cancelMatch && req.method === "POST") {
|
|
3156
|
+
handleTechStackCancel(res, deps2, decodeURIComponent(cancelMatch[1]));
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
const jobMatch = /^\/api\/techstack\/jobs\/([^/]+)$/.exec(url.pathname);
|
|
3160
|
+
if (jobMatch && req.method === "GET") {
|
|
3161
|
+
handleTechStackJobStatus(res, deps2, decodeURIComponent(jobMatch[1]));
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
const reportMatch = /^\/api\/techstack\/reports\/([^/]+)$/.exec(url.pathname);
|
|
3165
|
+
if (reportMatch && req.method === "GET") {
|
|
3166
|
+
const fmt = url.searchParams.get("format") === "json" ? "json" : "md";
|
|
3167
|
+
handleTechStackReport(res, deps2, decodeURIComponent(reportMatch[1]), fmt);
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
const researchMatch = /^\/api\/techstack\/deps\/([^/]+)\/research$/.exec(url.pathname);
|
|
3171
|
+
if (researchMatch && req.method === "POST") {
|
|
3172
|
+
await handleTechStackDependencyResearch(
|
|
3173
|
+
res,
|
|
3174
|
+
deps2,
|
|
3175
|
+
decodeURIComponent(researchMatch[1])
|
|
3176
|
+
);
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
} catch (error) {
|
|
3180
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
3181
|
+
res.end(JSON.stringify({
|
|
3182
|
+
error: "TechStack store unavailable",
|
|
3183
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
3184
|
+
}));
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
2813
3188
|
if (url.pathname === "/debug/watcher-metrics" && req.method === "GET") {
|
|
2814
3189
|
if (requireAccessToken && !accessTokenOk) {
|
|
2815
3190
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
@@ -2833,21 +3208,21 @@ function createHttpServer(opts) {
|
|
|
2833
3208
|
}
|
|
2834
3209
|
let filePath;
|
|
2835
3210
|
if (url.pathname === "/" || url.pathname === "") {
|
|
2836
|
-
filePath =
|
|
3211
|
+
filePath = path7.join(distDir, "index.html");
|
|
2837
3212
|
} else if (url.pathname.startsWith("/assets/")) {
|
|
2838
|
-
filePath =
|
|
3213
|
+
filePath = path7.join(distDir, url.pathname);
|
|
2839
3214
|
} else if (url.pathname.startsWith("/")) {
|
|
2840
|
-
filePath =
|
|
3215
|
+
filePath = path7.join(distDir, url.pathname);
|
|
2841
3216
|
} else {
|
|
2842
|
-
filePath =
|
|
3217
|
+
filePath = path7.join(distDir, "index.html");
|
|
2843
3218
|
}
|
|
2844
|
-
const resolvedPath =
|
|
3219
|
+
const resolvedPath = path7.resolve(filePath);
|
|
2845
3220
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
2846
3221
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
2847
3222
|
res.end("Forbidden");
|
|
2848
3223
|
return;
|
|
2849
3224
|
}
|
|
2850
|
-
const ext =
|
|
3225
|
+
const ext = path7.extname(resolvedPath);
|
|
2851
3226
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2852
3227
|
res.setHeader("Content-Type", contentType);
|
|
2853
3228
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
@@ -2859,18 +3234,18 @@ function createHttpServer(opts) {
|
|
|
2859
3234
|
"Content-Security-Policy",
|
|
2860
3235
|
buildCspHeader(wsPort, requestHostForCsp(req.headers.host), opts.publicWsUrl)
|
|
2861
3236
|
);
|
|
2862
|
-
const html = await
|
|
3237
|
+
const html = await fs6.readFile(resolvedPath, "utf8");
|
|
2863
3238
|
res.writeHead(200);
|
|
2864
3239
|
res.end(injectWsConfig(html, { wsPort, publicWsUrl: opts.publicWsUrl }));
|
|
2865
3240
|
return;
|
|
2866
3241
|
}
|
|
2867
|
-
const fileContent = await
|
|
3242
|
+
const fileContent = await fs6.readFile(resolvedPath);
|
|
2868
3243
|
res.writeHead(200);
|
|
2869
3244
|
res.end(fileContent);
|
|
2870
3245
|
} catch (err) {
|
|
2871
3246
|
if (err.code === "ENOENT") {
|
|
2872
3247
|
try {
|
|
2873
|
-
const html = await
|
|
3248
|
+
const html = await fs6.readFile(path7.join(distDir, "index.html"), "utf8");
|
|
2874
3249
|
res.writeHead(200, {
|
|
2875
3250
|
"Content-Type": "text/html",
|
|
2876
3251
|
"X-Content-Type-Options": "nosniff",
|
|
@@ -2893,18 +3268,26 @@ function createHttpServer(opts) {
|
|
|
2893
3268
|
}
|
|
2894
3269
|
}
|
|
2895
3270
|
});
|
|
3271
|
+
server.once("close", () => {
|
|
3272
|
+
void techStackRuntime?.then(({ store, runningJobs }) => {
|
|
3273
|
+
for (const controller of runningJobs.values()) controller.abort();
|
|
3274
|
+
runningJobs.clear();
|
|
3275
|
+
store.close();
|
|
3276
|
+
}).catch(() => void 0);
|
|
3277
|
+
});
|
|
3278
|
+
return server;
|
|
2896
3279
|
}
|
|
2897
3280
|
|
|
2898
3281
|
// src/server/instance-registry.ts
|
|
2899
3282
|
import * as os from "node:os";
|
|
2900
|
-
import * as
|
|
2901
|
-
import * as
|
|
3283
|
+
import * as path8 from "node:path";
|
|
3284
|
+
import * as fs7 from "node:fs/promises";
|
|
2902
3285
|
import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
|
|
2903
3286
|
function defaultBaseDir() {
|
|
2904
|
-
return
|
|
3287
|
+
return path8.join(os.homedir(), ".wrongstack");
|
|
2905
3288
|
}
|
|
2906
3289
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
2907
|
-
return
|
|
3290
|
+
return path8.join(baseDir, "webui-instances.json");
|
|
2908
3291
|
}
|
|
2909
3292
|
function isPidAlive(pid) {
|
|
2910
3293
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -2917,7 +3300,7 @@ function isPidAlive(pid) {
|
|
|
2917
3300
|
}
|
|
2918
3301
|
async function load(file) {
|
|
2919
3302
|
try {
|
|
2920
|
-
const raw = await
|
|
3303
|
+
const raw = await fs7.readFile(file, "utf8");
|
|
2921
3304
|
const parsed = JSON.parse(raw);
|
|
2922
3305
|
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
2923
3306
|
return parsed;
|
|
@@ -3210,7 +3593,7 @@ async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
|
3210
3593
|
payload: { name: serverName, resources, resourceTemplates }
|
|
3211
3594
|
});
|
|
3212
3595
|
} catch (err) {
|
|
3213
|
-
sendContentError(ws, "resources", serverName,
|
|
3596
|
+
sendContentError(ws, "resources", serverName, errorMessage2(err));
|
|
3214
3597
|
}
|
|
3215
3598
|
}
|
|
3216
3599
|
async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
@@ -3224,7 +3607,7 @@ async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
|
3224
3607
|
});
|
|
3225
3608
|
send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
|
|
3226
3609
|
} catch (err) {
|
|
3227
|
-
sendContentError(ws, "prompts", serverName,
|
|
3610
|
+
sendContentError(ws, "prompts", serverName, errorMessage2(err));
|
|
3228
3611
|
}
|
|
3229
3612
|
}
|
|
3230
3613
|
async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
@@ -3240,7 +3623,7 @@ async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
|
3240
3623
|
);
|
|
3241
3624
|
send(ws, { type: "mcp.content.selected", payload: insertion });
|
|
3242
3625
|
} catch (err) {
|
|
3243
|
-
sendContentError(ws, "resource.read", serverName,
|
|
3626
|
+
sendContentError(ws, "resource.read", serverName, errorMessage2(err));
|
|
3244
3627
|
}
|
|
3245
3628
|
}
|
|
3246
3629
|
async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
@@ -3256,7 +3639,7 @@ async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
|
3256
3639
|
);
|
|
3257
3640
|
send(ws, { type: "mcp.content.selected", payload: insertion });
|
|
3258
3641
|
} catch (err) {
|
|
3259
|
-
sendContentError(ws, "prompt.get", serverName,
|
|
3642
|
+
sendContentError(ws, "prompt.get", serverName, errorMessage2(err));
|
|
3260
3643
|
}
|
|
3261
3644
|
}
|
|
3262
3645
|
function payloadRecord(msg) {
|
|
@@ -3284,14 +3667,14 @@ function promptArguments(value) {
|
|
|
3284
3667
|
function sendContentError(ws, action, name2, error) {
|
|
3285
3668
|
send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
|
|
3286
3669
|
}
|
|
3287
|
-
function
|
|
3670
|
+
function errorMessage2(err) {
|
|
3288
3671
|
return err instanceof Error ? err.message : String(err);
|
|
3289
3672
|
}
|
|
3290
3673
|
|
|
3291
3674
|
// src/server/memory-handlers.ts
|
|
3292
3675
|
function isSuperMemoryStore(store) {
|
|
3293
3676
|
const s = store;
|
|
3294
|
-
return typeof s.stats === "function" && typeof s.listSuper === "function" && typeof s.getSuperMemory === "function" && typeof s.updateSuperMemory === "function" && typeof s.deleteSuperMemory === "function";
|
|
3677
|
+
return typeof s.stats === "function" && typeof s.listSuper === "function" && typeof s.getSuperMemory === "function" && typeof s.updateSuperMemory === "function" && typeof s.deleteSuperMemory === "function" && typeof s.acceptCandidate === "function" && typeof s.rejectCandidate === "function";
|
|
3295
3678
|
}
|
|
3296
3679
|
function requiresSuperMemory(command) {
|
|
3297
3680
|
return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
|
|
@@ -3415,6 +3798,7 @@ async function handleSuperMemoryRemember(ws, msg, memoryStore) {
|
|
|
3415
3798
|
importance: payload["importance"],
|
|
3416
3799
|
confidence: payload["confidence"],
|
|
3417
3800
|
freshness: payload["freshness"],
|
|
3801
|
+
audience: payload["audience"],
|
|
3418
3802
|
supersedes: payload["supersedes"],
|
|
3419
3803
|
contradicts: payload["contradicts"]
|
|
3420
3804
|
});
|
|
@@ -3428,18 +3812,172 @@ async function handleSuperMemoryDelete(ws, msg, memoryStore) {
|
|
|
3428
3812
|
send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
|
|
3429
3813
|
return;
|
|
3430
3814
|
}
|
|
3431
|
-
const { id, reason } = msg.payload;
|
|
3815
|
+
const { id, reason, neverInject } = msg.payload;
|
|
3432
3816
|
if (!id) {
|
|
3433
3817
|
send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
|
|
3434
3818
|
return;
|
|
3435
3819
|
}
|
|
3436
3820
|
try {
|
|
3437
|
-
await memoryStore.deleteSuperMemory(id, reason);
|
|
3821
|
+
if (neverInject === true) await memoryStore.deleteSuperMemory(id, reason, { neverInject: true });
|
|
3822
|
+
else await memoryStore.deleteSuperMemory(id, reason);
|
|
3438
3823
|
send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
|
|
3439
3824
|
} catch (err) {
|
|
3440
3825
|
send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
|
|
3441
3826
|
}
|
|
3442
3827
|
}
|
|
3828
|
+
async function handleSuperMemoryRecover(ws, msg, memoryStore) {
|
|
3829
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3830
|
+
send(ws, { type: "memory.super.recover", payload: { error: requiresSuperMemory("memory.super.recover") } });
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3833
|
+
const payload = msg.payload;
|
|
3834
|
+
const id = payload["id"];
|
|
3835
|
+
if (!id) {
|
|
3836
|
+
send(ws, { type: "memory.super.recover", payload: { error: "id is required" } });
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
const reason = payload["reason"];
|
|
3840
|
+
try {
|
|
3841
|
+
const preExisting = await memoryStore.getSuperMemory(id);
|
|
3842
|
+
if (!preExisting) {
|
|
3843
|
+
send(ws, { type: "memory.super.recover", payload: { error: `Super Memory "${id}" not found.` } });
|
|
3844
|
+
return;
|
|
3845
|
+
}
|
|
3846
|
+
if (preExisting.status === "active") {
|
|
3847
|
+
send(ws, { type: "memory.super.recover", payload: { recovered: true, memory: preExisting, noop: true } });
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3850
|
+
const memory = await memoryStore.recoverSuperMemory(id, reason);
|
|
3851
|
+
const noop = memory.id !== id;
|
|
3852
|
+
const response = { recovered: true, memory };
|
|
3853
|
+
if (noop) {
|
|
3854
|
+
response["activeId"] = memory.id;
|
|
3855
|
+
response["noop"] = true;
|
|
3856
|
+
}
|
|
3857
|
+
send(ws, { type: "memory.super.recover", payload: response });
|
|
3858
|
+
} catch (err) {
|
|
3859
|
+
send(ws, { type: "memory.super.recover", payload: { error: errMessage(err) } });
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
async function handleSuperMemoryCandidateResolve(ws, msg, memoryStore) {
|
|
3863
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3864
|
+
send(ws, {
|
|
3865
|
+
type: "memory.super.candidateResolve",
|
|
3866
|
+
payload: { error: requiresSuperMemory("memory.super.candidateResolve") }
|
|
3867
|
+
});
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
const payload = msg.payload;
|
|
3871
|
+
const candidateId = payload["candidateId"];
|
|
3872
|
+
const action = payload["action"];
|
|
3873
|
+
if (!candidateId) {
|
|
3874
|
+
send(ws, {
|
|
3875
|
+
type: "memory.super.candidateResolve",
|
|
3876
|
+
payload: { error: "candidateId is required" }
|
|
3877
|
+
});
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
if (action !== "accept" && action !== "reject") {
|
|
3881
|
+
send(ws, {
|
|
3882
|
+
type: "memory.super.candidateResolve",
|
|
3883
|
+
payload: { error: 'action must be "accept" or "reject"' }
|
|
3884
|
+
});
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3887
|
+
const reason = payload["reason"];
|
|
3888
|
+
try {
|
|
3889
|
+
let candidate;
|
|
3890
|
+
if (action === "accept") {
|
|
3891
|
+
const accepted = await memoryStore.acceptCandidate(candidateId);
|
|
3892
|
+
candidate = accepted ? { id: accepted.id, status: accepted.status ?? "active" } : void 0;
|
|
3893
|
+
} else {
|
|
3894
|
+
const rejected = await memoryStore.rejectCandidate(
|
|
3895
|
+
candidateId,
|
|
3896
|
+
reason ?? "Rejected via WebUI"
|
|
3897
|
+
);
|
|
3898
|
+
candidate = rejected ? { id: candidateId, status: "rejected" } : void 0;
|
|
3899
|
+
}
|
|
3900
|
+
if (!candidate) {
|
|
3901
|
+
send(ws, {
|
|
3902
|
+
type: "memory.super.candidateResolve",
|
|
3903
|
+
payload: { error: `Candidate "${candidateId}" not found` }
|
|
3904
|
+
});
|
|
3905
|
+
return;
|
|
3906
|
+
}
|
|
3907
|
+
send(ws, {
|
|
3908
|
+
type: "memory.super.candidateResolve",
|
|
3909
|
+
payload: { candidate, resolvedAction: action }
|
|
3910
|
+
});
|
|
3911
|
+
} catch (err) {
|
|
3912
|
+
send(ws, {
|
|
3913
|
+
type: "memory.super.candidateResolve",
|
|
3914
|
+
payload: { error: errMessage(err) }
|
|
3915
|
+
});
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
async function handleSuperMemoryBackfillRecoverable(ws, msg, memoryStore) {
|
|
3919
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3920
|
+
send(ws, {
|
|
3921
|
+
type: "memory.super.backfillRecoverable",
|
|
3922
|
+
payload: { error: requiresSuperMemory("memory.super.backfillRecoverable") }
|
|
3923
|
+
});
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
const payload = msg.payload ?? {};
|
|
3927
|
+
const apply = payload["apply"] === true;
|
|
3928
|
+
const rawFilter = payload["filter"] ?? {};
|
|
3929
|
+
const filter = {};
|
|
3930
|
+
if (Array.isArray(rawFilter["kinds"])) filter.kinds = rawFilter["kinds"];
|
|
3931
|
+
if (Array.isArray(rawFilter["scopes"])) filter.scopes = rawFilter["scopes"];
|
|
3932
|
+
if (typeof rawFilter["updatedAfter"] === "string") filter.updatedAfter = rawFilter["updatedAfter"];
|
|
3933
|
+
if (typeof rawFilter["updatedBefore"] === "string") filter.updatedBefore = rawFilter["updatedBefore"];
|
|
3934
|
+
try {
|
|
3935
|
+
const report = await memoryStore.backfillRecoverable({
|
|
3936
|
+
apply,
|
|
3937
|
+
...Object.keys(filter).length > 0 ? { filter } : {}
|
|
3938
|
+
});
|
|
3939
|
+
send(ws, {
|
|
3940
|
+
type: "memory.super.backfillRecoverable",
|
|
3941
|
+
payload: {
|
|
3942
|
+
examined: report.examined,
|
|
3943
|
+
recovered: report.recovered,
|
|
3944
|
+
recoverable: report.recoverable,
|
|
3945
|
+
dryRun: !apply
|
|
3946
|
+
}
|
|
3947
|
+
});
|
|
3948
|
+
} catch (err) {
|
|
3949
|
+
send(ws, {
|
|
3950
|
+
type: "memory.super.backfillRecoverable",
|
|
3951
|
+
payload: { error: errMessage(err) }
|
|
3952
|
+
});
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
async function handleSuperMemoryForFile(ws, msg, memoryStore) {
|
|
3956
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3957
|
+
send(ws, {
|
|
3958
|
+
type: "memory.super.forFile",
|
|
3959
|
+
payload: { error: requiresSuperMemory("memory.super.forFile") }
|
|
3960
|
+
});
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
const payload = msg.payload ?? {};
|
|
3964
|
+
const filePath = payload["filePath"];
|
|
3965
|
+
if (!filePath) {
|
|
3966
|
+
send(ws, { type: "memory.super.forFile", payload: { error: "filePath is required" } });
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3969
|
+
try {
|
|
3970
|
+
const response = await memoryStore.findMemoriesForFile(filePath, {
|
|
3971
|
+
...typeof payload["lineStart"] === "number" ? { lineStart: payload["lineStart"] } : {},
|
|
3972
|
+
...typeof payload["lineEnd"] === "number" ? { lineEnd: payload["lineEnd"] } : {},
|
|
3973
|
+
...typeof payload["limit"] === "number" ? { limit: payload["limit"] } : {},
|
|
3974
|
+
...payload["includeDeleted"] === true ? { includeDeleted: true } : {}
|
|
3975
|
+
});
|
|
3976
|
+
send(ws, { type: "memory.super.forFile", payload: response });
|
|
3977
|
+
} catch (err) {
|
|
3978
|
+
send(ws, { type: "memory.super.forFile", payload: { error: errMessage(err) } });
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3443
3981
|
|
|
3444
3982
|
// src/server/open-browser.ts
|
|
3445
3983
|
import { spawn } from "node:child_process";
|
|
@@ -3488,16 +4026,16 @@ function openBrowser(url, platform = process.platform) {
|
|
|
3488
4026
|
import * as net from "node:net";
|
|
3489
4027
|
import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
|
|
3490
4028
|
function isPortFree(host, port) {
|
|
3491
|
-
return new Promise((
|
|
4029
|
+
return new Promise((resolve12) => {
|
|
3492
4030
|
const srv = net.createServer();
|
|
3493
|
-
srv.once("error", () =>
|
|
4031
|
+
srv.once("error", () => resolve12(false));
|
|
3494
4032
|
srv.once("listening", () => {
|
|
3495
|
-
srv.close(() =>
|
|
4033
|
+
srv.close(() => resolve12(true));
|
|
3496
4034
|
});
|
|
3497
4035
|
try {
|
|
3498
4036
|
srv.listen(port, host);
|
|
3499
4037
|
} catch {
|
|
3500
|
-
|
|
4038
|
+
resolve12(false);
|
|
3501
4039
|
}
|
|
3502
4040
|
});
|
|
3503
4041
|
}
|
|
@@ -3724,13 +4262,13 @@ async function handlePromptsRecent(ws, ctx) {
|
|
|
3724
4262
|
}
|
|
3725
4263
|
|
|
3726
4264
|
// src/server/provider-config-io.ts
|
|
3727
|
-
import * as
|
|
4265
|
+
import * as fs8 from "node:fs/promises";
|
|
3728
4266
|
import { ConfigError, atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
3729
4267
|
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
3730
4268
|
async function loadSavedProviders(configPath, vault) {
|
|
3731
4269
|
let raw;
|
|
3732
4270
|
try {
|
|
3733
|
-
raw = await
|
|
4271
|
+
raw = await fs8.readFile(configPath, "utf8");
|
|
3734
4272
|
} catch {
|
|
3735
4273
|
return {};
|
|
3736
4274
|
}
|
|
@@ -3747,7 +4285,7 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
3747
4285
|
let raw;
|
|
3748
4286
|
let fileExists = true;
|
|
3749
4287
|
try {
|
|
3750
|
-
raw = await
|
|
4288
|
+
raw = await fs8.readFile(configPath, "utf8");
|
|
3751
4289
|
} catch (err) {
|
|
3752
4290
|
if (err.code !== "ENOENT") {
|
|
3753
4291
|
throw new ConfigError({
|
|
@@ -3781,6 +4319,10 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
3781
4319
|
|
|
3782
4320
|
// src/server/provider-keys.ts
|
|
3783
4321
|
import { expectDefined } from "@wrongstack/core";
|
|
4322
|
+
import {
|
|
4323
|
+
buildProviderConfigFromPreset,
|
|
4324
|
+
resolvePresetForAlias
|
|
4325
|
+
} from "@wrongstack/providers";
|
|
3784
4326
|
function normalizeKeys(cfg) {
|
|
3785
4327
|
if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
|
|
3786
4328
|
return cfg.apiKeys.map((k) => ({ ...k }));
|
|
@@ -3809,8 +4351,28 @@ function maskedKey(key) {
|
|
|
3809
4351
|
if (key.length <= 8) return "\u2022".repeat(key.length);
|
|
3810
4352
|
return `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
|
|
3811
4353
|
}
|
|
4354
|
+
function hydratePresetConfig(providerId, dest) {
|
|
4355
|
+
const preset = resolvePresetForAlias(providerId);
|
|
4356
|
+
if (!preset) return void 0;
|
|
4357
|
+
const template = buildProviderConfigFromPreset(preset);
|
|
4358
|
+
if (!dest.type) dest.type = preset.id;
|
|
4359
|
+
if (!dest.family) dest.family = preset.family;
|
|
4360
|
+
if (dest.baseUrl === void 0) dest.baseUrl = template.baseUrl;
|
|
4361
|
+
if (!dest.envVars || dest.envVars.length === 0) dest.envVars = template.envVars;
|
|
4362
|
+
if (!dest.models || dest.models.length === 0) dest.models = template.models;
|
|
4363
|
+
if (template.customModels && (!dest.customModels || Object.keys(dest.customModels).length === 0)) {
|
|
4364
|
+
dest.customModels = template.customModels;
|
|
4365
|
+
}
|
|
4366
|
+
if (template.quirks && dest.quirks === void 0) dest.quirks = template.quirks;
|
|
4367
|
+
return preset.id;
|
|
4368
|
+
}
|
|
3812
4369
|
function upsertKey(providers, providerId, label, apiKey, nowIso) {
|
|
3813
|
-
|
|
4370
|
+
let existing = providers[providerId];
|
|
4371
|
+
if (!existing) {
|
|
4372
|
+
existing = { type: providerId };
|
|
4373
|
+
const presetId = hydratePresetConfig(providerId, existing);
|
|
4374
|
+
if (presetId) existing.type = presetId;
|
|
4375
|
+
}
|
|
3814
4376
|
const keys = normalizeKeys(existing);
|
|
3815
4377
|
const idx = keys.findIndex((k) => k.label === label);
|
|
3816
4378
|
if (idx >= 0) {
|
|
@@ -3860,6 +4422,8 @@ function addProvider(providers, payload, nowIso) {
|
|
|
3860
4422
|
family: payload.family,
|
|
3861
4423
|
baseUrl: payload.baseUrl
|
|
3862
4424
|
};
|
|
4425
|
+
const presetId = hydratePresetConfig(payload.id, newProv);
|
|
4426
|
+
if (presetId) newProv.type = presetId;
|
|
3863
4427
|
if (payload.apiKey) {
|
|
3864
4428
|
newProv.apiKeys = [{ label: "default", apiKey: payload.apiKey, createdAt: nowIso }];
|
|
3865
4429
|
newProv.activeKey = "default";
|
|
@@ -4026,7 +4590,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
4026
4590
|
};
|
|
4027
4591
|
|
|
4028
4592
|
// src/server/sdd-wizard-wiring.ts
|
|
4029
|
-
import * as
|
|
4593
|
+
import * as path9 from "node:path";
|
|
4030
4594
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
4031
4595
|
import {
|
|
4032
4596
|
DefaultTaskStore,
|
|
@@ -4126,7 +4690,7 @@ function buildSddWizardDeps(opts) {
|
|
|
4126
4690
|
makeDriver: () => new SddInterviewDriver({
|
|
4127
4691
|
specStore: new SpecStore({ baseDir: opts.paths.projectSpecs }),
|
|
4128
4692
|
graphStore: new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs }),
|
|
4129
|
-
sessionPath:
|
|
4693
|
+
sessionPath: path9.join(opts.paths.projectDir, "sdd-wizard-session.json")
|
|
4130
4694
|
}),
|
|
4131
4695
|
runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
|
|
4132
4696
|
startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels, worktrees: useWorktrees }) => {
|
|
@@ -4328,8 +4892,8 @@ function toSessionHistoryEntries(summaries, currentSessionId) {
|
|
|
4328
4892
|
}
|
|
4329
4893
|
|
|
4330
4894
|
// src/server/shell-open.ts
|
|
4331
|
-
import * as
|
|
4332
|
-
import * as
|
|
4895
|
+
import * as fs9 from "node:fs/promises";
|
|
4896
|
+
import * as path10 from "node:path";
|
|
4333
4897
|
import { spawn as spawn2 } from "node:child_process";
|
|
4334
4898
|
var METACHAR_REGEX = /[&|<>^"'`'\n\r]/;
|
|
4335
4899
|
function shellQuote(s) {
|
|
@@ -4337,8 +4901,8 @@ function shellQuote(s) {
|
|
|
4337
4901
|
}
|
|
4338
4902
|
async function handleShellOpen(req, logger) {
|
|
4339
4903
|
try {
|
|
4340
|
-
const resolved =
|
|
4341
|
-
await
|
|
4904
|
+
const resolved = path10.resolve(req.path);
|
|
4905
|
+
await fs9.access(resolved);
|
|
4342
4906
|
if (METACHAR_REGEX.test(resolved)) {
|
|
4343
4907
|
return { success: false, message: "Path contains unsupported characters." };
|
|
4344
4908
|
}
|
|
@@ -4389,12 +4953,13 @@ async function handleShellOpen(req, logger) {
|
|
|
4389
4953
|
}
|
|
4390
4954
|
|
|
4391
4955
|
// src/server/skills-handlers.ts
|
|
4392
|
-
import { promises as
|
|
4393
|
-
import
|
|
4956
|
+
import { promises as fs10 } from "node:fs";
|
|
4957
|
+
import path11 from "node:path";
|
|
4394
4958
|
import { atomicWrite as atomicWrite5 } from "@wrongstack/core";
|
|
4395
4959
|
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
4396
4960
|
|
|
4397
4961
|
// src/server/ws-payload-validation.ts
|
|
4962
|
+
import { FORBIDDEN_PROTO_KEYS } from "@wrongstack/core/utils";
|
|
4398
4963
|
function isRecord(value) {
|
|
4399
4964
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4400
4965
|
}
|
|
@@ -4600,11 +5165,22 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4600
5165
|
"fallbackAuto",
|
|
4601
5166
|
"favoriteModelsOnly",
|
|
4602
5167
|
"breakerEnabled",
|
|
4603
|
-
"debugStream"
|
|
5168
|
+
"debugStream",
|
|
5169
|
+
// Chimera + auto-review master toggles
|
|
5170
|
+
"chimeraEnabled",
|
|
5171
|
+
"autoReviewEnabled",
|
|
5172
|
+
"showModelReasoning"
|
|
5173
|
+
]);
|
|
5174
|
+
var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
5175
|
+
"fallbackModels",
|
|
5176
|
+
"favoriteModels",
|
|
5177
|
+
// Auto-review explicit fallback chain (derived when fallbackProfile is unset;
|
|
5178
|
+
// surfaced for visibility/override).
|
|
5179
|
+
"autoReviewFallbackModels"
|
|
4604
5180
|
]);
|
|
4605
|
-
var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
|
|
4606
5181
|
var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
|
|
4607
5182
|
var MODEL_MATRIX_PREF_KEYS = /* @__PURE__ */ new Set(["modelMatrix"]);
|
|
5183
|
+
var BOOLEAN_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["pluginsEnabled"]);
|
|
4608
5184
|
var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
4609
5185
|
"autonomyDelayMs",
|
|
4610
5186
|
"autoProceedMaxIterations",
|
|
@@ -4612,7 +5188,12 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4612
5188
|
"maxConcurrent",
|
|
4613
5189
|
"enhanceDelayMs",
|
|
4614
5190
|
"tgLongToolMs",
|
|
4615
|
-
"breakerAutoKillResetMs"
|
|
5191
|
+
"breakerAutoKillResetMs",
|
|
5192
|
+
// Chimera + auto-review numeric knobs
|
|
5193
|
+
"chimeraMaxFiles",
|
|
5194
|
+
"autoReviewDebounceMs",
|
|
5195
|
+
"autoReviewMaxFilesPerBatch",
|
|
5196
|
+
"autoReviewMaxConcurrentReviews"
|
|
4616
5197
|
]);
|
|
4617
5198
|
var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
4618
5199
|
"hqUrl",
|
|
@@ -4621,7 +5202,13 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4621
5202
|
"thinkingWord",
|
|
4622
5203
|
"refinerProvider",
|
|
4623
5204
|
"refinerModel",
|
|
4624
|
-
"refinerFallbackProfile"
|
|
5205
|
+
"refinerFallbackProfile",
|
|
5206
|
+
// Chimera + auto-review override strings
|
|
5207
|
+
"chimeraProvider",
|
|
5208
|
+
"chimeraModel",
|
|
5209
|
+
"autoReviewProvider",
|
|
5210
|
+
"autoReviewModel",
|
|
5211
|
+
"autoReviewFallbackProfile"
|
|
4625
5212
|
]);
|
|
4626
5213
|
var ENUM_PREF_KEYS = {
|
|
4627
5214
|
autonomy: AUTONOMY_VALUES,
|
|
@@ -4636,36 +5223,39 @@ var ENUM_PREF_KEYS = {
|
|
|
4636
5223
|
cacheTtl: CACHE_TTL_VALUES,
|
|
4637
5224
|
statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
|
|
4638
5225
|
animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
|
|
4639
|
-
fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
|
|
5226
|
+
fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"]),
|
|
5227
|
+
// Chimera autoFix + auto-review cascade threshold
|
|
5228
|
+
chimeraAutoFix: /* @__PURE__ */ new Set(["off", "ask", "auto"]),
|
|
5229
|
+
autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"])
|
|
4640
5230
|
};
|
|
4641
|
-
function validateModelRuntimeValue(modelRuntime,
|
|
5231
|
+
function validateModelRuntimeValue(modelRuntime, path23) {
|
|
4642
5232
|
const reasoning = modelRuntime["reasoning"];
|
|
4643
5233
|
if (reasoning !== void 0) {
|
|
4644
|
-
if (!isRecord(reasoning)) return `${
|
|
5234
|
+
if (!isRecord(reasoning)) return `${path23}.reasoning must be an object when provided`;
|
|
4645
5235
|
const mode = reasoning["mode"];
|
|
4646
5236
|
const effort = reasoning["effort"];
|
|
4647
5237
|
const preserve = reasoning["preserve"];
|
|
4648
5238
|
if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
|
|
4649
|
-
return `${
|
|
5239
|
+
return `${path23}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
|
|
4650
5240
|
}
|
|
4651
5241
|
if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
|
|
4652
|
-
return `${
|
|
5242
|
+
return `${path23}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
|
|
4653
5243
|
}
|
|
4654
5244
|
if (preserve !== void 0 && typeof preserve !== "boolean") {
|
|
4655
|
-
return `${
|
|
5245
|
+
return `${path23}.reasoning.preserve must be a boolean when provided`;
|
|
4656
5246
|
}
|
|
4657
5247
|
}
|
|
4658
5248
|
const cache = modelRuntime["cache"];
|
|
4659
5249
|
if (cache !== void 0) {
|
|
4660
|
-
if (!isRecord(cache)) return `${
|
|
5250
|
+
if (!isRecord(cache)) return `${path23}.cache must be an object when provided`;
|
|
4661
5251
|
const ttl = cache["ttl"];
|
|
4662
5252
|
if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
|
|
4663
|
-
return `${
|
|
5253
|
+
return `${path23}.cache.ttl must be one of: 5m, 1h`;
|
|
4664
5254
|
}
|
|
4665
5255
|
}
|
|
4666
5256
|
const parameters = modelRuntime["parameters"];
|
|
4667
5257
|
if (parameters !== void 0 && !isRecord(parameters)) {
|
|
4668
|
-
return `${
|
|
5258
|
+
return `${path23}.parameters must be an object when provided`;
|
|
4669
5259
|
}
|
|
4670
5260
|
return null;
|
|
4671
5261
|
}
|
|
@@ -4687,6 +5277,16 @@ function validatePreferenceValue(key, value) {
|
|
|
4687
5277
|
(v) => Array.isArray(v) && v.every((item) => typeof item === "string")
|
|
4688
5278
|
) ? null : `prefs.update payload.${key} must be an object of string arrays`;
|
|
4689
5279
|
}
|
|
5280
|
+
if (BOOLEAN_RECORD_PREF_KEYS.has(key)) {
|
|
5281
|
+
if (!isRecord(value) || !Object.values(value).every((v) => typeof v === "boolean")) {
|
|
5282
|
+
return `prefs.update payload.${key} must be an object of booleans`;
|
|
5283
|
+
}
|
|
5284
|
+
const badKey = Object.keys(value).find((k) => FORBIDDEN_PROTO_KEYS.has(k));
|
|
5285
|
+
if (badKey) {
|
|
5286
|
+
return `prefs.update payload.${key} contains a forbidden key: ${badKey}`;
|
|
5287
|
+
}
|
|
5288
|
+
return null;
|
|
5289
|
+
}
|
|
4690
5290
|
if (MODEL_MATRIX_PREF_KEYS.has(key)) {
|
|
4691
5291
|
if (!isRecord(value)) return `prefs.update payload.${key} must be an object`;
|
|
4692
5292
|
for (const entry of Object.values(value)) {
|
|
@@ -4945,8 +5545,8 @@ function validateShellOpenPayload(payload) {
|
|
|
4945
5545
|
if (!isRecord(payload)) {
|
|
4946
5546
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
4947
5547
|
}
|
|
4948
|
-
const
|
|
4949
|
-
if (typeof
|
|
5548
|
+
const path23 = payload["path"];
|
|
5549
|
+
if (typeof path23 !== "string" || path23.trim().length === 0) {
|
|
4950
5550
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
4951
5551
|
}
|
|
4952
5552
|
const target = payload["target"];
|
|
@@ -4959,7 +5559,7 @@ function validateShellOpenPayload(payload) {
|
|
|
4959
5559
|
return {
|
|
4960
5560
|
ok: true,
|
|
4961
5561
|
value: {
|
|
4962
|
-
path:
|
|
5562
|
+
path: path23,
|
|
4963
5563
|
...target !== void 0 ? { target } : {}
|
|
4964
5564
|
}
|
|
4965
5565
|
};
|
|
@@ -4968,14 +5568,14 @@ function validateGitDiffPayload(payload) {
|
|
|
4968
5568
|
if (!isRecord(payload)) {
|
|
4969
5569
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
4970
5570
|
}
|
|
4971
|
-
const
|
|
4972
|
-
if (
|
|
5571
|
+
const path23 = payload["path"];
|
|
5572
|
+
if (path23 === void 0 || path23 === null) {
|
|
4973
5573
|
return { ok: true, value: { path: "" } };
|
|
4974
5574
|
}
|
|
4975
|
-
if (typeof
|
|
5575
|
+
if (typeof path23 !== "string") {
|
|
4976
5576
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
4977
5577
|
}
|
|
4978
|
-
return { ok: true, value: { path:
|
|
5578
|
+
return { ok: true, value: { path: path23 } };
|
|
4979
5579
|
}
|
|
4980
5580
|
|
|
4981
5581
|
// src/server/zip.ts
|
|
@@ -5121,19 +5721,19 @@ async function handleSkillsContent(ws, ctx, msg) {
|
|
|
5121
5721
|
send(ws, { type: "skills.content", payload: { name: name2, body: "", path: "", source, relatedFiles: [], references: [], error: `Skill "${name2}" not found` } });
|
|
5122
5722
|
return;
|
|
5123
5723
|
}
|
|
5124
|
-
const body = await
|
|
5125
|
-
const skillDir =
|
|
5724
|
+
const body = await fs10.readFile(entry.path, "utf8");
|
|
5725
|
+
const skillDir = path11.dirname(entry.path);
|
|
5126
5726
|
let relatedFiles = [];
|
|
5127
5727
|
try {
|
|
5128
|
-
const files = await
|
|
5129
|
-
relatedFiles = files.filter((f) => f !==
|
|
5728
|
+
const files = await fs10.readdir(skillDir);
|
|
5729
|
+
relatedFiles = files.filter((f) => f !== path11.basename(entry.path)).map((f) => path11.join(skillDir, f));
|
|
5130
5730
|
} catch {
|
|
5131
5731
|
}
|
|
5132
5732
|
const nameLower = name2.toLowerCase();
|
|
5133
5733
|
const refResults = await Promise.all(
|
|
5134
5734
|
entries.filter((e) => e.name.toLowerCase() !== nameLower).map(async (e) => {
|
|
5135
5735
|
try {
|
|
5136
|
-
const content = await
|
|
5736
|
+
const content = await fs10.readFile(e.path, "utf8");
|
|
5137
5737
|
return [e.name, content.toLowerCase().includes(nameLower)];
|
|
5138
5738
|
} catch {
|
|
5139
5739
|
return [e.name, false];
|
|
@@ -5223,20 +5823,20 @@ async function handleSkillsCreate(ws, ctx, msg) {
|
|
|
5223
5823
|
}
|
|
5224
5824
|
const createPayload = parsed.value;
|
|
5225
5825
|
try {
|
|
5226
|
-
const targetDir = createPayload.scope === "global" ?
|
|
5227
|
-
ctx.globalSkillsDir ??
|
|
5826
|
+
const targetDir = createPayload.scope === "global" ? path11.join(
|
|
5827
|
+
ctx.globalSkillsDir ?? path11.join(wstackGlobalRoot(), "skills"),
|
|
5228
5828
|
createPayload.name.trim()
|
|
5229
|
-
) :
|
|
5230
|
-
ctx.projectSkillsDir ??
|
|
5829
|
+
) : path11.join(
|
|
5830
|
+
ctx.projectSkillsDir ?? path11.join(ctx.projectRoot, ".wrongstack", "skills"),
|
|
5231
5831
|
createPayload.name.trim()
|
|
5232
5832
|
);
|
|
5233
5833
|
try {
|
|
5234
|
-
await
|
|
5834
|
+
await fs10.access(targetDir);
|
|
5235
5835
|
send(ws, { type: "skills.created", payload: { success: false, error: `Skill "${createPayload.name}" already exists` } });
|
|
5236
5836
|
return;
|
|
5237
5837
|
} catch {
|
|
5238
5838
|
}
|
|
5239
|
-
await
|
|
5839
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
5240
5840
|
const lines = createPayload.description.trim().split("\n");
|
|
5241
5841
|
const firstLine = (lines[0] ?? "").trim();
|
|
5242
5842
|
const bodyLines = lines.slice(1).map((l) => l.trim()).filter(Boolean);
|
|
@@ -5284,13 +5884,13 @@ ${trigger}
|
|
|
5284
5884
|
"- `bug-hunter` \u2014 for systematic bug detection patterns",
|
|
5285
5885
|
"- `output-standards` \u2014 for standardized `<nextsteps>` formatting"
|
|
5286
5886
|
].join("\n");
|
|
5287
|
-
await atomicWrite5(
|
|
5887
|
+
await atomicWrite5(path11.join(targetDir, "SKILL.md"), skillContent);
|
|
5288
5888
|
send(ws, {
|
|
5289
5889
|
type: "skills.created",
|
|
5290
5890
|
payload: {
|
|
5291
5891
|
success: true,
|
|
5292
5892
|
error: null,
|
|
5293
|
-
skill: { name: createPayload.name.trim(), path:
|
|
5893
|
+
skill: { name: createPayload.name.trim(), path: path11.join(targetDir, "SKILL.md"), scope: createPayload.scope }
|
|
5294
5894
|
}
|
|
5295
5895
|
});
|
|
5296
5896
|
} catch (err) {
|
|
@@ -5567,7 +6167,7 @@ function estimateContextBreakdown(input) {
|
|
|
5567
6167
|
}
|
|
5568
6168
|
|
|
5569
6169
|
// src/server/worktree-ws-handler.ts
|
|
5570
|
-
import { join as
|
|
6170
|
+
import { join as join8, resolve as resolve6, sep as sep3 } from "node:path";
|
|
5571
6171
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core";
|
|
5572
6172
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
5573
6173
|
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
@@ -5628,7 +6228,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5628
6228
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
5629
6229
|
/** Absolute managed-worktrees root for this project. */
|
|
5630
6230
|
worktreesRoot() {
|
|
5631
|
-
return resolve6(
|
|
6231
|
+
return resolve6(join8(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
5632
6232
|
}
|
|
5633
6233
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
5634
6234
|
underRoot(dir) {
|
|
@@ -5899,7 +6499,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
5899
6499
|
};
|
|
5900
6500
|
|
|
5901
6501
|
// src/server/server-runtime.ts
|
|
5902
|
-
import * as
|
|
6502
|
+
import * as path14 from "node:path";
|
|
5903
6503
|
import { createRequire } from "node:module";
|
|
5904
6504
|
import { fileURLToPath } from "node:url";
|
|
5905
6505
|
import { WebSocketServer } from "ws";
|
|
@@ -5941,12 +6541,103 @@ function registerShutdownHandlers(res) {
|
|
|
5941
6541
|
}
|
|
5942
6542
|
|
|
5943
6543
|
// src/server/setup-events.ts
|
|
5944
|
-
import * as fs10 from "node:fs/promises";
|
|
5945
6544
|
import { watch as fsWatch } from "node:fs";
|
|
5946
|
-
import * as
|
|
6545
|
+
import * as fs11 from "node:fs/promises";
|
|
6546
|
+
import * as path13 from "node:path";
|
|
6547
|
+
import { getBoard, getKanbanDir } from "@wrongstack/kanban";
|
|
6548
|
+
|
|
6549
|
+
// src/server/codemap-telemetry.ts
|
|
6550
|
+
import * as path12 from "node:path";
|
|
6551
|
+
var TOOL_OPERATION = {
|
|
6552
|
+
read: "read",
|
|
6553
|
+
read_file: "read",
|
|
6554
|
+
view: "read",
|
|
6555
|
+
write: "write",
|
|
6556
|
+
write_file: "write",
|
|
6557
|
+
create_file: "write",
|
|
6558
|
+
edit: "edit",
|
|
6559
|
+
replace: "edit",
|
|
6560
|
+
patch: "edit",
|
|
6561
|
+
apply_patch: "edit",
|
|
6562
|
+
delete: "delete",
|
|
6563
|
+
delete_file: "delete",
|
|
6564
|
+
remove: "delete",
|
|
6565
|
+
unlink: "delete",
|
|
6566
|
+
grep: "search",
|
|
6567
|
+
search: "search",
|
|
6568
|
+
codebase_search: "search",
|
|
6569
|
+
"codebase-search": "search"
|
|
6570
|
+
};
|
|
6571
|
+
function numberField(input, names) {
|
|
6572
|
+
for (const name2 of names) {
|
|
6573
|
+
const value = input[name2];
|
|
6574
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.floor(value);
|
|
6575
|
+
}
|
|
6576
|
+
return void 0;
|
|
6577
|
+
}
|
|
6578
|
+
function normalizeTarget(projectRoot, filePath) {
|
|
6579
|
+
return path12.normalize(path12.isAbsolute(filePath) ? filePath : path12.resolve(projectRoot, filePath));
|
|
6580
|
+
}
|
|
6581
|
+
function normalizeCodeMapFileTarget(projectRoot, filePath, operation = "edit", line, endLine) {
|
|
6582
|
+
return {
|
|
6583
|
+
filePath: normalizeTarget(projectRoot, filePath),
|
|
6584
|
+
operation: operation === "rename" ? "edit" : operation,
|
|
6585
|
+
...line ? { line } : {},
|
|
6586
|
+
...endLine ? { endLine } : {}
|
|
6587
|
+
};
|
|
6588
|
+
}
|
|
6589
|
+
function patchTargets(patch) {
|
|
6590
|
+
const targets = [];
|
|
6591
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
6592
|
+
const match = /^\+\+\+\s+(?:b\/)?(.+?)(?:\t.*)?$/.exec(line);
|
|
6593
|
+
const target = match?.[1]?.trim();
|
|
6594
|
+
if (target && target !== "/dev/null") targets.push(target);
|
|
6595
|
+
}
|
|
6596
|
+
return targets;
|
|
6597
|
+
}
|
|
6598
|
+
function extractCodeMapFileTargets(projectRoot, toolName, rawInput) {
|
|
6599
|
+
const operation = TOOL_OPERATION[toolName.toLowerCase()];
|
|
6600
|
+
if (!operation || !rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) return [];
|
|
6601
|
+
const input = rawInput;
|
|
6602
|
+
const rawPaths = [];
|
|
6603
|
+
for (const key of ["path", "file", "filePath", "target"]) {
|
|
6604
|
+
const value = input[key];
|
|
6605
|
+
if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
|
|
6606
|
+
}
|
|
6607
|
+
const files = input["files"];
|
|
6608
|
+
if (Array.isArray(files)) {
|
|
6609
|
+
for (const value of files)
|
|
6610
|
+
if (typeof value === "string" && value.trim()) rawPaths.push(value.trim());
|
|
6611
|
+
} else if (typeof files === "string" && files.trim() && !/[?*{}[\]]/.test(files)) {
|
|
6612
|
+
rawPaths.push(files.trim());
|
|
6613
|
+
}
|
|
6614
|
+
if ((toolName === "patch" || toolName === "apply_patch") && typeof input["patch"] === "string") {
|
|
6615
|
+
rawPaths.push(...patchTargets(input["patch"]));
|
|
6616
|
+
}
|
|
6617
|
+
const line = numberField(input, ["line", "offset", "startLine", "start_line", "line_start"]);
|
|
6618
|
+
const explicitEnd = numberField(input, ["endLine", "end_line", "line_end"]);
|
|
6619
|
+
const limit = numberField(input, ["limit"]);
|
|
6620
|
+
const endLine = explicitEnd ?? (line && limit ? line + limit - 1 : void 0);
|
|
6621
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6622
|
+
const targets = [];
|
|
6623
|
+
for (const rawPath of rawPaths) {
|
|
6624
|
+
const filePath = normalizeTarget(projectRoot, rawPath);
|
|
6625
|
+
if (seen.has(filePath)) continue;
|
|
6626
|
+
seen.add(filePath);
|
|
6627
|
+
targets.push({
|
|
6628
|
+
filePath,
|
|
6629
|
+
operation,
|
|
6630
|
+
...line ? { line } : {},
|
|
6631
|
+
...endLine ? { endLine } : {}
|
|
6632
|
+
});
|
|
6633
|
+
}
|
|
6634
|
+
return targets;
|
|
6635
|
+
}
|
|
6636
|
+
|
|
6637
|
+
// src/server/setup-events.ts
|
|
5947
6638
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
5948
6639
|
const raw = String(filename);
|
|
5949
|
-
const relative4 =
|
|
6640
|
+
const relative4 = path13.isAbsolute(raw) ? path13.relative(projectsDir, raw) : raw;
|
|
5950
6641
|
const parts = relative4.split(/[\\/]+/).filter(Boolean);
|
|
5951
6642
|
if (parts.length < 2) return null;
|
|
5952
6643
|
if (parts[parts.length - 1] !== "status.json") return null;
|
|
@@ -5957,12 +6648,76 @@ function shouldLogWatcherStats() {
|
|
|
5957
6648
|
return value === "1" || value === "true" || value === "yes" || value === "on";
|
|
5958
6649
|
}
|
|
5959
6650
|
function setupEvents(deps2) {
|
|
5960
|
-
const {
|
|
6651
|
+
const {
|
|
6652
|
+
events,
|
|
6653
|
+
broadcast: broadcast2,
|
|
6654
|
+
clients,
|
|
6655
|
+
config,
|
|
6656
|
+
context,
|
|
6657
|
+
pendingConfirms,
|
|
6658
|
+
globalConfigPath,
|
|
6659
|
+
sessionBridge,
|
|
6660
|
+
wpaths,
|
|
6661
|
+
watcherMetrics,
|
|
6662
|
+
onFleetBroadcaster
|
|
6663
|
+
} = deps2;
|
|
5961
6664
|
const disposers = [];
|
|
5962
6665
|
let disposed = false;
|
|
5963
6666
|
const on = (event, listener) => {
|
|
5964
6667
|
disposers.push(events.on(event, listener));
|
|
5965
6668
|
};
|
|
6669
|
+
const conversationState = context.state;
|
|
6670
|
+
if (typeof conversationState?.onChange === "function") {
|
|
6671
|
+
disposers.push(
|
|
6672
|
+
conversationState.onChange((change) => {
|
|
6673
|
+
if (change.kind !== "todos_replaced") return;
|
|
6674
|
+
broadcast2(clients, {
|
|
6675
|
+
type: "todos.updated",
|
|
6676
|
+
payload: {
|
|
6677
|
+
sessionId: context.session?.id ?? "",
|
|
6678
|
+
todos: [...change.todos],
|
|
6679
|
+
revision: conversationState.revision
|
|
6680
|
+
}
|
|
6681
|
+
});
|
|
6682
|
+
})
|
|
6683
|
+
);
|
|
6684
|
+
}
|
|
6685
|
+
let kanbanWatcher = null;
|
|
6686
|
+
let kanbanDebounce = null;
|
|
6687
|
+
const projectRoot = context.projectRoot;
|
|
6688
|
+
if (projectRoot) {
|
|
6689
|
+
try {
|
|
6690
|
+
const kanbanDir = getKanbanDir(projectRoot);
|
|
6691
|
+
kanbanWatcher = fsWatch(kanbanDir, { persistent: false }, (_eventType, filename) => {
|
|
6692
|
+
const name2 = filename?.toString();
|
|
6693
|
+
if (!name2?.endsWith(".json")) return;
|
|
6694
|
+
const boardId = name2.slice(0, -5);
|
|
6695
|
+
if (kanbanDebounce) clearTimeout(kanbanDebounce);
|
|
6696
|
+
kanbanDebounce = setTimeout(async () => {
|
|
6697
|
+
try {
|
|
6698
|
+
const board = await getBoard(projectRoot, boardId);
|
|
6699
|
+
if (board) {
|
|
6700
|
+
broadcast2(clients, {
|
|
6701
|
+
type: "kanban.get",
|
|
6702
|
+
// Wrap in the { board } envelope like every other kanban
|
|
6703
|
+
// broadcast so the client's isBoardEnvelope path handles it
|
|
6704
|
+
// without hijacking another tab's activeBoardId.
|
|
6705
|
+
payload: { success: true, data: { board } }
|
|
6706
|
+
});
|
|
6707
|
+
}
|
|
6708
|
+
} catch {
|
|
6709
|
+
}
|
|
6710
|
+
}, 60);
|
|
6711
|
+
});
|
|
6712
|
+
kanbanWatcher.on("error", () => kanbanWatcher?.close());
|
|
6713
|
+
disposers.push(() => {
|
|
6714
|
+
if (kanbanDebounce) clearTimeout(kanbanDebounce);
|
|
6715
|
+
kanbanWatcher?.close();
|
|
6716
|
+
kanbanWatcher = null;
|
|
6717
|
+
});
|
|
6718
|
+
} catch {
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
5966
6721
|
const currentSessionId = () => context.session?.id ?? "";
|
|
5967
6722
|
const sessionPayload2 = (payload) => {
|
|
5968
6723
|
const provided = payload["sessionId"];
|
|
@@ -5988,7 +6743,11 @@ function setupEvents(deps2) {
|
|
|
5988
6743
|
on("iteration.completed", (e) => {
|
|
5989
6744
|
broadcast2(clients, {
|
|
5990
6745
|
type: "iteration.completed",
|
|
5991
|
-
payload: sessionPayload2({
|
|
6746
|
+
payload: sessionPayload2({
|
|
6747
|
+
sessionId: e.sessionId,
|
|
6748
|
+
index: e.index,
|
|
6749
|
+
totalIterations: e.index + 1
|
|
6750
|
+
})
|
|
5992
6751
|
});
|
|
5993
6752
|
});
|
|
5994
6753
|
on("iteration.limit_reached", (e) => {
|
|
@@ -6002,10 +6761,16 @@ function setupEvents(deps2) {
|
|
|
6002
6761
|
});
|
|
6003
6762
|
});
|
|
6004
6763
|
on("provider.text_delta", (e) => {
|
|
6005
|
-
broadcast2(clients, {
|
|
6764
|
+
broadcast2(clients, {
|
|
6765
|
+
type: "provider.text_delta",
|
|
6766
|
+
payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" })
|
|
6767
|
+
});
|
|
6006
6768
|
});
|
|
6007
6769
|
on("provider.thinking_delta", (e) => {
|
|
6008
|
-
broadcast2(clients, {
|
|
6770
|
+
broadcast2(clients, {
|
|
6771
|
+
type: "provider.thinking_delta",
|
|
6772
|
+
payload: sessionPayload2({ sessionId: e.sessionId, text: e.text })
|
|
6773
|
+
});
|
|
6009
6774
|
});
|
|
6010
6775
|
on("provider.stream_error", (e) => {
|
|
6011
6776
|
broadcast2(clients, {
|
|
@@ -6016,7 +6781,17 @@ function setupEvents(deps2) {
|
|
|
6016
6781
|
on("tool.started", (e) => {
|
|
6017
6782
|
broadcast2(clients, {
|
|
6018
6783
|
type: "tool.started",
|
|
6019
|
-
payload: sessionPayload2({
|
|
6784
|
+
payload: sessionPayload2({
|
|
6785
|
+
sessionId: e.sessionId,
|
|
6786
|
+
traceId: e.traceId,
|
|
6787
|
+
agentId: e.agentId,
|
|
6788
|
+
agentName: e.agentName,
|
|
6789
|
+
id: e.id,
|
|
6790
|
+
name: e.name,
|
|
6791
|
+
input: e.input,
|
|
6792
|
+
fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
|
|
6793
|
+
messageId: `tool_${e.id}`
|
|
6794
|
+
})
|
|
6020
6795
|
});
|
|
6021
6796
|
appendForCurrentSession(e.sessionId, {
|
|
6022
6797
|
type: "tool_call_start",
|
|
@@ -6027,13 +6802,37 @@ function setupEvents(deps2) {
|
|
|
6027
6802
|
});
|
|
6028
6803
|
});
|
|
6029
6804
|
on("tool.progress", (e) => {
|
|
6805
|
+
const rawProgressPath = e.event.path ?? (typeof e.event.data?.["path"] === "string" ? e.event.data["path"] : void 0);
|
|
6806
|
+
const progressTarget = rawProgressPath ? normalizeCodeMapFileTarget(
|
|
6807
|
+
context.projectRoot,
|
|
6808
|
+
rawProgressPath,
|
|
6809
|
+
e.event.operation ?? "edit",
|
|
6810
|
+
e.event.line,
|
|
6811
|
+
e.event.endLine
|
|
6812
|
+
) : void 0;
|
|
6030
6813
|
broadcast2(clients, {
|
|
6031
6814
|
type: "tool.progress",
|
|
6032
6815
|
// Nested `event` shape — the client handler reads `payload.event?.text`
|
|
6033
6816
|
// and early-returns on a falsy text, so a flat { eventType, text } payload
|
|
6034
6817
|
// makes live tool progress (bash streaming, partial_output, warnings)
|
|
6035
6818
|
// never render. Must match WSToolProgress and the CLI server.
|
|
6036
|
-
payload: sessionPayload2({
|
|
6819
|
+
payload: sessionPayload2({
|
|
6820
|
+
sessionId: e.sessionId,
|
|
6821
|
+
traceId: e.traceId,
|
|
6822
|
+
agentId: e.agentId,
|
|
6823
|
+
agentName: e.agentName,
|
|
6824
|
+
id: e.id,
|
|
6825
|
+
name: e.name,
|
|
6826
|
+
event: {
|
|
6827
|
+
type: e.event.type,
|
|
6828
|
+
text: e.event.text,
|
|
6829
|
+
data: e.event.data,
|
|
6830
|
+
path: progressTarget?.filePath,
|
|
6831
|
+
operation: e.event.operation,
|
|
6832
|
+
line: progressTarget?.line,
|
|
6833
|
+
endLine: progressTarget?.endLine
|
|
6834
|
+
}
|
|
6835
|
+
})
|
|
6037
6836
|
});
|
|
6038
6837
|
appendForCurrentSession(e.sessionId, {
|
|
6039
6838
|
type: "tool_progress",
|
|
@@ -6050,7 +6849,23 @@ function setupEvents(deps2) {
|
|
|
6050
6849
|
on("tool.executed", (e) => {
|
|
6051
6850
|
broadcast2(clients, {
|
|
6052
6851
|
type: "tool.executed",
|
|
6053
|
-
payload: sessionPayload2({
|
|
6852
|
+
payload: sessionPayload2({
|
|
6853
|
+
sessionId: e.sessionId,
|
|
6854
|
+
traceId: e.traceId,
|
|
6855
|
+
agentId: e.agentId,
|
|
6856
|
+
agentName: e.agentName,
|
|
6857
|
+
id: e.id,
|
|
6858
|
+
name: e.name,
|
|
6859
|
+
durationMs: e.durationMs,
|
|
6860
|
+
ok: e.ok,
|
|
6861
|
+
input: e.input,
|
|
6862
|
+
fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
|
|
6863
|
+
output: e.output,
|
|
6864
|
+
outputBytes: e.outputBytes,
|
|
6865
|
+
outputTokens: e.outputTokens,
|
|
6866
|
+
outputLines: e.outputLines,
|
|
6867
|
+
metadata: e.metadata
|
|
6868
|
+
})
|
|
6054
6869
|
});
|
|
6055
6870
|
appendForCurrentSession(e.sessionId, {
|
|
6056
6871
|
type: "tool_call_end",
|
|
@@ -6064,7 +6879,10 @@ function setupEvents(deps2) {
|
|
|
6064
6879
|
outputTokens: e.outputTokens,
|
|
6065
6880
|
outputLines: e.outputLines
|
|
6066
6881
|
});
|
|
6067
|
-
broadcast2(clients, {
|
|
6882
|
+
broadcast2(clients, {
|
|
6883
|
+
type: "todos.updated",
|
|
6884
|
+
payload: sessionPayload2({ sessionId: e.sessionId, todos: [...context.todos] })
|
|
6885
|
+
});
|
|
6068
6886
|
const sideEffects = context.sideEffects ?? [];
|
|
6069
6887
|
if (sideEffects.length > 0) {
|
|
6070
6888
|
broadcast2(clients, {
|
|
@@ -6089,7 +6907,10 @@ function setupEvents(deps2) {
|
|
|
6089
6907
|
if (typeof taskPath === "string" && taskPath) {
|
|
6090
6908
|
const { loadTasks } = await import("@wrongstack/core");
|
|
6091
6909
|
const file = await loadTasks(taskPath);
|
|
6092
|
-
broadcast2(clients, {
|
|
6910
|
+
broadcast2(clients, {
|
|
6911
|
+
type: "tasks.updated",
|
|
6912
|
+
payload: sessionPayload2({ sessionId: e.sessionId, tasks: file?.tasks ?? [] })
|
|
6913
|
+
});
|
|
6093
6914
|
}
|
|
6094
6915
|
} catch {
|
|
6095
6916
|
}
|
|
@@ -6098,13 +6919,27 @@ function setupEvents(deps2) {
|
|
|
6098
6919
|
if (typeof planPath === "string" && planPath) {
|
|
6099
6920
|
const { loadPlan } = await import("@wrongstack/core");
|
|
6100
6921
|
const plan = await loadPlan(planPath);
|
|
6101
|
-
broadcast2(clients, {
|
|
6922
|
+
broadcast2(clients, {
|
|
6923
|
+
type: "plan.updated",
|
|
6924
|
+
payload: sessionPayload2({
|
|
6925
|
+
sessionId: e.sessionId,
|
|
6926
|
+
plan: plan ?? {
|
|
6927
|
+
version: 1,
|
|
6928
|
+
sessionId: e.sessionId ?? context.session?.id ?? "",
|
|
6929
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6930
|
+
items: []
|
|
6931
|
+
}
|
|
6932
|
+
})
|
|
6933
|
+
});
|
|
6102
6934
|
}
|
|
6103
6935
|
} catch {
|
|
6104
6936
|
}
|
|
6105
6937
|
})();
|
|
6106
6938
|
}
|
|
6107
6939
|
});
|
|
6940
|
+
on("file.activity", (e) => {
|
|
6941
|
+
broadcast2(clients, { type: "codemap.file_event", payload: e });
|
|
6942
|
+
});
|
|
6108
6943
|
on("tool.loop_detected", (e) => {
|
|
6109
6944
|
broadcast2(clients, {
|
|
6110
6945
|
type: "tool.loop_detected",
|
|
@@ -6120,7 +6955,12 @@ function setupEvents(deps2) {
|
|
|
6120
6955
|
on("trust.persisted", (e) => {
|
|
6121
6956
|
broadcast2(clients, {
|
|
6122
6957
|
type: "trust.persisted",
|
|
6123
|
-
payload: sessionPayload2({
|
|
6958
|
+
payload: sessionPayload2({
|
|
6959
|
+
sessionId: e.sessionId,
|
|
6960
|
+
tool: e.tool,
|
|
6961
|
+
pattern: e.pattern,
|
|
6962
|
+
decision: e.decision
|
|
6963
|
+
})
|
|
6124
6964
|
});
|
|
6125
6965
|
});
|
|
6126
6966
|
on("delegate.started", (e) => {
|
|
@@ -6162,7 +7002,12 @@ function setupEvents(deps2) {
|
|
|
6162
7002
|
on("ctx.pct", (e) => {
|
|
6163
7003
|
broadcast2(clients, {
|
|
6164
7004
|
type: "ctx.pct",
|
|
6165
|
-
payload: sessionPayload2({
|
|
7005
|
+
payload: sessionPayload2({
|
|
7006
|
+
sessionId: e.sessionId,
|
|
7007
|
+
load: e.load,
|
|
7008
|
+
tokens: e.tokens,
|
|
7009
|
+
maxContext: e.maxContext
|
|
7010
|
+
})
|
|
6166
7011
|
});
|
|
6167
7012
|
broadcast2(clients, {
|
|
6168
7013
|
type: "subagent.event",
|
|
@@ -6179,7 +7024,12 @@ function setupEvents(deps2) {
|
|
|
6179
7024
|
on("ctx.max_context", (e) => {
|
|
6180
7025
|
broadcast2(clients, {
|
|
6181
7026
|
type: "ctx.max_context",
|
|
6182
|
-
payload: sessionPayload2({
|
|
7027
|
+
payload: sessionPayload2({
|
|
7028
|
+
sessionId: e.sessionId,
|
|
7029
|
+
providerId: e.providerId,
|
|
7030
|
+
modelId: e.modelId,
|
|
7031
|
+
maxContext: e.maxContext
|
|
7032
|
+
})
|
|
6183
7033
|
});
|
|
6184
7034
|
});
|
|
6185
7035
|
on("token.threshold", (e) => {
|
|
@@ -6195,11 +7045,27 @@ function setupEvents(deps2) {
|
|
|
6195
7045
|
});
|
|
6196
7046
|
});
|
|
6197
7047
|
on("context.repaired", (e) => {
|
|
6198
|
-
broadcast2(clients, {
|
|
7048
|
+
broadcast2(clients, {
|
|
7049
|
+
type: "context.repaired",
|
|
7050
|
+
payload: sessionPayload2({
|
|
7051
|
+
sessionId: e.sessionId,
|
|
7052
|
+
removedToolUses: e.removedToolUses,
|
|
7053
|
+
removedToolResults: e.removedToolResults,
|
|
7054
|
+
removedMessages: e.removedMessages
|
|
7055
|
+
})
|
|
7056
|
+
});
|
|
6199
7057
|
});
|
|
6200
7058
|
on("tool.confirm_needed", (e) => {
|
|
6201
7059
|
const id = e.toolUseId ?? `confirm_${Date.now()}`;
|
|
6202
|
-
const payload = sessionPayload2({
|
|
7060
|
+
const payload = sessionPayload2({
|
|
7061
|
+
sessionId: e.sessionId,
|
|
7062
|
+
id,
|
|
7063
|
+
toolName: e.tool?.name ?? "unknown",
|
|
7064
|
+
input: e.input,
|
|
7065
|
+
suggestedPattern: e.suggestedPattern,
|
|
7066
|
+
decisionSource: e.decisionSource,
|
|
7067
|
+
riskTier: e.riskTier
|
|
7068
|
+
});
|
|
6203
7069
|
pendingConfirms.set(id, {
|
|
6204
7070
|
resolve: e.resolve,
|
|
6205
7071
|
decisionSource: e.decisionSource,
|
|
@@ -6209,7 +7075,14 @@ function setupEvents(deps2) {
|
|
|
6209
7075
|
broadcast2(clients, { type: "tool.confirm_needed", payload });
|
|
6210
7076
|
});
|
|
6211
7077
|
on("error", (e) => {
|
|
6212
|
-
broadcast2(clients, {
|
|
7078
|
+
broadcast2(clients, {
|
|
7079
|
+
type: "error",
|
|
7080
|
+
payload: sessionPayload2({
|
|
7081
|
+
sessionId: e.sessionId,
|
|
7082
|
+
phase: e.phase,
|
|
7083
|
+
message: e.err instanceof Error ? e.err.message : String(e.err)
|
|
7084
|
+
})
|
|
7085
|
+
});
|
|
6213
7086
|
appendForCurrentSession(e.sessionId, {
|
|
6214
7087
|
type: "error",
|
|
6215
7088
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6280,6 +7153,34 @@ function setupEvents(deps2) {
|
|
|
6280
7153
|
description: e.description
|
|
6281
7154
|
});
|
|
6282
7155
|
});
|
|
7156
|
+
on("provider.status_changed", (e) => {
|
|
7157
|
+
broadcast2(clients, {
|
|
7158
|
+
type: "provider.status_changed",
|
|
7159
|
+
payload: sessionPayload2({
|
|
7160
|
+
providerId: e.providerId,
|
|
7161
|
+
model: e.model,
|
|
7162
|
+
oldState: e.oldState,
|
|
7163
|
+
newState: e.newState,
|
|
7164
|
+
reason: e.reason,
|
|
7165
|
+
timestamp: e.timestamp
|
|
7166
|
+
})
|
|
7167
|
+
});
|
|
7168
|
+
});
|
|
7169
|
+
on("provider.active_blocked", (e) => {
|
|
7170
|
+
broadcast2(clients, {
|
|
7171
|
+
type: "provider.active_blocked",
|
|
7172
|
+
payload: sessionPayload2({
|
|
7173
|
+
sessionId: e.sessionId,
|
|
7174
|
+
providerId: e.providerId,
|
|
7175
|
+
model: e.model,
|
|
7176
|
+
state: e.state,
|
|
7177
|
+
fallbackProviderId: e.fallbackProviderId,
|
|
7178
|
+
fallbackModel: e.fallbackModel,
|
|
7179
|
+
lastError: e.lastError,
|
|
7180
|
+
timestamp: e.timestamp
|
|
7181
|
+
})
|
|
7182
|
+
});
|
|
7183
|
+
});
|
|
6283
7184
|
on("provider.error", (e) => {
|
|
6284
7185
|
broadcast2(clients, {
|
|
6285
7186
|
type: "provider.error",
|
|
@@ -6385,16 +7286,137 @@ function setupEvents(deps2) {
|
|
|
6385
7286
|
broadcast2(clients, { type: "mailbox.agent_registered", payload });
|
|
6386
7287
|
});
|
|
6387
7288
|
const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
|
|
6388
|
-
on(
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
7289
|
+
on(
|
|
7290
|
+
"subagent.spawned",
|
|
7291
|
+
(e) => forwardSubagent("spawned", {
|
|
7292
|
+
sessionId: e.sessionId,
|
|
7293
|
+
subagentId: e.subagentId,
|
|
7294
|
+
taskId: e.taskId,
|
|
7295
|
+
name: e.name,
|
|
7296
|
+
provider: e.provider,
|
|
7297
|
+
model: e.model,
|
|
7298
|
+
description: e.description
|
|
7299
|
+
})
|
|
7300
|
+
);
|
|
7301
|
+
on(
|
|
7302
|
+
"subagent.task_started",
|
|
7303
|
+
(e) => forwardSubagent("task_started", {
|
|
7304
|
+
sessionId: e.sessionId,
|
|
7305
|
+
subagentId: e.subagentId,
|
|
7306
|
+
taskId: e.taskId,
|
|
7307
|
+
description: e.description
|
|
7308
|
+
})
|
|
7309
|
+
);
|
|
7310
|
+
on("subagent.tool_started", (e) => {
|
|
7311
|
+
broadcast2(clients, {
|
|
7312
|
+
type: "codemap.tool_started",
|
|
7313
|
+
payload: {
|
|
7314
|
+
sessionId: e.agentSessionId ?? e.sessionId ?? "",
|
|
7315
|
+
parentSessionId: e.sessionId,
|
|
7316
|
+
traceId: e.traceId,
|
|
7317
|
+
agentId: e.subagentId,
|
|
7318
|
+
agentName: e.agentName ?? e.subagentId,
|
|
7319
|
+
id: e.id,
|
|
7320
|
+
name: e.name,
|
|
7321
|
+
input: e.input,
|
|
7322
|
+
fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input)
|
|
7323
|
+
}
|
|
7324
|
+
});
|
|
7325
|
+
});
|
|
7326
|
+
on("subagent.tool_executed", (e) => {
|
|
7327
|
+
broadcast2(clients, {
|
|
7328
|
+
type: "codemap.tool_executed",
|
|
7329
|
+
payload: {
|
|
7330
|
+
sessionId: e.agentSessionId ?? e.sessionId ?? "",
|
|
7331
|
+
parentSessionId: e.sessionId,
|
|
7332
|
+
traceId: e.traceId,
|
|
7333
|
+
agentId: e.subagentId,
|
|
7334
|
+
agentName: e.agentName ?? e.subagentId,
|
|
7335
|
+
id: e.id,
|
|
7336
|
+
name: e.name,
|
|
7337
|
+
durationMs: e.durationMs,
|
|
7338
|
+
ok: e.ok,
|
|
7339
|
+
input: e.input,
|
|
7340
|
+
fileTargets: extractCodeMapFileTargets(context.projectRoot, e.name, e.input),
|
|
7341
|
+
output: e.output,
|
|
7342
|
+
outputBytes: e.outputBytes,
|
|
7343
|
+
outputTokens: e.outputTokens,
|
|
7344
|
+
outputLines: e.outputLines
|
|
7345
|
+
}
|
|
7346
|
+
});
|
|
7347
|
+
forwardSubagent("tool_executed", {
|
|
7348
|
+
sessionId: e.sessionId,
|
|
7349
|
+
subagentId: e.subagentId,
|
|
7350
|
+
toolName: e.name,
|
|
7351
|
+
durationMs: e.durationMs,
|
|
7352
|
+
ok: e.ok
|
|
7353
|
+
});
|
|
7354
|
+
});
|
|
7355
|
+
on(
|
|
7356
|
+
"subagent.iteration_summary",
|
|
7357
|
+
(e) => forwardSubagent("iteration_summary", {
|
|
7358
|
+
sessionId: e.sessionId,
|
|
7359
|
+
subagentId: e.subagentId,
|
|
7360
|
+
iteration: e.iteration,
|
|
7361
|
+
toolCalls: e.toolCalls,
|
|
7362
|
+
costUsd: e.costUsd,
|
|
7363
|
+
currentTool: e.currentTool,
|
|
7364
|
+
partialText: e.partialText
|
|
7365
|
+
})
|
|
7366
|
+
);
|
|
7367
|
+
on(
|
|
7368
|
+
"subagent.budget_warning",
|
|
7369
|
+
(e) => forwardSubagent("budget_warning", {
|
|
7370
|
+
sessionId: e.sessionId,
|
|
7371
|
+
subagentId: e.subagentId,
|
|
7372
|
+
budgetKind: e.kind,
|
|
7373
|
+
used: e.used,
|
|
7374
|
+
limit: e.limit
|
|
7375
|
+
})
|
|
7376
|
+
);
|
|
7377
|
+
on(
|
|
7378
|
+
"subagent.budget_extended",
|
|
7379
|
+
(e) => forwardSubagent("budget_extended", {
|
|
7380
|
+
sessionId: e.sessionId,
|
|
7381
|
+
subagentId: e.subagentId,
|
|
7382
|
+
budgetKind: e.kind,
|
|
7383
|
+
newLimit: e.newLimit,
|
|
7384
|
+
totalExtensions: e.totalExtensions
|
|
7385
|
+
})
|
|
7386
|
+
);
|
|
7387
|
+
on(
|
|
7388
|
+
"subagent.ctx_pct",
|
|
7389
|
+
(e) => forwardSubagent("ctx_pct", {
|
|
7390
|
+
sessionId: e.sessionId,
|
|
7391
|
+
subagentId: e.subagentId,
|
|
7392
|
+
load: e.load,
|
|
7393
|
+
tokens: e.tokens,
|
|
7394
|
+
maxContext: e.maxContext
|
|
7395
|
+
})
|
|
7396
|
+
);
|
|
7397
|
+
on(
|
|
7398
|
+
"subagent.task_completed",
|
|
7399
|
+
(e) => forwardSubagent("task_completed", {
|
|
7400
|
+
sessionId: e.sessionId,
|
|
7401
|
+
subagentId: e.subagentId,
|
|
7402
|
+
status: e.status,
|
|
7403
|
+
iterations: e.iterations,
|
|
7404
|
+
toolCalls: e.toolCalls,
|
|
7405
|
+
finalText: e.finalText,
|
|
7406
|
+
failureReason: e.error?.kind,
|
|
7407
|
+
error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0
|
|
7408
|
+
})
|
|
7409
|
+
);
|
|
7410
|
+
on(
|
|
7411
|
+
"subagent.removed",
|
|
7412
|
+
(e) => forwardSubagent("removed", {
|
|
7413
|
+
sessionId: e.sessionId,
|
|
7414
|
+
subagentId: e.subagentId,
|
|
7415
|
+
reason: e.reason
|
|
7416
|
+
})
|
|
7417
|
+
);
|
|
6397
7418
|
on("agent.timeline.message", (e) => {
|
|
7419
|
+
const timeline = e;
|
|
6398
7420
|
broadcast2(clients, {
|
|
6399
7421
|
type: "agent.timeline.message",
|
|
6400
7422
|
payload: sessionPayload2({
|
|
@@ -6406,6 +7428,7 @@ function setupEvents(deps2) {
|
|
|
6406
7428
|
iteration: e.iteration,
|
|
6407
7429
|
ts: e.ts,
|
|
6408
7430
|
toolName: e.toolName,
|
|
7431
|
+
...typeof timeline.toolOk === "boolean" ? { toolOk: timeline.toolOk } : {},
|
|
6409
7432
|
costUsd: e.costUsd
|
|
6410
7433
|
})
|
|
6411
7434
|
});
|
|
@@ -6514,9 +7537,9 @@ function setupEvents(deps2) {
|
|
|
6514
7537
|
if (wpaths?.projectStatus) {
|
|
6515
7538
|
try {
|
|
6516
7539
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
6517
|
-
const dir =
|
|
6518
|
-
await
|
|
6519
|
-
await
|
|
7540
|
+
const dir = path13.dirname(statusFile);
|
|
7541
|
+
await fs11.mkdir(dir, { recursive: true });
|
|
7542
|
+
await fs11.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
6520
7543
|
} catch (err) {
|
|
6521
7544
|
console.error(
|
|
6522
7545
|
JSON.stringify({
|
|
@@ -6530,7 +7553,7 @@ function setupEvents(deps2) {
|
|
|
6530
7553
|
}
|
|
6531
7554
|
});
|
|
6532
7555
|
if (wpaths?.projectStatus && wpaths.configDir) {
|
|
6533
|
-
const projectsDir =
|
|
7556
|
+
const projectsDir = path13.join(wpaths.configDir, "projects");
|
|
6534
7557
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
6535
7558
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
6536
7559
|
const DEBOUNCE_MS = 150;
|
|
@@ -6594,26 +7617,32 @@ function setupEvents(deps2) {
|
|
|
6594
7617
|
let watcher;
|
|
6595
7618
|
const startWatcher = async () => {
|
|
6596
7619
|
try {
|
|
6597
|
-
await
|
|
7620
|
+
await fs11.mkdir(projectsDir, { recursive: true });
|
|
6598
7621
|
if (disposed) return;
|
|
6599
|
-
watcher = fsWatch(
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
7622
|
+
watcher = fsWatch(
|
|
7623
|
+
projectsDir,
|
|
7624
|
+
{ persistent: true, recursive: true },
|
|
7625
|
+
async (eventType, filename) => {
|
|
7626
|
+
if (eventType !== "change" && eventType !== "rename") return;
|
|
7627
|
+
if (filename == null) return;
|
|
7628
|
+
const projectHash = statusProjectHashFromWatchFilename(projectsDir, filename);
|
|
7629
|
+
if (!projectHash) return;
|
|
7630
|
+
if (watcherMetrics) watcherMetrics.fileChangesDetected++;
|
|
7631
|
+
if (!knownProjectHashes.has(projectHash)) return;
|
|
7632
|
+
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
7633
|
+
try {
|
|
7634
|
+
const targetFile = path13.join(projectsDir, projectHash, "status.json");
|
|
7635
|
+
const content = await fs11.readFile(targetFile, "utf-8");
|
|
7636
|
+
const statusData = JSON.parse(content);
|
|
7637
|
+
scheduleBroadcast(projectHash, statusData);
|
|
7638
|
+
} catch {
|
|
7639
|
+
}
|
|
6613
7640
|
}
|
|
6614
|
-
|
|
7641
|
+
);
|
|
6615
7642
|
if (logWatcherMetricsEnabled) {
|
|
6616
|
-
console.log(
|
|
7643
|
+
console.log(
|
|
7644
|
+
`[setup-events] Watching ${projectsDir} for status.json changes (hash-filtered, debounced)`
|
|
7645
|
+
);
|
|
6617
7646
|
}
|
|
6618
7647
|
} catch (err) {
|
|
6619
7648
|
console.error(
|
|
@@ -6658,17 +7687,19 @@ function setupEvents(deps2) {
|
|
|
6658
7687
|
}
|
|
6659
7688
|
});
|
|
6660
7689
|
}
|
|
6661
|
-
const globalRoot = globalConfigPath ?
|
|
7690
|
+
const globalRoot = globalConfigPath ? path13.dirname(globalConfigPath) : void 0;
|
|
6662
7691
|
if (globalRoot) {
|
|
6663
7692
|
const broadcastSessions = async () => {
|
|
6664
7693
|
try {
|
|
6665
7694
|
const { SessionRegistry } = await import("@wrongstack/core");
|
|
6666
7695
|
const registry = new SessionRegistry(globalRoot);
|
|
6667
7696
|
const sessions = await registry.list();
|
|
6668
|
-
const
|
|
6669
|
-
const
|
|
6670
|
-
|
|
6671
|
-
|
|
7697
|
+
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
7698
|
+
const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
|
|
7699
|
+
const myRoot = path13.resolve(context.projectRoot);
|
|
7700
|
+
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter(
|
|
7701
|
+
(s) => mySlug ? s.projectSlug === mySlug : path13.resolve(s.projectRoot) === myRoot
|
|
7702
|
+
).map((s) => ({
|
|
6672
7703
|
sessionId: s.sessionId,
|
|
6673
7704
|
projectName: s.projectName,
|
|
6674
7705
|
projectSlug: s.projectSlug,
|
|
@@ -6680,12 +7711,15 @@ function setupEvents(deps2) {
|
|
|
6680
7711
|
status: s.status,
|
|
6681
7712
|
pid: s.pid,
|
|
6682
7713
|
startedAt: s.startedAt,
|
|
7714
|
+
lastHeartbeatAt: s.lastHeartbeatAt,
|
|
6683
7715
|
agentCount: s.agentCount,
|
|
6684
7716
|
agents: (s.agents ?? []).map((a) => ({
|
|
6685
7717
|
id: a.id,
|
|
6686
7718
|
name: a.name,
|
|
6687
7719
|
status: a.status,
|
|
6688
7720
|
currentTool: a.currentTool,
|
|
7721
|
+
currentTask: a.currentTask,
|
|
7722
|
+
taskId: a.taskId,
|
|
6689
7723
|
iterations: a.iterations,
|
|
6690
7724
|
toolCalls: a.toolCalls,
|
|
6691
7725
|
costUsd: a.costUsd,
|
|
@@ -6694,6 +7728,12 @@ function setupEvents(deps2) {
|
|
|
6694
7728
|
ctxPct: a.ctxPct,
|
|
6695
7729
|
model: a.model,
|
|
6696
7730
|
partialText: a.partialText,
|
|
7731
|
+
recentTools: a.recentTools,
|
|
7732
|
+
recentMail: a.recentMail,
|
|
7733
|
+
todos: a.todos,
|
|
7734
|
+
latestPrompt: a.latestPrompt,
|
|
7735
|
+
latestPromptAt: a.latestPromptAt,
|
|
7736
|
+
activity: a.activity,
|
|
6697
7737
|
lastActivityAt: a.lastActivityAt
|
|
6698
7738
|
}))
|
|
6699
7739
|
}));
|
|
@@ -6919,7 +7959,7 @@ function createSessionStartPayload(g) {
|
|
|
6919
7959
|
inputCost,
|
|
6920
7960
|
outputCost,
|
|
6921
7961
|
cacheReadCost,
|
|
6922
|
-
projectName:
|
|
7962
|
+
projectName: path14.basename(projectRoot) || projectRoot,
|
|
6923
7963
|
projectRoot,
|
|
6924
7964
|
cwd: g.getWorkingDir(),
|
|
6925
7965
|
mode: g.getModeId(),
|
|
@@ -7017,13 +8057,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, wsPort, setupInput, watcher
|
|
|
7017
8057
|
};
|
|
7018
8058
|
}
|
|
7019
8059
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
7020
|
-
if (explicitDistDir) return
|
|
8060
|
+
if (explicitDistDir) return path14.resolve(explicitDistDir);
|
|
7021
8061
|
try {
|
|
7022
8062
|
const requireFromHere2 = createRequire(fromUrl);
|
|
7023
8063
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
7024
|
-
return
|
|
8064
|
+
return path14.dirname(serverEntry);
|
|
7025
8065
|
} catch {
|
|
7026
|
-
return
|
|
8066
|
+
return path14.resolve(path14.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
7027
8067
|
}
|
|
7028
8068
|
}
|
|
7029
8069
|
function startHttpServer(opts) {
|
|
@@ -7036,15 +8076,18 @@ function startHttpServer(opts) {
|
|
|
7036
8076
|
apiToken: opts.wsToken,
|
|
7037
8077
|
requireToken: opts.requireToken,
|
|
7038
8078
|
watcherMetrics: opts.watcherMetrics,
|
|
7039
|
-
onFleetPing: opts.onFleetPing
|
|
8079
|
+
onFleetPing: opts.onFleetPing,
|
|
8080
|
+
onTechStackEvent: opts.onTechStackEvent,
|
|
8081
|
+
getLlm: opts.getLlm,
|
|
8082
|
+
projectRoot: opts.projectRoot
|
|
7040
8083
|
});
|
|
7041
|
-
const registryBaseDir =
|
|
8084
|
+
const registryBaseDir = path14.dirname(opts.globalConfigPath);
|
|
7042
8085
|
httpServer.listen(opts.httpPort, opts.wsHost, () => {
|
|
7043
8086
|
const openUrl = buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, token: opts.wsToken, publicUrl: opts.publicUrl });
|
|
7044
8087
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
7045
8088
|
if (opts.openBrowser) openBrowser(openUrl);
|
|
7046
8089
|
void registerInstance(
|
|
7047
|
-
{ pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName:
|
|
8090
|
+
{ pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path14.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
|
|
7048
8091
|
registryBaseDir
|
|
7049
8092
|
).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
|
|
7050
8093
|
});
|
|
@@ -7060,7 +8103,7 @@ function registerShutdown(deps2) {
|
|
|
7060
8103
|
}
|
|
7061
8104
|
|
|
7062
8105
|
// src/server/pre-context-services.ts
|
|
7063
|
-
import * as
|
|
8106
|
+
import * as path17 from "node:path";
|
|
7064
8107
|
import { createRequire as createRequire2 } from "node:module";
|
|
7065
8108
|
import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
|
|
7066
8109
|
import {
|
|
@@ -7206,6 +8249,7 @@ function resolveSetupProvider(opts) {
|
|
|
7206
8249
|
}
|
|
7207
8250
|
|
|
7208
8251
|
// src/server/context-meta.ts
|
|
8252
|
+
import { FallbackProfileManager } from "@wrongstack/core";
|
|
7209
8253
|
function seedContextMeta(config, context) {
|
|
7210
8254
|
const meta = context.meta;
|
|
7211
8255
|
const autonomyCfg = config.autonomy ?? {};
|
|
@@ -7259,6 +8303,7 @@ function seedContextMeta(config, context) {
|
|
|
7259
8303
|
meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
|
|
7260
8304
|
meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
|
|
7261
8305
|
meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
|
|
8306
|
+
meta["showModelReasoning"] = autonomyCfg["showModelReasoning"] !== false;
|
|
7262
8307
|
meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
|
|
7263
8308
|
meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
|
|
7264
8309
|
{
|
|
@@ -7279,11 +8324,42 @@ function seedContextMeta(config, context) {
|
|
|
7279
8324
|
meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
|
|
7280
8325
|
const tgMs = tgExt?.["longToolThresholdMs"];
|
|
7281
8326
|
meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
|
|
8327
|
+
const chimeraExt = config.extensions?.["wstack-chimera"];
|
|
8328
|
+
meta["chimeraEnabled"] = chimeraExt?.["enabled"] !== false;
|
|
8329
|
+
meta["chimeraProvider"] = chimeraExt?.["provider"] ?? "";
|
|
8330
|
+
meta["chimeraModel"] = chimeraExt?.["model"] ?? "";
|
|
8331
|
+
meta["chimeraMaxFiles"] = typeof chimeraExt?.["maxFiles"] === "number" && chimeraExt["maxFiles"] >= 1 ? chimeraExt["maxFiles"] : 15;
|
|
8332
|
+
const autoFix = chimeraExt?.["autoFix"];
|
|
8333
|
+
meta["chimeraAutoFix"] = autoFix === "off" || autoFix === "ask" || autoFix === "auto" ? autoFix : "off";
|
|
8334
|
+
const autoReviewExt = config.extensions?.["wstack-auto-review"];
|
|
8335
|
+
meta["autoReviewEnabled"] = autoReviewExt?.["enabled"] === true;
|
|
8336
|
+
meta["autoReviewProvider"] = autoReviewExt?.["provider"] ?? "";
|
|
8337
|
+
meta["autoReviewModel"] = autoReviewExt?.["model"] ?? "";
|
|
8338
|
+
meta["autoReviewFallbackProfile"] = autoReviewExt?.["fallbackProfile"] ?? "";
|
|
8339
|
+
meta["autoReviewFallbackModels"] = Array.isArray(autoReviewExt?.["fallbackModels"]) ? autoReviewExt?.["fallbackModels"] : [];
|
|
8340
|
+
meta["autoReviewDebounceMs"] = typeof autoReviewExt?.["debounceMs"] === "number" && autoReviewExt["debounceMs"] >= 0 ? autoReviewExt["debounceMs"] : 5e3;
|
|
8341
|
+
meta["autoReviewMaxFilesPerBatch"] = typeof autoReviewExt?.["maxFilesPerBatch"] === "number" && autoReviewExt["maxFilesPerBatch"] >= 1 ? autoReviewExt["maxFilesPerBatch"] : 15;
|
|
8342
|
+
meta["autoReviewMaxConcurrentReviews"] = typeof autoReviewExt?.["maxConcurrentReviews"] === "number" && autoReviewExt["maxConcurrentReviews"] >= 1 ? autoReviewExt["maxConcurrentReviews"] : 2;
|
|
8343
|
+
const cascade = autoReviewExt?.["cascadeOn"];
|
|
8344
|
+
meta["autoReviewCascadeOn"] = cascade === "critical" || cascade === "high" ? cascade : "off";
|
|
8345
|
+
{
|
|
8346
|
+
let resolvedChain = [];
|
|
8347
|
+
try {
|
|
8348
|
+
const mgr = new FallbackProfileManager(config);
|
|
8349
|
+
const named = autoReviewExt?.["fallbackProfile"];
|
|
8350
|
+
resolvedChain = typeof named === "string" && named.length > 0 ? mgr.resolve(named) : mgr.resolveEffective({ fallbackAuto: true });
|
|
8351
|
+
} catch {
|
|
8352
|
+
resolvedChain = [];
|
|
8353
|
+
}
|
|
8354
|
+
meta["autoReviewFallbackModels"] = resolvedChain.map(
|
|
8355
|
+
(e) => `${e.providerId}/${e.model}`
|
|
8356
|
+
);
|
|
8357
|
+
}
|
|
7282
8358
|
}
|
|
7283
8359
|
|
|
7284
8360
|
// src/server/model-auto-discovery.ts
|
|
7285
|
-
import * as
|
|
7286
|
-
import * as
|
|
8361
|
+
import * as fs12 from "node:fs/promises";
|
|
8362
|
+
import * as path15 from "node:path";
|
|
7287
8363
|
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
7288
8364
|
function isOverlayRegistry(value) {
|
|
7289
8365
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
@@ -7309,7 +8385,7 @@ function eligibleProviders(config) {
|
|
|
7309
8385
|
}
|
|
7310
8386
|
async function readCache(file) {
|
|
7311
8387
|
try {
|
|
7312
|
-
return JSON.parse(await
|
|
8388
|
+
return JSON.parse(await fs12.readFile(file, "utf8"));
|
|
7313
8389
|
} catch {
|
|
7314
8390
|
return {};
|
|
7315
8391
|
}
|
|
@@ -7319,7 +8395,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
7319
8395
|
if (!isOverlayRegistry(registry)) return;
|
|
7320
8396
|
const targets = eligibleProviders(opts.config);
|
|
7321
8397
|
if (targets.length === 0) return;
|
|
7322
|
-
const cacheFile =
|
|
8398
|
+
const cacheFile = path15.join(opts.cacheDir, "discovered-models-cache.json");
|
|
7323
8399
|
const cache = await readCache(cacheFile);
|
|
7324
8400
|
let cacheDirty = false;
|
|
7325
8401
|
await Promise.all(
|
|
@@ -7356,8 +8432,8 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
7356
8432
|
);
|
|
7357
8433
|
if (cacheDirty) {
|
|
7358
8434
|
try {
|
|
7359
|
-
await
|
|
7360
|
-
await
|
|
8435
|
+
await fs12.mkdir(path15.dirname(cacheFile), { recursive: true });
|
|
8436
|
+
await fs12.writeFile(cacheFile, JSON.stringify(cache), "utf8");
|
|
7361
8437
|
} catch {
|
|
7362
8438
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
7363
8439
|
}
|
|
@@ -7365,7 +8441,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
7365
8441
|
}
|
|
7366
8442
|
|
|
7367
8443
|
// src/server/standalone-session-identity.ts
|
|
7368
|
-
import * as
|
|
8444
|
+
import * as path16 from "node:path";
|
|
7369
8445
|
import {
|
|
7370
8446
|
AgentStatusTracker,
|
|
7371
8447
|
FleetNotifier,
|
|
@@ -7396,7 +8472,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
7396
8472
|
sessionId,
|
|
7397
8473
|
projectSlug: paths.projectSlug,
|
|
7398
8474
|
projectRoot: paths.projectRoot,
|
|
7399
|
-
projectName:
|
|
8475
|
+
projectName: path16.basename(paths.projectRoot),
|
|
7400
8476
|
workingDir: opts.workingDir,
|
|
7401
8477
|
clientType: "webui",
|
|
7402
8478
|
pid: process.pid,
|
|
@@ -7405,7 +8481,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
7405
8481
|
});
|
|
7406
8482
|
fleetNotifier.notify();
|
|
7407
8483
|
} catch (err) {
|
|
7408
|
-
logger.debug?.(`WebUI session registry update failed: ${
|
|
8484
|
+
logger.debug?.(`WebUI session registry update failed: ${errorMessage3(err)}`);
|
|
7409
8485
|
}
|
|
7410
8486
|
};
|
|
7411
8487
|
await register(activeSessionId);
|
|
@@ -7431,7 +8507,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
7431
8507
|
const publisher = core.createHqPublisherFromEnv({
|
|
7432
8508
|
clientKind: "webui",
|
|
7433
8509
|
projectRoot: paths.projectRoot,
|
|
7434
|
-
projectName:
|
|
8510
|
+
projectName: path16.basename(paths.projectRoot),
|
|
7435
8511
|
appConfig: opts.config,
|
|
7436
8512
|
socketFactory: (url) => new WebSocket2(url)
|
|
7437
8513
|
});
|
|
@@ -7453,7 +8529,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
7453
8529
|
events,
|
|
7454
8530
|
sessionId,
|
|
7455
8531
|
projectRoot: paths.projectRoot,
|
|
7456
|
-
projectName:
|
|
8532
|
+
projectName: path16.basename(paths.projectRoot),
|
|
7457
8533
|
globalRoot: paths.globalRoot,
|
|
7458
8534
|
initialAgents: statusTracker.getAgents(),
|
|
7459
8535
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7490,7 +8566,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
7490
8566
|
restartHqBridges(activeSessionId);
|
|
7491
8567
|
}
|
|
7492
8568
|
} catch (err) {
|
|
7493
|
-
logger.debug?.(`WebUI HQ telemetry unavailable: ${
|
|
8569
|
+
logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage3(err)}`);
|
|
7494
8570
|
}
|
|
7495
8571
|
}
|
|
7496
8572
|
const repointRecovery = async (sessionId) => {
|
|
@@ -7513,7 +8589,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
7513
8589
|
try {
|
|
7514
8590
|
restartHqBridges(sessionId);
|
|
7515
8591
|
} catch (err) {
|
|
7516
|
-
logger.debug?.(`WebUI HQ session swap failed: ${
|
|
8592
|
+
logger.debug?.(`WebUI HQ session swap failed: ${errorMessage3(err)}`);
|
|
7517
8593
|
}
|
|
7518
8594
|
});
|
|
7519
8595
|
await transition;
|
|
@@ -7538,7 +8614,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
7538
8614
|
};
|
|
7539
8615
|
return { statusTracker, activate, stop };
|
|
7540
8616
|
}
|
|
7541
|
-
function
|
|
8617
|
+
function errorMessage3(err) {
|
|
7542
8618
|
return err instanceof Error ? err.message : String(err);
|
|
7543
8619
|
}
|
|
7544
8620
|
|
|
@@ -7564,7 +8640,7 @@ async function createPreContextServices(input) {
|
|
|
7564
8640
|
await discoverAndMergeWebuiProviders({
|
|
7565
8641
|
config,
|
|
7566
8642
|
registry: modelsRegistry,
|
|
7567
|
-
cacheDir:
|
|
8643
|
+
cacheDir: path17.dirname(wpaths.modelsCache),
|
|
7568
8644
|
logger
|
|
7569
8645
|
});
|
|
7570
8646
|
} catch (err) {
|
|
@@ -7609,7 +8685,7 @@ async function createPreContextServices(input) {
|
|
|
7609
8685
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
7610
8686
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
7611
8687
|
const mcpTokenStore = new MCPVaultTokenStore(
|
|
7612
|
-
|
|
8688
|
+
path17.join(wpaths.projectDir, "mcp-auth.json"),
|
|
7613
8689
|
vault
|
|
7614
8690
|
);
|
|
7615
8691
|
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
@@ -7704,7 +8780,7 @@ async function createPreContextServices(input) {
|
|
|
7704
8780
|
const modelCapabilitiesRef = { current: modelCapabilities };
|
|
7705
8781
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
7706
8782
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
7707
|
-
manifestPath:
|
|
8783
|
+
manifestPath: path17.join(wpaths.globalRoot, "installed-skills.json"),
|
|
7708
8784
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
7709
8785
|
globalSkillsDir: wpaths.globalSkills,
|
|
7710
8786
|
projectHash: wpaths.projectHash,
|
|
@@ -7714,7 +8790,7 @@ async function createPreContextServices(input) {
|
|
|
7714
8790
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
7715
8791
|
try {
|
|
7716
8792
|
const req = createRequire2(import.meta.url);
|
|
7717
|
-
return
|
|
8793
|
+
return path17.join(path17.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
|
|
7718
8794
|
} catch {
|
|
7719
8795
|
return void 0;
|
|
7720
8796
|
}
|
|
@@ -7815,7 +8891,7 @@ function isSuperMemoryService(memoryStore) {
|
|
|
7815
8891
|
}
|
|
7816
8892
|
|
|
7817
8893
|
// src/server/start-webui.ts
|
|
7818
|
-
import * as
|
|
8894
|
+
import * as path22 from "node:path";
|
|
7819
8895
|
import {
|
|
7820
8896
|
createDefaultPipelines,
|
|
7821
8897
|
createSessionEventBridge,
|
|
@@ -7845,7 +8921,7 @@ function patchConfig(config, updates) {
|
|
|
7845
8921
|
}
|
|
7846
8922
|
|
|
7847
8923
|
// src/server/backend-services.ts
|
|
7848
|
-
import { join as
|
|
8924
|
+
import { join as join13 } from "node:path";
|
|
7849
8925
|
import {
|
|
7850
8926
|
Agent,
|
|
7851
8927
|
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
@@ -7871,7 +8947,7 @@ import {
|
|
|
7871
8947
|
} from "@wrongstack/core";
|
|
7872
8948
|
|
|
7873
8949
|
// src/server/collaboration-ws-handler.ts
|
|
7874
|
-
import { randomUUID } from "node:crypto";
|
|
8950
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
7875
8951
|
import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
|
|
7876
8952
|
var REPLAY_LIMIT = 50;
|
|
7877
8953
|
var PAUSE_TIMEOUT_MS = 6e4;
|
|
@@ -8003,7 +9079,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
8003
9079
|
return;
|
|
8004
9080
|
}
|
|
8005
9081
|
const participant = {
|
|
8006
|
-
participantId:
|
|
9082
|
+
participantId: randomUUID2(),
|
|
8007
9083
|
ws,
|
|
8008
9084
|
sessionId,
|
|
8009
9085
|
role,
|
|
@@ -8623,8 +9699,8 @@ var CollaborationWebSocketHandler = class {
|
|
|
8623
9699
|
};
|
|
8624
9700
|
|
|
8625
9701
|
// src/server/codebase-indexing.ts
|
|
8626
|
-
import * as
|
|
8627
|
-
import * as
|
|
9702
|
+
import * as fs13 from "node:fs";
|
|
9703
|
+
import * as path18 from "node:path";
|
|
8628
9704
|
import {
|
|
8629
9705
|
cancelPendingReindexes,
|
|
8630
9706
|
enqueueReindex,
|
|
@@ -8644,17 +9720,15 @@ var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
|
8644
9720
|
".nyc_output"
|
|
8645
9721
|
]);
|
|
8646
9722
|
function setupWebUICodebaseIndexing(deps2) {
|
|
8647
|
-
const
|
|
8648
|
-
if (!indexing) return noopIndexing();
|
|
8649
|
-
const idx = indexing;
|
|
9723
|
+
const idx = deps2.config.indexing;
|
|
8650
9724
|
const indexDir = typeof deps2.context.meta["codebaseIndexDir"] === "string" ? deps2.context.meta["codebaseIndexDir"] : void 0;
|
|
8651
|
-
const debounceMs = idx
|
|
9725
|
+
const debounceMs = idx?.debounceMs ?? 400;
|
|
8652
9726
|
const onError = (err) => {
|
|
8653
9727
|
deps2.logger.debug(
|
|
8654
9728
|
`webui codebase auto-index failed: ${err instanceof Error ? err.message : String(err)}`
|
|
8655
9729
|
);
|
|
8656
9730
|
};
|
|
8657
|
-
if (idx
|
|
9731
|
+
if (idx?.onSessionStart) {
|
|
8658
9732
|
void runStartupIndex({
|
|
8659
9733
|
projectRoot: deps2.projectRoot,
|
|
8660
9734
|
indexDir,
|
|
@@ -8671,14 +9745,27 @@ function setupWebUICodebaseIndexing(deps2) {
|
|
|
8671
9745
|
});
|
|
8672
9746
|
}
|
|
8673
9747
|
let watcher;
|
|
8674
|
-
|
|
9748
|
+
const lastWatcherEvent = /* @__PURE__ */ new Map();
|
|
9749
|
+
if (idx?.watchExternal || deps2.events) {
|
|
8675
9750
|
try {
|
|
8676
|
-
watcher =
|
|
9751
|
+
watcher = fs13.watch(deps2.projectRoot, { recursive: true }, (eventType, filename) => {
|
|
8677
9752
|
if (!filename) return;
|
|
8678
9753
|
const rel = filename.toString();
|
|
8679
9754
|
if (isIgnored(rel)) return;
|
|
8680
|
-
const abs =
|
|
8681
|
-
|
|
9755
|
+
const abs = path18.resolve(deps2.projectRoot, rel);
|
|
9756
|
+
if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
|
|
9757
|
+
const now = Date.now();
|
|
9758
|
+
if (now - (lastWatcherEvent.get(abs) ?? 0) > 75) {
|
|
9759
|
+
lastWatcherEvent.set(abs, now);
|
|
9760
|
+
deps2.events?.emit("file.activity", {
|
|
9761
|
+
filePath: path18.normalize(abs),
|
|
9762
|
+
operation: eventType === "rename" && !fs13.existsSync(abs) ? "delete" : "edit",
|
|
9763
|
+
phase: "changed",
|
|
9764
|
+
source: "watcher",
|
|
9765
|
+
at: now
|
|
9766
|
+
});
|
|
9767
|
+
}
|
|
9768
|
+
if (idx?.watchExternal) enqueueFile(abs);
|
|
8682
9769
|
});
|
|
8683
9770
|
watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
|
|
8684
9771
|
watcher.unref?.();
|
|
@@ -8689,8 +9776,8 @@ function setupWebUICodebaseIndexing(deps2) {
|
|
|
8689
9776
|
}
|
|
8690
9777
|
}
|
|
8691
9778
|
function enqueueFile(filePath) {
|
|
8692
|
-
if (!idx.onEdit && !idx.watchExternal) return;
|
|
8693
|
-
const abs =
|
|
9779
|
+
if (!idx || !idx.onEdit && !idx.watchExternal) return;
|
|
9780
|
+
const abs = path18.isAbsolute(filePath) ? path18.normalize(filePath) : path18.resolve(deps2.projectRoot, filePath);
|
|
8694
9781
|
if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
|
|
8695
9782
|
enqueueReindex({
|
|
8696
9783
|
projectRoot: deps2.projectRoot,
|
|
@@ -8703,23 +9790,28 @@ function setupWebUICodebaseIndexing(deps2) {
|
|
|
8703
9790
|
}
|
|
8704
9791
|
return {
|
|
8705
9792
|
onFileWritten(filePath) {
|
|
8706
|
-
|
|
9793
|
+
const abs = path18.isAbsolute(filePath) ? path18.normalize(filePath) : path18.resolve(deps2.projectRoot, filePath);
|
|
9794
|
+
deps2.events?.emit("file.activity", {
|
|
9795
|
+
filePath: abs,
|
|
9796
|
+
operation: "write",
|
|
9797
|
+
phase: "completed",
|
|
9798
|
+
source: "editor",
|
|
9799
|
+
at: Date.now(),
|
|
9800
|
+
sessionId: deps2.context.session?.id,
|
|
9801
|
+
agentId: "webui-editor",
|
|
9802
|
+
agentName: "WebUI Editor"
|
|
9803
|
+
});
|
|
9804
|
+
if (idx?.onEdit) enqueueFile(abs);
|
|
8707
9805
|
},
|
|
8708
9806
|
dispose() {
|
|
8709
9807
|
try {
|
|
8710
9808
|
watcher?.close();
|
|
8711
9809
|
} catch {
|
|
8712
9810
|
}
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
}
|
|
8718
|
-
function noopIndexing() {
|
|
8719
|
-
return {
|
|
8720
|
-
onFileWritten() {
|
|
8721
|
-
},
|
|
8722
|
-
dispose() {
|
|
9811
|
+
if (idx) {
|
|
9812
|
+
cancelPendingReindexes();
|
|
9813
|
+
shutdownCodebaseIndexHost();
|
|
9814
|
+
}
|
|
8723
9815
|
}
|
|
8724
9816
|
};
|
|
8725
9817
|
}
|
|
@@ -8727,16 +9819,16 @@ function isIgnored(rel) {
|
|
|
8727
9819
|
return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
|
|
8728
9820
|
}
|
|
8729
9821
|
function isInside2(root, target) {
|
|
8730
|
-
const normalizedRoot =
|
|
8731
|
-
const normalizedTarget =
|
|
8732
|
-
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot +
|
|
9822
|
+
const normalizedRoot = path18.resolve(root);
|
|
9823
|
+
const normalizedTarget = path18.resolve(target);
|
|
9824
|
+
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path18.sep);
|
|
8733
9825
|
}
|
|
8734
9826
|
|
|
8735
9827
|
// src/server/discover-mailbox-bridge.ts
|
|
8736
9828
|
import { spawn as spawn3 } from "node:child_process";
|
|
8737
9829
|
import { createRequire as createRequire3 } from "node:module";
|
|
8738
|
-
import { existsSync } from "node:fs";
|
|
8739
|
-
import { dirname as
|
|
9830
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
9831
|
+
import { dirname as dirname8, join as join12 } from "node:path";
|
|
8740
9832
|
import { resolveProjectDir, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
|
|
8741
9833
|
import { readLiveLock } from "@wrongstack/core/coordination";
|
|
8742
9834
|
var MAILBOX_BRIDGE_BOOT_TIMEOUT_MS = 5e3;
|
|
@@ -8864,16 +9956,16 @@ function mailboxServeInvocation(projectRoot) {
|
|
|
8864
9956
|
function findWorkspaceCliEntry(projectRoot) {
|
|
8865
9957
|
let dir = projectRoot;
|
|
8866
9958
|
for (let i = 0; i < 6; i++) {
|
|
8867
|
-
const candidate =
|
|
8868
|
-
if (
|
|
8869
|
-
const parent =
|
|
9959
|
+
const candidate = join12(dir, "packages", "cli", "dist", "index.js");
|
|
9960
|
+
if (existsSync2(candidate)) return candidate;
|
|
9961
|
+
const parent = dirname8(dir);
|
|
8870
9962
|
if (parent === dir) return null;
|
|
8871
9963
|
dir = parent;
|
|
8872
9964
|
}
|
|
8873
9965
|
return null;
|
|
8874
9966
|
}
|
|
8875
9967
|
function sleep(ms) {
|
|
8876
|
-
return new Promise((
|
|
9968
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
8877
9969
|
}
|
|
8878
9970
|
|
|
8879
9971
|
// src/server/terminal-ws-handler.ts
|
|
@@ -9128,7 +10220,8 @@ async function createAgentServices(input) {
|
|
|
9128
10220
|
config,
|
|
9129
10221
|
context,
|
|
9130
10222
|
projectRoot,
|
|
9131
|
-
logger
|
|
10223
|
+
logger,
|
|
10224
|
+
events
|
|
9132
10225
|
});
|
|
9133
10226
|
const compactor = createStrategyCompactor({
|
|
9134
10227
|
strategy: config.context?.strategy,
|
|
@@ -9265,7 +10358,7 @@ async function createAgentServices(input) {
|
|
|
9265
10358
|
const brainCfg = resolveBrainConfigDefaults(config.brain, {
|
|
9266
10359
|
fallbackModels: config.fallbackModels
|
|
9267
10360
|
});
|
|
9268
|
-
const brainLedgerPath =
|
|
10361
|
+
const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
|
|
9269
10362
|
let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
|
|
9270
10363
|
let brainLedger;
|
|
9271
10364
|
const startBrainLedger = () => {
|
|
@@ -9375,7 +10468,7 @@ async function createAgentServices(input) {
|
|
|
9375
10468
|
});
|
|
9376
10469
|
brainMonitor.start();
|
|
9377
10470
|
console.log("[WebUI] Brain initialized (tiered policy \u2192 LLM, monitor active)");
|
|
9378
|
-
const
|
|
10471
|
+
const goalHandler = new GoalWebSocketHandler(
|
|
9379
10472
|
agent,
|
|
9380
10473
|
context,
|
|
9381
10474
|
logger,
|
|
@@ -9447,7 +10540,7 @@ async function createAgentServices(input) {
|
|
|
9447
10540
|
return brainLedger;
|
|
9448
10541
|
},
|
|
9449
10542
|
codebaseIndexing,
|
|
9450
|
-
|
|
10543
|
+
goalHandler,
|
|
9451
10544
|
specsHandler,
|
|
9452
10545
|
sddBoardHandler,
|
|
9453
10546
|
sddWizardHandler,
|
|
@@ -9520,6 +10613,9 @@ function createConnectionHandler(opts) {
|
|
|
9520
10613
|
}
|
|
9521
10614
|
void opts.sessionStartPayload().then(async (payload) => {
|
|
9522
10615
|
const enriched = { ...payload };
|
|
10616
|
+
if (typeof opts.context.lastRequestTokens === "number" && opts.context.lastRequestTokens > 0) {
|
|
10617
|
+
enriched.lastInputTokens = opts.context.lastRequestTokens;
|
|
10618
|
+
}
|
|
9523
10619
|
try {
|
|
9524
10620
|
const replay = await opts.loadReplay?.();
|
|
9525
10621
|
const live = replay?.messages ?? opts.context.messages ?? [];
|
|
@@ -9549,7 +10645,7 @@ function createConnectionHandler(opts) {
|
|
|
9549
10645
|
})
|
|
9550
10646
|
);
|
|
9551
10647
|
});
|
|
9552
|
-
opts.
|
|
10648
|
+
opts.goalHandler.addClient(ws);
|
|
9553
10649
|
opts.specsHandler.addClient(ws);
|
|
9554
10650
|
opts.sddBoardHandler.addClient(ws);
|
|
9555
10651
|
opts.sddWizardHandler.addClient(ws);
|
|
@@ -9567,8 +10663,21 @@ function createConnectionHandler(opts) {
|
|
|
9567
10663
|
});
|
|
9568
10664
|
return;
|
|
9569
10665
|
}
|
|
10666
|
+
let rawObj;
|
|
10667
|
+
try {
|
|
10668
|
+
rawObj = JSON.parse(data.toString());
|
|
10669
|
+
} catch (err) {
|
|
10670
|
+
console.error(
|
|
10671
|
+
JSON.stringify({
|
|
10672
|
+
level: "error",
|
|
10673
|
+
event: "webui.ws_message_parse_failed",
|
|
10674
|
+
message: err instanceof Error ? err.message : String(err),
|
|
10675
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10676
|
+
})
|
|
10677
|
+
);
|
|
10678
|
+
return;
|
|
10679
|
+
}
|
|
9570
10680
|
try {
|
|
9571
|
-
const rawObj = JSON.parse(data.toString());
|
|
9572
10681
|
if (typeof rawObj === "object" && rawObj !== null) {
|
|
9573
10682
|
const obj = rawObj;
|
|
9574
10683
|
if (Object.hasOwn(obj, "__proto__") || Object.hasOwn(obj, "constructor") || Object.hasOwn(obj, "prototype")) {
|
|
@@ -9586,7 +10695,7 @@ function createConnectionHandler(opts) {
|
|
|
9586
10695
|
console.error(
|
|
9587
10696
|
JSON.stringify({
|
|
9588
10697
|
level: "error",
|
|
9589
|
-
event: "webui.
|
|
10698
|
+
event: "webui.ws_message_handler_failed",
|
|
9590
10699
|
message: err instanceof Error ? err.message : String(err),
|
|
9591
10700
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9592
10701
|
})
|
|
@@ -9621,7 +10730,12 @@ function createConnectionHandler(opts) {
|
|
|
9621
10730
|
}
|
|
9622
10731
|
|
|
9623
10732
|
// src/server/message-dispatcher.ts
|
|
9624
|
-
import
|
|
10733
|
+
import path19 from "node:path";
|
|
10734
|
+
import {
|
|
10735
|
+
ChronicleQueryEngine,
|
|
10736
|
+
resolveWstackPaths as resolveWstackPaths2
|
|
10737
|
+
} from "@wrongstack/core";
|
|
10738
|
+
import * as os2 from "node:os";
|
|
9625
10739
|
import {
|
|
9626
10740
|
buildUserContentBlocks,
|
|
9627
10741
|
IncomingImageError,
|
|
@@ -9634,9 +10748,9 @@ import {
|
|
|
9634
10748
|
VisionUrlBlockedError
|
|
9635
10749
|
} from "@wrongstack/runtime/vision";
|
|
9636
10750
|
|
|
9637
|
-
// src/server/
|
|
9638
|
-
async function
|
|
9639
|
-
if (!msg.type.startsWith("
|
|
10751
|
+
// src/server/goal-routes.ts
|
|
10752
|
+
async function handleGoalRoute(_ws, msg, handlers) {
|
|
10753
|
+
if (!msg.type.startsWith("goal.")) return false;
|
|
9640
10754
|
await handlers.handleMessage(msg);
|
|
9641
10755
|
return true;
|
|
9642
10756
|
}
|
|
@@ -9672,9 +10786,9 @@ async function handleGoalGet(projectRoot, broadcast2) {
|
|
|
9672
10786
|
const { readFile: readFile10 } = await import("node:fs/promises");
|
|
9673
10787
|
const raw = await readFile10(goalPath, "utf8");
|
|
9674
10788
|
const goal = JSON.parse(raw);
|
|
9675
|
-
broadcast2({ type: "goal.updated", payload: goal });
|
|
10789
|
+
broadcast2({ type: "goal-state.updated", payload: goal });
|
|
9676
10790
|
} catch {
|
|
9677
|
-
broadcast2({ type: "goal.updated", payload: null });
|
|
10791
|
+
broadcast2({ type: "goal-state.updated", payload: null });
|
|
9678
10792
|
}
|
|
9679
10793
|
}
|
|
9680
10794
|
|
|
@@ -9924,17 +11038,19 @@ import {
|
|
|
9924
11038
|
duplicateBoard,
|
|
9925
11039
|
exportBoardToTaskGraph,
|
|
9926
11040
|
generateBoardFromDescription,
|
|
9927
|
-
getBoard,
|
|
11041
|
+
getBoard as getBoard2,
|
|
9928
11042
|
getKanbanOrchestrationSnapshot,
|
|
9929
11043
|
getKanbanQueueHealth,
|
|
9930
11044
|
getTask,
|
|
9931
11045
|
getTaskChain,
|
|
9932
11046
|
listBoards,
|
|
9933
11047
|
listReadyTasks,
|
|
11048
|
+
listTaskActivity,
|
|
9934
11049
|
mergeTasks,
|
|
9935
11050
|
moveTask,
|
|
9936
11051
|
parseLinesIntoTasks,
|
|
9937
11052
|
reconcileKanbanBoard,
|
|
11053
|
+
recordTaskActivity,
|
|
9938
11054
|
recoverStaleTaskAssignments,
|
|
9939
11055
|
releaseTaskClaim,
|
|
9940
11056
|
removeBoard,
|
|
@@ -9943,7 +11059,9 @@ import {
|
|
|
9943
11059
|
setTaskChain,
|
|
9944
11060
|
splitTask,
|
|
9945
11061
|
syncBoardFromTaskGraph,
|
|
11062
|
+
touchKanbanPresence,
|
|
9946
11063
|
transferTaskToBoard,
|
|
11064
|
+
transitionTask,
|
|
9947
11065
|
updateBoard,
|
|
9948
11066
|
updateCheckOnTask,
|
|
9949
11067
|
updateGoalMetricOnTask,
|
|
@@ -9970,6 +11088,29 @@ function fail(ws, type, message) {
|
|
|
9970
11088
|
function has(payload, key) {
|
|
9971
11089
|
return payload !== void 0 && Object.hasOwn(payload, key);
|
|
9972
11090
|
}
|
|
11091
|
+
function activityContext(ctx, actor, note) {
|
|
11092
|
+
const sessionId = ctx.context?.session?.id;
|
|
11093
|
+
return {
|
|
11094
|
+
...sessionId ? { sessionId } : {},
|
|
11095
|
+
...actor ? { actor } : {},
|
|
11096
|
+
...note?.trim() ? { note: note.trim() } : {}
|
|
11097
|
+
};
|
|
11098
|
+
}
|
|
11099
|
+
async function touchTaskPresence(ctx, boardId, taskId) {
|
|
11100
|
+
const context = ctx.context;
|
|
11101
|
+
const sessionId = context?.session?.id;
|
|
11102
|
+
if (!context || !sessionId) return null;
|
|
11103
|
+
try {
|
|
11104
|
+
return await touchKanbanPresence(ctx.projectRoot, boardId, {
|
|
11105
|
+
sessionId,
|
|
11106
|
+
agentId: context.agentId || "webui",
|
|
11107
|
+
agentName: context.agentName || context.agentId || "WebUI",
|
|
11108
|
+
taskId
|
|
11109
|
+
});
|
|
11110
|
+
} catch {
|
|
11111
|
+
return null;
|
|
11112
|
+
}
|
|
11113
|
+
}
|
|
9973
11114
|
async function handleKanbanRoute(ws, msg, ctx) {
|
|
9974
11115
|
if (!msg.type.startsWith("kanban.")) return false;
|
|
9975
11116
|
const payload = msg.payload;
|
|
@@ -9985,7 +11126,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9985
11126
|
fail(ws, type, "boardId required");
|
|
9986
11127
|
return true;
|
|
9987
11128
|
}
|
|
9988
|
-
const board = await
|
|
11129
|
+
const board = await getBoard2(ctx.projectRoot, boardId);
|
|
9989
11130
|
board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
|
|
9990
11131
|
return true;
|
|
9991
11132
|
}
|
|
@@ -10005,7 +11146,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10005
11146
|
fail(ws, type, "boardId required");
|
|
10006
11147
|
return true;
|
|
10007
11148
|
}
|
|
10008
|
-
const board = await
|
|
11149
|
+
const board = await getBoard2(ctx.projectRoot, boardId);
|
|
10009
11150
|
if (!board) {
|
|
10010
11151
|
fail(ws, type, `Board not found: ${boardId}`);
|
|
10011
11152
|
return true;
|
|
@@ -10043,7 +11184,8 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10043
11184
|
title,
|
|
10044
11185
|
...payload?.description ? { description: payload.description } : {},
|
|
10045
11186
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
10046
|
-
...payload?.columns ? { columns: payload.columns } : {}
|
|
11187
|
+
...payload?.columns ? { columns: payload.columns } : {},
|
|
11188
|
+
...has(payload, "lifecycle") ? { lifecycle: payload?.lifecycle } : {}
|
|
10047
11189
|
})
|
|
10048
11190
|
);
|
|
10049
11191
|
return true;
|
|
@@ -10059,6 +11201,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10059
11201
|
...payload?.description ? { description: payload.description } : {},
|
|
10060
11202
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
10061
11203
|
...payload?.columns ? { columns: payload.columns } : {},
|
|
11204
|
+
...has(payload, "lifecycle") ? {
|
|
11205
|
+
lifecycle: payload?.lifecycle ?? null
|
|
11206
|
+
} : {},
|
|
10062
11207
|
...has(payload, "supervisor") ? {
|
|
10063
11208
|
supervisor: payload?.supervisor ?? null
|
|
10064
11209
|
} : {}
|
|
@@ -10087,7 +11232,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10087
11232
|
fail(ws, type, "boardId required");
|
|
10088
11233
|
return true;
|
|
10089
11234
|
}
|
|
10090
|
-
const board = await
|
|
11235
|
+
const board = await getBoard2(ctx.projectRoot, boardId);
|
|
10091
11236
|
const activeSessionId = ctx.context?.session?.id;
|
|
10092
11237
|
if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
|
|
10093
11238
|
fail(ws, type, "The active session Kanban board cannot be deleted.");
|
|
@@ -10119,7 +11264,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10119
11264
|
)) {
|
|
10120
11265
|
await addTask(ctx.projectRoot, board.id, taskInput);
|
|
10121
11266
|
}
|
|
10122
|
-
ok(ws, type, await
|
|
11267
|
+
ok(ws, type, await getBoard2(ctx.projectRoot, board.id) ?? board);
|
|
10123
11268
|
return true;
|
|
10124
11269
|
}
|
|
10125
11270
|
case "kanban.task.ready": {
|
|
@@ -10209,14 +11354,20 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10209
11354
|
fail(ws, type, "boardId and title required");
|
|
10210
11355
|
return true;
|
|
10211
11356
|
}
|
|
10212
|
-
const result = await addTask(
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
11357
|
+
const result = await addTask(
|
|
11358
|
+
ctx.projectRoot,
|
|
11359
|
+
boardId,
|
|
11360
|
+
{
|
|
11361
|
+
title,
|
|
11362
|
+
columnId: payload?.columnId ?? "backlog",
|
|
11363
|
+
...payload?.description ? { description: payload.description } : {},
|
|
11364
|
+
...payload?.dueDate ? { dueDate: payload.dueDate } : {},
|
|
11365
|
+
...payload?.priority ? { priority: payload.priority } : {},
|
|
11366
|
+
...payload?.assignedAgent ? { assignedAgent: payload.assignedAgent } : {},
|
|
11367
|
+
...payload?.labels ? { labels: payload.labels } : {}
|
|
11368
|
+
},
|
|
11369
|
+
activityContext(ctx, "webui", payload?.activityNote)
|
|
11370
|
+
);
|
|
10220
11371
|
result ? ok(ws, type, result.task) : fail(ws, type, `Board not found: ${boardId}`);
|
|
10221
11372
|
return true;
|
|
10222
11373
|
}
|
|
@@ -10272,25 +11423,32 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10272
11423
|
fail(ws, type, "boardId and taskId required");
|
|
10273
11424
|
return true;
|
|
10274
11425
|
}
|
|
10275
|
-
const board = await updateTask(
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
11426
|
+
const board = await updateTask(
|
|
11427
|
+
ctx.projectRoot,
|
|
11428
|
+
boardId,
|
|
11429
|
+
taskId,
|
|
11430
|
+
{
|
|
11431
|
+
...has(payload, "title") ? { title: payload?.title } : {},
|
|
11432
|
+
...has(payload, "description") ? { description: payload?.description ?? "" } : {},
|
|
11433
|
+
...has(payload, "dueDate") ? { dueDate: payload?.dueDate ?? null } : {},
|
|
11434
|
+
...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
|
|
11435
|
+
...has(payload, "priority") ? { priority: payload?.priority } : {},
|
|
11436
|
+
...has(payload, "type") ? { type: payload?.type } : {},
|
|
11437
|
+
...has(payload, "status") ? { status: payload?.status } : {},
|
|
11438
|
+
...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
|
|
11439
|
+
...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
|
|
11440
|
+
...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
|
|
11441
|
+
...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
|
|
11442
|
+
...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
|
|
11443
|
+
...has(payload, "retryPolicy") ? {
|
|
11444
|
+
retryPolicy: payload?.retryPolicy ?? null
|
|
11445
|
+
} : {},
|
|
11446
|
+
...has(payload, "costCeilingUsd") ? {
|
|
11447
|
+
costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
|
|
11448
|
+
} : {}
|
|
11449
|
+
},
|
|
11450
|
+
activityContext(ctx, "webui", payload?.activityNote)
|
|
11451
|
+
);
|
|
10294
11452
|
if (!board) {
|
|
10295
11453
|
fail(ws, type, "Board or task not found");
|
|
10296
11454
|
return true;
|
|
@@ -10300,6 +11458,32 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10300
11458
|
ok(ws, type, task);
|
|
10301
11459
|
return true;
|
|
10302
11460
|
}
|
|
11461
|
+
case "kanban.task.transition": {
|
|
11462
|
+
const boardId = payload?.boardId;
|
|
11463
|
+
const taskId = payload?.taskId;
|
|
11464
|
+
const to = payload?.to;
|
|
11465
|
+
const actor = payload?.actor;
|
|
11466
|
+
const comment = payload?.comment;
|
|
11467
|
+
if (!boardId || !taskId || !to || !actor || !comment) {
|
|
11468
|
+
fail(ws, type, "boardId, taskId, to, actor, and comment required");
|
|
11469
|
+
return true;
|
|
11470
|
+
}
|
|
11471
|
+
const result = await transitionTask(ctx.projectRoot, boardId, taskId, {
|
|
11472
|
+
to,
|
|
11473
|
+
actor,
|
|
11474
|
+
comment,
|
|
11475
|
+
...payload?.action ? { action: payload.action } : {},
|
|
11476
|
+
...payload?.attachment ? { attachment: payload.attachment } : {},
|
|
11477
|
+
...payload?.patch ? { patch: payload.patch } : {}
|
|
11478
|
+
});
|
|
11479
|
+
if (!result) {
|
|
11480
|
+
fail(ws, type, "Board or task not found");
|
|
11481
|
+
return true;
|
|
11482
|
+
}
|
|
11483
|
+
await syncSessionSource(ctx, result.task);
|
|
11484
|
+
ok(ws, type, result);
|
|
11485
|
+
return true;
|
|
11486
|
+
}
|
|
10303
11487
|
case "kanban.task.move": {
|
|
10304
11488
|
const boardId = payload?.boardId;
|
|
10305
11489
|
const taskId = payload?.taskId;
|
|
@@ -10313,7 +11497,8 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10313
11497
|
boardId,
|
|
10314
11498
|
taskId,
|
|
10315
11499
|
columnId,
|
|
10316
|
-
payload?.order
|
|
11500
|
+
payload?.order,
|
|
11501
|
+
activityContext(ctx, "webui", payload?.activityNote)
|
|
10317
11502
|
);
|
|
10318
11503
|
if (!board) {
|
|
10319
11504
|
fail(ws, type, "Move failed");
|
|
@@ -10418,14 +11603,24 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10418
11603
|
fail(ws, type, "boardId, taskId, and name required");
|
|
10419
11604
|
return true;
|
|
10420
11605
|
}
|
|
10421
|
-
const board = await addGoalMetricToTask(
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
11606
|
+
const board = await addGoalMetricToTask(
|
|
11607
|
+
ctx.projectRoot,
|
|
11608
|
+
boardId,
|
|
11609
|
+
taskId,
|
|
11610
|
+
{
|
|
11611
|
+
name: name2,
|
|
11612
|
+
...payload?.status ? { status: payload.status } : {},
|
|
11613
|
+
...payload?.target !== void 0 ? { target: payload.target } : {},
|
|
11614
|
+
...payload?.current !== void 0 ? { current: payload.current } : {},
|
|
11615
|
+
...payload?.unit ? { unit: payload.unit } : {},
|
|
11616
|
+
...payload?.notes ? { notes: payload.notes } : {}
|
|
11617
|
+
},
|
|
11618
|
+
activityContext(
|
|
11619
|
+
ctx,
|
|
11620
|
+
"webui",
|
|
11621
|
+
payload?.activityNote ?? `Goal metric added: ${name2}.`
|
|
11622
|
+
)
|
|
11623
|
+
);
|
|
10429
11624
|
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10430
11625
|
return true;
|
|
10431
11626
|
}
|
|
@@ -10437,14 +11632,25 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10437
11632
|
fail(ws, type, "boardId, taskId, and metricId required");
|
|
10438
11633
|
return true;
|
|
10439
11634
|
}
|
|
10440
|
-
const board = await updateGoalMetricOnTask(
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
11635
|
+
const board = await updateGoalMetricOnTask(
|
|
11636
|
+
ctx.projectRoot,
|
|
11637
|
+
boardId,
|
|
11638
|
+
taskId,
|
|
11639
|
+
metricId,
|
|
11640
|
+
{
|
|
11641
|
+
...payload?.name ? { name: payload.name } : {},
|
|
11642
|
+
...payload?.status ? { status: payload.status } : {},
|
|
11643
|
+
...payload?.target !== void 0 ? { target: payload.target } : {},
|
|
11644
|
+
...payload?.current !== void 0 ? { current: payload.current } : {},
|
|
11645
|
+
...payload?.unit ? { unit: payload.unit } : {},
|
|
11646
|
+
...payload?.notes ? { notes: payload.notes } : {}
|
|
11647
|
+
},
|
|
11648
|
+
activityContext(
|
|
11649
|
+
ctx,
|
|
11650
|
+
"webui",
|
|
11651
|
+
payload?.activityNote ?? "Goal metric updated in WebUI."
|
|
11652
|
+
)
|
|
11653
|
+
);
|
|
10448
11654
|
board ? ok(ws, type, board) : fail(ws, type, "Metric not found");
|
|
10449
11655
|
return true;
|
|
10450
11656
|
}
|
|
@@ -10455,23 +11661,29 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10455
11661
|
fail(ws, type, "boardId and taskId required");
|
|
10456
11662
|
return true;
|
|
10457
11663
|
}
|
|
10458
|
-
const board = await assignTask(
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
11664
|
+
const board = await assignTask(
|
|
11665
|
+
ctx.projectRoot,
|
|
11666
|
+
boardId,
|
|
11667
|
+
taskId,
|
|
11668
|
+
{
|
|
11669
|
+
...payload?.agentId ? { agentId: payload.agentId } : {},
|
|
11670
|
+
...payload?.name ? { name: payload.name } : {},
|
|
11671
|
+
...payload?.role ? { role: payload.role } : {},
|
|
11672
|
+
...payload?.provider ? { provider: payload.provider } : {},
|
|
11673
|
+
...payload?.model ? { model: payload.model } : {},
|
|
11674
|
+
...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
|
|
11675
|
+
...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
|
|
11676
|
+
...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
|
|
11677
|
+
...payload?.skills ? { skills: payload.skills } : {},
|
|
11678
|
+
...payload?.tools ? { tools: payload.tools } : {},
|
|
11679
|
+
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
|
|
11680
|
+
...payload?.assignee ? { assignee: payload.assignee } : {},
|
|
11681
|
+
...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
|
|
11682
|
+
...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
|
|
11683
|
+
...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
|
|
11684
|
+
},
|
|
11685
|
+
activityContext(ctx, void 0, payload?.activityNote)
|
|
11686
|
+
);
|
|
10475
11687
|
board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
|
|
10476
11688
|
return true;
|
|
10477
11689
|
}
|
|
@@ -10483,11 +11695,21 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10483
11695
|
fail(ws, type, "boardId, taskId, and description required");
|
|
10484
11696
|
return true;
|
|
10485
11697
|
}
|
|
10486
|
-
const board = await addCheckToTask(
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
11698
|
+
const board = await addCheckToTask(
|
|
11699
|
+
ctx.projectRoot,
|
|
11700
|
+
boardId,
|
|
11701
|
+
taskId,
|
|
11702
|
+
{
|
|
11703
|
+
description,
|
|
11704
|
+
type: payload?.checkType ?? "manual",
|
|
11705
|
+
status: payload?.status ?? "pending"
|
|
11706
|
+
},
|
|
11707
|
+
activityContext(
|
|
11708
|
+
ctx,
|
|
11709
|
+
"webui",
|
|
11710
|
+
payload?.activityNote ?? `Acceptance check added: ${description}.`
|
|
11711
|
+
)
|
|
11712
|
+
);
|
|
10491
11713
|
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10492
11714
|
return true;
|
|
10493
11715
|
}
|
|
@@ -10499,9 +11721,20 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10499
11721
|
fail(ws, type, "boardId, taskId, and checkId required");
|
|
10500
11722
|
return true;
|
|
10501
11723
|
}
|
|
10502
|
-
const board = await updateCheckOnTask(
|
|
10503
|
-
|
|
10504
|
-
|
|
11724
|
+
const board = await updateCheckOnTask(
|
|
11725
|
+
ctx.projectRoot,
|
|
11726
|
+
boardId,
|
|
11727
|
+
taskId,
|
|
11728
|
+
checkId,
|
|
11729
|
+
{
|
|
11730
|
+
...has(payload, "status") ? { status: payload?.status } : {}
|
|
11731
|
+
},
|
|
11732
|
+
activityContext(
|
|
11733
|
+
ctx,
|
|
11734
|
+
"webui",
|
|
11735
|
+
payload?.activityNote ?? `Acceptance check updated${payload?.status ? ` to ${String(payload.status)}` : ""}.`
|
|
11736
|
+
)
|
|
11737
|
+
);
|
|
10505
11738
|
if (!board) fail(ws, type, "Check not found");
|
|
10506
11739
|
else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
|
|
10507
11740
|
return true;
|
|
@@ -10514,10 +11747,17 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10514
11747
|
fail(ws, type, "boardId, taskId, and content required");
|
|
10515
11748
|
return true;
|
|
10516
11749
|
}
|
|
10517
|
-
const
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
11750
|
+
const author = payload?.author ?? "webui";
|
|
11751
|
+
const board = await addNoteToTask(
|
|
11752
|
+
ctx.projectRoot,
|
|
11753
|
+
boardId,
|
|
11754
|
+
taskId,
|
|
11755
|
+
{
|
|
11756
|
+
author,
|
|
11757
|
+
content
|
|
11758
|
+
},
|
|
11759
|
+
activityContext(ctx, author)
|
|
11760
|
+
);
|
|
10521
11761
|
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10522
11762
|
return true;
|
|
10523
11763
|
}
|
|
@@ -10560,7 +11800,62 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10560
11800
|
return true;
|
|
10561
11801
|
}
|
|
10562
11802
|
const task = await getTask(ctx.projectRoot, boardId, taskId);
|
|
10563
|
-
|
|
11803
|
+
if (task) {
|
|
11804
|
+
await touchTaskPresence(ctx, boardId, task.id);
|
|
11805
|
+
ok(ws, type, task);
|
|
11806
|
+
} else {
|
|
11807
|
+
fail(ws, type, "Task not found");
|
|
11808
|
+
}
|
|
11809
|
+
return true;
|
|
11810
|
+
}
|
|
11811
|
+
case "kanban.task.activity": {
|
|
11812
|
+
const boardId = payload?.boardId;
|
|
11813
|
+
const taskId = payload?.taskId;
|
|
11814
|
+
if (!boardId || !taskId) {
|
|
11815
|
+
fail(ws, type, "boardId and taskId required");
|
|
11816
|
+
return true;
|
|
11817
|
+
}
|
|
11818
|
+
const presenceBoard = await touchTaskPresence(ctx, boardId, taskId);
|
|
11819
|
+
const events = await listTaskActivity(ctx.projectRoot, boardId, taskId, {
|
|
11820
|
+
...typeof payload?.limit === "number" ? { limit: payload.limit } : {}
|
|
11821
|
+
});
|
|
11822
|
+
ok(ws, type, {
|
|
11823
|
+
boardId,
|
|
11824
|
+
taskId,
|
|
11825
|
+
events,
|
|
11826
|
+
presence: presenceBoard?.presence?.filter((entry) => entry.taskId === taskId) ?? []
|
|
11827
|
+
});
|
|
11828
|
+
return true;
|
|
11829
|
+
}
|
|
11830
|
+
case "kanban.task.activity.add": {
|
|
11831
|
+
const boardId = payload?.boardId;
|
|
11832
|
+
const taskId = payload?.taskId;
|
|
11833
|
+
const kind = payload?.kind;
|
|
11834
|
+
const summary = payload?.summary;
|
|
11835
|
+
const allowedKinds = ["decision", "attempt", "result", "blocker", "observation"];
|
|
11836
|
+
const allowedOutcomes = ["succeeded", "failed", "partial", "skipped", "unknown"];
|
|
11837
|
+
if (!boardId || !taskId || !summary?.trim() || !allowedKinds.includes(kind)) {
|
|
11838
|
+
fail(ws, type, "boardId, taskId, summary, and a valid activity kind required");
|
|
11839
|
+
return true;
|
|
11840
|
+
}
|
|
11841
|
+
const requestedOutcome = payload?.outcome;
|
|
11842
|
+
const outcome = allowedOutcomes.includes(requestedOutcome) ? requestedOutcome : "unknown";
|
|
11843
|
+
const board = await recordTaskActivity(
|
|
11844
|
+
ctx.projectRoot,
|
|
11845
|
+
boardId,
|
|
11846
|
+
taskId,
|
|
11847
|
+
{
|
|
11848
|
+
kind,
|
|
11849
|
+
summary: summary.trim(),
|
|
11850
|
+
outcome,
|
|
11851
|
+
...typeof payload?.details === "string" && payload.details.trim() ? { details: payload.details.trim() } : {}
|
|
11852
|
+
},
|
|
11853
|
+
activityContext(
|
|
11854
|
+
ctx,
|
|
11855
|
+
payload?.actor ?? ctx.context?.agentId ?? "webui"
|
|
11856
|
+
)
|
|
11857
|
+
);
|
|
11858
|
+
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10564
11859
|
return true;
|
|
10565
11860
|
}
|
|
10566
11861
|
case "kanban.column.add": {
|
|
@@ -11038,6 +12333,18 @@ async function handleSpecsRoute(_ws, msg, handlers) {
|
|
|
11038
12333
|
}
|
|
11039
12334
|
|
|
11040
12335
|
// src/server/message-dispatcher.ts
|
|
12336
|
+
var chronicleCache = /* @__PURE__ */ new Map();
|
|
12337
|
+
async function chronicleEngine(projectRoot) {
|
|
12338
|
+
const now = Date.now();
|
|
12339
|
+
const cached = chronicleCache.get(projectRoot);
|
|
12340
|
+
if (cached && now - cached.loadedAt < 1e3) return cached.engine;
|
|
12341
|
+
const paths = resolveWstackPaths2({ projectRoot, userHome: os2.homedir() });
|
|
12342
|
+
const engine = await ChronicleQueryEngine.fromDirectory(
|
|
12343
|
+
path19.join(paths.projectDir, "chronicle")
|
|
12344
|
+
);
|
|
12345
|
+
chronicleCache.set(projectRoot, { loadedAt: now, engine });
|
|
12346
|
+
return engine;
|
|
12347
|
+
}
|
|
11041
12348
|
function createMessageDispatcher(opts) {
|
|
11042
12349
|
const { state, deps: deps2, cb, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
11043
12350
|
function makeWorklistContext() {
|
|
@@ -11049,7 +12356,8 @@ function createMessageDispatcher(opts) {
|
|
|
11049
12356
|
state: deps2.context.state
|
|
11050
12357
|
},
|
|
11051
12358
|
send: (w, m) => send(w, m),
|
|
11052
|
-
broadcast: (m) => broadcast(state.getClients(), m)
|
|
12359
|
+
broadcast: (m) => broadcast(state.getClients(), m),
|
|
12360
|
+
replaceTodos: (todos) => deps2.context.state.replaceTodos(todos)
|
|
11053
12361
|
};
|
|
11054
12362
|
}
|
|
11055
12363
|
function makeSkillsContext() {
|
|
@@ -11058,7 +12366,7 @@ function createMessageDispatcher(opts) {
|
|
|
11058
12366
|
skillLoader: deps2.skillLoader,
|
|
11059
12367
|
skillInstaller: deps2.skillInstaller,
|
|
11060
12368
|
projectRoot,
|
|
11061
|
-
projectSkillsDir:
|
|
12369
|
+
projectSkillsDir: path19.join(projectRoot, ".wrongstack", "skills"),
|
|
11062
12370
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
11063
12371
|
};
|
|
11064
12372
|
}
|
|
@@ -11096,7 +12404,7 @@ function createMessageDispatcher(opts) {
|
|
|
11096
12404
|
if (await handleMailboxRoute(ws, msg, routes.mailboxRoutes)) return;
|
|
11097
12405
|
if (await handleMcpRoute(ws, msg, routes.mcpRoutes)) return;
|
|
11098
12406
|
if (await handleBrainRoute(ws, msg, routes.brainRoutes)) return;
|
|
11099
|
-
if (await
|
|
12407
|
+
if (await handleGoalRoute(ws, msg, routes.goalRoutes)) return;
|
|
11100
12408
|
if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
|
|
11101
12409
|
if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
|
|
11102
12410
|
if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
|
|
@@ -11300,6 +12608,14 @@ function createMessageDispatcher(opts) {
|
|
|
11300
12608
|
return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
|
|
11301
12609
|
case "memory.super.remember":
|
|
11302
12610
|
return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
|
|
12611
|
+
case "memory.super.recover":
|
|
12612
|
+
return handleSuperMemoryRecover(ws, msg, deps2.memoryStore);
|
|
12613
|
+
case "memory.super.candidateResolve":
|
|
12614
|
+
return handleSuperMemoryCandidateResolve(ws, msg, deps2.memoryStore);
|
|
12615
|
+
case "memory.super.backfillRecoverable":
|
|
12616
|
+
return handleSuperMemoryBackfillRecoverable(ws, msg, deps2.memoryStore);
|
|
12617
|
+
case "memory.super.forFile":
|
|
12618
|
+
return handleSuperMemoryForFile(ws, msg, deps2.memoryStore);
|
|
11303
12619
|
// ── MCP tripwires — handleMcpRoute claims these upstream. ──
|
|
11304
12620
|
case "mcp.list":
|
|
11305
12621
|
throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
|
|
@@ -11530,6 +12846,59 @@ function createMessageDispatcher(opts) {
|
|
|
11530
12846
|
});
|
|
11531
12847
|
break;
|
|
11532
12848
|
}
|
|
12849
|
+
// ── Chronicle journal queries (parity with embedded webui-server) ──
|
|
12850
|
+
// Mirrors packages/cli/src/webui-server/message-router.ts:645-664.
|
|
12851
|
+
// The engine is cached for 1s to avoid re-reading the journal on every
|
|
12852
|
+
// query; the cache is module-scoped so it survives across messages on
|
|
12853
|
+
// the same connection.
|
|
12854
|
+
case "chronicle.query": {
|
|
12855
|
+
const payload = msg.payload ?? {};
|
|
12856
|
+
const engine = await chronicleEngine(state.getProjectRoot());
|
|
12857
|
+
send(ws, { type: "chronicle.query_result", payload: engine.query(payload.query ?? {}) });
|
|
12858
|
+
break;
|
|
12859
|
+
}
|
|
12860
|
+
case "chronicle.facet": {
|
|
12861
|
+
const payload = msg.payload ?? {};
|
|
12862
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
12863
|
+
"eventType",
|
|
12864
|
+
"outcome",
|
|
12865
|
+
"projectId",
|
|
12866
|
+
"sessionId",
|
|
12867
|
+
"agentId",
|
|
12868
|
+
"taskId",
|
|
12869
|
+
"providerId",
|
|
12870
|
+
"modelId",
|
|
12871
|
+
"resourceKind",
|
|
12872
|
+
"resourcePath",
|
|
12873
|
+
"toolCallId"
|
|
12874
|
+
]);
|
|
12875
|
+
if (!payload.field || !allowed.has(payload.field)) {
|
|
12876
|
+
send(ws, {
|
|
12877
|
+
type: "chronicle.error",
|
|
12878
|
+
payload: { message: "Invalid Chronicle facet field." }
|
|
12879
|
+
});
|
|
12880
|
+
break;
|
|
12881
|
+
}
|
|
12882
|
+
const engine = await chronicleEngine(state.getProjectRoot());
|
|
12883
|
+
send(ws, {
|
|
12884
|
+
type: "chronicle.facet_result",
|
|
12885
|
+
payload: {
|
|
12886
|
+
field: payload.field,
|
|
12887
|
+
values: engine.facet(payload.field, payload.query ?? {}, payload.limit),
|
|
12888
|
+
diagnostics: engine.diagnostics
|
|
12889
|
+
}
|
|
12890
|
+
});
|
|
12891
|
+
break;
|
|
12892
|
+
}
|
|
12893
|
+
case "chronicle.graph": {
|
|
12894
|
+
const payload = msg.payload ?? {};
|
|
12895
|
+
const engine = await chronicleEngine(state.getProjectRoot());
|
|
12896
|
+
send(ws, {
|
|
12897
|
+
type: "chronicle.graph_result",
|
|
12898
|
+
payload: engine.graph(payload.seed ?? {}, payload.hops, payload.maxNodes)
|
|
12899
|
+
});
|
|
12900
|
+
break;
|
|
12901
|
+
}
|
|
11533
12902
|
case "process.list": {
|
|
11534
12903
|
await handleProcessList(ws);
|
|
11535
12904
|
break;
|
|
@@ -11547,7 +12916,7 @@ function createMessageDispatcher(opts) {
|
|
|
11547
12916
|
process.kill(process.pid, "SIGINT");
|
|
11548
12917
|
break;
|
|
11549
12918
|
}
|
|
11550
|
-
case "goal.get": {
|
|
12919
|
+
case "goal-state.get": {
|
|
11551
12920
|
await handleGoalGet(state.getProjectRoot(), (m) => broadcast(state.getClients(), m));
|
|
11552
12921
|
break;
|
|
11553
12922
|
}
|
|
@@ -11581,9 +12950,9 @@ function createMessageDispatcher(opts) {
|
|
|
11581
12950
|
}
|
|
11582
12951
|
|
|
11583
12952
|
// src/server/pref-helpers.ts
|
|
11584
|
-
import { atomicWrite as atomicWrite6 } from "@wrongstack/core/utils";
|
|
12953
|
+
import { atomicWrite as atomicWrite6, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
|
|
11585
12954
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
|
|
11586
|
-
import * as
|
|
12955
|
+
import * as fs14 from "node:fs/promises";
|
|
11587
12956
|
var PREF_KEYS = [
|
|
11588
12957
|
"autonomy",
|
|
11589
12958
|
"autonomyDelayMs",
|
|
@@ -11638,11 +13007,31 @@ var PREF_KEYS = [
|
|
|
11638
13007
|
"thinkingWord",
|
|
11639
13008
|
"statuslineMode",
|
|
11640
13009
|
"animationStyle",
|
|
13010
|
+
"showModelReasoning",
|
|
11641
13011
|
// Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
|
|
11642
13012
|
"breakerEnabled",
|
|
11643
13013
|
"breakerAutoKillResetMs",
|
|
11644
13014
|
"fsAccess",
|
|
11645
|
-
"debugStream"
|
|
13015
|
+
"debugStream",
|
|
13016
|
+
// Chimera (post-session) + auto-review (mid-session) settings.
|
|
13017
|
+
// Persisted to config.extensions['wstack-chimera'] / ['wstack-auto-review']
|
|
13018
|
+
// so the running plugins pick up changes after a session restart.
|
|
13019
|
+
"chimeraEnabled",
|
|
13020
|
+
"chimeraProvider",
|
|
13021
|
+
"chimeraModel",
|
|
13022
|
+
"chimeraMaxFiles",
|
|
13023
|
+
"chimeraAutoFix",
|
|
13024
|
+
"autoReviewEnabled",
|
|
13025
|
+
"autoReviewProvider",
|
|
13026
|
+
"autoReviewModel",
|
|
13027
|
+
"autoReviewFallbackProfile",
|
|
13028
|
+
"autoReviewFallbackModels",
|
|
13029
|
+
"autoReviewDebounceMs",
|
|
13030
|
+
"autoReviewMaxFilesPerBatch",
|
|
13031
|
+
"autoReviewMaxConcurrentReviews",
|
|
13032
|
+
"autoReviewCascadeOn",
|
|
13033
|
+
// Per-plugin enable/disable map (parity with the embedded server).
|
|
13034
|
+
"pluginsEnabled"
|
|
11646
13035
|
];
|
|
11647
13036
|
function prefSnapshot(contextMeta) {
|
|
11648
13037
|
const snapshot = {};
|
|
@@ -11656,7 +13045,7 @@ async function updateGlobalConfig(deps2, holder, mutate, errorLabel) {
|
|
|
11656
13045
|
const write = async () => {
|
|
11657
13046
|
let raw;
|
|
11658
13047
|
try {
|
|
11659
|
-
raw = await
|
|
13048
|
+
raw = await fs14.readFile(globalConfigPath, "utf8");
|
|
11660
13049
|
} catch {
|
|
11661
13050
|
raw = "{}";
|
|
11662
13051
|
}
|
|
@@ -11725,6 +13114,8 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
11725
13114
|
setAutonomy("statuslineMode", payload["statuslineMode"]);
|
|
11726
13115
|
if (typeof payload["animationStyle"] === "string")
|
|
11727
13116
|
setAutonomy("animationStyle", payload["animationStyle"]);
|
|
13117
|
+
if (typeof payload["showModelReasoning"] === "boolean")
|
|
13118
|
+
setAutonomy("showModelReasoning", payload["showModelReasoning"]);
|
|
11728
13119
|
if (autonomyTouched) decrypted.autonomy = autonomyCfg;
|
|
11729
13120
|
if (typeof payload["nextPrediction"] === "boolean")
|
|
11730
13121
|
decrypted.nextPrediction = payload["nextPrediction"];
|
|
@@ -11860,41 +13251,76 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
11860
13251
|
}
|
|
11861
13252
|
if (typeof payload["debugStream"] === "boolean")
|
|
11862
13253
|
decrypted.debugStream = payload["debugStream"];
|
|
13254
|
+
if (typeof payload["pluginsEnabled"] === "object" && payload["pluginsEnabled"] !== null) {
|
|
13255
|
+
const ext = decrypted.extensions ?? {};
|
|
13256
|
+
for (const [pluginName, enabled] of Object.entries(
|
|
13257
|
+
payload["pluginsEnabled"]
|
|
13258
|
+
)) {
|
|
13259
|
+
if (FORBIDDEN_PROTO_KEYS2.has(pluginName)) continue;
|
|
13260
|
+
const pExt = ext[pluginName] ?? {};
|
|
13261
|
+
pExt["enabled"] = enabled;
|
|
13262
|
+
ext[pluginName] = pExt;
|
|
13263
|
+
}
|
|
13264
|
+
decrypted.extensions = ext;
|
|
13265
|
+
}
|
|
13266
|
+
const chimeraTouched = typeof payload["chimeraEnabled"] === "boolean" || typeof payload["chimeraProvider"] === "string" || typeof payload["chimeraModel"] === "string" || typeof payload["chimeraMaxFiles"] === "number" || typeof payload["chimeraAutoFix"] === "string";
|
|
13267
|
+
if (chimeraTouched) {
|
|
13268
|
+
const ext = decrypted.extensions ?? {};
|
|
13269
|
+
const chimera = ext["wstack-chimera"] ?? {};
|
|
13270
|
+
if (typeof payload["chimeraEnabled"] === "boolean")
|
|
13271
|
+
chimera["enabled"] = payload["chimeraEnabled"];
|
|
13272
|
+
if (typeof payload["chimeraProvider"] === "string")
|
|
13273
|
+
chimera["provider"] = payload["chimeraProvider"];
|
|
13274
|
+
if (typeof payload["chimeraModel"] === "string")
|
|
13275
|
+
chimera["model"] = payload["chimeraModel"];
|
|
13276
|
+
if (typeof payload["chimeraMaxFiles"] === "number" && payload["chimeraMaxFiles"] >= 1) {
|
|
13277
|
+
chimera["maxFiles"] = payload["chimeraMaxFiles"];
|
|
13278
|
+
}
|
|
13279
|
+
if (typeof payload["chimeraAutoFix"] === "string") {
|
|
13280
|
+
if (payload["chimeraAutoFix"] === "off" || payload["chimeraAutoFix"] === "ask" || payload["chimeraAutoFix"] === "auto") {
|
|
13281
|
+
chimera["autoFix"] = payload["chimeraAutoFix"];
|
|
13282
|
+
}
|
|
13283
|
+
}
|
|
13284
|
+
ext["wstack-chimera"] = chimera;
|
|
13285
|
+
decrypted.extensions = ext;
|
|
13286
|
+
}
|
|
13287
|
+
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";
|
|
13288
|
+
if (autoReviewTouched) {
|
|
13289
|
+
const ext = decrypted.extensions ?? {};
|
|
13290
|
+
const ar = ext["wstack-auto-review"] ?? {};
|
|
13291
|
+
if (typeof payload["autoReviewEnabled"] === "boolean")
|
|
13292
|
+
ar["enabled"] = payload["autoReviewEnabled"];
|
|
13293
|
+
if (typeof payload["autoReviewProvider"] === "string")
|
|
13294
|
+
ar["provider"] = payload["autoReviewProvider"];
|
|
13295
|
+
if (typeof payload["autoReviewModel"] === "string")
|
|
13296
|
+
ar["model"] = payload["autoReviewModel"];
|
|
13297
|
+
if (typeof payload["autoReviewFallbackProfile"] === "string") {
|
|
13298
|
+
if (payload["autoReviewFallbackProfile"] === "") {
|
|
13299
|
+
delete ar["fallbackProfile"];
|
|
13300
|
+
} else {
|
|
13301
|
+
ar["fallbackProfile"] = payload["autoReviewFallbackProfile"];
|
|
13302
|
+
}
|
|
13303
|
+
}
|
|
13304
|
+
if (typeof payload["autoReviewDebounceMs"] === "number" && payload["autoReviewDebounceMs"] >= 0) {
|
|
13305
|
+
ar["debounceMs"] = payload["autoReviewDebounceMs"];
|
|
13306
|
+
}
|
|
13307
|
+
if (typeof payload["autoReviewMaxFilesPerBatch"] === "number" && payload["autoReviewMaxFilesPerBatch"] >= 1) {
|
|
13308
|
+
ar["maxFilesPerBatch"] = payload["autoReviewMaxFilesPerBatch"];
|
|
13309
|
+
}
|
|
13310
|
+
if (typeof payload["autoReviewMaxConcurrentReviews"] === "number" && payload["autoReviewMaxConcurrentReviews"] >= 1) {
|
|
13311
|
+
ar["maxConcurrentReviews"] = payload["autoReviewMaxConcurrentReviews"];
|
|
13312
|
+
}
|
|
13313
|
+
if (typeof payload["autoReviewCascadeOn"] === "string") {
|
|
13314
|
+
if (payload["autoReviewCascadeOn"] === "off" || payload["autoReviewCascadeOn"] === "critical" || payload["autoReviewCascadeOn"] === "high") {
|
|
13315
|
+
ar["cascadeOn"] = payload["autoReviewCascadeOn"];
|
|
13316
|
+
}
|
|
13317
|
+
}
|
|
13318
|
+
ext["wstack-auto-review"] = ar;
|
|
13319
|
+
decrypted.extensions = ext;
|
|
13320
|
+
}
|
|
11863
13321
|
}, "prefs");
|
|
11864
13322
|
}
|
|
11865
13323
|
|
|
11866
|
-
// src/server/projects-manifest.ts
|
|
11867
|
-
import * as fs14 from "node:fs/promises";
|
|
11868
|
-
import * as path18 from "node:path";
|
|
11869
|
-
import { projectSlug } from "@wrongstack/core";
|
|
11870
|
-
function projectsJsonPath(globalConfigPath) {
|
|
11871
|
-
const base = path18.dirname(globalConfigPath);
|
|
11872
|
-
return path18.join(base, "projects.json");
|
|
11873
|
-
}
|
|
11874
|
-
async function loadManifest(globalConfigPath) {
|
|
11875
|
-
try {
|
|
11876
|
-
const raw = await fs14.readFile(projectsJsonPath(globalConfigPath), "utf8");
|
|
11877
|
-
const parsed = JSON.parse(raw);
|
|
11878
|
-
return { projects: parsed.projects ?? [] };
|
|
11879
|
-
} catch {
|
|
11880
|
-
return { projects: [] };
|
|
11881
|
-
}
|
|
11882
|
-
}
|
|
11883
|
-
async function saveManifest(manifest, globalConfigPath) {
|
|
11884
|
-
const file = projectsJsonPath(globalConfigPath);
|
|
11885
|
-
await fs14.mkdir(path18.dirname(file), { recursive: true });
|
|
11886
|
-
await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
11887
|
-
}
|
|
11888
|
-
function generateProjectSlug(rootPath) {
|
|
11889
|
-
return projectSlug(rootPath);
|
|
11890
|
-
}
|
|
11891
|
-
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
11892
|
-
const base = path18.dirname(globalConfigPath);
|
|
11893
|
-
const dir = path18.join(base, "projects", slug);
|
|
11894
|
-
await fs14.mkdir(dir, { recursive: true });
|
|
11895
|
-
return dir;
|
|
11896
|
-
}
|
|
11897
|
-
|
|
11898
13324
|
// src/server/provider-handlers.ts
|
|
11899
13325
|
import { DefaultSecretScrubber } from "@wrongstack/core";
|
|
11900
13326
|
import {
|
|
@@ -12223,8 +13649,9 @@ function createProviderHandlers(deps2) {
|
|
|
12223
13649
|
}
|
|
12224
13650
|
|
|
12225
13651
|
// src/server/routes.ts
|
|
12226
|
-
import
|
|
13652
|
+
import path21 from "node:path";
|
|
12227
13653
|
import {
|
|
13654
|
+
buildRefinerContextSections,
|
|
12228
13655
|
enhanceUserPrompt,
|
|
12229
13656
|
gatedEnhancerReasoning,
|
|
12230
13657
|
nextEnhanceTimeout,
|
|
@@ -12340,7 +13767,7 @@ async function handleMailboxCompact(ws, deps2, opts) {
|
|
|
12340
13767
|
// src/server/mode-handlers.ts
|
|
12341
13768
|
import {
|
|
12342
13769
|
DefaultSystemPromptBuilder as DefaultSystemPromptBuilder2,
|
|
12343
|
-
resolveWstackPaths as
|
|
13770
|
+
resolveWstackPaths as resolveWstackPaths3,
|
|
12344
13771
|
ToolValidationError as ToolValidationError5
|
|
12345
13772
|
} from "@wrongstack/core";
|
|
12346
13773
|
function createModeHandlers(ctx) {
|
|
@@ -12380,6 +13807,7 @@ function createModeHandlers(ctx) {
|
|
|
12380
13807
|
}
|
|
12381
13808
|
const { id } = parsed.value;
|
|
12382
13809
|
try {
|
|
13810
|
+
const prev = await ctx.modeStore.getActiveMode();
|
|
12383
13811
|
if (id === "default") {
|
|
12384
13812
|
await ctx.modeStore.setActiveMode(null);
|
|
12385
13813
|
} else {
|
|
@@ -12390,8 +13818,13 @@ function createModeHandlers(ctx) {
|
|
|
12390
13818
|
await ctx.modeStore.setActiveMode(id);
|
|
12391
13819
|
}
|
|
12392
13820
|
ctx.setModeId(id);
|
|
13821
|
+
const fromMode = prev?.id ?? "default";
|
|
13822
|
+
if (ctx.context.session && fromMode !== id) {
|
|
13823
|
+
void ctx.context.session.append({ type: "mode_changed", ts: (/* @__PURE__ */ new Date()).toISOString(), from: fromMode, to: id }).catch(() => {
|
|
13824
|
+
});
|
|
13825
|
+
}
|
|
12393
13826
|
const modePrompt = id === "default" ? "" : (await ctx.modeStore.getMode(id))?.prompt ?? "";
|
|
12394
|
-
const paths =
|
|
13827
|
+
const paths = resolveWstackPaths3({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
|
|
12395
13828
|
const freshBuilder = new DefaultSystemPromptBuilder2({
|
|
12396
13829
|
memoryStore: ctx.memoryStore,
|
|
12397
13830
|
// Single injection channel: Super Memory turn middleware, not a static section.
|
|
@@ -12426,7 +13859,7 @@ function createModeHandlers(ctx) {
|
|
|
12426
13859
|
}
|
|
12427
13860
|
|
|
12428
13861
|
// src/server/project-handlers.ts
|
|
12429
|
-
import * as
|
|
13862
|
+
import * as path20 from "node:path";
|
|
12430
13863
|
function createProjectHandlers(ctx) {
|
|
12431
13864
|
return {
|
|
12432
13865
|
listProjects: async (ws) => {
|
|
@@ -12452,7 +13885,7 @@ function createProjectHandlers(ctx) {
|
|
|
12452
13885
|
selectProject: async (ws, msg) => {
|
|
12453
13886
|
const payload = msg.payload;
|
|
12454
13887
|
const root = typeof payload?.root === "string" ? payload.root : "";
|
|
12455
|
-
const name2 = typeof payload?.name === "string" ? payload.name : root ?
|
|
13888
|
+
const name2 = typeof payload?.name === "string" ? payload.name : root ? path20.basename(root) : "";
|
|
12456
13889
|
send(ws, {
|
|
12457
13890
|
type: "projects.selected",
|
|
12458
13891
|
payload: {
|
|
@@ -12489,6 +13922,7 @@ function createProjectHandlers(ctx) {
|
|
|
12489
13922
|
// src/server/session-handlers.ts
|
|
12490
13923
|
import {
|
|
12491
13924
|
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
13925
|
+
loadTodosCheckpoint,
|
|
12492
13926
|
repairToolUseAdjacency,
|
|
12493
13927
|
resolveContextWindowPolicy as resolveContextWindowPolicy3
|
|
12494
13928
|
} from "@wrongstack/core";
|
|
@@ -12526,13 +13960,14 @@ function createSessionHandlers(ctx) {
|
|
|
12526
13960
|
}).catch(() => void 0);
|
|
12527
13961
|
await writer.close().catch(() => void 0);
|
|
12528
13962
|
};
|
|
12529
|
-
const activateSession = async (next, messages, usage) => {
|
|
13963
|
+
const activateSession = async (next, messages, usage, todos = []) => {
|
|
12530
13964
|
const current = ctx.getSession();
|
|
12531
13965
|
if (current !== next) await finalizeSession(current);
|
|
12532
13966
|
ctx.setSession(next);
|
|
12533
13967
|
ctx.context.session = next;
|
|
12534
13968
|
ctx.context.state.replaceMessages(messages);
|
|
12535
|
-
ctx.context.
|
|
13969
|
+
await ctx.context.flushConversationJournal?.();
|
|
13970
|
+
ctx.context.state.replaceTodos(todos);
|
|
12536
13971
|
ctx.context.readFiles.clear();
|
|
12537
13972
|
ctx.context.fileMtimes.clear();
|
|
12538
13973
|
ctx.context.state.setMeta(
|
|
@@ -12816,7 +14251,15 @@ function createSessionHandlers(ctx) {
|
|
|
12816
14251
|
return;
|
|
12817
14252
|
}
|
|
12818
14253
|
const resumed = await ctx.getSessionStore().resume(id);
|
|
12819
|
-
await
|
|
14254
|
+
const restoredTodos = await loadTodosCheckpoint(
|
|
14255
|
+
sessionScopedPath2(ctx.sessionsDir, resumed.writer.id, ".todos.json")
|
|
14256
|
+
).catch(() => null) ?? [];
|
|
14257
|
+
await activateSession(
|
|
14258
|
+
resumed.writer,
|
|
14259
|
+
resumed.data.messages,
|
|
14260
|
+
resumed.data.usage,
|
|
14261
|
+
restoredTodos
|
|
14262
|
+
);
|
|
12820
14263
|
broadcast(ctx.clients, {
|
|
12821
14264
|
type: "session.start",
|
|
12822
14265
|
payload: {
|
|
@@ -12826,6 +14269,10 @@ function createSessionHandlers(ctx) {
|
|
|
12826
14269
|
replayUsage: resumed.data.usage
|
|
12827
14270
|
}
|
|
12828
14271
|
});
|
|
14272
|
+
broadcast(ctx.clients, {
|
|
14273
|
+
type: "todos.updated",
|
|
14274
|
+
payload: { sessionId: resumed.writer.id, todos: restoredTodos }
|
|
14275
|
+
});
|
|
12829
14276
|
sendResult(ws, true, `Resumed session ${id}`);
|
|
12830
14277
|
} catch (err) {
|
|
12831
14278
|
sendResult(ws, false, errMessage(err));
|
|
@@ -12851,11 +14298,17 @@ function createSessionHandlers(ctx) {
|
|
|
12851
14298
|
if (!ensureCurrentSession(ws, msg, "session.rewind")) return;
|
|
12852
14299
|
const { checkpointIndex } = msg.payload;
|
|
12853
14300
|
try {
|
|
12854
|
-
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
14301
|
+
const { applyRewindToConversation, DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
12855
14302
|
const projectRoot = ctx.getProjectRoot();
|
|
12856
14303
|
const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
|
|
12857
|
-
await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
|
|
12858
|
-
await
|
|
14304
|
+
const reverted = await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
|
|
14305
|
+
await applyRewindToConversation({
|
|
14306
|
+
session: ctx.context.session,
|
|
14307
|
+
state: ctx.context.state,
|
|
14308
|
+
sessionsDir: ctx.sessionsDir,
|
|
14309
|
+
promptIndex: checkpointIndex,
|
|
14310
|
+
revertedFiles: reverted.revertedFiles
|
|
14311
|
+
});
|
|
12859
14312
|
sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
|
|
12860
14313
|
broadcast(ctx.clients, {
|
|
12861
14314
|
type: "session.start",
|
|
@@ -13070,6 +14523,11 @@ function buildRoutes(state, deps2, cb) {
|
|
|
13070
14523
|
const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
|
|
13071
14524
|
try {
|
|
13072
14525
|
const history = recentTextTurns(deps2.context.messages);
|
|
14526
|
+
const contextSections = await buildRefinerContextSections({
|
|
14527
|
+
text,
|
|
14528
|
+
memoryStore: deps2.memoryStore,
|
|
14529
|
+
context: deps2.context
|
|
14530
|
+
});
|
|
13073
14531
|
const resolved = await resolveProviderModelMetadata(
|
|
13074
14532
|
deps2.modelsRegistry,
|
|
13075
14533
|
providerId,
|
|
@@ -13083,6 +14541,14 @@ function buildRoutes(state, deps2, cb) {
|
|
|
13083
14541
|
model,
|
|
13084
14542
|
text,
|
|
13085
14543
|
history,
|
|
14544
|
+
contextSections,
|
|
14545
|
+
...payload.previousRefined ? {
|
|
14546
|
+
previousRefinement: {
|
|
14547
|
+
refined: payload.previousRefined,
|
|
14548
|
+
english: payload.previousEnglish || payload.previousRefined
|
|
14549
|
+
}
|
|
14550
|
+
} : {},
|
|
14551
|
+
...payload.retryFeedback ? { retryFeedback: payload.retryFeedback } : {},
|
|
13086
14552
|
timeoutMs,
|
|
13087
14553
|
...reasoning ? { reasoning } : {},
|
|
13088
14554
|
onError: (reason, kind) => {
|
|
@@ -13242,6 +14708,17 @@ function buildRoutes(state, deps2, cb) {
|
|
|
13242
14708
|
cfg.modelMatrix = payload["modelMatrix"];
|
|
13243
14709
|
}
|
|
13244
14710
|
if (typeof payload["fallbackAuto"] === "boolean") cfg.fallbackAuto = payload["fallbackAuto"];
|
|
14711
|
+
const routingPatch = {};
|
|
14712
|
+
if (Array.isArray(payload["fallbackModels"])) routingPatch.fallbackModels = payload["fallbackModels"];
|
|
14713
|
+
if (payload["fallbackProfiles"] && typeof payload["fallbackProfiles"] === "object" && !Array.isArray(payload["fallbackProfiles"]))
|
|
14714
|
+
routingPatch.fallbackProfiles = payload["fallbackProfiles"];
|
|
14715
|
+
if (Array.isArray(payload["favoriteModels"])) routingPatch.favoriteModels = payload["favoriteModels"];
|
|
14716
|
+
if (typeof payload["favoriteModelsOnly"] === "boolean") routingPatch.favoriteModelsOnly = payload["favoriteModelsOnly"];
|
|
14717
|
+
if (payload["modelMatrix"] && typeof payload["modelMatrix"] === "object" && !Array.isArray(payload["modelMatrix"]))
|
|
14718
|
+
routingPatch.modelMatrix = payload["modelMatrix"];
|
|
14719
|
+
if (typeof payload["fallbackAuto"] === "boolean") routingPatch.fallbackAuto = payload["fallbackAuto"];
|
|
14720
|
+
if (Object.keys(routingPatch).length > 0)
|
|
14721
|
+
deps2.configStore.update(routingPatch);
|
|
13245
14722
|
if (typeof payload["contextAutoCompact"] === "boolean") {
|
|
13246
14723
|
if (payload["contextAutoCompact"] && deps2.autoCompactor) {
|
|
13247
14724
|
deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
@@ -13310,7 +14787,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
13310
14787
|
}
|
|
13311
14788
|
return handleMailboxMessages(
|
|
13312
14789
|
ws,
|
|
13313
|
-
{ projectRoot: state.getProjectRoot(), globalRoot:
|
|
14790
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
|
|
13314
14791
|
parsed.value
|
|
13315
14792
|
);
|
|
13316
14793
|
},
|
|
@@ -13322,13 +14799,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
13322
14799
|
}
|
|
13323
14800
|
return handleMailboxAgents(
|
|
13324
14801
|
ws,
|
|
13325
|
-
{ projectRoot: state.getProjectRoot(), globalRoot:
|
|
14802
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
|
|
13326
14803
|
parsed.value
|
|
13327
14804
|
);
|
|
13328
14805
|
},
|
|
13329
14806
|
clear: (ws) => handleMailboxClear(ws, {
|
|
13330
14807
|
projectRoot: state.getProjectRoot(),
|
|
13331
|
-
globalRoot:
|
|
14808
|
+
globalRoot: path21.dirname(deps2.globalConfigPath)
|
|
13332
14809
|
}),
|
|
13333
14810
|
purge: (ws, msg) => {
|
|
13334
14811
|
const parsed = validateMailboxPurgePayload(msg.payload);
|
|
@@ -13338,14 +14815,14 @@ function buildRoutes(state, deps2, cb) {
|
|
|
13338
14815
|
}
|
|
13339
14816
|
return handleMailboxPurge(
|
|
13340
14817
|
ws,
|
|
13341
|
-
{ projectRoot: state.getProjectRoot(), globalRoot:
|
|
14818
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
|
|
13342
14819
|
parsed.value
|
|
13343
14820
|
);
|
|
13344
14821
|
},
|
|
13345
14822
|
compact: (ws, msg) => {
|
|
13346
14823
|
return handleMailboxCompact(
|
|
13347
14824
|
ws,
|
|
13348
|
-
{ projectRoot: state.getProjectRoot(), globalRoot:
|
|
14825
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
|
|
13349
14826
|
msg.payload ?? {}
|
|
13350
14827
|
);
|
|
13351
14828
|
}
|
|
@@ -13454,8 +14931,8 @@ function buildRoutes(state, deps2, cb) {
|
|
|
13454
14931
|
}
|
|
13455
14932
|
}
|
|
13456
14933
|
};
|
|
13457
|
-
const
|
|
13458
|
-
handleMessage: (msg) => deps2.
|
|
14934
|
+
const goalRoutes = {
|
|
14935
|
+
handleMessage: (msg) => deps2.goalHandler.handleMessage(msg)
|
|
13459
14936
|
};
|
|
13460
14937
|
const specsRoutes = {
|
|
13461
14938
|
handleMessage: (msg) => deps2.specsHandler.handleMessage(msg)
|
|
@@ -13476,7 +14953,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
13476
14953
|
mailboxRoutes,
|
|
13477
14954
|
mcpRoutes,
|
|
13478
14955
|
brainRoutes,
|
|
13479
|
-
|
|
14956
|
+
goalRoutes,
|
|
13480
14957
|
specsRoutes,
|
|
13481
14958
|
sddBoardRoutes,
|
|
13482
14959
|
sddWizardRoutes
|
|
@@ -13597,7 +15074,7 @@ async function startWebUI(opts = {}) {
|
|
|
13597
15074
|
brainLog,
|
|
13598
15075
|
brainMonitor,
|
|
13599
15076
|
codebaseIndexing,
|
|
13600
|
-
|
|
15077
|
+
goalHandler,
|
|
13601
15078
|
specsHandler,
|
|
13602
15079
|
sddBoardHandler,
|
|
13603
15080
|
sddWizardHandler,
|
|
@@ -13658,21 +15135,21 @@ async function startWebUI(opts = {}) {
|
|
|
13658
15135
|
wpaths
|
|
13659
15136
|
}, watcherMetricsRef);
|
|
13660
15137
|
async function touchProjectEntry(root, workDir) {
|
|
13661
|
-
const resolved =
|
|
15138
|
+
const resolved = path22.resolve(root);
|
|
13662
15139
|
const manifest = await loadManifest(globalConfigPath);
|
|
13663
15140
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13664
|
-
const existing = manifest.projects.find((p) =>
|
|
15141
|
+
const existing = manifest.projects.find((p) => path22.resolve(p.root) === resolved);
|
|
13665
15142
|
if (existing) {
|
|
13666
15143
|
existing.lastSeen = now;
|
|
13667
|
-
if (workDir) existing.lastWorkingDir =
|
|
15144
|
+
if (workDir) existing.lastWorkingDir = path22.resolve(workDir);
|
|
13668
15145
|
} else {
|
|
13669
15146
|
manifest.projects.push({
|
|
13670
|
-
name:
|
|
15147
|
+
name: path22.basename(resolved),
|
|
13671
15148
|
root: resolved,
|
|
13672
15149
|
slug: generateProjectSlug(resolved),
|
|
13673
15150
|
createdAt: now,
|
|
13674
15151
|
lastSeen: now,
|
|
13675
|
-
lastWorkingDir: workDir ?
|
|
15152
|
+
lastWorkingDir: workDir ? path22.resolve(workDir) : void 0
|
|
13676
15153
|
});
|
|
13677
15154
|
}
|
|
13678
15155
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -13755,7 +15232,7 @@ async function startWebUI(opts = {}) {
|
|
|
13755
15232
|
httpPort,
|
|
13756
15233
|
wssPrimary,
|
|
13757
15234
|
wssSecondary,
|
|
13758
|
-
|
|
15235
|
+
goalHandler,
|
|
13759
15236
|
specsHandler,
|
|
13760
15237
|
sddBoardHandler,
|
|
13761
15238
|
sddWizardHandler,
|
|
@@ -13799,7 +15276,13 @@ async function startWebUI(opts = {}) {
|
|
|
13799
15276
|
deps2.configStore.update({
|
|
13800
15277
|
providers: snapshot.providers,
|
|
13801
15278
|
...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
|
|
13802
|
-
...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
|
|
15279
|
+
...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {},
|
|
15280
|
+
...snapshot.fallbackModels !== void 0 ? { fallbackModels: snapshot.fallbackModels } : {},
|
|
15281
|
+
...snapshot.fallbackProfiles !== void 0 ? { fallbackProfiles: snapshot.fallbackProfiles } : {},
|
|
15282
|
+
...snapshot.favoriteModels !== void 0 ? { favoriteModels: snapshot.favoriteModels } : {},
|
|
15283
|
+
...snapshot.favoriteModelsOnly !== void 0 ? { favoriteModelsOnly: snapshot.favoriteModelsOnly } : {},
|
|
15284
|
+
...snapshot.modelMatrix !== void 0 ? { modelMatrix: snapshot.modelMatrix } : {},
|
|
15285
|
+
...snapshot.fallbackAuto !== void 0 ? { fallbackAuto: snapshot.fallbackAuto } : {}
|
|
13803
15286
|
});
|
|
13804
15287
|
broadcast(clients, {
|
|
13805
15288
|
type: "providers.saved",
|
|
@@ -13860,7 +15343,7 @@ async function startWebUI(opts = {}) {
|
|
|
13860
15343
|
},
|
|
13861
15344
|
clients,
|
|
13862
15345
|
pendingConfirms,
|
|
13863
|
-
|
|
15346
|
+
goalHandler,
|
|
13864
15347
|
specsHandler,
|
|
13865
15348
|
sddBoardHandler,
|
|
13866
15349
|
sddWizardHandler,
|
|
@@ -13887,6 +15370,11 @@ async function startWebUI(opts = {}) {
|
|
|
13887
15370
|
onFleetPing: () => {
|
|
13888
15371
|
void eventArming.getFleetBroadcast()?.();
|
|
13889
15372
|
},
|
|
15373
|
+
onTechStackEvent: (event) => broadcast(clients, event),
|
|
15374
|
+
// Read through `context` on every call rather than capturing: the running
|
|
15375
|
+
// loop swaps provider/model when the user switches (same live source the
|
|
15376
|
+
// completion handler reads).
|
|
15377
|
+
getLlm: () => context.provider && context.model ? { provider: context.provider, model: context.model } : void 0,
|
|
13890
15378
|
distDir: opts.distDir
|
|
13891
15379
|
});
|
|
13892
15380
|
registerShutdown({
|
|
@@ -13916,7 +15404,7 @@ async function startWebUI(opts = {}) {
|
|
|
13916
15404
|
archiveLowConfidenceAfterDays: config.superMemory?.hygiene?.archiveLowConfidenceAfterDays
|
|
13917
15405
|
}).catch((err) => logger.warn(`super-memory session hygiene failed: ${toErrorMessage10(err)}`));
|
|
13918
15406
|
}
|
|
13919
|
-
await unregisterInstance(process.pid,
|
|
15407
|
+
await unregisterInstance(process.pid, path22.dirname(globalConfigPath));
|
|
13920
15408
|
}
|
|
13921
15409
|
});
|
|
13922
15410
|
}
|
|
@@ -13956,7 +15444,7 @@ function envFlag2(name2) {
|
|
|
13956
15444
|
return value === "1" || value === "true" || value === "yes" || value === "on";
|
|
13957
15445
|
}
|
|
13958
15446
|
function printHelp() {
|
|
13959
|
-
console.log(`Usage:
|
|
15447
|
+
console.log(`Usage: wstack --webui [options]
|
|
13960
15448
|
|
|
13961
15449
|
Options:
|
|
13962
15450
|
--host <host> Bind host/interface (default: 127.0.0.1)
|