@wrongstack/webui 0.272.1 → 0.273.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/assets/index-BGzM4-Zu.css +2 -0
- package/dist/assets/index-CM62rXoC.js +140 -0
- package/dist/assets/vendor-Doh9e_v3.css +1 -0
- package/dist/assets/{vendor-cIhL9uWi.js → vendor-P9eRrO6V.js} +296 -296
- package/dist/index.html +4 -4
- package/dist/index.js +8086 -4829
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +842 -151
- package/dist/server/entry.js.map +1 -1
- package/dist/server/handlers.js +5 -2
- package/dist/server/handlers.js.map +1 -1
- package/dist/server/index.d.ts +165 -2
- package/dist/server/index.js +847 -152
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +200 -1
- package/package.json +6 -6
- package/dist/assets/index-CTDuGGlX.css +0 -2
- package/dist/assets/index-DSfCNNx4.js +0 -122
- package/dist/assets/vendor-BzqIVtlT.css +0 -1
package/dist/server/entry.js
CHANGED
|
@@ -8,7 +8,10 @@ function isRecord(value) {
|
|
|
8
8
|
}
|
|
9
9
|
function validateModelSwitchPayload(payload) {
|
|
10
10
|
if (!isRecord(payload)) {
|
|
11
|
-
return {
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
message: "model.switch payload must be an object with string provider and model"
|
|
14
|
+
};
|
|
12
15
|
}
|
|
13
16
|
const provider = payload["provider"];
|
|
14
17
|
const model = payload["model"];
|
|
@@ -30,13 +33,22 @@ function validateMailboxMessagesPayload(payload) {
|
|
|
30
33
|
const agentId = payload["agentId"];
|
|
31
34
|
const unreadOnly = payload["unreadOnly"];
|
|
32
35
|
if (limit !== void 0 && (typeof limit !== "number" || !Number.isFinite(limit) || limit < 1)) {
|
|
33
|
-
return {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
message: "mailbox.messages payload.limit must be a positive number when provided"
|
|
39
|
+
};
|
|
34
40
|
}
|
|
35
41
|
if (agentId !== void 0 && typeof agentId !== "string") {
|
|
36
|
-
return {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
message: "mailbox.messages payload.agentId must be a string when provided"
|
|
45
|
+
};
|
|
37
46
|
}
|
|
38
47
|
if (unreadOnly !== void 0 && typeof unreadOnly !== "boolean") {
|
|
39
|
-
return {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
message: "mailbox.messages payload.unreadOnly must be a boolean when provided"
|
|
51
|
+
};
|
|
40
52
|
}
|
|
41
53
|
return { ok: true, value: { limit, agentId, unreadOnly } };
|
|
42
54
|
}
|
|
@@ -47,7 +59,10 @@ function validateMailboxAgentsPayload(payload) {
|
|
|
47
59
|
}
|
|
48
60
|
const onlineOnly = payload["onlineOnly"];
|
|
49
61
|
if (onlineOnly !== void 0 && typeof onlineOnly !== "boolean") {
|
|
50
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
message: "mailbox.agents payload.onlineOnly must be a boolean when provided"
|
|
65
|
+
};
|
|
51
66
|
}
|
|
52
67
|
return { ok: true, value: { onlineOnly } };
|
|
53
68
|
}
|
|
@@ -59,10 +74,16 @@ function validateMailboxPurgePayload(payload) {
|
|
|
59
74
|
const completedMaxAgeMs = payload["completedMaxAgeMs"];
|
|
60
75
|
const incompleteMaxAgeMs = payload["incompleteMaxAgeMs"];
|
|
61
76
|
if (completedMaxAgeMs !== void 0 && (typeof completedMaxAgeMs !== "number" || !Number.isFinite(completedMaxAgeMs) || completedMaxAgeMs < 0)) {
|
|
62
|
-
return {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
message: "mailbox.purge payload.completedMaxAgeMs must be a non-negative number when provided"
|
|
80
|
+
};
|
|
63
81
|
}
|
|
64
82
|
if (incompleteMaxAgeMs !== void 0 && (typeof incompleteMaxAgeMs !== "number" || !Number.isFinite(incompleteMaxAgeMs) || incompleteMaxAgeMs < 0)) {
|
|
65
|
-
return {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
message: "mailbox.purge payload.incompleteMaxAgeMs must be a non-negative number when provided"
|
|
86
|
+
};
|
|
66
87
|
}
|
|
67
88
|
return { ok: true, value: { completedMaxAgeMs, incompleteMaxAgeMs } };
|
|
68
89
|
}
|
|
@@ -73,7 +94,10 @@ function validateBrainRiskPayload(payload) {
|
|
|
73
94
|
}
|
|
74
95
|
const level = payload["level"];
|
|
75
96
|
if (typeof level !== "string" || !BRAIN_RISK_VALUES.has(level)) {
|
|
76
|
-
return {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
message: "brain.risk payload.level must be one of off, low, medium, high, all"
|
|
100
|
+
};
|
|
77
101
|
}
|
|
78
102
|
return { ok: true, value: { level } };
|
|
79
103
|
}
|
|
@@ -99,7 +123,10 @@ function validateAutonomySwitchPayload(payload) {
|
|
|
99
123
|
}
|
|
100
124
|
function validatePlanTemplateUsePayload(payload) {
|
|
101
125
|
if (!isRecord(payload)) {
|
|
102
|
-
return {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
message: "plan.template_use payload must be an object with string template"
|
|
129
|
+
};
|
|
103
130
|
}
|
|
104
131
|
const template = payload["template"];
|
|
105
132
|
if (typeof template !== "string" || template.trim().length === 0) {
|
|
@@ -114,7 +141,15 @@ var ENHANCE_LANGUAGE_VALUES = /* @__PURE__ */ new Set(["original", "english"]);
|
|
|
114
141
|
var LOG_LEVEL_VALUES = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
115
142
|
var AUDIT_LEVEL_VALUES = /* @__PURE__ */ new Set(["minimal", "standard", "full"]);
|
|
116
143
|
var REASONING_MODE_VALUES = /* @__PURE__ */ new Set(["auto", "on", "off"]);
|
|
117
|
-
var REASONING_EFFORT_VALUES = /* @__PURE__ */ new Set([
|
|
144
|
+
var REASONING_EFFORT_VALUES = /* @__PURE__ */ new Set([
|
|
145
|
+
"none",
|
|
146
|
+
"minimal",
|
|
147
|
+
"low",
|
|
148
|
+
"medium",
|
|
149
|
+
"high",
|
|
150
|
+
"xhigh",
|
|
151
|
+
"max"
|
|
152
|
+
]);
|
|
118
153
|
var CACHE_TTL_VALUES = /* @__PURE__ */ new Set(["default", "5m", "1h"]);
|
|
119
154
|
var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
120
155
|
"yolo",
|
|
@@ -135,8 +170,10 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
135
170
|
"tgDelegate",
|
|
136
171
|
"reasoningPreserve",
|
|
137
172
|
"hqEnabled",
|
|
138
|
-
"hqRawContent"
|
|
173
|
+
"hqRawContent",
|
|
174
|
+
"fallbackAuto"
|
|
139
175
|
]);
|
|
176
|
+
var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels"]);
|
|
140
177
|
var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
141
178
|
"autonomyDelayMs",
|
|
142
179
|
"autoProceedMaxIterations",
|
|
@@ -168,6 +205,9 @@ function validatePreferenceValue(key, value) {
|
|
|
168
205
|
if (STRING_PREF_KEYS.has(key)) {
|
|
169
206
|
return typeof value === "string" ? null : `prefs.update payload.${key} must be a string`;
|
|
170
207
|
}
|
|
208
|
+
if (STRING_ARRAY_PREF_KEYS.has(key)) {
|
|
209
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string") ? null : `prefs.update payload.${key} must be an array of strings`;
|
|
210
|
+
}
|
|
171
211
|
const allowed = ENUM_PREF_KEYS[key];
|
|
172
212
|
if (allowed) {
|
|
173
213
|
return typeof value === "string" && allowed.has(value) ? null : `prefs.update payload.${key} must be one of: ${Array.from(allowed).join(", ")}`;
|
|
@@ -288,16 +328,25 @@ function validateContextModeCreatePayload(payload) {
|
|
|
288
328
|
return { ok: false, message: "context.mode.create payload.description must be a string" };
|
|
289
329
|
}
|
|
290
330
|
if (!isRecord(thresholds)) {
|
|
291
|
-
return {
|
|
331
|
+
return {
|
|
332
|
+
ok: false,
|
|
333
|
+
message: "context.mode.create payload.thresholds must be an object with warn/soft/hard numbers"
|
|
334
|
+
};
|
|
292
335
|
}
|
|
293
336
|
if (!isFiniteNumber(thresholds["warn"]) || !isFiniteNumber(thresholds["soft"]) || !isFiniteNumber(thresholds["hard"])) {
|
|
294
|
-
return {
|
|
337
|
+
return {
|
|
338
|
+
ok: false,
|
|
339
|
+
message: "context.mode.create payload.thresholds.warn/soft/hard must be finite numbers"
|
|
340
|
+
};
|
|
295
341
|
}
|
|
296
342
|
if (!isFiniteNumber(preserveK)) {
|
|
297
343
|
return { ok: false, message: "context.mode.create payload.preserveK must be a finite number" };
|
|
298
344
|
}
|
|
299
345
|
if (!isFiniteNumber(eliseThreshold)) {
|
|
300
|
-
return {
|
|
346
|
+
return {
|
|
347
|
+
ok: false,
|
|
348
|
+
message: "context.mode.create payload.eliseThreshold must be a finite number"
|
|
349
|
+
};
|
|
301
350
|
}
|
|
302
351
|
return {
|
|
303
352
|
ok: true,
|
|
@@ -321,22 +370,34 @@ function validateContextModeUpdatePayload(payload) {
|
|
|
321
370
|
}
|
|
322
371
|
const name2 = payload["name"];
|
|
323
372
|
if (name2 !== void 0 && typeof name2 !== "string") {
|
|
324
|
-
return {
|
|
373
|
+
return {
|
|
374
|
+
ok: false,
|
|
375
|
+
message: "context.mode.update payload.name must be a string when provided"
|
|
376
|
+
};
|
|
325
377
|
}
|
|
326
378
|
const description = payload["description"];
|
|
327
379
|
if (description !== void 0 && typeof description !== "string") {
|
|
328
|
-
return {
|
|
380
|
+
return {
|
|
381
|
+
ok: false,
|
|
382
|
+
message: "context.mode.update payload.description must be a string when provided"
|
|
383
|
+
};
|
|
329
384
|
}
|
|
330
385
|
const thresholds = payload["thresholds"];
|
|
331
386
|
let validatedThresholds;
|
|
332
387
|
if (thresholds !== void 0) {
|
|
333
388
|
if (!isRecord(thresholds)) {
|
|
334
|
-
return {
|
|
389
|
+
return {
|
|
390
|
+
ok: false,
|
|
391
|
+
message: "context.mode.update payload.thresholds must be an object when provided"
|
|
392
|
+
};
|
|
335
393
|
}
|
|
336
394
|
for (const key of ["warn", "soft", "hard"]) {
|
|
337
395
|
const val = thresholds[key];
|
|
338
396
|
if (val !== void 0 && !isFiniteNumber(val)) {
|
|
339
|
-
return {
|
|
397
|
+
return {
|
|
398
|
+
ok: false,
|
|
399
|
+
message: `context.mode.update payload.thresholds.${key} must be a finite number when provided`
|
|
400
|
+
};
|
|
340
401
|
}
|
|
341
402
|
}
|
|
342
403
|
validatedThresholds = {
|
|
@@ -347,11 +408,17 @@ function validateContextModeUpdatePayload(payload) {
|
|
|
347
408
|
}
|
|
348
409
|
const preserveK = payload["preserveK"];
|
|
349
410
|
if (preserveK !== void 0 && !isFiniteNumber(preserveK)) {
|
|
350
|
-
return {
|
|
411
|
+
return {
|
|
412
|
+
ok: false,
|
|
413
|
+
message: "context.mode.update payload.preserveK must be a finite number when provided"
|
|
414
|
+
};
|
|
351
415
|
}
|
|
352
416
|
const eliseThreshold = payload["eliseThreshold"];
|
|
353
417
|
if (eliseThreshold !== void 0 && !isFiniteNumber(eliseThreshold)) {
|
|
354
|
-
return {
|
|
418
|
+
return {
|
|
419
|
+
ok: false,
|
|
420
|
+
message: "context.mode.update payload.eliseThreshold must be a finite number when provided"
|
|
421
|
+
};
|
|
355
422
|
}
|
|
356
423
|
return {
|
|
357
424
|
ok: true,
|
|
@@ -369,28 +436,31 @@ function validateShellOpenPayload(payload) {
|
|
|
369
436
|
if (!isRecord(payload)) {
|
|
370
437
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
371
438
|
}
|
|
372
|
-
const
|
|
373
|
-
if (typeof
|
|
439
|
+
const path17 = payload["path"];
|
|
440
|
+
if (typeof path17 !== "string" || path17.trim().length === 0) {
|
|
374
441
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
375
442
|
}
|
|
376
443
|
const target = payload["target"];
|
|
377
444
|
if (target !== void 0 && target !== "file" && target !== "terminal") {
|
|
378
|
-
return {
|
|
445
|
+
return {
|
|
446
|
+
ok: false,
|
|
447
|
+
message: 'shell.open payload.target must be "file" or "terminal" when provided'
|
|
448
|
+
};
|
|
379
449
|
}
|
|
380
|
-
return { ok: true, value: { path:
|
|
450
|
+
return { ok: true, value: { path: path17, target } };
|
|
381
451
|
}
|
|
382
452
|
function validateGitDiffPayload(payload) {
|
|
383
453
|
if (!isRecord(payload)) {
|
|
384
454
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
385
455
|
}
|
|
386
|
-
const
|
|
387
|
-
if (
|
|
456
|
+
const path17 = payload["path"];
|
|
457
|
+
if (path17 === void 0 || path17 === null) {
|
|
388
458
|
return { ok: true, value: { path: "" } };
|
|
389
459
|
}
|
|
390
|
-
if (typeof
|
|
460
|
+
if (typeof path17 !== "string") {
|
|
391
461
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
392
462
|
}
|
|
393
|
-
return { ok: true, value: { path:
|
|
463
|
+
return { ok: true, value: { path: path17 } };
|
|
394
464
|
}
|
|
395
465
|
function validateProjectsAddPayload(payload) {
|
|
396
466
|
if (!isRecord(payload)) {
|
|
@@ -570,7 +640,7 @@ async function handlePlanItemUpdate(ctx, ws, payload) {
|
|
|
570
640
|
return;
|
|
571
641
|
}
|
|
572
642
|
try {
|
|
573
|
-
const {
|
|
643
|
+
const { mutatePlan, setPlanItemStatus } = await import("@wrongstack/core");
|
|
574
644
|
let changed = false;
|
|
575
645
|
const plan = await mutatePlan(planPath, sessionId, async (p) => {
|
|
576
646
|
const before = p.updatedAt;
|
|
@@ -650,7 +720,7 @@ import {
|
|
|
650
720
|
createTieredBrainArbiter
|
|
651
721
|
} from "@wrongstack/core";
|
|
652
722
|
import * as fs13 from "fs/promises";
|
|
653
|
-
import * as
|
|
723
|
+
import * as path16 from "path";
|
|
654
724
|
|
|
655
725
|
// src/server/http-server.ts
|
|
656
726
|
import * as fs from "fs/promises";
|
|
@@ -2813,6 +2883,7 @@ import {
|
|
|
2813
2883
|
DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
|
|
2814
2884
|
DEFAULT_SESSION_PRUNE_DAYS,
|
|
2815
2885
|
DEFAULT_TOOLS_CONFIG,
|
|
2886
|
+
applyToolDescriptionModes,
|
|
2816
2887
|
resolveContextWindowPolicy as resolveContextWindowPolicy2,
|
|
2817
2888
|
enhanceUserPrompt,
|
|
2818
2889
|
gatedEnhancerReasoning,
|
|
@@ -2857,7 +2928,8 @@ function createDefaultContainer(opts) {
|
|
|
2857
2928
|
() => new DefaultErrorHandler(
|
|
2858
2929
|
buildRecoveryStrategies({
|
|
2859
2930
|
compactor: container.resolve(TOKENS.Compactor),
|
|
2860
|
-
modelsRegistry
|
|
2931
|
+
modelsRegistry,
|
|
2932
|
+
getConfig: () => configStore.get()
|
|
2861
2933
|
})
|
|
2862
2934
|
)
|
|
2863
2935
|
);
|
|
@@ -2944,6 +3016,7 @@ function patchConfig(config, updates) {
|
|
|
2944
3016
|
import { spawnSync } from "child_process";
|
|
2945
3017
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
2946
3018
|
import {
|
|
3019
|
+
assignNickname,
|
|
2947
3020
|
AutoPhasePlanner,
|
|
2948
3021
|
PhaseGraphBuilder,
|
|
2949
3022
|
PhaseOrchestrator,
|
|
@@ -2981,6 +3054,8 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
2981
3054
|
abort = null;
|
|
2982
3055
|
/** Optional per-phase git-worktree isolation (lazily created at start). */
|
|
2983
3056
|
worktrees = null;
|
|
3057
|
+
/** Per-run worker identities so the board can show "who is on what". */
|
|
3058
|
+
usedNicknames = /* @__PURE__ */ new Set();
|
|
2984
3059
|
addClient(ws) {
|
|
2985
3060
|
const client = { ws, id: crypto.randomUUID() };
|
|
2986
3061
|
this.clients.add(client);
|
|
@@ -3023,6 +3098,29 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
3023
3098
|
await this.handleTaskStatusChange(taskId, status);
|
|
3024
3099
|
break;
|
|
3025
3100
|
}
|
|
3101
|
+
case "autophase.moveTask": {
|
|
3102
|
+
const { taskId, toPhaseId } = msg.payload;
|
|
3103
|
+
if (this.orchestrator?.moveTask(taskId, toPhaseId)) this.afterBoardMutation();
|
|
3104
|
+
break;
|
|
3105
|
+
}
|
|
3106
|
+
case "autophase.assignTask": {
|
|
3107
|
+
const { taskId, agentId, agentName } = msg.payload;
|
|
3108
|
+
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName)) this.afterBoardMutation();
|
|
3109
|
+
break;
|
|
3110
|
+
}
|
|
3111
|
+
case "autophase.addTask": {
|
|
3112
|
+
const { phaseId, title, description, type, priority } = msg.payload;
|
|
3113
|
+
if (title?.trim() && this.orchestrator?.addTask(phaseId, { title: title.trim(), description, type, priority })) {
|
|
3114
|
+
this.afterBoardMutation();
|
|
3115
|
+
}
|
|
3116
|
+
break;
|
|
3117
|
+
}
|
|
3118
|
+
case "autophase.retryTask":
|
|
3119
|
+
case "autophase.runTask": {
|
|
3120
|
+
const { taskId } = msg.payload;
|
|
3121
|
+
if (this.orchestrator?.requeueTask(taskId)) this.afterBoardMutation();
|
|
3122
|
+
break;
|
|
3123
|
+
}
|
|
3026
3124
|
case "autophase.toggleAutonomous": {
|
|
3027
3125
|
const autonomous = msg.payload?.autonomous ?? !this.graph?.autonomous;
|
|
3028
3126
|
if (this.graph) {
|
|
@@ -3150,6 +3248,13 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
3150
3248
|
return this.defaultPhases();
|
|
3151
3249
|
}
|
|
3152
3250
|
async executeTaskWithAgent(task, phaseId, env) {
|
|
3251
|
+
if (!task.assignee) {
|
|
3252
|
+
const nick = assignNickname("executor", this.usedNicknames);
|
|
3253
|
+
this.usedNicknames.add(nick.key);
|
|
3254
|
+
task.assignee = nick.display.replace(/\s*\([^)]*\)\s*$/, "");
|
|
3255
|
+
task.updatedAt = Date.now();
|
|
3256
|
+
this.broadcastState();
|
|
3257
|
+
}
|
|
3153
3258
|
const prompt = `Execute task: ${task.title}
|
|
3154
3259
|
|
|
3155
3260
|
Description: ${task.description}
|
|
@@ -3165,6 +3270,11 @@ Type: ${task.type}`;
|
|
|
3165
3270
|
this.context.cwd = prevCwd;
|
|
3166
3271
|
}
|
|
3167
3272
|
}
|
|
3273
|
+
/** Persist + broadcast after an interactive board mutation. */
|
|
3274
|
+
afterBoardMutation() {
|
|
3275
|
+
if (this.graph) void this.store.save(this.graph);
|
|
3276
|
+
this.broadcastState();
|
|
3277
|
+
}
|
|
3168
3278
|
async handleTaskStatusChange(taskId, status) {
|
|
3169
3279
|
if (!this.graph) return;
|
|
3170
3280
|
for (const phase of this.graph.phases.values()) {
|
|
@@ -3208,23 +3318,7 @@ Type: ${task.type}`;
|
|
|
3208
3318
|
(sum, p) => sum + Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "completed").length,
|
|
3209
3319
|
0
|
|
3210
3320
|
);
|
|
3211
|
-
const
|
|
3212
|
-
id: p.id,
|
|
3213
|
-
name: p.name,
|
|
3214
|
-
description: p.description,
|
|
3215
|
-
status: p.status,
|
|
3216
|
-
priority: p.priority,
|
|
3217
|
-
estimateHours: p.estimateHours,
|
|
3218
|
-
actualDurationMs: p.actualDurationMs,
|
|
3219
|
-
startedAt: p.startedAt,
|
|
3220
|
-
completedAt: p.completedAt,
|
|
3221
|
-
progressPercent: p.taskGraph.nodes.size > 0 ? Math.round(Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "completed").length / p.taskGraph.nodes.size * 100) : 0,
|
|
3222
|
-
taskCount: p.taskGraph.nodes.size,
|
|
3223
|
-
completedTasks: Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "completed").length,
|
|
3224
|
-
assignedAgents: p.assignedAgents,
|
|
3225
|
-
isActive: p.id === currentActiveId
|
|
3226
|
-
}));
|
|
3227
|
-
const taskItems = activePhase ? Array.from(activePhase.taskGraph.nodes.values()).map((t) => ({
|
|
3321
|
+
const mapTask = (t) => ({
|
|
3228
3322
|
id: t.id,
|
|
3229
3323
|
title: t.title,
|
|
3230
3324
|
description: t.description,
|
|
@@ -3237,7 +3331,31 @@ Type: ${task.type}`;
|
|
|
3237
3331
|
tags: t.tags || [],
|
|
3238
3332
|
startedAt: t.startedAt,
|
|
3239
3333
|
completedAt: t.completedAt
|
|
3240
|
-
})
|
|
3334
|
+
});
|
|
3335
|
+
const phaseItems = phases.map((p) => {
|
|
3336
|
+
const nodes = Array.from(p.taskGraph.nodes.values());
|
|
3337
|
+
const done = nodes.filter((t) => t.status === "completed").length;
|
|
3338
|
+
return {
|
|
3339
|
+
id: p.id,
|
|
3340
|
+
name: p.name,
|
|
3341
|
+
description: p.description,
|
|
3342
|
+
status: p.status,
|
|
3343
|
+
priority: p.priority,
|
|
3344
|
+
estimateHours: p.estimateHours,
|
|
3345
|
+
actualDurationMs: p.actualDurationMs,
|
|
3346
|
+
startedAt: p.startedAt,
|
|
3347
|
+
completedAt: p.completedAt,
|
|
3348
|
+
progressPercent: nodes.length > 0 ? Math.round(done / nodes.length * 100) : 0,
|
|
3349
|
+
taskCount: nodes.length,
|
|
3350
|
+
completedTasks: done,
|
|
3351
|
+
assignedAgents: p.assignedAgents,
|
|
3352
|
+
isActive: p.id === currentActiveId,
|
|
3353
|
+
// Every phase carries its full task list so the board can render each
|
|
3354
|
+
// phase as a column (not just the selected one).
|
|
3355
|
+
tasks: nodes.map(mapTask)
|
|
3356
|
+
};
|
|
3357
|
+
});
|
|
3358
|
+
const taskItems = activePhase ? Array.from(activePhase.taskGraph.nodes.values()).map(mapTask) : [];
|
|
3241
3359
|
const completedPhases = phases.filter((p) => p.status === "completed").length;
|
|
3242
3360
|
return {
|
|
3243
3361
|
title: this.graph.title,
|
|
@@ -3270,6 +3388,513 @@ Type: ${task.type}`;
|
|
|
3270
3388
|
}
|
|
3271
3389
|
};
|
|
3272
3390
|
|
|
3391
|
+
// src/server/specs-ws-handler.ts
|
|
3392
|
+
import {
|
|
3393
|
+
computeTaskProgress,
|
|
3394
|
+
SpecStore,
|
|
3395
|
+
TaskGraphStore
|
|
3396
|
+
} from "@wrongstack/core";
|
|
3397
|
+
var SpecsWebSocketHandler = class {
|
|
3398
|
+
specStore;
|
|
3399
|
+
graphStore;
|
|
3400
|
+
clients = /* @__PURE__ */ new Set();
|
|
3401
|
+
constructor(specsDir, taskGraphsDir) {
|
|
3402
|
+
this.specStore = new SpecStore({ baseDir: specsDir });
|
|
3403
|
+
this.graphStore = new TaskGraphStore({ baseDir: taskGraphsDir });
|
|
3404
|
+
}
|
|
3405
|
+
addClient(ws) {
|
|
3406
|
+
const client = { ws, id: crypto.randomUUID() };
|
|
3407
|
+
this.clients.add(client);
|
|
3408
|
+
ws.on("close", () => this.clients.delete(client));
|
|
3409
|
+
ws.on("error", () => this.clients.delete(client));
|
|
3410
|
+
void this.sendList(client);
|
|
3411
|
+
}
|
|
3412
|
+
async handleMessage(msg) {
|
|
3413
|
+
switch (msg.type) {
|
|
3414
|
+
case "specs.list":
|
|
3415
|
+
await this.broadcastList();
|
|
3416
|
+
break;
|
|
3417
|
+
case "specs.get": {
|
|
3418
|
+
const specId = msg.payload?.specId;
|
|
3419
|
+
if (specId) await this.broadcastDetail(specId);
|
|
3420
|
+
break;
|
|
3421
|
+
}
|
|
3422
|
+
case "specs.taskStatus": {
|
|
3423
|
+
const { graphId, taskId, status } = msg.payload;
|
|
3424
|
+
await this.updateTaskStatus(graphId, taskId, status);
|
|
3425
|
+
break;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
// ── List ──────────────────────────────────────────────────────────────────
|
|
3430
|
+
async buildList() {
|
|
3431
|
+
const [specs, graphs] = await Promise.all([this.specStore.list(), this.graphStore.list()]);
|
|
3432
|
+
return specs.map((s, i) => {
|
|
3433
|
+
const graph = graphs.find((g) => g.specId === s.id);
|
|
3434
|
+
return {
|
|
3435
|
+
id: s.id,
|
|
3436
|
+
// FORGE-style display id (spec-001…). The real UUID stays in `id`.
|
|
3437
|
+
displayId: `spec-${String(i + 1).padStart(3, "0")}`,
|
|
3438
|
+
title: s.title,
|
|
3439
|
+
status: s.status,
|
|
3440
|
+
graphId: graph?.id,
|
|
3441
|
+
total: graph?.nodeCount ?? 0,
|
|
3442
|
+
completed: graph?.completedCount ?? 0
|
|
3443
|
+
};
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
async broadcastList() {
|
|
3447
|
+
this.broadcast({ type: "specs.list", payload: { specs: await this.buildList() } });
|
|
3448
|
+
}
|
|
3449
|
+
async sendList(client) {
|
|
3450
|
+
this.send(client, { type: "specs.list", payload: { specs: await this.buildList() } });
|
|
3451
|
+
}
|
|
3452
|
+
// ── Detail (dependency board) ───────────────────────────────────────────────
|
|
3453
|
+
async broadcastDetail(specId) {
|
|
3454
|
+
const spec = await this.specStore.load(specId);
|
|
3455
|
+
const graph = await this.findGraphForSpec(specId);
|
|
3456
|
+
if (!spec || !graph) {
|
|
3457
|
+
this.broadcast({ type: "specs.detail", payload: { specId, columns: [], notFound: true } });
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
this.broadcast({ type: "specs.detail", payload: this.buildDetail(spec, graph) });
|
|
3461
|
+
}
|
|
3462
|
+
async findGraphForSpec(specId) {
|
|
3463
|
+
const entry = (await this.graphStore.list()).find((g) => g.specId === specId);
|
|
3464
|
+
if (!entry) return null;
|
|
3465
|
+
return this.graphStore.load(entry.id);
|
|
3466
|
+
}
|
|
3467
|
+
buildDetail(spec, graph) {
|
|
3468
|
+
const nodes = Array.from(graph.nodes.values()).sort((a, b) => a.createdAt - b.createdAt);
|
|
3469
|
+
const shortId = /* @__PURE__ */ new Map();
|
|
3470
|
+
nodes.forEach((n, i) => {
|
|
3471
|
+
shortId.set(n.id, `t${String(i + 1).padStart(2, "0")}`);
|
|
3472
|
+
});
|
|
3473
|
+
const blockers = /* @__PURE__ */ new Map();
|
|
3474
|
+
for (const n of nodes) blockers.set(n.id, []);
|
|
3475
|
+
for (const e of graph.edges) {
|
|
3476
|
+
if (e.type === "depends_on") blockers.get(e.to)?.push(e.from);
|
|
3477
|
+
}
|
|
3478
|
+
const statusOf = (id) => graph.nodes.get(id)?.status;
|
|
3479
|
+
const depthCache = /* @__PURE__ */ new Map();
|
|
3480
|
+
const depthOf = (id, seen = /* @__PURE__ */ new Set()) => {
|
|
3481
|
+
const cached = depthCache.get(id);
|
|
3482
|
+
if (cached !== void 0) return cached;
|
|
3483
|
+
if (seen.has(id)) return 0;
|
|
3484
|
+
seen.add(id);
|
|
3485
|
+
const deps2 = blockers.get(id) ?? [];
|
|
3486
|
+
const d = deps2.length === 0 ? 0 : 1 + Math.max(...deps2.map((b) => depthOf(b, seen)));
|
|
3487
|
+
depthCache.set(id, d);
|
|
3488
|
+
return d;
|
|
3489
|
+
};
|
|
3490
|
+
const toBoardTask = (n) => {
|
|
3491
|
+
const deps2 = blockers.get(n.id) ?? [];
|
|
3492
|
+
const allDepsDone = deps2.every((b) => statusOf(b) === "completed");
|
|
3493
|
+
const displayStatus = n.status === "pending" && deps2.length > 0 && allDepsDone ? "queued" : n.status;
|
|
3494
|
+
return {
|
|
3495
|
+
id: n.id,
|
|
3496
|
+
shortId: shortId.get(n.id) ?? n.id.slice(0, 6),
|
|
3497
|
+
title: n.title,
|
|
3498
|
+
description: n.description,
|
|
3499
|
+
priority: n.priority,
|
|
3500
|
+
type: n.type,
|
|
3501
|
+
status: n.status,
|
|
3502
|
+
displayStatus,
|
|
3503
|
+
deps: deps2.map((b) => shortId.get(b) ?? b.slice(0, 6))
|
|
3504
|
+
};
|
|
3505
|
+
};
|
|
3506
|
+
const byDepth = /* @__PURE__ */ new Map();
|
|
3507
|
+
for (const n of nodes) {
|
|
3508
|
+
const d = depthOf(n.id);
|
|
3509
|
+
if (!byDepth.has(d)) byDepth.set(d, []);
|
|
3510
|
+
byDepth.get(d)?.push(toBoardTask(n));
|
|
3511
|
+
}
|
|
3512
|
+
const columns = [...byDepth.keys()].sort((a, b) => a - b).map((d) => ({ label: d === 0 ? "Start" : `Phase ${d}`, tasks: byDepth.get(d) ?? [] }));
|
|
3513
|
+
const progress = computeTaskProgress(graph);
|
|
3514
|
+
return {
|
|
3515
|
+
specId: spec.id,
|
|
3516
|
+
graphId: graph.id,
|
|
3517
|
+
title: spec.title,
|
|
3518
|
+
overview: spec.overview,
|
|
3519
|
+
status: spec.status,
|
|
3520
|
+
total: progress.total,
|
|
3521
|
+
completed: progress.completed,
|
|
3522
|
+
running: progress.inProgress,
|
|
3523
|
+
pending: progress.pending,
|
|
3524
|
+
columns
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
async updateTaskStatus(graphId, taskId, status) {
|
|
3528
|
+
const graph = await this.graphStore.load(graphId);
|
|
3529
|
+
const node = graph?.nodes.get(taskId);
|
|
3530
|
+
if (!graph || !node) return;
|
|
3531
|
+
node.status = status;
|
|
3532
|
+
node.updatedAt = Date.now();
|
|
3533
|
+
graph.updatedAt = Date.now();
|
|
3534
|
+
await this.graphStore.save(graph);
|
|
3535
|
+
this.broadcastDetail(graph.specId).catch(() => {
|
|
3536
|
+
});
|
|
3537
|
+
await this.broadcastList();
|
|
3538
|
+
}
|
|
3539
|
+
// ── Transport ───────────────────────────────────────────────────────────────
|
|
3540
|
+
broadcast(msg) {
|
|
3541
|
+
const data = JSON.stringify(msg);
|
|
3542
|
+
for (const client of this.clients) {
|
|
3543
|
+
if (client.ws.readyState === 1) client.ws.send(data);
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
send(client, msg) {
|
|
3547
|
+
if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
|
|
3548
|
+
}
|
|
3549
|
+
};
|
|
3550
|
+
|
|
3551
|
+
// src/server/sdd-board-ws-handler.ts
|
|
3552
|
+
import { SddBoardStore } from "@wrongstack/core";
|
|
3553
|
+
var CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
3554
|
+
"pause",
|
|
3555
|
+
"resume",
|
|
3556
|
+
"stop",
|
|
3557
|
+
"retry",
|
|
3558
|
+
"retry_all_failed",
|
|
3559
|
+
"reassign",
|
|
3560
|
+
// Per-task model / fallback / verification assignment + stop/delete (drained by start-sdd-run).
|
|
3561
|
+
"set_task_model",
|
|
3562
|
+
"set_task_fallbacks",
|
|
3563
|
+
"set_task_verification",
|
|
3564
|
+
"cancel_task",
|
|
3565
|
+
"delete_task",
|
|
3566
|
+
"split_task",
|
|
3567
|
+
// Lifecycle (pair with a prior `stop`): sweep worktrees / revert merged commits.
|
|
3568
|
+
"cleanup_worktrees",
|
|
3569
|
+
"rollback"
|
|
3570
|
+
]);
|
|
3571
|
+
var SddBoardWebSocketHandler = class {
|
|
3572
|
+
store;
|
|
3573
|
+
clients = /* @__PURE__ */ new Set();
|
|
3574
|
+
latest = null;
|
|
3575
|
+
poll = null;
|
|
3576
|
+
unsub = null;
|
|
3577
|
+
constructor(boardsDir, events) {
|
|
3578
|
+
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
3579
|
+
if (events) {
|
|
3580
|
+
const handler = (e) => {
|
|
3581
|
+
this.latest = e.snapshot;
|
|
3582
|
+
this.broadcast({ type: "sdd.board.snapshot", payload: e.snapshot });
|
|
3583
|
+
};
|
|
3584
|
+
this.unsub = events.on("sdd.board.snapshot", handler);
|
|
3585
|
+
} else {
|
|
3586
|
+
this.poll = setInterval(() => void this.pollLatest(), 1e3);
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
addClient(ws) {
|
|
3590
|
+
const client = { ws, id: crypto.randomUUID() };
|
|
3591
|
+
this.clients.add(client);
|
|
3592
|
+
ws.on("close", () => this.clients.delete(client));
|
|
3593
|
+
ws.on("error", () => this.clients.delete(client));
|
|
3594
|
+
void this.sendCurrent(client);
|
|
3595
|
+
}
|
|
3596
|
+
async handleMessage(msg) {
|
|
3597
|
+
if (msg.type === "sdd.board.get") {
|
|
3598
|
+
await this.broadcastCurrent();
|
|
3599
|
+
return;
|
|
3600
|
+
}
|
|
3601
|
+
if (msg.type === "sdd.board.list") {
|
|
3602
|
+
const boards = await this.store.list();
|
|
3603
|
+
this.broadcast({ type: "sdd.board.list", payload: { boards } });
|
|
3604
|
+
return;
|
|
3605
|
+
}
|
|
3606
|
+
const action = msg.type.replace(/^sdd\.board\./, "");
|
|
3607
|
+
if (CONTROL_TYPES.has(action)) {
|
|
3608
|
+
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.store.list())[0]?.runId;
|
|
3609
|
+
if (runId) {
|
|
3610
|
+
await this.store.appendControl(runId, {
|
|
3611
|
+
ts: Date.now(),
|
|
3612
|
+
type: action,
|
|
3613
|
+
payload: msg.payload
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
dispose() {
|
|
3619
|
+
if (this.poll) clearInterval(this.poll);
|
|
3620
|
+
this.unsub?.();
|
|
3621
|
+
this.poll = null;
|
|
3622
|
+
this.unsub = null;
|
|
3623
|
+
}
|
|
3624
|
+
// ── internal ────────────────────────────────────────────────────────────
|
|
3625
|
+
async pollLatest() {
|
|
3626
|
+
const entry = (await this.store.list())[0];
|
|
3627
|
+
if (!entry) return;
|
|
3628
|
+
if (this.latest && this.latest.updatedAt >= entry.updatedAt && this.latest.runId === entry.runId) {
|
|
3629
|
+
return;
|
|
3630
|
+
}
|
|
3631
|
+
const snap = await this.store.load(entry.runId);
|
|
3632
|
+
if (snap) {
|
|
3633
|
+
this.latest = snap;
|
|
3634
|
+
this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
async sendCurrent(client) {
|
|
3638
|
+
const snap = this.latest ?? await this.loadLatestFromDisk();
|
|
3639
|
+
if (snap) this.send(client, { type: "sdd.board.snapshot", payload: snap });
|
|
3640
|
+
}
|
|
3641
|
+
async broadcastCurrent() {
|
|
3642
|
+
const snap = this.latest ?? await this.loadLatestFromDisk();
|
|
3643
|
+
if (snap) this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
3644
|
+
}
|
|
3645
|
+
async loadLatestFromDisk() {
|
|
3646
|
+
const entry = (await this.store.list())[0];
|
|
3647
|
+
return entry ? this.store.load(entry.runId) : null;
|
|
3648
|
+
}
|
|
3649
|
+
broadcast(msg) {
|
|
3650
|
+
const data = JSON.stringify(msg);
|
|
3651
|
+
for (const client of this.clients) {
|
|
3652
|
+
if (client.ws.readyState === 1) client.ws.send(data);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
send(client, msg) {
|
|
3656
|
+
if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
|
|
3657
|
+
}
|
|
3658
|
+
};
|
|
3659
|
+
|
|
3660
|
+
// src/server/sdd-wizard-ws-handler.ts
|
|
3661
|
+
var SddWizardWebSocketHandler = class {
|
|
3662
|
+
constructor(deps2) {
|
|
3663
|
+
this.deps = deps2;
|
|
3664
|
+
}
|
|
3665
|
+
deps;
|
|
3666
|
+
clients = /* @__PURE__ */ new Set();
|
|
3667
|
+
driver = null;
|
|
3668
|
+
/** The agent's most recent question — paired with the next user answer. */
|
|
3669
|
+
lastAgentText = "";
|
|
3670
|
+
/** Guards against overlapping interview turns (one in flight at a time). */
|
|
3671
|
+
busy = false;
|
|
3672
|
+
addClient(ws) {
|
|
3673
|
+
const client = { ws, id: crypto.randomUUID() };
|
|
3674
|
+
this.clients.add(client);
|
|
3675
|
+
ws.on("close", () => this.clients.delete(client));
|
|
3676
|
+
ws.on("error", () => this.clients.delete(client));
|
|
3677
|
+
if (this.driver) this.send(client, this.snapshotMsg());
|
|
3678
|
+
}
|
|
3679
|
+
async handleMessage(msg) {
|
|
3680
|
+
try {
|
|
3681
|
+
switch (msg.type) {
|
|
3682
|
+
case "sdd.spec.start":
|
|
3683
|
+
await this.onStart(String(msg.payload?.goal ?? "").trim());
|
|
3684
|
+
break;
|
|
3685
|
+
case "sdd.spec.message":
|
|
3686
|
+
await this.onMessage(String(msg.payload?.text ?? ""));
|
|
3687
|
+
break;
|
|
3688
|
+
case "sdd.spec.approve":
|
|
3689
|
+
await this.onApprove();
|
|
3690
|
+
break;
|
|
3691
|
+
case "sdd.spec.get":
|
|
3692
|
+
if (this.driver) this.broadcast(this.snapshotMsg());
|
|
3693
|
+
break;
|
|
3694
|
+
case "sdd.run.start":
|
|
3695
|
+
await this.onRunStart({
|
|
3696
|
+
parallelSlots: msg.payload?.parallelSlots,
|
|
3697
|
+
defaultModel: msg.payload?.model,
|
|
3698
|
+
defaultProvider: msg.payload?.provider,
|
|
3699
|
+
fallbackModels: Array.isArray(msg.payload?.fallbackModels) ? msg.payload?.fallbackModels : void 0
|
|
3700
|
+
});
|
|
3701
|
+
break;
|
|
3702
|
+
}
|
|
3703
|
+
} catch (err) {
|
|
3704
|
+
this.busy = false;
|
|
3705
|
+
this.broadcast({
|
|
3706
|
+
type: "sdd.spec.error",
|
|
3707
|
+
payload: { message: err instanceof Error ? err.message : String(err) }
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
// ── message handlers ──────────────────────────────────────────────────────
|
|
3712
|
+
async onStart(goal) {
|
|
3713
|
+
if (!goal) {
|
|
3714
|
+
this.broadcast({ type: "sdd.spec.error", payload: { message: "A goal is required." } });
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
if (this.busy) return;
|
|
3718
|
+
this.driver = this.deps.makeDriver();
|
|
3719
|
+
const prompt = this.driver.start(goal);
|
|
3720
|
+
await this.runTurn(prompt);
|
|
3721
|
+
}
|
|
3722
|
+
async onMessage(text) {
|
|
3723
|
+
if (!this.driver || this.busy) return;
|
|
3724
|
+
if (this.driver.phase() === "questioning" && this.lastAgentText) {
|
|
3725
|
+
this.driver.submitAnswer(this.lastAgentText, text);
|
|
3726
|
+
} else {
|
|
3727
|
+
this.driver.submitAnswer(this.lastAgentText || "(feedback)", text);
|
|
3728
|
+
}
|
|
3729
|
+
await this.runTurn(this.driver.currentPrompt());
|
|
3730
|
+
}
|
|
3731
|
+
async onApprove() {
|
|
3732
|
+
if (!this.driver || this.busy) return;
|
|
3733
|
+
const { phase, prompt } = await this.driver.approve();
|
|
3734
|
+
if (phase === "executing") {
|
|
3735
|
+
this.broadcast(this.snapshotMsg());
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
await this.runTurn(prompt);
|
|
3739
|
+
}
|
|
3740
|
+
async onRunStart(opts) {
|
|
3741
|
+
if (!this.driver) {
|
|
3742
|
+
this.broadcast({ type: "sdd.spec.error", payload: { message: "No active spec session." } });
|
|
3743
|
+
return;
|
|
3744
|
+
}
|
|
3745
|
+
const graph = await this.driver.ensureTaskGraph();
|
|
3746
|
+
if (!graph) {
|
|
3747
|
+
this.broadcast({
|
|
3748
|
+
type: "sdd.spec.error",
|
|
3749
|
+
payload: { message: "No spec yet \u2014 finish the interview before starting a run." }
|
|
3750
|
+
});
|
|
3751
|
+
return;
|
|
3752
|
+
}
|
|
3753
|
+
const { runId } = await this.deps.startRun(this.driver, opts);
|
|
3754
|
+
this.broadcast({ type: "sdd.run.started", payload: { runId } });
|
|
3755
|
+
}
|
|
3756
|
+
// ── internals ───────────────────────────────────────────────────────────
|
|
3757
|
+
/** Run one interview turn against the isolated agent, then ingest + broadcast. */
|
|
3758
|
+
async runTurn(prompt) {
|
|
3759
|
+
this.busy = true;
|
|
3760
|
+
this.broadcast(this.snapshotMsg());
|
|
3761
|
+
try {
|
|
3762
|
+
const text = await this.deps.runInterviewTurn(prompt);
|
|
3763
|
+
this.lastAgentText = text;
|
|
3764
|
+
if (this.driver) await this.driver.ingestAgentOutput(text);
|
|
3765
|
+
this.broadcast({ type: "sdd.spec.agent_text", payload: { text } });
|
|
3766
|
+
} finally {
|
|
3767
|
+
this.busy = false;
|
|
3768
|
+
this.broadcast(this.snapshotMsg());
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
snapshotMsg() {
|
|
3772
|
+
const snap = this.driver?.snapshot();
|
|
3773
|
+
return {
|
|
3774
|
+
type: "sdd.spec.snapshot",
|
|
3775
|
+
payload: { ...snap, busy: this.busy }
|
|
3776
|
+
};
|
|
3777
|
+
}
|
|
3778
|
+
broadcast(msg) {
|
|
3779
|
+
const data = JSON.stringify(msg);
|
|
3780
|
+
for (const client of this.clients) {
|
|
3781
|
+
if (client.ws.readyState === 1) client.ws.send(data);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
send(client, msg) {
|
|
3785
|
+
if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
|
|
3786
|
+
}
|
|
3787
|
+
};
|
|
3788
|
+
|
|
3789
|
+
// src/server/sdd-wizard-wiring.ts
|
|
3790
|
+
import * as path6 from "path";
|
|
3791
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3792
|
+
import {
|
|
3793
|
+
makeCommandVerifier,
|
|
3794
|
+
makeLlmSubtaskGenerator,
|
|
3795
|
+
SddBoardStore as SddBoardStore2,
|
|
3796
|
+
SddInterviewDriver,
|
|
3797
|
+
SddRunRegistry,
|
|
3798
|
+
SddSupervisor,
|
|
3799
|
+
SpecStore as SpecStore2,
|
|
3800
|
+
startSddRun,
|
|
3801
|
+
TaskGraphStore as TaskGraphStore2,
|
|
3802
|
+
WorktreeManager as WorktreeManager2
|
|
3803
|
+
} from "@wrongstack/core";
|
|
3804
|
+
function buildSddWizardDeps(opts) {
|
|
3805
|
+
const registry = new SddRunRegistry();
|
|
3806
|
+
let isolatedSeq = 0;
|
|
3807
|
+
const runIsolatedTurn = async (prompt, name2) => {
|
|
3808
|
+
const result = await opts.subagentFactory({
|
|
3809
|
+
id: `sdd-${name2.toLowerCase().replace(/\s+/g, "-")}-${isolatedSeq++}`,
|
|
3810
|
+
role: "executor",
|
|
3811
|
+
name: name2,
|
|
3812
|
+
disabledTools: ["delegate"],
|
|
3813
|
+
allowedCapabilities: ["fs.read", "net.outbound"]
|
|
3814
|
+
});
|
|
3815
|
+
try {
|
|
3816
|
+
const res = await result.agent.run([{ type: "text", text: prompt }]);
|
|
3817
|
+
return res.finalText ?? "";
|
|
3818
|
+
} finally {
|
|
3819
|
+
await result.dispose?.();
|
|
3820
|
+
}
|
|
3821
|
+
};
|
|
3822
|
+
return {
|
|
3823
|
+
makeDriver: () => new SddInterviewDriver({
|
|
3824
|
+
specStore: new SpecStore2({ baseDir: opts.paths.projectSpecs }),
|
|
3825
|
+
graphStore: new TaskGraphStore2({ baseDir: opts.paths.projectTaskGraphs }),
|
|
3826
|
+
sessionPath: path6.join(opts.paths.projectDir, "sdd-wizard-session.json")
|
|
3827
|
+
}),
|
|
3828
|
+
runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
|
|
3829
|
+
startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels }) => {
|
|
3830
|
+
const graph = driver.getGraph();
|
|
3831
|
+
const tracker = driver.getTracker();
|
|
3832
|
+
if (!graph || !tracker) {
|
|
3833
|
+
throw new Error("No task graph to run \u2014 finish the interview first.");
|
|
3834
|
+
}
|
|
3835
|
+
let worktrees;
|
|
3836
|
+
if (process.env["WRONGSTACK_SDD_WORKTREES"] !== "0") {
|
|
3837
|
+
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3838
|
+
cwd: opts.projectRoot,
|
|
3839
|
+
encoding: "utf8",
|
|
3840
|
+
windowsHide: true
|
|
3841
|
+
}).stdout?.trim() === "true";
|
|
3842
|
+
if (inGit) worktrees = new WorktreeManager2({ projectRoot: opts.projectRoot, events: opts.events });
|
|
3843
|
+
}
|
|
3844
|
+
const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
|
|
3845
|
+
const verifyTask = makeCommandVerifier();
|
|
3846
|
+
const superviseFailure = opts.brain ? new SddSupervisor({
|
|
3847
|
+
brain: opts.brain,
|
|
3848
|
+
// The run-level fallback chain (chosen in the wizard) doubles as the
|
|
3849
|
+
// supervisor's reassign options — a `reassign` verdict rotates the
|
|
3850
|
+
// worker model on retry. Empty/undefined → reassign option dropped.
|
|
3851
|
+
reassignModels: fallbackModels,
|
|
3852
|
+
// LLM auto-split: decompose a retry-exhausted task into smaller
|
|
3853
|
+
// sub-tasks on an isolated read-only turn. Heavily validated +
|
|
3854
|
+
// bounded; an empty result degrades the split into a retry.
|
|
3855
|
+
generateSubtasks: makeLlmSubtaskGenerator({
|
|
3856
|
+
run: (prompt) => runIsolatedTurn(prompt, "Task Splitter")
|
|
3857
|
+
}),
|
|
3858
|
+
// The standalone brain is a tiered policy→LLM arbiter with NO
|
|
3859
|
+
// human-escalation wrapper (see index.ts), so it never blocks on a
|
|
3860
|
+
// human prompt — an unresolved verdict degrades to a bounded retry.
|
|
3861
|
+
// Safe to let the LLM layer actually pick reassign/split.
|
|
3862
|
+
requestLlmVerdict: true
|
|
3863
|
+
}).superviseFailure : void 0;
|
|
3864
|
+
const handle = startSddRun({
|
|
3865
|
+
tracker,
|
|
3866
|
+
graph,
|
|
3867
|
+
agent: opts.agent,
|
|
3868
|
+
projectRoot: opts.projectRoot,
|
|
3869
|
+
events: opts.events,
|
|
3870
|
+
subagentFactory: opts.subagentFactory,
|
|
3871
|
+
worktrees,
|
|
3872
|
+
boardStore,
|
|
3873
|
+
registry,
|
|
3874
|
+
parallelSlots,
|
|
3875
|
+
defaultModel,
|
|
3876
|
+
defaultProvider,
|
|
3877
|
+
fallbackModels,
|
|
3878
|
+
verifyTask,
|
|
3879
|
+
superviseFailure
|
|
3880
|
+
});
|
|
3881
|
+
void handle.completion.catch(() => {
|
|
3882
|
+
});
|
|
3883
|
+
return { runId: handle.runId };
|
|
3884
|
+
}
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
// src/server/sdd-wizard-routes.ts
|
|
3889
|
+
async function handleSddWizardRoute(_ws, msg, handlers) {
|
|
3890
|
+
if (!(msg.type.startsWith("sdd.spec.") || msg.type.startsWith("sdd.run."))) return false;
|
|
3891
|
+
await handlers.handleMessage(msg);
|
|
3892
|
+
return true;
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
// src/server/index.ts
|
|
3896
|
+
import { makeLightSubagentFactory } from "@wrongstack/runtime";
|
|
3897
|
+
|
|
3273
3898
|
// src/server/collaboration-ws-handler.ts
|
|
3274
3899
|
import { randomUUID } from "crypto";
|
|
3275
3900
|
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
@@ -3997,11 +4622,11 @@ var CollaborationWebSocketHandler = class {
|
|
|
3997
4622
|
|
|
3998
4623
|
// src/server/projects-manifest.ts
|
|
3999
4624
|
import * as fs5 from "fs/promises";
|
|
4000
|
-
import * as
|
|
4625
|
+
import * as path7 from "path";
|
|
4001
4626
|
import { projectSlug } from "@wrongstack/core";
|
|
4002
4627
|
function projectsJsonPath(globalConfigPath) {
|
|
4003
|
-
const base =
|
|
4004
|
-
return
|
|
4628
|
+
const base = path7.dirname(globalConfigPath);
|
|
4629
|
+
return path7.join(base, "projects.json");
|
|
4005
4630
|
}
|
|
4006
4631
|
async function loadManifest(globalConfigPath) {
|
|
4007
4632
|
try {
|
|
@@ -4014,15 +4639,15 @@ async function loadManifest(globalConfigPath) {
|
|
|
4014
4639
|
}
|
|
4015
4640
|
async function saveManifest(manifest, globalConfigPath) {
|
|
4016
4641
|
const file = projectsJsonPath(globalConfigPath);
|
|
4017
|
-
await fs5.mkdir(
|
|
4642
|
+
await fs5.mkdir(path7.dirname(file), { recursive: true });
|
|
4018
4643
|
await fs5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
4019
4644
|
}
|
|
4020
4645
|
function generateProjectSlug(rootPath) {
|
|
4021
4646
|
return projectSlug(rootPath);
|
|
4022
4647
|
}
|
|
4023
4648
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
4024
|
-
const base =
|
|
4025
|
-
const dir =
|
|
4649
|
+
const base = path7.dirname(globalConfigPath);
|
|
4650
|
+
const dir = path7.join(base, "projects", slug);
|
|
4026
4651
|
await fs5.mkdir(dir, { recursive: true });
|
|
4027
4652
|
return dir;
|
|
4028
4653
|
}
|
|
@@ -4449,14 +5074,14 @@ function registerShutdownHandlers(res) {
|
|
|
4449
5074
|
|
|
4450
5075
|
// src/server/instance-registry.ts
|
|
4451
5076
|
import * as os from "os";
|
|
4452
|
-
import * as
|
|
5077
|
+
import * as path8 from "path";
|
|
4453
5078
|
import * as fs6 from "fs/promises";
|
|
4454
5079
|
import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
|
|
4455
5080
|
function defaultBaseDir() {
|
|
4456
|
-
return
|
|
5081
|
+
return path8.join(os.homedir(), ".wrongstack");
|
|
4457
5082
|
}
|
|
4458
5083
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
4459
|
-
return
|
|
5084
|
+
return path8.join(baseDir, "webui-instances.json");
|
|
4460
5085
|
}
|
|
4461
5086
|
function isPidAlive(pid) {
|
|
4462
5087
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -4578,9 +5203,10 @@ function openBrowser(url, platform = process.platform) {
|
|
|
4578
5203
|
if (child.pid) {
|
|
4579
5204
|
try {
|
|
4580
5205
|
import("@wrongstack/tools").then(({ getProcessRegistry }) => {
|
|
5206
|
+
const pid = child.pid;
|
|
5207
|
+
if (pid === void 0) return;
|
|
4581
5208
|
getProcessRegistry().register({
|
|
4582
|
-
|
|
4583
|
-
pid: child.pid,
|
|
5209
|
+
pid,
|
|
4584
5210
|
name: "browser",
|
|
4585
5211
|
command: `${command} ${args.join(" ")}`,
|
|
4586
5212
|
startedAt: Date.now(),
|
|
@@ -4588,7 +5214,7 @@ function openBrowser(url, platform = process.platform) {
|
|
|
4588
5214
|
protected: true
|
|
4589
5215
|
});
|
|
4590
5216
|
child.on("exit", () => {
|
|
4591
|
-
getProcessRegistry().unregister(
|
|
5217
|
+
getProcessRegistry().unregister(pid);
|
|
4592
5218
|
});
|
|
4593
5219
|
}).catch(() => {
|
|
4594
5220
|
});
|
|
@@ -4618,7 +5244,7 @@ import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
|
4618
5244
|
|
|
4619
5245
|
// src/server/provider-config-io.ts
|
|
4620
5246
|
import * as fs7 from "fs/promises";
|
|
4621
|
-
import * as
|
|
5247
|
+
import * as path9 from "path";
|
|
4622
5248
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
4623
5249
|
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
4624
5250
|
import { DefaultSecretVault } from "@wrongstack/core";
|
|
@@ -4816,7 +5442,10 @@ function createProviderHandlers(deps2) {
|
|
|
4816
5442
|
try {
|
|
4817
5443
|
const providers = await loadConfigProviders();
|
|
4818
5444
|
const result = upsertKey(providers, providerId, label, apiKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
4819
|
-
if (result.ok)
|
|
5445
|
+
if (result.ok) {
|
|
5446
|
+
await saveConfigProviders(providers);
|
|
5447
|
+
broadcastSaved(providers);
|
|
5448
|
+
}
|
|
4820
5449
|
sendResult2(ws, result.ok, result.message);
|
|
4821
5450
|
} catch (err) {
|
|
4822
5451
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -4826,7 +5455,10 @@ function createProviderHandlers(deps2) {
|
|
|
4826
5455
|
try {
|
|
4827
5456
|
const providers = await loadConfigProviders();
|
|
4828
5457
|
const result = deleteKey(providers, providerId, label);
|
|
4829
|
-
if (result.ok)
|
|
5458
|
+
if (result.ok) {
|
|
5459
|
+
await saveConfigProviders(providers);
|
|
5460
|
+
broadcastSaved(providers);
|
|
5461
|
+
}
|
|
4830
5462
|
sendResult2(ws, result.ok, result.message);
|
|
4831
5463
|
} catch (err) {
|
|
4832
5464
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -4836,7 +5468,10 @@ function createProviderHandlers(deps2) {
|
|
|
4836
5468
|
try {
|
|
4837
5469
|
const providers = await loadConfigProviders();
|
|
4838
5470
|
const result = setActiveKey(providers, providerId, label);
|
|
4839
|
-
if (result.ok)
|
|
5471
|
+
if (result.ok) {
|
|
5472
|
+
await saveConfigProviders(providers);
|
|
5473
|
+
broadcastSaved(providers);
|
|
5474
|
+
}
|
|
4840
5475
|
sendResult2(ws, result.ok, result.message);
|
|
4841
5476
|
} catch (err) {
|
|
4842
5477
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -4846,11 +5481,13 @@ function createProviderHandlers(deps2) {
|
|
|
4846
5481
|
try {
|
|
4847
5482
|
const providers = await loadConfigProviders();
|
|
4848
5483
|
const result = addProvider(providers, payload, (/* @__PURE__ */ new Date()).toISOString());
|
|
4849
|
-
if (result.ok)
|
|
5484
|
+
if (result.ok) {
|
|
5485
|
+
await saveConfigProviders(providers);
|
|
5486
|
+
broadcastSaved(providers);
|
|
5487
|
+
}
|
|
4850
5488
|
sendResult2(ws, result.ok, result.message);
|
|
4851
5489
|
if (result.ok) {
|
|
4852
5490
|
console.log(`[WebUI] Provider "${payload.id}" added via provider.add`);
|
|
4853
|
-
broadcastSaved(providers);
|
|
4854
5491
|
}
|
|
4855
5492
|
} catch (err) {
|
|
4856
5493
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -4860,7 +5497,10 @@ function createProviderHandlers(deps2) {
|
|
|
4860
5497
|
try {
|
|
4861
5498
|
const providers = await loadConfigProviders();
|
|
4862
5499
|
const result = removeProvider(providers, providerId);
|
|
4863
|
-
if (result.ok)
|
|
5500
|
+
if (result.ok) {
|
|
5501
|
+
await saveConfigProviders(providers);
|
|
5502
|
+
broadcastSaved(providers);
|
|
5503
|
+
}
|
|
4864
5504
|
sendResult2(ws, result.ok, result.message);
|
|
4865
5505
|
} catch (err) {
|
|
4866
5506
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -5043,7 +5683,7 @@ function createModeHandlers(ctx) {
|
|
|
5043
5683
|
|
|
5044
5684
|
// src/server/project-handlers.ts
|
|
5045
5685
|
import * as fs9 from "fs/promises";
|
|
5046
|
-
import * as
|
|
5686
|
+
import * as path11 from "path";
|
|
5047
5687
|
import {
|
|
5048
5688
|
DefaultSessionStore as DefaultSessionStore2,
|
|
5049
5689
|
DefaultSystemPromptBuilder as DefaultSystemPromptBuilder3,
|
|
@@ -5052,13 +5692,13 @@ import {
|
|
|
5052
5692
|
|
|
5053
5693
|
// src/server/path-containment.ts
|
|
5054
5694
|
import * as fs8 from "fs/promises";
|
|
5055
|
-
import * as
|
|
5695
|
+
import * as path10 from "path";
|
|
5056
5696
|
function isPathInside(root, target) {
|
|
5057
|
-
const relative3 =
|
|
5058
|
-
return relative3 === "" || !relative3.startsWith("..") && !
|
|
5697
|
+
const relative3 = path10.relative(root, target);
|
|
5698
|
+
return relative3 === "" || !relative3.startsWith("..") && !path10.isAbsolute(relative3);
|
|
5059
5699
|
}
|
|
5060
5700
|
async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
|
|
5061
|
-
const resolved =
|
|
5701
|
+
const resolved = path10.resolve(projectRoot, inputPath);
|
|
5062
5702
|
let stat3;
|
|
5063
5703
|
try {
|
|
5064
5704
|
stat3 = await fs8.stat(resolved);
|
|
@@ -5100,7 +5740,7 @@ function createProjectHandlers(ctx) {
|
|
|
5100
5740
|
}
|
|
5101
5741
|
const { root: addRoot, name: displayName } = parsed.value;
|
|
5102
5742
|
try {
|
|
5103
|
-
const resolved =
|
|
5743
|
+
const resolved = path11.resolve(addRoot);
|
|
5104
5744
|
await fs9.access(resolved);
|
|
5105
5745
|
const stat3 = await fs9.stat(resolved);
|
|
5106
5746
|
if (!stat3.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
@@ -5118,7 +5758,7 @@ function createProjectHandlers(ctx) {
|
|
|
5118
5758
|
});
|
|
5119
5759
|
return;
|
|
5120
5760
|
}
|
|
5121
|
-
const name2 = displayName?.trim() ||
|
|
5761
|
+
const name2 = displayName?.trim() || path11.basename(resolved);
|
|
5122
5762
|
const slug = generateProjectSlug(resolved);
|
|
5123
5763
|
await ensureProjectDataDir(slug, ctx.globalConfigPath);
|
|
5124
5764
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5131,7 +5771,7 @@ function createProjectHandlers(ctx) {
|
|
|
5131
5771
|
} catch (err) {
|
|
5132
5772
|
send(ws, {
|
|
5133
5773
|
type: "projects.added",
|
|
5134
|
-
payload: { name:
|
|
5774
|
+
payload: { name: path11.basename(addRoot), root: addRoot, slug: "", message: errMessage(err) }
|
|
5135
5775
|
});
|
|
5136
5776
|
}
|
|
5137
5777
|
},
|
|
@@ -5146,7 +5786,7 @@ function createProjectHandlers(ctx) {
|
|
|
5146
5786
|
}
|
|
5147
5787
|
const { root: selRoot, name: selName } = parsed.value;
|
|
5148
5788
|
try {
|
|
5149
|
-
const resolved =
|
|
5789
|
+
const resolved = path11.resolve(selRoot);
|
|
5150
5790
|
try {
|
|
5151
5791
|
await fs9.access(resolved);
|
|
5152
5792
|
const stat3 = await fs9.stat(resolved);
|
|
@@ -5156,7 +5796,7 @@ function createProjectHandlers(ctx) {
|
|
|
5156
5796
|
type: "projects.selected",
|
|
5157
5797
|
payload: {
|
|
5158
5798
|
root: selRoot,
|
|
5159
|
-
name: selName ||
|
|
5799
|
+
name: selName || path11.basename(selRoot),
|
|
5160
5800
|
message: `Cannot switch: ${errMessage(err)}`
|
|
5161
5801
|
}
|
|
5162
5802
|
});
|
|
@@ -5168,7 +5808,7 @@ function createProjectHandlers(ctx) {
|
|
|
5168
5808
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5169
5809
|
entry.lastWorkingDir = resolved;
|
|
5170
5810
|
} else {
|
|
5171
|
-
const name2 = selName?.trim() ||
|
|
5811
|
+
const name2 = selName?.trim() || path11.basename(resolved);
|
|
5172
5812
|
const slug = generateProjectSlug(resolved);
|
|
5173
5813
|
manifest.projects.push({
|
|
5174
5814
|
name: name2,
|
|
@@ -5207,8 +5847,8 @@ function createProjectHandlers(ctx) {
|
|
|
5207
5847
|
});
|
|
5208
5848
|
} catch {
|
|
5209
5849
|
}
|
|
5210
|
-
const newSessionsDir =
|
|
5211
|
-
|
|
5850
|
+
const newSessionsDir = path11.join(
|
|
5851
|
+
path11.dirname(ctx.globalConfigPath),
|
|
5212
5852
|
"projects",
|
|
5213
5853
|
switchSlug,
|
|
5214
5854
|
"sessions"
|
|
@@ -5247,7 +5887,7 @@ function createProjectHandlers(ctx) {
|
|
|
5247
5887
|
sessionId: newSession.id,
|
|
5248
5888
|
projectSlug: switchSlug,
|
|
5249
5889
|
projectRoot: resolved,
|
|
5250
|
-
projectName:
|
|
5890
|
+
projectName: path11.basename(resolved),
|
|
5251
5891
|
workingDir: resolved,
|
|
5252
5892
|
clientType: "webui",
|
|
5253
5893
|
pid: process.pid,
|
|
@@ -5259,8 +5899,8 @@ function createProjectHandlers(ctx) {
|
|
|
5259
5899
|
type: "projects.selected",
|
|
5260
5900
|
payload: {
|
|
5261
5901
|
root: resolved,
|
|
5262
|
-
name: selName ||
|
|
5263
|
-
message: `Switched to ${selName ||
|
|
5902
|
+
name: selName || path11.basename(resolved),
|
|
5903
|
+
message: `Switched to ${selName || path11.basename(resolved)}`
|
|
5264
5904
|
}
|
|
5265
5905
|
});
|
|
5266
5906
|
broadcast(ctx.clients, {
|
|
@@ -5280,7 +5920,7 @@ function createProjectHandlers(ctx) {
|
|
|
5280
5920
|
type: "projects.selected",
|
|
5281
5921
|
payload: {
|
|
5282
5922
|
root: selRoot,
|
|
5283
|
-
name: selName ||
|
|
5923
|
+
name: selName || path11.basename(selRoot),
|
|
5284
5924
|
message: errMessage(err)
|
|
5285
5925
|
}
|
|
5286
5926
|
});
|
|
@@ -5311,7 +5951,7 @@ function createProjectHandlers(ctx) {
|
|
|
5311
5951
|
}
|
|
5312
5952
|
|
|
5313
5953
|
// src/server/session-handlers.ts
|
|
5314
|
-
import * as
|
|
5954
|
+
import * as path12 from "path";
|
|
5315
5955
|
import {
|
|
5316
5956
|
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
5317
5957
|
repairToolUseAdjacency,
|
|
@@ -5653,7 +6293,7 @@ function createSessionHandlers(ctx) {
|
|
|
5653
6293
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
5654
6294
|
const projectRoot = ctx.getProjectRoot();
|
|
5655
6295
|
const rewinder = new DefaultSessionRewinder(
|
|
5656
|
-
|
|
6296
|
+
path12.join(projectRoot, ".wrongstack", "sessions"),
|
|
5657
6297
|
projectRoot
|
|
5658
6298
|
);
|
|
5659
6299
|
const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
|
|
@@ -5668,7 +6308,7 @@ function createSessionHandlers(ctx) {
|
|
|
5668
6308
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
5669
6309
|
const projectRoot = ctx.getProjectRoot();
|
|
5670
6310
|
const rewinder = new DefaultSessionRewinder(
|
|
5671
|
-
|
|
6311
|
+
path12.join(projectRoot, ".wrongstack", "sessions"),
|
|
5672
6312
|
projectRoot
|
|
5673
6313
|
);
|
|
5674
6314
|
await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
|
|
@@ -5975,10 +6615,24 @@ async function handleAutoPhaseRoute(_ws, msg, handlers) {
|
|
|
5975
6615
|
return true;
|
|
5976
6616
|
}
|
|
5977
6617
|
|
|
6618
|
+
// src/server/specs-routes.ts
|
|
6619
|
+
async function handleSpecsRoute(_ws, msg, handlers) {
|
|
6620
|
+
if (!msg.type.startsWith("specs.")) return false;
|
|
6621
|
+
await handlers.handleMessage(msg);
|
|
6622
|
+
return true;
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6625
|
+
// src/server/sdd-board-routes.ts
|
|
6626
|
+
async function handleSddBoardRoute(_ws, msg, handlers) {
|
|
6627
|
+
if (!msg.type.startsWith("sdd.board.")) return false;
|
|
6628
|
+
await handlers.handleMessage(msg);
|
|
6629
|
+
return true;
|
|
6630
|
+
}
|
|
6631
|
+
|
|
5978
6632
|
// src/server/setup-events.ts
|
|
5979
6633
|
import * as fs10 from "fs/promises";
|
|
5980
6634
|
import { watch as fsWatch } from "fs";
|
|
5981
|
-
import * as
|
|
6635
|
+
import * as path13 from "path";
|
|
5982
6636
|
function setupEvents(deps2) {
|
|
5983
6637
|
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
|
|
5984
6638
|
const disposers = [];
|
|
@@ -6443,7 +7097,7 @@ function setupEvents(deps2) {
|
|
|
6443
7097
|
if (wpaths?.projectStatus) {
|
|
6444
7098
|
try {
|
|
6445
7099
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
6446
|
-
const dir =
|
|
7100
|
+
const dir = path13.dirname(statusFile);
|
|
6447
7101
|
await fs10.mkdir(dir, { recursive: true });
|
|
6448
7102
|
await fs10.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
6449
7103
|
} catch (err) {
|
|
@@ -6452,7 +7106,7 @@ function setupEvents(deps2) {
|
|
|
6452
7106
|
}
|
|
6453
7107
|
});
|
|
6454
7108
|
if (wpaths?.projectStatus && wpaths.configDir) {
|
|
6455
|
-
const projectsDir =
|
|
7109
|
+
const projectsDir = path13.join(wpaths.configDir, "projects");
|
|
6456
7110
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
6457
7111
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
6458
7112
|
const DEBOUNCE_MS = 150;
|
|
@@ -6479,7 +7133,7 @@ function setupEvents(deps2) {
|
|
|
6479
7133
|
);
|
|
6480
7134
|
};
|
|
6481
7135
|
const metricsInterval = setInterval(logWatcherMetrics, 6e4);
|
|
6482
|
-
const broadcastStatus = (
|
|
7136
|
+
const broadcastStatus = (_projectHash, statusData, actualDelayMs) => {
|
|
6483
7137
|
broadcast2(clients, { type: "client.status_update", payload: statusData });
|
|
6484
7138
|
if (watcherMetrics) {
|
|
6485
7139
|
watcherMetrics.broadcastsSent++;
|
|
@@ -6520,9 +7174,9 @@ function setupEvents(deps2) {
|
|
|
6520
7174
|
if (eventType === "change") {
|
|
6521
7175
|
if (filename == null) return;
|
|
6522
7176
|
if (watcherMetrics) watcherMetrics.fileChangesDetected++;
|
|
6523
|
-
const targetFile =
|
|
7177
|
+
const targetFile = path13.join(projectsDir, String(filename));
|
|
6524
7178
|
if (targetFile.endsWith("status.json")) {
|
|
6525
|
-
const projectHash2 =
|
|
7179
|
+
const projectHash2 = path13.basename(path13.dirname(targetFile));
|
|
6526
7180
|
if (knownProjectHashes.size > 0 && !knownProjectHashes.has(projectHash2)) {
|
|
6527
7181
|
return;
|
|
6528
7182
|
}
|
|
@@ -6580,7 +7234,7 @@ function setupEvents(deps2) {
|
|
|
6580
7234
|
}
|
|
6581
7235
|
});
|
|
6582
7236
|
}
|
|
6583
|
-
const globalRoot = globalConfigPath ?
|
|
7237
|
+
const globalRoot = globalConfigPath ? path13.dirname(globalConfigPath) : void 0;
|
|
6584
7238
|
if (globalRoot) {
|
|
6585
7239
|
const broadcastSessions = async () => {
|
|
6586
7240
|
try {
|
|
@@ -6654,10 +7308,10 @@ function setupEvents(deps2) {
|
|
|
6654
7308
|
// src/server/custom-context-modes.ts
|
|
6655
7309
|
import { listContextWindowModes, atomicWrite as atomicWrite5 } from "@wrongstack/core";
|
|
6656
7310
|
import * as fs11 from "fs/promises";
|
|
6657
|
-
import * as
|
|
7311
|
+
import * as path14 from "path";
|
|
6658
7312
|
var STORE_FILENAME = "custom-context-modes.json";
|
|
6659
7313
|
function storePath(wrongstackDir) {
|
|
6660
|
-
return
|
|
7314
|
+
return path14.join(wrongstackDir, STORE_FILENAME);
|
|
6661
7315
|
}
|
|
6662
7316
|
var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
|
|
6663
7317
|
function createCustomModeStore(wrongstackDir) {
|
|
@@ -6789,12 +7443,12 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
|
6789
7443
|
|
|
6790
7444
|
// src/server/shell-open.ts
|
|
6791
7445
|
import * as fs12 from "fs/promises";
|
|
6792
|
-
import * as
|
|
7446
|
+
import * as path15 from "path";
|
|
6793
7447
|
import { spawn as spawn2 } from "child_process";
|
|
6794
7448
|
var METACHAR_REGEX = /[&|<>^"'`\n\r]/;
|
|
6795
7449
|
async function handleShellOpen(req, logger) {
|
|
6796
7450
|
try {
|
|
6797
|
-
const resolved =
|
|
7451
|
+
const resolved = path15.resolve(req.path);
|
|
6798
7452
|
await fs12.access(resolved);
|
|
6799
7453
|
if (METACHAR_REGEX.test(resolved)) {
|
|
6800
7454
|
return { success: false, message: "Path contains unsupported characters." };
|
|
@@ -6904,15 +7558,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
6904
7558
|
if (!m) continue;
|
|
6905
7559
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
6906
7560
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
6907
|
-
let
|
|
6908
|
-
if (
|
|
7561
|
+
let path17 = m[3] ?? "";
|
|
7562
|
+
if (path17 === "") {
|
|
6909
7563
|
i += 1;
|
|
6910
|
-
|
|
7564
|
+
path17 = parts[i + 1] ?? parts[i] ?? "";
|
|
6911
7565
|
i += 1;
|
|
6912
7566
|
}
|
|
6913
|
-
if (!
|
|
6914
|
-
const prev = counts.get(
|
|
6915
|
-
counts.set(
|
|
7567
|
+
if (!path17) continue;
|
|
7568
|
+
const prev = counts.get(path17) ?? { added: 0, deleted: 0 };
|
|
7569
|
+
counts.set(path17, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
6916
7570
|
}
|
|
6917
7571
|
};
|
|
6918
7572
|
parseNumstat(unstagedNumstat);
|
|
@@ -6924,7 +7578,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
6924
7578
|
if (!rec || rec.length < 3) continue;
|
|
6925
7579
|
const x = rec[0] ?? " ";
|
|
6926
7580
|
const y = rec[1] ?? " ";
|
|
6927
|
-
const
|
|
7581
|
+
const path17 = rec.slice(3);
|
|
6928
7582
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
6929
7583
|
if (isRename) i += 1;
|
|
6930
7584
|
let status;
|
|
@@ -6936,13 +7590,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
6936
7590
|
else if (x === "D" || y === "D") status = "D";
|
|
6937
7591
|
else status = "M";
|
|
6938
7592
|
const staged = x !== " " && x !== "?";
|
|
6939
|
-
let added = counts.get(
|
|
6940
|
-
let deleted = counts.get(
|
|
7593
|
+
let added = counts.get(path17)?.added ?? 0;
|
|
7594
|
+
let deleted = counts.get(path17)?.deleted ?? 0;
|
|
6941
7595
|
if (status === "?") {
|
|
6942
7596
|
added = 0;
|
|
6943
7597
|
deleted = 0;
|
|
6944
7598
|
}
|
|
6945
|
-
files.push({ path:
|
|
7599
|
+
files.push({ path: path17, status, added, deleted, staged });
|
|
6946
7600
|
}
|
|
6947
7601
|
send(ws, { type: "git.changes", payload: { files } });
|
|
6948
7602
|
} catch (err) {
|
|
@@ -6953,21 +7607,21 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
6953
7607
|
}
|
|
6954
7608
|
}
|
|
6955
7609
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
6956
|
-
async function handleGitDiff(ws, projectRoot,
|
|
7610
|
+
async function handleGitDiff(ws, projectRoot, path17) {
|
|
6957
7611
|
const cwd = projectRoot || void 0;
|
|
6958
|
-
const reply = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
6959
|
-
if (!
|
|
7612
|
+
const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path17, ...extra } });
|
|
7613
|
+
if (!path17 || path17.includes("\0") || path17.includes("..") || nodePath.isAbsolute(path17)) {
|
|
6960
7614
|
reply({ oldText: "", newText: "", error: "invalid path" });
|
|
6961
7615
|
return;
|
|
6962
7616
|
}
|
|
6963
7617
|
try {
|
|
6964
7618
|
const git = makeGit(cwd);
|
|
6965
7619
|
const { readFile: readFile9 } = await import("fs/promises");
|
|
6966
|
-
const { join:
|
|
6967
|
-
const oldText = await git(["show", `HEAD:${
|
|
7620
|
+
const { join: join12 } = await import("path");
|
|
7621
|
+
const oldText = await git(["show", `HEAD:${path17}`]);
|
|
6968
7622
|
let newText = "";
|
|
6969
7623
|
try {
|
|
6970
|
-
const abs = cwd ?
|
|
7624
|
+
const abs = cwd ? join12(cwd, path17) : path17;
|
|
6971
7625
|
const buf = await readFile9(abs);
|
|
6972
7626
|
if (buf.includes(0)) {
|
|
6973
7627
|
reply({ oldText: "", newText: "", binary: true });
|
|
@@ -7178,6 +7832,7 @@ async function startWebUI(opts = {}) {
|
|
|
7178
7832
|
toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
|
|
7179
7833
|
toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
|
|
7180
7834
|
toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
|
|
7835
|
+
applyToolDescriptionModes(toolRegistry, config.tools?.descriptionMode);
|
|
7181
7836
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
7182
7837
|
const mcpRegistry = new MCPRegistry({
|
|
7183
7838
|
toolRegistry,
|
|
@@ -7221,7 +7876,7 @@ async function startWebUI(opts = {}) {
|
|
|
7221
7876
|
sessionId: session.id,
|
|
7222
7877
|
projectSlug: wpaths.projectSlug,
|
|
7223
7878
|
projectRoot,
|
|
7224
|
-
projectName:
|
|
7879
|
+
projectName: path16.basename(projectRoot),
|
|
7225
7880
|
workingDir,
|
|
7226
7881
|
clientType: "webui",
|
|
7227
7882
|
pid: process.pid,
|
|
@@ -7241,7 +7896,7 @@ async function startWebUI(opts = {}) {
|
|
|
7241
7896
|
const hqTelemetry = createHqPublisherFromEnv({
|
|
7242
7897
|
clientKind: "webui",
|
|
7243
7898
|
projectRoot,
|
|
7244
|
-
projectName:
|
|
7899
|
+
projectName: path16.basename(projectRoot),
|
|
7245
7900
|
appConfig: config,
|
|
7246
7901
|
socketFactory: (url) => new WebSocket2(url)
|
|
7247
7902
|
});
|
|
@@ -7253,7 +7908,7 @@ async function startWebUI(opts = {}) {
|
|
|
7253
7908
|
events,
|
|
7254
7909
|
sessionId: session.id,
|
|
7255
7910
|
projectRoot,
|
|
7256
|
-
projectName:
|
|
7911
|
+
projectName: path16.basename(projectRoot),
|
|
7257
7912
|
globalRoot: wpaths.globalRoot,
|
|
7258
7913
|
initialAgents: statusTracker?.getAgents(),
|
|
7259
7914
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7309,9 +7964,9 @@ async function startWebUI(opts = {}) {
|
|
|
7309
7964
|
};
|
|
7310
7965
|
const skillLoader = config.features.skills ? new DefaultSkillLoader2({ paths: wpaths }) : void 0;
|
|
7311
7966
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
7312
|
-
manifestPath:
|
|
7313
|
-
projectSkillsDir:
|
|
7314
|
-
globalSkillsDir:
|
|
7967
|
+
manifestPath: path16.join(wstackGlobalRoot2(), "installed-skills.json"),
|
|
7968
|
+
projectSkillsDir: path16.join(projectRoot, ".wrongstack", "skills"),
|
|
7969
|
+
globalSkillsDir: path16.join(wstackGlobalRoot2(), "skills"),
|
|
7315
7970
|
projectHash: projectHash(projectRoot),
|
|
7316
7971
|
skillLoader
|
|
7317
7972
|
}) : void 0;
|
|
@@ -7415,6 +8070,8 @@ async function startWebUI(opts = {}) {
|
|
|
7415
8070
|
context.meta["enhanceDelayMs"] = autonomyCfg["enhanceDelayMs"] ?? 6e4;
|
|
7416
8071
|
context.meta["enhanceLanguage"] = autonomyCfg["enhanceLanguage"] ?? "original";
|
|
7417
8072
|
context.meta["nextPrediction"] = config.nextPrediction ?? false;
|
|
8073
|
+
context.meta["fallbackModels"] = config.fallbackModels ?? [];
|
|
8074
|
+
context.meta["fallbackAuto"] = config.fallbackAuto !== false;
|
|
7418
8075
|
context.meta["featureMcp"] = config.features.mcp !== false;
|
|
7419
8076
|
context.meta["featurePlugins"] = config.features.plugins !== false;
|
|
7420
8077
|
context.meta["featureMemory"] = config.features.memory !== false;
|
|
@@ -7472,7 +8129,9 @@ async function startWebUI(opts = {}) {
|
|
|
7472
8129
|
"reasoningMode",
|
|
7473
8130
|
"reasoningEffort",
|
|
7474
8131
|
"reasoningPreserve",
|
|
7475
|
-
"cacheTtl"
|
|
8132
|
+
"cacheTtl",
|
|
8133
|
+
"fallbackModels",
|
|
8134
|
+
"fallbackAuto"
|
|
7476
8135
|
];
|
|
7477
8136
|
const prefSnapshot = () => {
|
|
7478
8137
|
const snapshot = {};
|
|
@@ -7503,6 +8162,8 @@ async function startWebUI(opts = {}) {
|
|
|
7503
8162
|
if (typeof payload["enhanceLanguage"] === "string") setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
|
|
7504
8163
|
if (autonomyTouched) decrypted.autonomy = autonomyCfg;
|
|
7505
8164
|
if (typeof payload["nextPrediction"] === "boolean") decrypted.nextPrediction = payload["nextPrediction"];
|
|
8165
|
+
if (Array.isArray(payload["fallbackModels"])) decrypted.fallbackModels = payload["fallbackModels"];
|
|
8166
|
+
if (typeof payload["fallbackAuto"] === "boolean") decrypted.fallbackAuto = payload["fallbackAuto"];
|
|
7506
8167
|
const FEATURE_MAP = {
|
|
7507
8168
|
featureMcp: "mcp",
|
|
7508
8169
|
featurePlugins: "plugins",
|
|
@@ -7783,6 +8444,29 @@ async function startWebUI(opts = {}) {
|
|
|
7783
8444
|
events,
|
|
7784
8445
|
projectRoot
|
|
7785
8446
|
);
|
|
8447
|
+
const specsHandler = new SpecsWebSocketHandler(wpaths.projectSpecs, wpaths.projectTaskGraphs);
|
|
8448
|
+
const sddBoardHandler = new SddBoardWebSocketHandler(wpaths.projectSddBoards);
|
|
8449
|
+
const sddWizardHandler = new SddWizardWebSocketHandler(
|
|
8450
|
+
buildSddWizardDeps({
|
|
8451
|
+
agent,
|
|
8452
|
+
events,
|
|
8453
|
+
projectRoot,
|
|
8454
|
+
brain,
|
|
8455
|
+
subagentFactory: makeLightSubagentFactory({
|
|
8456
|
+
container,
|
|
8457
|
+
providerRegistry,
|
|
8458
|
+
toolRegistry,
|
|
8459
|
+
session,
|
|
8460
|
+
projectRoot
|
|
8461
|
+
}),
|
|
8462
|
+
paths: {
|
|
8463
|
+
projectSpecs: wpaths.projectSpecs,
|
|
8464
|
+
projectTaskGraphs: wpaths.projectTaskGraphs,
|
|
8465
|
+
projectSddBoards: wpaths.projectSddBoards,
|
|
8466
|
+
projectDir: wpaths.projectDir
|
|
8467
|
+
}
|
|
8468
|
+
})
|
|
8469
|
+
);
|
|
7786
8470
|
const worktreeHandler = new WorktreeWebSocketHandler(events, logger);
|
|
7787
8471
|
const terminalHandler = new TerminalWebSocketHandler(() => workingDir, logger);
|
|
7788
8472
|
const collabHandler = new CollaborationWebSocketHandler(
|
|
@@ -7822,7 +8506,7 @@ async function startWebUI(opts = {}) {
|
|
|
7822
8506
|
inputCost,
|
|
7823
8507
|
outputCost,
|
|
7824
8508
|
cacheReadCost,
|
|
7825
|
-
projectName:
|
|
8509
|
+
projectName: path16.basename(projectRoot) || projectRoot,
|
|
7826
8510
|
projectRoot,
|
|
7827
8511
|
cwd: workingDir,
|
|
7828
8512
|
mode: modeId,
|
|
@@ -7914,6 +8598,9 @@ async function startWebUI(opts = {}) {
|
|
|
7914
8598
|
}));
|
|
7915
8599
|
});
|
|
7916
8600
|
autoPhaseHandler.addClient(ws);
|
|
8601
|
+
specsHandler.addClient(ws);
|
|
8602
|
+
sddBoardHandler.addClient(ws);
|
|
8603
|
+
sddWizardHandler.addClient(ws);
|
|
7917
8604
|
worktreeHandler.addClient(ws);
|
|
7918
8605
|
collabHandler.addClient(ws);
|
|
7919
8606
|
terminalHandler.addClient(ws);
|
|
@@ -8038,21 +8725,21 @@ async function startWebUI(opts = {}) {
|
|
|
8038
8725
|
});
|
|
8039
8726
|
}
|
|
8040
8727
|
async function touchProjectEntry(root, workDir) {
|
|
8041
|
-
const resolved =
|
|
8728
|
+
const resolved = path16.resolve(root);
|
|
8042
8729
|
const manifest = await loadManifest(globalConfigPath);
|
|
8043
8730
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8044
|
-
const existing = manifest.projects.find((p) =>
|
|
8731
|
+
const existing = manifest.projects.find((p) => path16.resolve(p.root) === resolved);
|
|
8045
8732
|
if (existing) {
|
|
8046
8733
|
existing.lastSeen = now;
|
|
8047
|
-
if (workDir) existing.lastWorkingDir =
|
|
8734
|
+
if (workDir) existing.lastWorkingDir = path16.resolve(workDir);
|
|
8048
8735
|
} else {
|
|
8049
8736
|
manifest.projects.push({
|
|
8050
|
-
name:
|
|
8737
|
+
name: path16.basename(resolved),
|
|
8051
8738
|
root: resolved,
|
|
8052
8739
|
slug: generateProjectSlug(resolved),
|
|
8053
8740
|
createdAt: now,
|
|
8054
8741
|
lastSeen: now,
|
|
8055
|
-
lastWorkingDir: workDir ?
|
|
8742
|
+
lastWorkingDir: workDir ? path16.resolve(workDir) : void 0
|
|
8056
8743
|
});
|
|
8057
8744
|
}
|
|
8058
8745
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -8078,6 +8765,9 @@ async function startWebUI(opts = {}) {
|
|
|
8078
8765
|
let mailboxRoutes;
|
|
8079
8766
|
let brainRoutes;
|
|
8080
8767
|
let autoPhaseRoutes;
|
|
8768
|
+
let specsRoutes;
|
|
8769
|
+
let sddBoardRoutes;
|
|
8770
|
+
let sddWizardRoutes;
|
|
8081
8771
|
async function handleMessage(ws, _client, msg) {
|
|
8082
8772
|
if (await handleProviderRoute(ws, msg, providerRoutes)) return;
|
|
8083
8773
|
if (await handleSessionRoute(ws, msg, sessionRoutes)) return;
|
|
@@ -8087,6 +8777,9 @@ async function startWebUI(opts = {}) {
|
|
|
8087
8777
|
if (await handleMailboxRoute(ws, msg, mailboxRoutes)) return;
|
|
8088
8778
|
if (await handleBrainRoute(ws, msg, brainRoutes)) return;
|
|
8089
8779
|
if (await handleAutoPhaseRoute(ws, msg, autoPhaseRoutes)) return;
|
|
8780
|
+
if (await handleSpecsRoute(ws, msg, specsRoutes)) return;
|
|
8781
|
+
if (await handleSddBoardRoute(ws, msg, sddBoardRoutes)) return;
|
|
8782
|
+
if (await handleSddWizardRoute(ws, msg, sddWizardRoutes)) return;
|
|
8090
8783
|
switch (msg.type) {
|
|
8091
8784
|
// Collaboration messages short-circuit the user/agent flow.
|
|
8092
8785
|
// They don't touch runLock, the agent loop, or the message queue —
|
|
@@ -8383,6 +9076,10 @@ async function startWebUI(opts = {}) {
|
|
|
8383
9076
|
config.features.skills = payload["featureSkills"];
|
|
8384
9077
|
if (typeof payload["featureModelsRegistry"] === "boolean")
|
|
8385
9078
|
config.features.modelsRegistry = payload["featureModelsRegistry"];
|
|
9079
|
+
if (Array.isArray(payload["fallbackModels"]))
|
|
9080
|
+
config.fallbackModels = payload["fallbackModels"];
|
|
9081
|
+
if (typeof payload["fallbackAuto"] === "boolean")
|
|
9082
|
+
config.fallbackAuto = payload["fallbackAuto"];
|
|
8386
9083
|
if (typeof payload["contextAutoCompact"] === "boolean") {
|
|
8387
9084
|
if (payload["contextAutoCompact"] && autoCompactor) {
|
|
8388
9085
|
pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
@@ -8445,22 +9142,7 @@ async function startWebUI(opts = {}) {
|
|
|
8445
9142
|
const saved = await providerHandlers.loadConfigProviders();
|
|
8446
9143
|
send(ws, {
|
|
8447
9144
|
type: "providers.saved",
|
|
8448
|
-
payload: {
|
|
8449
|
-
providers: Object.entries(saved).map(([id, cfg]) => {
|
|
8450
|
-
const keys = normalizeKeys(cfg);
|
|
8451
|
-
return {
|
|
8452
|
-
id,
|
|
8453
|
-
family: cfg.family ?? id,
|
|
8454
|
-
baseUrl: cfg.baseUrl,
|
|
8455
|
-
apiKeys: keys.map((k) => ({
|
|
8456
|
-
label: k.label,
|
|
8457
|
-
maskedKey: maskedKey(k.apiKey),
|
|
8458
|
-
isActive: k.label === cfg.activeKey,
|
|
8459
|
-
createdAt: k.createdAt
|
|
8460
|
-
}))
|
|
8461
|
-
};
|
|
8462
|
-
})
|
|
8463
|
-
}
|
|
9145
|
+
payload: { providers: projectSavedProviders(saved) }
|
|
8464
9146
|
});
|
|
8465
9147
|
},
|
|
8466
9148
|
listProviderModels: async (ws, msg) => {
|
|
@@ -8670,7 +9352,7 @@ async function startWebUI(opts = {}) {
|
|
|
8670
9352
|
sendResult2(ws, false, parsed.message);
|
|
8671
9353
|
return;
|
|
8672
9354
|
}
|
|
8673
|
-
return handleMailboxMessages(ws, { projectRoot, globalRoot:
|
|
9355
|
+
return handleMailboxMessages(ws, { projectRoot, globalRoot: path16.dirname(globalConfigPath) }, parsed.value);
|
|
8674
9356
|
},
|
|
8675
9357
|
agents: (ws, msg) => {
|
|
8676
9358
|
const parsed = validateMailboxAgentsPayload(msg.payload);
|
|
@@ -8678,16 +9360,16 @@ async function startWebUI(opts = {}) {
|
|
|
8678
9360
|
sendResult2(ws, false, parsed.message);
|
|
8679
9361
|
return;
|
|
8680
9362
|
}
|
|
8681
|
-
return handleMailboxAgents(ws, { projectRoot, globalRoot:
|
|
9363
|
+
return handleMailboxAgents(ws, { projectRoot, globalRoot: path16.dirname(globalConfigPath) }, parsed.value);
|
|
8682
9364
|
},
|
|
8683
|
-
clear: (ws) => handleMailboxClear(ws, { projectRoot, globalRoot:
|
|
9365
|
+
clear: (ws) => handleMailboxClear(ws, { projectRoot, globalRoot: path16.dirname(globalConfigPath) }),
|
|
8684
9366
|
purge: (ws, msg) => {
|
|
8685
9367
|
const parsed = validateMailboxPurgePayload(msg.payload);
|
|
8686
9368
|
if (!parsed.ok) {
|
|
8687
9369
|
sendResult2(ws, false, parsed.message);
|
|
8688
9370
|
return;
|
|
8689
9371
|
}
|
|
8690
|
-
return handleMailboxPurge(ws, { projectRoot, globalRoot:
|
|
9372
|
+
return handleMailboxPurge(ws, { projectRoot, globalRoot: path16.dirname(globalConfigPath) }, parsed.value);
|
|
8691
9373
|
}
|
|
8692
9374
|
};
|
|
8693
9375
|
brainRoutes = {
|
|
@@ -8734,6 +9416,15 @@ async function startWebUI(opts = {}) {
|
|
|
8734
9416
|
autoPhaseRoutes = {
|
|
8735
9417
|
handleMessage: (msg) => autoPhaseHandler.handleMessage(msg)
|
|
8736
9418
|
};
|
|
9419
|
+
specsRoutes = {
|
|
9420
|
+
handleMessage: (msg) => specsHandler.handleMessage(msg)
|
|
9421
|
+
};
|
|
9422
|
+
sddBoardRoutes = {
|
|
9423
|
+
handleMessage: (msg) => sddBoardHandler.handleMessage(msg)
|
|
9424
|
+
};
|
|
9425
|
+
sddWizardRoutes = {
|
|
9426
|
+
handleMessage: (msg) => sddWizardHandler.handleMessage(msg)
|
|
9427
|
+
};
|
|
8737
9428
|
const watcherMetrics = {
|
|
8738
9429
|
fileChangesDetected: 0,
|
|
8739
9430
|
filesProcessed: 0,
|
|
@@ -8746,7 +9437,7 @@ async function startWebUI(opts = {}) {
|
|
|
8746
9437
|
};
|
|
8747
9438
|
const httpServer = createHttpServer({
|
|
8748
9439
|
host: wsHost,
|
|
8749
|
-
distDir:
|
|
9440
|
+
distDir: path16.resolve(import.meta.dirname, "../../dist"),
|
|
8750
9441
|
wsPort,
|
|
8751
9442
|
globalRoot: wpaths.globalRoot,
|
|
8752
9443
|
apiToken: wsToken,
|
|
@@ -8755,7 +9446,7 @@ async function startWebUI(opts = {}) {
|
|
|
8755
9446
|
void fleetBroadcast?.();
|
|
8756
9447
|
}
|
|
8757
9448
|
});
|
|
8758
|
-
const registryBaseDir =
|
|
9449
|
+
const registryBaseDir = path16.dirname(globalConfigPath);
|
|
8759
9450
|
httpServer.listen(httpPort, wsHost, () => {
|
|
8760
9451
|
const openUrl = `http://${wsHost}:${httpPort}`;
|
|
8761
9452
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
@@ -8767,7 +9458,7 @@ async function startWebUI(opts = {}) {
|
|
|
8767
9458
|
wsPort,
|
|
8768
9459
|
host: wsHost,
|
|
8769
9460
|
projectRoot,
|
|
8770
|
-
projectName:
|
|
9461
|
+
projectName: path16.basename(projectRoot) || projectRoot,
|
|
8771
9462
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8772
9463
|
url: `http://${wsHost}:${httpPort}`
|
|
8773
9464
|
},
|