@wrongstack/webui 0.272.2 → 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/index.js
CHANGED
|
@@ -7,7 +7,10 @@ function isRecord(value) {
|
|
|
7
7
|
}
|
|
8
8
|
function validateModelSwitchPayload(payload) {
|
|
9
9
|
if (!isRecord(payload)) {
|
|
10
|
-
return {
|
|
10
|
+
return {
|
|
11
|
+
ok: false,
|
|
12
|
+
message: "model.switch payload must be an object with string provider and model"
|
|
13
|
+
};
|
|
11
14
|
}
|
|
12
15
|
const provider = payload["provider"];
|
|
13
16
|
const model = payload["model"];
|
|
@@ -29,13 +32,22 @@ function validateMailboxMessagesPayload(payload) {
|
|
|
29
32
|
const agentId = payload["agentId"];
|
|
30
33
|
const unreadOnly = payload["unreadOnly"];
|
|
31
34
|
if (limit !== void 0 && (typeof limit !== "number" || !Number.isFinite(limit) || limit < 1)) {
|
|
32
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
message: "mailbox.messages payload.limit must be a positive number when provided"
|
|
38
|
+
};
|
|
33
39
|
}
|
|
34
40
|
if (agentId !== void 0 && typeof agentId !== "string") {
|
|
35
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
message: "mailbox.messages payload.agentId must be a string when provided"
|
|
44
|
+
};
|
|
36
45
|
}
|
|
37
46
|
if (unreadOnly !== void 0 && typeof unreadOnly !== "boolean") {
|
|
38
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
message: "mailbox.messages payload.unreadOnly must be a boolean when provided"
|
|
50
|
+
};
|
|
39
51
|
}
|
|
40
52
|
return { ok: true, value: { limit, agentId, unreadOnly } };
|
|
41
53
|
}
|
|
@@ -46,7 +58,10 @@ function validateMailboxAgentsPayload(payload) {
|
|
|
46
58
|
}
|
|
47
59
|
const onlineOnly = payload["onlineOnly"];
|
|
48
60
|
if (onlineOnly !== void 0 && typeof onlineOnly !== "boolean") {
|
|
49
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
message: "mailbox.agents payload.onlineOnly must be a boolean when provided"
|
|
64
|
+
};
|
|
50
65
|
}
|
|
51
66
|
return { ok: true, value: { onlineOnly } };
|
|
52
67
|
}
|
|
@@ -58,10 +73,16 @@ function validateMailboxPurgePayload(payload) {
|
|
|
58
73
|
const completedMaxAgeMs = payload["completedMaxAgeMs"];
|
|
59
74
|
const incompleteMaxAgeMs = payload["incompleteMaxAgeMs"];
|
|
60
75
|
if (completedMaxAgeMs !== void 0 && (typeof completedMaxAgeMs !== "number" || !Number.isFinite(completedMaxAgeMs) || completedMaxAgeMs < 0)) {
|
|
61
|
-
return {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
message: "mailbox.purge payload.completedMaxAgeMs must be a non-negative number when provided"
|
|
79
|
+
};
|
|
62
80
|
}
|
|
63
81
|
if (incompleteMaxAgeMs !== void 0 && (typeof incompleteMaxAgeMs !== "number" || !Number.isFinite(incompleteMaxAgeMs) || incompleteMaxAgeMs < 0)) {
|
|
64
|
-
return {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
message: "mailbox.purge payload.incompleteMaxAgeMs must be a non-negative number when provided"
|
|
85
|
+
};
|
|
65
86
|
}
|
|
66
87
|
return { ok: true, value: { completedMaxAgeMs, incompleteMaxAgeMs } };
|
|
67
88
|
}
|
|
@@ -72,7 +93,10 @@ function validateBrainRiskPayload(payload) {
|
|
|
72
93
|
}
|
|
73
94
|
const level = payload["level"];
|
|
74
95
|
if (typeof level !== "string" || !BRAIN_RISK_VALUES.has(level)) {
|
|
75
|
-
return {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
message: "brain.risk payload.level must be one of off, low, medium, high, all"
|
|
99
|
+
};
|
|
76
100
|
}
|
|
77
101
|
return { ok: true, value: { level } };
|
|
78
102
|
}
|
|
@@ -98,7 +122,10 @@ function validateAutonomySwitchPayload(payload) {
|
|
|
98
122
|
}
|
|
99
123
|
function validatePlanTemplateUsePayload(payload) {
|
|
100
124
|
if (!isRecord(payload)) {
|
|
101
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
message: "plan.template_use payload must be an object with string template"
|
|
128
|
+
};
|
|
102
129
|
}
|
|
103
130
|
const template = payload["template"];
|
|
104
131
|
if (typeof template !== "string" || template.trim().length === 0) {
|
|
@@ -113,7 +140,15 @@ var ENHANCE_LANGUAGE_VALUES = /* @__PURE__ */ new Set(["original", "english"]);
|
|
|
113
140
|
var LOG_LEVEL_VALUES = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
114
141
|
var AUDIT_LEVEL_VALUES = /* @__PURE__ */ new Set(["minimal", "standard", "full"]);
|
|
115
142
|
var REASONING_MODE_VALUES = /* @__PURE__ */ new Set(["auto", "on", "off"]);
|
|
116
|
-
var REASONING_EFFORT_VALUES = /* @__PURE__ */ new Set([
|
|
143
|
+
var REASONING_EFFORT_VALUES = /* @__PURE__ */ new Set([
|
|
144
|
+
"none",
|
|
145
|
+
"minimal",
|
|
146
|
+
"low",
|
|
147
|
+
"medium",
|
|
148
|
+
"high",
|
|
149
|
+
"xhigh",
|
|
150
|
+
"max"
|
|
151
|
+
]);
|
|
117
152
|
var CACHE_TTL_VALUES = /* @__PURE__ */ new Set(["default", "5m", "1h"]);
|
|
118
153
|
var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
119
154
|
"yolo",
|
|
@@ -134,8 +169,10 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
134
169
|
"tgDelegate",
|
|
135
170
|
"reasoningPreserve",
|
|
136
171
|
"hqEnabled",
|
|
137
|
-
"hqRawContent"
|
|
172
|
+
"hqRawContent",
|
|
173
|
+
"fallbackAuto"
|
|
138
174
|
]);
|
|
175
|
+
var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels"]);
|
|
139
176
|
var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
140
177
|
"autonomyDelayMs",
|
|
141
178
|
"autoProceedMaxIterations",
|
|
@@ -167,6 +204,9 @@ function validatePreferenceValue(key, value) {
|
|
|
167
204
|
if (STRING_PREF_KEYS.has(key)) {
|
|
168
205
|
return typeof value === "string" ? null : `prefs.update payload.${key} must be a string`;
|
|
169
206
|
}
|
|
207
|
+
if (STRING_ARRAY_PREF_KEYS.has(key)) {
|
|
208
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string") ? null : `prefs.update payload.${key} must be an array of strings`;
|
|
209
|
+
}
|
|
170
210
|
const allowed = ENUM_PREF_KEYS[key];
|
|
171
211
|
if (allowed) {
|
|
172
212
|
return typeof value === "string" && allowed.has(value) ? null : `prefs.update payload.${key} must be one of: ${Array.from(allowed).join(", ")}`;
|
|
@@ -287,16 +327,25 @@ function validateContextModeCreatePayload(payload) {
|
|
|
287
327
|
return { ok: false, message: "context.mode.create payload.description must be a string" };
|
|
288
328
|
}
|
|
289
329
|
if (!isRecord(thresholds)) {
|
|
290
|
-
return {
|
|
330
|
+
return {
|
|
331
|
+
ok: false,
|
|
332
|
+
message: "context.mode.create payload.thresholds must be an object with warn/soft/hard numbers"
|
|
333
|
+
};
|
|
291
334
|
}
|
|
292
335
|
if (!isFiniteNumber(thresholds["warn"]) || !isFiniteNumber(thresholds["soft"]) || !isFiniteNumber(thresholds["hard"])) {
|
|
293
|
-
return {
|
|
336
|
+
return {
|
|
337
|
+
ok: false,
|
|
338
|
+
message: "context.mode.create payload.thresholds.warn/soft/hard must be finite numbers"
|
|
339
|
+
};
|
|
294
340
|
}
|
|
295
341
|
if (!isFiniteNumber(preserveK)) {
|
|
296
342
|
return { ok: false, message: "context.mode.create payload.preserveK must be a finite number" };
|
|
297
343
|
}
|
|
298
344
|
if (!isFiniteNumber(eliseThreshold)) {
|
|
299
|
-
return {
|
|
345
|
+
return {
|
|
346
|
+
ok: false,
|
|
347
|
+
message: "context.mode.create payload.eliseThreshold must be a finite number"
|
|
348
|
+
};
|
|
300
349
|
}
|
|
301
350
|
return {
|
|
302
351
|
ok: true,
|
|
@@ -320,22 +369,34 @@ function validateContextModeUpdatePayload(payload) {
|
|
|
320
369
|
}
|
|
321
370
|
const name2 = payload["name"];
|
|
322
371
|
if (name2 !== void 0 && typeof name2 !== "string") {
|
|
323
|
-
return {
|
|
372
|
+
return {
|
|
373
|
+
ok: false,
|
|
374
|
+
message: "context.mode.update payload.name must be a string when provided"
|
|
375
|
+
};
|
|
324
376
|
}
|
|
325
377
|
const description = payload["description"];
|
|
326
378
|
if (description !== void 0 && typeof description !== "string") {
|
|
327
|
-
return {
|
|
379
|
+
return {
|
|
380
|
+
ok: false,
|
|
381
|
+
message: "context.mode.update payload.description must be a string when provided"
|
|
382
|
+
};
|
|
328
383
|
}
|
|
329
384
|
const thresholds = payload["thresholds"];
|
|
330
385
|
let validatedThresholds;
|
|
331
386
|
if (thresholds !== void 0) {
|
|
332
387
|
if (!isRecord(thresholds)) {
|
|
333
|
-
return {
|
|
388
|
+
return {
|
|
389
|
+
ok: false,
|
|
390
|
+
message: "context.mode.update payload.thresholds must be an object when provided"
|
|
391
|
+
};
|
|
334
392
|
}
|
|
335
393
|
for (const key of ["warn", "soft", "hard"]) {
|
|
336
394
|
const val = thresholds[key];
|
|
337
395
|
if (val !== void 0 && !isFiniteNumber(val)) {
|
|
338
|
-
return {
|
|
396
|
+
return {
|
|
397
|
+
ok: false,
|
|
398
|
+
message: `context.mode.update payload.thresholds.${key} must be a finite number when provided`
|
|
399
|
+
};
|
|
339
400
|
}
|
|
340
401
|
}
|
|
341
402
|
validatedThresholds = {
|
|
@@ -346,11 +407,17 @@ function validateContextModeUpdatePayload(payload) {
|
|
|
346
407
|
}
|
|
347
408
|
const preserveK = payload["preserveK"];
|
|
348
409
|
if (preserveK !== void 0 && !isFiniteNumber(preserveK)) {
|
|
349
|
-
return {
|
|
410
|
+
return {
|
|
411
|
+
ok: false,
|
|
412
|
+
message: "context.mode.update payload.preserveK must be a finite number when provided"
|
|
413
|
+
};
|
|
350
414
|
}
|
|
351
415
|
const eliseThreshold = payload["eliseThreshold"];
|
|
352
416
|
if (eliseThreshold !== void 0 && !isFiniteNumber(eliseThreshold)) {
|
|
353
|
-
return {
|
|
417
|
+
return {
|
|
418
|
+
ok: false,
|
|
419
|
+
message: "context.mode.update payload.eliseThreshold must be a finite number when provided"
|
|
420
|
+
};
|
|
354
421
|
}
|
|
355
422
|
return {
|
|
356
423
|
ok: true,
|
|
@@ -368,28 +435,31 @@ function validateShellOpenPayload(payload) {
|
|
|
368
435
|
if (!isRecord(payload)) {
|
|
369
436
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
370
437
|
}
|
|
371
|
-
const
|
|
372
|
-
if (typeof
|
|
438
|
+
const path17 = payload["path"];
|
|
439
|
+
if (typeof path17 !== "string" || path17.trim().length === 0) {
|
|
373
440
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
374
441
|
}
|
|
375
442
|
const target = payload["target"];
|
|
376
443
|
if (target !== void 0 && target !== "file" && target !== "terminal") {
|
|
377
|
-
return {
|
|
444
|
+
return {
|
|
445
|
+
ok: false,
|
|
446
|
+
message: 'shell.open payload.target must be "file" or "terminal" when provided'
|
|
447
|
+
};
|
|
378
448
|
}
|
|
379
|
-
return { ok: true, value: { path:
|
|
449
|
+
return { ok: true, value: { path: path17, target } };
|
|
380
450
|
}
|
|
381
451
|
function validateGitDiffPayload(payload) {
|
|
382
452
|
if (!isRecord(payload)) {
|
|
383
453
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
384
454
|
}
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
455
|
+
const path17 = payload["path"];
|
|
456
|
+
if (path17 === void 0 || path17 === null) {
|
|
387
457
|
return { ok: true, value: { path: "" } };
|
|
388
458
|
}
|
|
389
|
-
if (typeof
|
|
459
|
+
if (typeof path17 !== "string") {
|
|
390
460
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
391
461
|
}
|
|
392
|
-
return { ok: true, value: { path:
|
|
462
|
+
return { ok: true, value: { path: path17 } };
|
|
393
463
|
}
|
|
394
464
|
function validateProjectsAddPayload(payload) {
|
|
395
465
|
if (!isRecord(payload)) {
|
|
@@ -569,7 +639,7 @@ async function handlePlanItemUpdate(ctx, ws, payload) {
|
|
|
569
639
|
return;
|
|
570
640
|
}
|
|
571
641
|
try {
|
|
572
|
-
const {
|
|
642
|
+
const { mutatePlan, setPlanItemStatus } = await import("@wrongstack/core");
|
|
573
643
|
let changed = false;
|
|
574
644
|
const plan = await mutatePlan(planPath, sessionId, async (p) => {
|
|
575
645
|
const before = p.updatedAt;
|
|
@@ -649,7 +719,7 @@ import {
|
|
|
649
719
|
createTieredBrainArbiter
|
|
650
720
|
} from "@wrongstack/core";
|
|
651
721
|
import * as fs13 from "fs/promises";
|
|
652
|
-
import * as
|
|
722
|
+
import * as path16 from "path";
|
|
653
723
|
|
|
654
724
|
// src/server/http-server.ts
|
|
655
725
|
import * as fs from "fs/promises";
|
|
@@ -2812,6 +2882,7 @@ import {
|
|
|
2812
2882
|
DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2,
|
|
2813
2883
|
DEFAULT_SESSION_PRUNE_DAYS,
|
|
2814
2884
|
DEFAULT_TOOLS_CONFIG,
|
|
2885
|
+
applyToolDescriptionModes,
|
|
2815
2886
|
resolveContextWindowPolicy as resolveContextWindowPolicy2,
|
|
2816
2887
|
enhanceUserPrompt,
|
|
2817
2888
|
gatedEnhancerReasoning,
|
|
@@ -2856,7 +2927,8 @@ function createDefaultContainer(opts) {
|
|
|
2856
2927
|
() => new DefaultErrorHandler(
|
|
2857
2928
|
buildRecoveryStrategies({
|
|
2858
2929
|
compactor: container.resolve(TOKENS.Compactor),
|
|
2859
|
-
modelsRegistry
|
|
2930
|
+
modelsRegistry,
|
|
2931
|
+
getConfig: () => configStore.get()
|
|
2860
2932
|
})
|
|
2861
2933
|
)
|
|
2862
2934
|
);
|
|
@@ -2943,6 +3015,7 @@ function patchConfig(config, updates) {
|
|
|
2943
3015
|
import { spawnSync } from "child_process";
|
|
2944
3016
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
2945
3017
|
import {
|
|
3018
|
+
assignNickname,
|
|
2946
3019
|
AutoPhasePlanner,
|
|
2947
3020
|
PhaseGraphBuilder,
|
|
2948
3021
|
PhaseOrchestrator,
|
|
@@ -2980,6 +3053,8 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
2980
3053
|
abort = null;
|
|
2981
3054
|
/** Optional per-phase git-worktree isolation (lazily created at start). */
|
|
2982
3055
|
worktrees = null;
|
|
3056
|
+
/** Per-run worker identities so the board can show "who is on what". */
|
|
3057
|
+
usedNicknames = /* @__PURE__ */ new Set();
|
|
2983
3058
|
addClient(ws) {
|
|
2984
3059
|
const client = { ws, id: crypto.randomUUID() };
|
|
2985
3060
|
this.clients.add(client);
|
|
@@ -3022,6 +3097,29 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
3022
3097
|
await this.handleTaskStatusChange(taskId, status);
|
|
3023
3098
|
break;
|
|
3024
3099
|
}
|
|
3100
|
+
case "autophase.moveTask": {
|
|
3101
|
+
const { taskId, toPhaseId } = msg.payload;
|
|
3102
|
+
if (this.orchestrator?.moveTask(taskId, toPhaseId)) this.afterBoardMutation();
|
|
3103
|
+
break;
|
|
3104
|
+
}
|
|
3105
|
+
case "autophase.assignTask": {
|
|
3106
|
+
const { taskId, agentId, agentName } = msg.payload;
|
|
3107
|
+
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName)) this.afterBoardMutation();
|
|
3108
|
+
break;
|
|
3109
|
+
}
|
|
3110
|
+
case "autophase.addTask": {
|
|
3111
|
+
const { phaseId, title, description, type, priority } = msg.payload;
|
|
3112
|
+
if (title?.trim() && this.orchestrator?.addTask(phaseId, { title: title.trim(), description, type, priority })) {
|
|
3113
|
+
this.afterBoardMutation();
|
|
3114
|
+
}
|
|
3115
|
+
break;
|
|
3116
|
+
}
|
|
3117
|
+
case "autophase.retryTask":
|
|
3118
|
+
case "autophase.runTask": {
|
|
3119
|
+
const { taskId } = msg.payload;
|
|
3120
|
+
if (this.orchestrator?.requeueTask(taskId)) this.afterBoardMutation();
|
|
3121
|
+
break;
|
|
3122
|
+
}
|
|
3025
3123
|
case "autophase.toggleAutonomous": {
|
|
3026
3124
|
const autonomous = msg.payload?.autonomous ?? !this.graph?.autonomous;
|
|
3027
3125
|
if (this.graph) {
|
|
@@ -3149,6 +3247,13 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
3149
3247
|
return this.defaultPhases();
|
|
3150
3248
|
}
|
|
3151
3249
|
async executeTaskWithAgent(task, phaseId, env) {
|
|
3250
|
+
if (!task.assignee) {
|
|
3251
|
+
const nick = assignNickname("executor", this.usedNicknames);
|
|
3252
|
+
this.usedNicknames.add(nick.key);
|
|
3253
|
+
task.assignee = nick.display.replace(/\s*\([^)]*\)\s*$/, "");
|
|
3254
|
+
task.updatedAt = Date.now();
|
|
3255
|
+
this.broadcastState();
|
|
3256
|
+
}
|
|
3152
3257
|
const prompt = `Execute task: ${task.title}
|
|
3153
3258
|
|
|
3154
3259
|
Description: ${task.description}
|
|
@@ -3164,6 +3269,11 @@ Type: ${task.type}`;
|
|
|
3164
3269
|
this.context.cwd = prevCwd;
|
|
3165
3270
|
}
|
|
3166
3271
|
}
|
|
3272
|
+
/** Persist + broadcast after an interactive board mutation. */
|
|
3273
|
+
afterBoardMutation() {
|
|
3274
|
+
if (this.graph) void this.store.save(this.graph);
|
|
3275
|
+
this.broadcastState();
|
|
3276
|
+
}
|
|
3167
3277
|
async handleTaskStatusChange(taskId, status) {
|
|
3168
3278
|
if (!this.graph) return;
|
|
3169
3279
|
for (const phase of this.graph.phases.values()) {
|
|
@@ -3207,23 +3317,7 @@ Type: ${task.type}`;
|
|
|
3207
3317
|
(sum, p) => sum + Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "completed").length,
|
|
3208
3318
|
0
|
|
3209
3319
|
);
|
|
3210
|
-
const
|
|
3211
|
-
id: p.id,
|
|
3212
|
-
name: p.name,
|
|
3213
|
-
description: p.description,
|
|
3214
|
-
status: p.status,
|
|
3215
|
-
priority: p.priority,
|
|
3216
|
-
estimateHours: p.estimateHours,
|
|
3217
|
-
actualDurationMs: p.actualDurationMs,
|
|
3218
|
-
startedAt: p.startedAt,
|
|
3219
|
-
completedAt: p.completedAt,
|
|
3220
|
-
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,
|
|
3221
|
-
taskCount: p.taskGraph.nodes.size,
|
|
3222
|
-
completedTasks: Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "completed").length,
|
|
3223
|
-
assignedAgents: p.assignedAgents,
|
|
3224
|
-
isActive: p.id === currentActiveId
|
|
3225
|
-
}));
|
|
3226
|
-
const taskItems = activePhase ? Array.from(activePhase.taskGraph.nodes.values()).map((t) => ({
|
|
3320
|
+
const mapTask = (t) => ({
|
|
3227
3321
|
id: t.id,
|
|
3228
3322
|
title: t.title,
|
|
3229
3323
|
description: t.description,
|
|
@@ -3236,7 +3330,31 @@ Type: ${task.type}`;
|
|
|
3236
3330
|
tags: t.tags || [],
|
|
3237
3331
|
startedAt: t.startedAt,
|
|
3238
3332
|
completedAt: t.completedAt
|
|
3239
|
-
})
|
|
3333
|
+
});
|
|
3334
|
+
const phaseItems = phases.map((p) => {
|
|
3335
|
+
const nodes = Array.from(p.taskGraph.nodes.values());
|
|
3336
|
+
const done = nodes.filter((t) => t.status === "completed").length;
|
|
3337
|
+
return {
|
|
3338
|
+
id: p.id,
|
|
3339
|
+
name: p.name,
|
|
3340
|
+
description: p.description,
|
|
3341
|
+
status: p.status,
|
|
3342
|
+
priority: p.priority,
|
|
3343
|
+
estimateHours: p.estimateHours,
|
|
3344
|
+
actualDurationMs: p.actualDurationMs,
|
|
3345
|
+
startedAt: p.startedAt,
|
|
3346
|
+
completedAt: p.completedAt,
|
|
3347
|
+
progressPercent: nodes.length > 0 ? Math.round(done / nodes.length * 100) : 0,
|
|
3348
|
+
taskCount: nodes.length,
|
|
3349
|
+
completedTasks: done,
|
|
3350
|
+
assignedAgents: p.assignedAgents,
|
|
3351
|
+
isActive: p.id === currentActiveId,
|
|
3352
|
+
// Every phase carries its full task list so the board can render each
|
|
3353
|
+
// phase as a column (not just the selected one).
|
|
3354
|
+
tasks: nodes.map(mapTask)
|
|
3355
|
+
};
|
|
3356
|
+
});
|
|
3357
|
+
const taskItems = activePhase ? Array.from(activePhase.taskGraph.nodes.values()).map(mapTask) : [];
|
|
3240
3358
|
const completedPhases = phases.filter((p) => p.status === "completed").length;
|
|
3241
3359
|
return {
|
|
3242
3360
|
title: this.graph.title,
|
|
@@ -3269,6 +3387,513 @@ Type: ${task.type}`;
|
|
|
3269
3387
|
}
|
|
3270
3388
|
};
|
|
3271
3389
|
|
|
3390
|
+
// src/server/specs-ws-handler.ts
|
|
3391
|
+
import {
|
|
3392
|
+
computeTaskProgress,
|
|
3393
|
+
SpecStore,
|
|
3394
|
+
TaskGraphStore
|
|
3395
|
+
} from "@wrongstack/core";
|
|
3396
|
+
var SpecsWebSocketHandler = class {
|
|
3397
|
+
specStore;
|
|
3398
|
+
graphStore;
|
|
3399
|
+
clients = /* @__PURE__ */ new Set();
|
|
3400
|
+
constructor(specsDir, taskGraphsDir) {
|
|
3401
|
+
this.specStore = new SpecStore({ baseDir: specsDir });
|
|
3402
|
+
this.graphStore = new TaskGraphStore({ baseDir: taskGraphsDir });
|
|
3403
|
+
}
|
|
3404
|
+
addClient(ws) {
|
|
3405
|
+
const client = { ws, id: crypto.randomUUID() };
|
|
3406
|
+
this.clients.add(client);
|
|
3407
|
+
ws.on("close", () => this.clients.delete(client));
|
|
3408
|
+
ws.on("error", () => this.clients.delete(client));
|
|
3409
|
+
void this.sendList(client);
|
|
3410
|
+
}
|
|
3411
|
+
async handleMessage(msg) {
|
|
3412
|
+
switch (msg.type) {
|
|
3413
|
+
case "specs.list":
|
|
3414
|
+
await this.broadcastList();
|
|
3415
|
+
break;
|
|
3416
|
+
case "specs.get": {
|
|
3417
|
+
const specId = msg.payload?.specId;
|
|
3418
|
+
if (specId) await this.broadcastDetail(specId);
|
|
3419
|
+
break;
|
|
3420
|
+
}
|
|
3421
|
+
case "specs.taskStatus": {
|
|
3422
|
+
const { graphId, taskId, status } = msg.payload;
|
|
3423
|
+
await this.updateTaskStatus(graphId, taskId, status);
|
|
3424
|
+
break;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
// ── List ──────────────────────────────────────────────────────────────────
|
|
3429
|
+
async buildList() {
|
|
3430
|
+
const [specs, graphs] = await Promise.all([this.specStore.list(), this.graphStore.list()]);
|
|
3431
|
+
return specs.map((s, i) => {
|
|
3432
|
+
const graph = graphs.find((g) => g.specId === s.id);
|
|
3433
|
+
return {
|
|
3434
|
+
id: s.id,
|
|
3435
|
+
// FORGE-style display id (spec-001…). The real UUID stays in `id`.
|
|
3436
|
+
displayId: `spec-${String(i + 1).padStart(3, "0")}`,
|
|
3437
|
+
title: s.title,
|
|
3438
|
+
status: s.status,
|
|
3439
|
+
graphId: graph?.id,
|
|
3440
|
+
total: graph?.nodeCount ?? 0,
|
|
3441
|
+
completed: graph?.completedCount ?? 0
|
|
3442
|
+
};
|
|
3443
|
+
});
|
|
3444
|
+
}
|
|
3445
|
+
async broadcastList() {
|
|
3446
|
+
this.broadcast({ type: "specs.list", payload: { specs: await this.buildList() } });
|
|
3447
|
+
}
|
|
3448
|
+
async sendList(client) {
|
|
3449
|
+
this.send(client, { type: "specs.list", payload: { specs: await this.buildList() } });
|
|
3450
|
+
}
|
|
3451
|
+
// ── Detail (dependency board) ───────────────────────────────────────────────
|
|
3452
|
+
async broadcastDetail(specId) {
|
|
3453
|
+
const spec = await this.specStore.load(specId);
|
|
3454
|
+
const graph = await this.findGraphForSpec(specId);
|
|
3455
|
+
if (!spec || !graph) {
|
|
3456
|
+
this.broadcast({ type: "specs.detail", payload: { specId, columns: [], notFound: true } });
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
this.broadcast({ type: "specs.detail", payload: this.buildDetail(spec, graph) });
|
|
3460
|
+
}
|
|
3461
|
+
async findGraphForSpec(specId) {
|
|
3462
|
+
const entry = (await this.graphStore.list()).find((g) => g.specId === specId);
|
|
3463
|
+
if (!entry) return null;
|
|
3464
|
+
return this.graphStore.load(entry.id);
|
|
3465
|
+
}
|
|
3466
|
+
buildDetail(spec, graph) {
|
|
3467
|
+
const nodes = Array.from(graph.nodes.values()).sort((a, b) => a.createdAt - b.createdAt);
|
|
3468
|
+
const shortId = /* @__PURE__ */ new Map();
|
|
3469
|
+
nodes.forEach((n, i) => {
|
|
3470
|
+
shortId.set(n.id, `t${String(i + 1).padStart(2, "0")}`);
|
|
3471
|
+
});
|
|
3472
|
+
const blockers = /* @__PURE__ */ new Map();
|
|
3473
|
+
for (const n of nodes) blockers.set(n.id, []);
|
|
3474
|
+
for (const e of graph.edges) {
|
|
3475
|
+
if (e.type === "depends_on") blockers.get(e.to)?.push(e.from);
|
|
3476
|
+
}
|
|
3477
|
+
const statusOf = (id) => graph.nodes.get(id)?.status;
|
|
3478
|
+
const depthCache = /* @__PURE__ */ new Map();
|
|
3479
|
+
const depthOf = (id, seen = /* @__PURE__ */ new Set()) => {
|
|
3480
|
+
const cached = depthCache.get(id);
|
|
3481
|
+
if (cached !== void 0) return cached;
|
|
3482
|
+
if (seen.has(id)) return 0;
|
|
3483
|
+
seen.add(id);
|
|
3484
|
+
const deps2 = blockers.get(id) ?? [];
|
|
3485
|
+
const d = deps2.length === 0 ? 0 : 1 + Math.max(...deps2.map((b) => depthOf(b, seen)));
|
|
3486
|
+
depthCache.set(id, d);
|
|
3487
|
+
return d;
|
|
3488
|
+
};
|
|
3489
|
+
const toBoardTask = (n) => {
|
|
3490
|
+
const deps2 = blockers.get(n.id) ?? [];
|
|
3491
|
+
const allDepsDone = deps2.every((b) => statusOf(b) === "completed");
|
|
3492
|
+
const displayStatus = n.status === "pending" && deps2.length > 0 && allDepsDone ? "queued" : n.status;
|
|
3493
|
+
return {
|
|
3494
|
+
id: n.id,
|
|
3495
|
+
shortId: shortId.get(n.id) ?? n.id.slice(0, 6),
|
|
3496
|
+
title: n.title,
|
|
3497
|
+
description: n.description,
|
|
3498
|
+
priority: n.priority,
|
|
3499
|
+
type: n.type,
|
|
3500
|
+
status: n.status,
|
|
3501
|
+
displayStatus,
|
|
3502
|
+
deps: deps2.map((b) => shortId.get(b) ?? b.slice(0, 6))
|
|
3503
|
+
};
|
|
3504
|
+
};
|
|
3505
|
+
const byDepth = /* @__PURE__ */ new Map();
|
|
3506
|
+
for (const n of nodes) {
|
|
3507
|
+
const d = depthOf(n.id);
|
|
3508
|
+
if (!byDepth.has(d)) byDepth.set(d, []);
|
|
3509
|
+
byDepth.get(d)?.push(toBoardTask(n));
|
|
3510
|
+
}
|
|
3511
|
+
const columns = [...byDepth.keys()].sort((a, b) => a - b).map((d) => ({ label: d === 0 ? "Start" : `Phase ${d}`, tasks: byDepth.get(d) ?? [] }));
|
|
3512
|
+
const progress = computeTaskProgress(graph);
|
|
3513
|
+
return {
|
|
3514
|
+
specId: spec.id,
|
|
3515
|
+
graphId: graph.id,
|
|
3516
|
+
title: spec.title,
|
|
3517
|
+
overview: spec.overview,
|
|
3518
|
+
status: spec.status,
|
|
3519
|
+
total: progress.total,
|
|
3520
|
+
completed: progress.completed,
|
|
3521
|
+
running: progress.inProgress,
|
|
3522
|
+
pending: progress.pending,
|
|
3523
|
+
columns
|
|
3524
|
+
};
|
|
3525
|
+
}
|
|
3526
|
+
async updateTaskStatus(graphId, taskId, status) {
|
|
3527
|
+
const graph = await this.graphStore.load(graphId);
|
|
3528
|
+
const node = graph?.nodes.get(taskId);
|
|
3529
|
+
if (!graph || !node) return;
|
|
3530
|
+
node.status = status;
|
|
3531
|
+
node.updatedAt = Date.now();
|
|
3532
|
+
graph.updatedAt = Date.now();
|
|
3533
|
+
await this.graphStore.save(graph);
|
|
3534
|
+
this.broadcastDetail(graph.specId).catch(() => {
|
|
3535
|
+
});
|
|
3536
|
+
await this.broadcastList();
|
|
3537
|
+
}
|
|
3538
|
+
// ── Transport ───────────────────────────────────────────────────────────────
|
|
3539
|
+
broadcast(msg) {
|
|
3540
|
+
const data = JSON.stringify(msg);
|
|
3541
|
+
for (const client of this.clients) {
|
|
3542
|
+
if (client.ws.readyState === 1) client.ws.send(data);
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
send(client, msg) {
|
|
3546
|
+
if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
|
|
3547
|
+
}
|
|
3548
|
+
};
|
|
3549
|
+
|
|
3550
|
+
// src/server/sdd-board-ws-handler.ts
|
|
3551
|
+
import { SddBoardStore } from "@wrongstack/core";
|
|
3552
|
+
var CONTROL_TYPES = /* @__PURE__ */ new Set([
|
|
3553
|
+
"pause",
|
|
3554
|
+
"resume",
|
|
3555
|
+
"stop",
|
|
3556
|
+
"retry",
|
|
3557
|
+
"retry_all_failed",
|
|
3558
|
+
"reassign",
|
|
3559
|
+
// Per-task model / fallback / verification assignment + stop/delete (drained by start-sdd-run).
|
|
3560
|
+
"set_task_model",
|
|
3561
|
+
"set_task_fallbacks",
|
|
3562
|
+
"set_task_verification",
|
|
3563
|
+
"cancel_task",
|
|
3564
|
+
"delete_task",
|
|
3565
|
+
"split_task",
|
|
3566
|
+
// Lifecycle (pair with a prior `stop`): sweep worktrees / revert merged commits.
|
|
3567
|
+
"cleanup_worktrees",
|
|
3568
|
+
"rollback"
|
|
3569
|
+
]);
|
|
3570
|
+
var SddBoardWebSocketHandler = class {
|
|
3571
|
+
store;
|
|
3572
|
+
clients = /* @__PURE__ */ new Set();
|
|
3573
|
+
latest = null;
|
|
3574
|
+
poll = null;
|
|
3575
|
+
unsub = null;
|
|
3576
|
+
constructor(boardsDir, events) {
|
|
3577
|
+
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
3578
|
+
if (events) {
|
|
3579
|
+
const handler = (e) => {
|
|
3580
|
+
this.latest = e.snapshot;
|
|
3581
|
+
this.broadcast({ type: "sdd.board.snapshot", payload: e.snapshot });
|
|
3582
|
+
};
|
|
3583
|
+
this.unsub = events.on("sdd.board.snapshot", handler);
|
|
3584
|
+
} else {
|
|
3585
|
+
this.poll = setInterval(() => void this.pollLatest(), 1e3);
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
addClient(ws) {
|
|
3589
|
+
const client = { ws, id: crypto.randomUUID() };
|
|
3590
|
+
this.clients.add(client);
|
|
3591
|
+
ws.on("close", () => this.clients.delete(client));
|
|
3592
|
+
ws.on("error", () => this.clients.delete(client));
|
|
3593
|
+
void this.sendCurrent(client);
|
|
3594
|
+
}
|
|
3595
|
+
async handleMessage(msg) {
|
|
3596
|
+
if (msg.type === "sdd.board.get") {
|
|
3597
|
+
await this.broadcastCurrent();
|
|
3598
|
+
return;
|
|
3599
|
+
}
|
|
3600
|
+
if (msg.type === "sdd.board.list") {
|
|
3601
|
+
const boards = await this.store.list();
|
|
3602
|
+
this.broadcast({ type: "sdd.board.list", payload: { boards } });
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
const action = msg.type.replace(/^sdd\.board\./, "");
|
|
3606
|
+
if (CONTROL_TYPES.has(action)) {
|
|
3607
|
+
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.store.list())[0]?.runId;
|
|
3608
|
+
if (runId) {
|
|
3609
|
+
await this.store.appendControl(runId, {
|
|
3610
|
+
ts: Date.now(),
|
|
3611
|
+
type: action,
|
|
3612
|
+
payload: msg.payload
|
|
3613
|
+
});
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
dispose() {
|
|
3618
|
+
if (this.poll) clearInterval(this.poll);
|
|
3619
|
+
this.unsub?.();
|
|
3620
|
+
this.poll = null;
|
|
3621
|
+
this.unsub = null;
|
|
3622
|
+
}
|
|
3623
|
+
// ── internal ────────────────────────────────────────────────────────────
|
|
3624
|
+
async pollLatest() {
|
|
3625
|
+
const entry = (await this.store.list())[0];
|
|
3626
|
+
if (!entry) return;
|
|
3627
|
+
if (this.latest && this.latest.updatedAt >= entry.updatedAt && this.latest.runId === entry.runId) {
|
|
3628
|
+
return;
|
|
3629
|
+
}
|
|
3630
|
+
const snap = await this.store.load(entry.runId);
|
|
3631
|
+
if (snap) {
|
|
3632
|
+
this.latest = snap;
|
|
3633
|
+
this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
async sendCurrent(client) {
|
|
3637
|
+
const snap = this.latest ?? await this.loadLatestFromDisk();
|
|
3638
|
+
if (snap) this.send(client, { type: "sdd.board.snapshot", payload: snap });
|
|
3639
|
+
}
|
|
3640
|
+
async broadcastCurrent() {
|
|
3641
|
+
const snap = this.latest ?? await this.loadLatestFromDisk();
|
|
3642
|
+
if (snap) this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
3643
|
+
}
|
|
3644
|
+
async loadLatestFromDisk() {
|
|
3645
|
+
const entry = (await this.store.list())[0];
|
|
3646
|
+
return entry ? this.store.load(entry.runId) : null;
|
|
3647
|
+
}
|
|
3648
|
+
broadcast(msg) {
|
|
3649
|
+
const data = JSON.stringify(msg);
|
|
3650
|
+
for (const client of this.clients) {
|
|
3651
|
+
if (client.ws.readyState === 1) client.ws.send(data);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
send(client, msg) {
|
|
3655
|
+
if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
|
|
3656
|
+
}
|
|
3657
|
+
};
|
|
3658
|
+
|
|
3659
|
+
// src/server/sdd-wizard-ws-handler.ts
|
|
3660
|
+
var SddWizardWebSocketHandler = class {
|
|
3661
|
+
constructor(deps2) {
|
|
3662
|
+
this.deps = deps2;
|
|
3663
|
+
}
|
|
3664
|
+
deps;
|
|
3665
|
+
clients = /* @__PURE__ */ new Set();
|
|
3666
|
+
driver = null;
|
|
3667
|
+
/** The agent's most recent question — paired with the next user answer. */
|
|
3668
|
+
lastAgentText = "";
|
|
3669
|
+
/** Guards against overlapping interview turns (one in flight at a time). */
|
|
3670
|
+
busy = false;
|
|
3671
|
+
addClient(ws) {
|
|
3672
|
+
const client = { ws, id: crypto.randomUUID() };
|
|
3673
|
+
this.clients.add(client);
|
|
3674
|
+
ws.on("close", () => this.clients.delete(client));
|
|
3675
|
+
ws.on("error", () => this.clients.delete(client));
|
|
3676
|
+
if (this.driver) this.send(client, this.snapshotMsg());
|
|
3677
|
+
}
|
|
3678
|
+
async handleMessage(msg) {
|
|
3679
|
+
try {
|
|
3680
|
+
switch (msg.type) {
|
|
3681
|
+
case "sdd.spec.start":
|
|
3682
|
+
await this.onStart(String(msg.payload?.goal ?? "").trim());
|
|
3683
|
+
break;
|
|
3684
|
+
case "sdd.spec.message":
|
|
3685
|
+
await this.onMessage(String(msg.payload?.text ?? ""));
|
|
3686
|
+
break;
|
|
3687
|
+
case "sdd.spec.approve":
|
|
3688
|
+
await this.onApprove();
|
|
3689
|
+
break;
|
|
3690
|
+
case "sdd.spec.get":
|
|
3691
|
+
if (this.driver) this.broadcast(this.snapshotMsg());
|
|
3692
|
+
break;
|
|
3693
|
+
case "sdd.run.start":
|
|
3694
|
+
await this.onRunStart({
|
|
3695
|
+
parallelSlots: msg.payload?.parallelSlots,
|
|
3696
|
+
defaultModel: msg.payload?.model,
|
|
3697
|
+
defaultProvider: msg.payload?.provider,
|
|
3698
|
+
fallbackModels: Array.isArray(msg.payload?.fallbackModels) ? msg.payload?.fallbackModels : void 0
|
|
3699
|
+
});
|
|
3700
|
+
break;
|
|
3701
|
+
}
|
|
3702
|
+
} catch (err) {
|
|
3703
|
+
this.busy = false;
|
|
3704
|
+
this.broadcast({
|
|
3705
|
+
type: "sdd.spec.error",
|
|
3706
|
+
payload: { message: err instanceof Error ? err.message : String(err) }
|
|
3707
|
+
});
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
// ── message handlers ──────────────────────────────────────────────────────
|
|
3711
|
+
async onStart(goal) {
|
|
3712
|
+
if (!goal) {
|
|
3713
|
+
this.broadcast({ type: "sdd.spec.error", payload: { message: "A goal is required." } });
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
if (this.busy) return;
|
|
3717
|
+
this.driver = this.deps.makeDriver();
|
|
3718
|
+
const prompt = this.driver.start(goal);
|
|
3719
|
+
await this.runTurn(prompt);
|
|
3720
|
+
}
|
|
3721
|
+
async onMessage(text) {
|
|
3722
|
+
if (!this.driver || this.busy) return;
|
|
3723
|
+
if (this.driver.phase() === "questioning" && this.lastAgentText) {
|
|
3724
|
+
this.driver.submitAnswer(this.lastAgentText, text);
|
|
3725
|
+
} else {
|
|
3726
|
+
this.driver.submitAnswer(this.lastAgentText || "(feedback)", text);
|
|
3727
|
+
}
|
|
3728
|
+
await this.runTurn(this.driver.currentPrompt());
|
|
3729
|
+
}
|
|
3730
|
+
async onApprove() {
|
|
3731
|
+
if (!this.driver || this.busy) return;
|
|
3732
|
+
const { phase, prompt } = await this.driver.approve();
|
|
3733
|
+
if (phase === "executing") {
|
|
3734
|
+
this.broadcast(this.snapshotMsg());
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
await this.runTurn(prompt);
|
|
3738
|
+
}
|
|
3739
|
+
async onRunStart(opts) {
|
|
3740
|
+
if (!this.driver) {
|
|
3741
|
+
this.broadcast({ type: "sdd.spec.error", payload: { message: "No active spec session." } });
|
|
3742
|
+
return;
|
|
3743
|
+
}
|
|
3744
|
+
const graph = await this.driver.ensureTaskGraph();
|
|
3745
|
+
if (!graph) {
|
|
3746
|
+
this.broadcast({
|
|
3747
|
+
type: "sdd.spec.error",
|
|
3748
|
+
payload: { message: "No spec yet \u2014 finish the interview before starting a run." }
|
|
3749
|
+
});
|
|
3750
|
+
return;
|
|
3751
|
+
}
|
|
3752
|
+
const { runId } = await this.deps.startRun(this.driver, opts);
|
|
3753
|
+
this.broadcast({ type: "sdd.run.started", payload: { runId } });
|
|
3754
|
+
}
|
|
3755
|
+
// ── internals ───────────────────────────────────────────────────────────
|
|
3756
|
+
/** Run one interview turn against the isolated agent, then ingest + broadcast. */
|
|
3757
|
+
async runTurn(prompt) {
|
|
3758
|
+
this.busy = true;
|
|
3759
|
+
this.broadcast(this.snapshotMsg());
|
|
3760
|
+
try {
|
|
3761
|
+
const text = await this.deps.runInterviewTurn(prompt);
|
|
3762
|
+
this.lastAgentText = text;
|
|
3763
|
+
if (this.driver) await this.driver.ingestAgentOutput(text);
|
|
3764
|
+
this.broadcast({ type: "sdd.spec.agent_text", payload: { text } });
|
|
3765
|
+
} finally {
|
|
3766
|
+
this.busy = false;
|
|
3767
|
+
this.broadcast(this.snapshotMsg());
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
snapshotMsg() {
|
|
3771
|
+
const snap = this.driver?.snapshot();
|
|
3772
|
+
return {
|
|
3773
|
+
type: "sdd.spec.snapshot",
|
|
3774
|
+
payload: { ...snap, busy: this.busy }
|
|
3775
|
+
};
|
|
3776
|
+
}
|
|
3777
|
+
broadcast(msg) {
|
|
3778
|
+
const data = JSON.stringify(msg);
|
|
3779
|
+
for (const client of this.clients) {
|
|
3780
|
+
if (client.ws.readyState === 1) client.ws.send(data);
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
send(client, msg) {
|
|
3784
|
+
if (client.ws.readyState === 1) client.ws.send(JSON.stringify(msg));
|
|
3785
|
+
}
|
|
3786
|
+
};
|
|
3787
|
+
|
|
3788
|
+
// src/server/sdd-wizard-wiring.ts
|
|
3789
|
+
import * as path6 from "path";
|
|
3790
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3791
|
+
import {
|
|
3792
|
+
makeCommandVerifier,
|
|
3793
|
+
makeLlmSubtaskGenerator,
|
|
3794
|
+
SddBoardStore as SddBoardStore2,
|
|
3795
|
+
SddInterviewDriver,
|
|
3796
|
+
SddRunRegistry,
|
|
3797
|
+
SddSupervisor,
|
|
3798
|
+
SpecStore as SpecStore2,
|
|
3799
|
+
startSddRun,
|
|
3800
|
+
TaskGraphStore as TaskGraphStore2,
|
|
3801
|
+
WorktreeManager as WorktreeManager2
|
|
3802
|
+
} from "@wrongstack/core";
|
|
3803
|
+
function buildSddWizardDeps(opts) {
|
|
3804
|
+
const registry = new SddRunRegistry();
|
|
3805
|
+
let isolatedSeq = 0;
|
|
3806
|
+
const runIsolatedTurn = async (prompt, name2) => {
|
|
3807
|
+
const result = await opts.subagentFactory({
|
|
3808
|
+
id: `sdd-${name2.toLowerCase().replace(/\s+/g, "-")}-${isolatedSeq++}`,
|
|
3809
|
+
role: "executor",
|
|
3810
|
+
name: name2,
|
|
3811
|
+
disabledTools: ["delegate"],
|
|
3812
|
+
allowedCapabilities: ["fs.read", "net.outbound"]
|
|
3813
|
+
});
|
|
3814
|
+
try {
|
|
3815
|
+
const res = await result.agent.run([{ type: "text", text: prompt }]);
|
|
3816
|
+
return res.finalText ?? "";
|
|
3817
|
+
} finally {
|
|
3818
|
+
await result.dispose?.();
|
|
3819
|
+
}
|
|
3820
|
+
};
|
|
3821
|
+
return {
|
|
3822
|
+
makeDriver: () => new SddInterviewDriver({
|
|
3823
|
+
specStore: new SpecStore2({ baseDir: opts.paths.projectSpecs }),
|
|
3824
|
+
graphStore: new TaskGraphStore2({ baseDir: opts.paths.projectTaskGraphs }),
|
|
3825
|
+
sessionPath: path6.join(opts.paths.projectDir, "sdd-wizard-session.json")
|
|
3826
|
+
}),
|
|
3827
|
+
runInterviewTurn: (prompt) => runIsolatedTurn(prompt, "Spec Architect"),
|
|
3828
|
+
startRun: async (driver, { parallelSlots, defaultModel, defaultProvider, fallbackModels }) => {
|
|
3829
|
+
const graph = driver.getGraph();
|
|
3830
|
+
const tracker = driver.getTracker();
|
|
3831
|
+
if (!graph || !tracker) {
|
|
3832
|
+
throw new Error("No task graph to run \u2014 finish the interview first.");
|
|
3833
|
+
}
|
|
3834
|
+
let worktrees;
|
|
3835
|
+
if (process.env["WRONGSTACK_SDD_WORKTREES"] !== "0") {
|
|
3836
|
+
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3837
|
+
cwd: opts.projectRoot,
|
|
3838
|
+
encoding: "utf8",
|
|
3839
|
+
windowsHide: true
|
|
3840
|
+
}).stdout?.trim() === "true";
|
|
3841
|
+
if (inGit) worktrees = new WorktreeManager2({ projectRoot: opts.projectRoot, events: opts.events });
|
|
3842
|
+
}
|
|
3843
|
+
const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
|
|
3844
|
+
const verifyTask = makeCommandVerifier();
|
|
3845
|
+
const superviseFailure = opts.brain ? new SddSupervisor({
|
|
3846
|
+
brain: opts.brain,
|
|
3847
|
+
// The run-level fallback chain (chosen in the wizard) doubles as the
|
|
3848
|
+
// supervisor's reassign options — a `reassign` verdict rotates the
|
|
3849
|
+
// worker model on retry. Empty/undefined → reassign option dropped.
|
|
3850
|
+
reassignModels: fallbackModels,
|
|
3851
|
+
// LLM auto-split: decompose a retry-exhausted task into smaller
|
|
3852
|
+
// sub-tasks on an isolated read-only turn. Heavily validated +
|
|
3853
|
+
// bounded; an empty result degrades the split into a retry.
|
|
3854
|
+
generateSubtasks: makeLlmSubtaskGenerator({
|
|
3855
|
+
run: (prompt) => runIsolatedTurn(prompt, "Task Splitter")
|
|
3856
|
+
}),
|
|
3857
|
+
// The standalone brain is a tiered policy→LLM arbiter with NO
|
|
3858
|
+
// human-escalation wrapper (see index.ts), so it never blocks on a
|
|
3859
|
+
// human prompt — an unresolved verdict degrades to a bounded retry.
|
|
3860
|
+
// Safe to let the LLM layer actually pick reassign/split.
|
|
3861
|
+
requestLlmVerdict: true
|
|
3862
|
+
}).superviseFailure : void 0;
|
|
3863
|
+
const handle = startSddRun({
|
|
3864
|
+
tracker,
|
|
3865
|
+
graph,
|
|
3866
|
+
agent: opts.agent,
|
|
3867
|
+
projectRoot: opts.projectRoot,
|
|
3868
|
+
events: opts.events,
|
|
3869
|
+
subagentFactory: opts.subagentFactory,
|
|
3870
|
+
worktrees,
|
|
3871
|
+
boardStore,
|
|
3872
|
+
registry,
|
|
3873
|
+
parallelSlots,
|
|
3874
|
+
defaultModel,
|
|
3875
|
+
defaultProvider,
|
|
3876
|
+
fallbackModels,
|
|
3877
|
+
verifyTask,
|
|
3878
|
+
superviseFailure
|
|
3879
|
+
});
|
|
3880
|
+
void handle.completion.catch(() => {
|
|
3881
|
+
});
|
|
3882
|
+
return { runId: handle.runId };
|
|
3883
|
+
}
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
// src/server/sdd-wizard-routes.ts
|
|
3888
|
+
async function handleSddWizardRoute(_ws, msg, handlers) {
|
|
3889
|
+
if (!(msg.type.startsWith("sdd.spec.") || msg.type.startsWith("sdd.run."))) return false;
|
|
3890
|
+
await handlers.handleMessage(msg);
|
|
3891
|
+
return true;
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
// src/server/index.ts
|
|
3895
|
+
import { makeLightSubagentFactory } from "@wrongstack/runtime";
|
|
3896
|
+
|
|
3272
3897
|
// src/server/collaboration-ws-handler.ts
|
|
3273
3898
|
import { randomUUID } from "crypto";
|
|
3274
3899
|
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
@@ -3996,11 +4621,11 @@ var CollaborationWebSocketHandler = class {
|
|
|
3996
4621
|
|
|
3997
4622
|
// src/server/projects-manifest.ts
|
|
3998
4623
|
import * as fs5 from "fs/promises";
|
|
3999
|
-
import * as
|
|
4624
|
+
import * as path7 from "path";
|
|
4000
4625
|
import { projectSlug } from "@wrongstack/core";
|
|
4001
4626
|
function projectsJsonPath(globalConfigPath) {
|
|
4002
|
-
const base =
|
|
4003
|
-
return
|
|
4627
|
+
const base = path7.dirname(globalConfigPath);
|
|
4628
|
+
return path7.join(base, "projects.json");
|
|
4004
4629
|
}
|
|
4005
4630
|
async function loadManifest(globalConfigPath) {
|
|
4006
4631
|
try {
|
|
@@ -4013,15 +4638,15 @@ async function loadManifest(globalConfigPath) {
|
|
|
4013
4638
|
}
|
|
4014
4639
|
async function saveManifest(manifest, globalConfigPath) {
|
|
4015
4640
|
const file = projectsJsonPath(globalConfigPath);
|
|
4016
|
-
await fs5.mkdir(
|
|
4641
|
+
await fs5.mkdir(path7.dirname(file), { recursive: true });
|
|
4017
4642
|
await fs5.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
4018
4643
|
}
|
|
4019
4644
|
function generateProjectSlug(rootPath) {
|
|
4020
4645
|
return projectSlug(rootPath);
|
|
4021
4646
|
}
|
|
4022
4647
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
4023
|
-
const base =
|
|
4024
|
-
const dir =
|
|
4648
|
+
const base = path7.dirname(globalConfigPath);
|
|
4649
|
+
const dir = path7.join(base, "projects", slug);
|
|
4025
4650
|
await fs5.mkdir(dir, { recursive: true });
|
|
4026
4651
|
return dir;
|
|
4027
4652
|
}
|
|
@@ -4448,14 +5073,14 @@ function registerShutdownHandlers(res) {
|
|
|
4448
5073
|
|
|
4449
5074
|
// src/server/instance-registry.ts
|
|
4450
5075
|
import * as os from "os";
|
|
4451
|
-
import * as
|
|
5076
|
+
import * as path8 from "path";
|
|
4452
5077
|
import * as fs6 from "fs/promises";
|
|
4453
5078
|
import { atomicWrite as atomicWrite3 } from "@wrongstack/core";
|
|
4454
5079
|
function defaultBaseDir() {
|
|
4455
|
-
return
|
|
5080
|
+
return path8.join(os.homedir(), ".wrongstack");
|
|
4456
5081
|
}
|
|
4457
5082
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
4458
|
-
return
|
|
5083
|
+
return path8.join(baseDir, "webui-instances.json");
|
|
4459
5084
|
}
|
|
4460
5085
|
function isPidAlive(pid) {
|
|
4461
5086
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -4577,9 +5202,10 @@ function openBrowser(url, platform = process.platform) {
|
|
|
4577
5202
|
if (child.pid) {
|
|
4578
5203
|
try {
|
|
4579
5204
|
import("@wrongstack/tools").then(({ getProcessRegistry }) => {
|
|
5205
|
+
const pid = child.pid;
|
|
5206
|
+
if (pid === void 0) return;
|
|
4580
5207
|
getProcessRegistry().register({
|
|
4581
|
-
|
|
4582
|
-
pid: child.pid,
|
|
5208
|
+
pid,
|
|
4583
5209
|
name: "browser",
|
|
4584
5210
|
command: `${command} ${args.join(" ")}`,
|
|
4585
5211
|
startedAt: Date.now(),
|
|
@@ -4587,7 +5213,7 @@ function openBrowser(url, platform = process.platform) {
|
|
|
4587
5213
|
protected: true
|
|
4588
5214
|
});
|
|
4589
5215
|
child.on("exit", () => {
|
|
4590
|
-
getProcessRegistry().unregister(
|
|
5216
|
+
getProcessRegistry().unregister(pid);
|
|
4591
5217
|
});
|
|
4592
5218
|
}).catch(() => {
|
|
4593
5219
|
});
|
|
@@ -4617,7 +5243,7 @@ import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
|
4617
5243
|
|
|
4618
5244
|
// src/server/provider-config-io.ts
|
|
4619
5245
|
import * as fs7 from "fs/promises";
|
|
4620
|
-
import * as
|
|
5246
|
+
import * as path9 from "path";
|
|
4621
5247
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core";
|
|
4622
5248
|
import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
4623
5249
|
import { DefaultSecretVault } from "@wrongstack/core";
|
|
@@ -4669,7 +5295,7 @@ async function saveProviders(configPath, vault, providers) {
|
|
|
4669
5295
|
await atomicWrite4(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
4670
5296
|
}
|
|
4671
5297
|
function createProviderConfigIO(configPath) {
|
|
4672
|
-
const keyFile =
|
|
5298
|
+
const keyFile = path9.join(path9.dirname(configPath), ".key");
|
|
4673
5299
|
const vault = new DefaultSecretVault({ keyFile });
|
|
4674
5300
|
return {
|
|
4675
5301
|
load: () => loadSavedProviders(configPath, vault),
|
|
@@ -4823,7 +5449,10 @@ function createProviderHandlers(deps2) {
|
|
|
4823
5449
|
try {
|
|
4824
5450
|
const providers = await loadConfigProviders();
|
|
4825
5451
|
const result = upsertKey(providers, providerId, label, apiKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
4826
|
-
if (result.ok)
|
|
5452
|
+
if (result.ok) {
|
|
5453
|
+
await saveConfigProviders(providers);
|
|
5454
|
+
broadcastSaved(providers);
|
|
5455
|
+
}
|
|
4827
5456
|
sendResult2(ws, result.ok, result.message);
|
|
4828
5457
|
} catch (err) {
|
|
4829
5458
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -4833,7 +5462,10 @@ function createProviderHandlers(deps2) {
|
|
|
4833
5462
|
try {
|
|
4834
5463
|
const providers = await loadConfigProviders();
|
|
4835
5464
|
const result = deleteKey(providers, providerId, label);
|
|
4836
|
-
if (result.ok)
|
|
5465
|
+
if (result.ok) {
|
|
5466
|
+
await saveConfigProviders(providers);
|
|
5467
|
+
broadcastSaved(providers);
|
|
5468
|
+
}
|
|
4837
5469
|
sendResult2(ws, result.ok, result.message);
|
|
4838
5470
|
} catch (err) {
|
|
4839
5471
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -4843,7 +5475,10 @@ function createProviderHandlers(deps2) {
|
|
|
4843
5475
|
try {
|
|
4844
5476
|
const providers = await loadConfigProviders();
|
|
4845
5477
|
const result = setActiveKey(providers, providerId, label);
|
|
4846
|
-
if (result.ok)
|
|
5478
|
+
if (result.ok) {
|
|
5479
|
+
await saveConfigProviders(providers);
|
|
5480
|
+
broadcastSaved(providers);
|
|
5481
|
+
}
|
|
4847
5482
|
sendResult2(ws, result.ok, result.message);
|
|
4848
5483
|
} catch (err) {
|
|
4849
5484
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -4853,11 +5488,13 @@ function createProviderHandlers(deps2) {
|
|
|
4853
5488
|
try {
|
|
4854
5489
|
const providers = await loadConfigProviders();
|
|
4855
5490
|
const result = addProvider(providers, payload, (/* @__PURE__ */ new Date()).toISOString());
|
|
4856
|
-
if (result.ok)
|
|
5491
|
+
if (result.ok) {
|
|
5492
|
+
await saveConfigProviders(providers);
|
|
5493
|
+
broadcastSaved(providers);
|
|
5494
|
+
}
|
|
4857
5495
|
sendResult2(ws, result.ok, result.message);
|
|
4858
5496
|
if (result.ok) {
|
|
4859
5497
|
console.log(`[WebUI] Provider "${payload.id}" added via provider.add`);
|
|
4860
|
-
broadcastSaved(providers);
|
|
4861
5498
|
}
|
|
4862
5499
|
} catch (err) {
|
|
4863
5500
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -4867,7 +5504,10 @@ function createProviderHandlers(deps2) {
|
|
|
4867
5504
|
try {
|
|
4868
5505
|
const providers = await loadConfigProviders();
|
|
4869
5506
|
const result = removeProvider(providers, providerId);
|
|
4870
|
-
if (result.ok)
|
|
5507
|
+
if (result.ok) {
|
|
5508
|
+
await saveConfigProviders(providers);
|
|
5509
|
+
broadcastSaved(providers);
|
|
5510
|
+
}
|
|
4871
5511
|
sendResult2(ws, result.ok, result.message);
|
|
4872
5512
|
} catch (err) {
|
|
4873
5513
|
sendResult2(ws, false, errMessage(err));
|
|
@@ -5050,7 +5690,7 @@ function createModeHandlers(ctx) {
|
|
|
5050
5690
|
|
|
5051
5691
|
// src/server/project-handlers.ts
|
|
5052
5692
|
import * as fs9 from "fs/promises";
|
|
5053
|
-
import * as
|
|
5693
|
+
import * as path11 from "path";
|
|
5054
5694
|
import {
|
|
5055
5695
|
DefaultSessionStore as DefaultSessionStore2,
|
|
5056
5696
|
DefaultSystemPromptBuilder as DefaultSystemPromptBuilder3,
|
|
@@ -5059,13 +5699,13 @@ import {
|
|
|
5059
5699
|
|
|
5060
5700
|
// src/server/path-containment.ts
|
|
5061
5701
|
import * as fs8 from "fs/promises";
|
|
5062
|
-
import * as
|
|
5702
|
+
import * as path10 from "path";
|
|
5063
5703
|
function isPathInside(root, target) {
|
|
5064
|
-
const relative3 =
|
|
5065
|
-
return relative3 === "" || !relative3.startsWith("..") && !
|
|
5704
|
+
const relative3 = path10.relative(root, target);
|
|
5705
|
+
return relative3 === "" || !relative3.startsWith("..") && !path10.isAbsolute(relative3);
|
|
5066
5706
|
}
|
|
5067
5707
|
async function resolveWorkingDirInsideProject(projectRoot, inputPath) {
|
|
5068
|
-
const resolved =
|
|
5708
|
+
const resolved = path10.resolve(projectRoot, inputPath);
|
|
5069
5709
|
let stat3;
|
|
5070
5710
|
try {
|
|
5071
5711
|
stat3 = await fs8.stat(resolved);
|
|
@@ -5107,7 +5747,7 @@ function createProjectHandlers(ctx) {
|
|
|
5107
5747
|
}
|
|
5108
5748
|
const { root: addRoot, name: displayName } = parsed.value;
|
|
5109
5749
|
try {
|
|
5110
|
-
const resolved =
|
|
5750
|
+
const resolved = path11.resolve(addRoot);
|
|
5111
5751
|
await fs9.access(resolved);
|
|
5112
5752
|
const stat3 = await fs9.stat(resolved);
|
|
5113
5753
|
if (!stat3.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
@@ -5125,7 +5765,7 @@ function createProjectHandlers(ctx) {
|
|
|
5125
5765
|
});
|
|
5126
5766
|
return;
|
|
5127
5767
|
}
|
|
5128
|
-
const name2 = displayName?.trim() ||
|
|
5768
|
+
const name2 = displayName?.trim() || path11.basename(resolved);
|
|
5129
5769
|
const slug = generateProjectSlug(resolved);
|
|
5130
5770
|
await ensureProjectDataDir(slug, ctx.globalConfigPath);
|
|
5131
5771
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5138,7 +5778,7 @@ function createProjectHandlers(ctx) {
|
|
|
5138
5778
|
} catch (err) {
|
|
5139
5779
|
send(ws, {
|
|
5140
5780
|
type: "projects.added",
|
|
5141
|
-
payload: { name:
|
|
5781
|
+
payload: { name: path11.basename(addRoot), root: addRoot, slug: "", message: errMessage(err) }
|
|
5142
5782
|
});
|
|
5143
5783
|
}
|
|
5144
5784
|
},
|
|
@@ -5153,7 +5793,7 @@ function createProjectHandlers(ctx) {
|
|
|
5153
5793
|
}
|
|
5154
5794
|
const { root: selRoot, name: selName } = parsed.value;
|
|
5155
5795
|
try {
|
|
5156
|
-
const resolved =
|
|
5796
|
+
const resolved = path11.resolve(selRoot);
|
|
5157
5797
|
try {
|
|
5158
5798
|
await fs9.access(resolved);
|
|
5159
5799
|
const stat3 = await fs9.stat(resolved);
|
|
@@ -5163,7 +5803,7 @@ function createProjectHandlers(ctx) {
|
|
|
5163
5803
|
type: "projects.selected",
|
|
5164
5804
|
payload: {
|
|
5165
5805
|
root: selRoot,
|
|
5166
|
-
name: selName ||
|
|
5806
|
+
name: selName || path11.basename(selRoot),
|
|
5167
5807
|
message: `Cannot switch: ${errMessage(err)}`
|
|
5168
5808
|
}
|
|
5169
5809
|
});
|
|
@@ -5175,7 +5815,7 @@ function createProjectHandlers(ctx) {
|
|
|
5175
5815
|
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
5176
5816
|
entry.lastWorkingDir = resolved;
|
|
5177
5817
|
} else {
|
|
5178
|
-
const name2 = selName?.trim() ||
|
|
5818
|
+
const name2 = selName?.trim() || path11.basename(resolved);
|
|
5179
5819
|
const slug = generateProjectSlug(resolved);
|
|
5180
5820
|
manifest.projects.push({
|
|
5181
5821
|
name: name2,
|
|
@@ -5214,8 +5854,8 @@ function createProjectHandlers(ctx) {
|
|
|
5214
5854
|
});
|
|
5215
5855
|
} catch {
|
|
5216
5856
|
}
|
|
5217
|
-
const newSessionsDir =
|
|
5218
|
-
|
|
5857
|
+
const newSessionsDir = path11.join(
|
|
5858
|
+
path11.dirname(ctx.globalConfigPath),
|
|
5219
5859
|
"projects",
|
|
5220
5860
|
switchSlug,
|
|
5221
5861
|
"sessions"
|
|
@@ -5254,7 +5894,7 @@ function createProjectHandlers(ctx) {
|
|
|
5254
5894
|
sessionId: newSession.id,
|
|
5255
5895
|
projectSlug: switchSlug,
|
|
5256
5896
|
projectRoot: resolved,
|
|
5257
|
-
projectName:
|
|
5897
|
+
projectName: path11.basename(resolved),
|
|
5258
5898
|
workingDir: resolved,
|
|
5259
5899
|
clientType: "webui",
|
|
5260
5900
|
pid: process.pid,
|
|
@@ -5266,8 +5906,8 @@ function createProjectHandlers(ctx) {
|
|
|
5266
5906
|
type: "projects.selected",
|
|
5267
5907
|
payload: {
|
|
5268
5908
|
root: resolved,
|
|
5269
|
-
name: selName ||
|
|
5270
|
-
message: `Switched to ${selName ||
|
|
5909
|
+
name: selName || path11.basename(resolved),
|
|
5910
|
+
message: `Switched to ${selName || path11.basename(resolved)}`
|
|
5271
5911
|
}
|
|
5272
5912
|
});
|
|
5273
5913
|
broadcast(ctx.clients, {
|
|
@@ -5287,7 +5927,7 @@ function createProjectHandlers(ctx) {
|
|
|
5287
5927
|
type: "projects.selected",
|
|
5288
5928
|
payload: {
|
|
5289
5929
|
root: selRoot,
|
|
5290
|
-
name: selName ||
|
|
5930
|
+
name: selName || path11.basename(selRoot),
|
|
5291
5931
|
message: errMessage(err)
|
|
5292
5932
|
}
|
|
5293
5933
|
});
|
|
@@ -5318,7 +5958,7 @@ function createProjectHandlers(ctx) {
|
|
|
5318
5958
|
}
|
|
5319
5959
|
|
|
5320
5960
|
// src/server/session-handlers.ts
|
|
5321
|
-
import * as
|
|
5961
|
+
import * as path12 from "path";
|
|
5322
5962
|
import {
|
|
5323
5963
|
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
5324
5964
|
repairToolUseAdjacency,
|
|
@@ -5660,7 +6300,7 @@ function createSessionHandlers(ctx) {
|
|
|
5660
6300
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
5661
6301
|
const projectRoot = ctx.getProjectRoot();
|
|
5662
6302
|
const rewinder = new DefaultSessionRewinder(
|
|
5663
|
-
|
|
6303
|
+
path12.join(projectRoot, ".wrongstack", "sessions"),
|
|
5664
6304
|
projectRoot
|
|
5665
6305
|
);
|
|
5666
6306
|
const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
|
|
@@ -5675,7 +6315,7 @@ function createSessionHandlers(ctx) {
|
|
|
5675
6315
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
5676
6316
|
const projectRoot = ctx.getProjectRoot();
|
|
5677
6317
|
const rewinder = new DefaultSessionRewinder(
|
|
5678
|
-
|
|
6318
|
+
path12.join(projectRoot, ".wrongstack", "sessions"),
|
|
5679
6319
|
projectRoot
|
|
5680
6320
|
);
|
|
5681
6321
|
await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
|
|
@@ -5982,10 +6622,24 @@ async function handleAutoPhaseRoute(_ws, msg, handlers) {
|
|
|
5982
6622
|
return true;
|
|
5983
6623
|
}
|
|
5984
6624
|
|
|
6625
|
+
// src/server/specs-routes.ts
|
|
6626
|
+
async function handleSpecsRoute(_ws, msg, handlers) {
|
|
6627
|
+
if (!msg.type.startsWith("specs.")) return false;
|
|
6628
|
+
await handlers.handleMessage(msg);
|
|
6629
|
+
return true;
|
|
6630
|
+
}
|
|
6631
|
+
|
|
6632
|
+
// src/server/sdd-board-routes.ts
|
|
6633
|
+
async function handleSddBoardRoute(_ws, msg, handlers) {
|
|
6634
|
+
if (!msg.type.startsWith("sdd.board.")) return false;
|
|
6635
|
+
await handlers.handleMessage(msg);
|
|
6636
|
+
return true;
|
|
6637
|
+
}
|
|
6638
|
+
|
|
5985
6639
|
// src/server/setup-events.ts
|
|
5986
6640
|
import * as fs10 from "fs/promises";
|
|
5987
6641
|
import { watch as fsWatch } from "fs";
|
|
5988
|
-
import * as
|
|
6642
|
+
import * as path13 from "path";
|
|
5989
6643
|
function setupEvents(deps2) {
|
|
5990
6644
|
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
|
|
5991
6645
|
const disposers = [];
|
|
@@ -6450,7 +7104,7 @@ function setupEvents(deps2) {
|
|
|
6450
7104
|
if (wpaths?.projectStatus) {
|
|
6451
7105
|
try {
|
|
6452
7106
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
6453
|
-
const dir =
|
|
7107
|
+
const dir = path13.dirname(statusFile);
|
|
6454
7108
|
await fs10.mkdir(dir, { recursive: true });
|
|
6455
7109
|
await fs10.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
6456
7110
|
} catch (err) {
|
|
@@ -6459,7 +7113,7 @@ function setupEvents(deps2) {
|
|
|
6459
7113
|
}
|
|
6460
7114
|
});
|
|
6461
7115
|
if (wpaths?.projectStatus && wpaths.configDir) {
|
|
6462
|
-
const projectsDir =
|
|
7116
|
+
const projectsDir = path13.join(wpaths.configDir, "projects");
|
|
6463
7117
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
6464
7118
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
6465
7119
|
const DEBOUNCE_MS = 150;
|
|
@@ -6486,7 +7140,7 @@ function setupEvents(deps2) {
|
|
|
6486
7140
|
);
|
|
6487
7141
|
};
|
|
6488
7142
|
const metricsInterval = setInterval(logWatcherMetrics, 6e4);
|
|
6489
|
-
const broadcastStatus = (
|
|
7143
|
+
const broadcastStatus = (_projectHash, statusData, actualDelayMs) => {
|
|
6490
7144
|
broadcast2(clients, { type: "client.status_update", payload: statusData });
|
|
6491
7145
|
if (watcherMetrics) {
|
|
6492
7146
|
watcherMetrics.broadcastsSent++;
|
|
@@ -6527,9 +7181,9 @@ function setupEvents(deps2) {
|
|
|
6527
7181
|
if (eventType === "change") {
|
|
6528
7182
|
if (filename == null) return;
|
|
6529
7183
|
if (watcherMetrics) watcherMetrics.fileChangesDetected++;
|
|
6530
|
-
const targetFile =
|
|
7184
|
+
const targetFile = path13.join(projectsDir, String(filename));
|
|
6531
7185
|
if (targetFile.endsWith("status.json")) {
|
|
6532
|
-
const projectHash2 =
|
|
7186
|
+
const projectHash2 = path13.basename(path13.dirname(targetFile));
|
|
6533
7187
|
if (knownProjectHashes.size > 0 && !knownProjectHashes.has(projectHash2)) {
|
|
6534
7188
|
return;
|
|
6535
7189
|
}
|
|
@@ -6587,7 +7241,7 @@ function setupEvents(deps2) {
|
|
|
6587
7241
|
}
|
|
6588
7242
|
});
|
|
6589
7243
|
}
|
|
6590
|
-
const globalRoot = globalConfigPath ?
|
|
7244
|
+
const globalRoot = globalConfigPath ? path13.dirname(globalConfigPath) : void 0;
|
|
6591
7245
|
if (globalRoot) {
|
|
6592
7246
|
const broadcastSessions = async () => {
|
|
6593
7247
|
try {
|
|
@@ -6661,10 +7315,10 @@ function setupEvents(deps2) {
|
|
|
6661
7315
|
// src/server/custom-context-modes.ts
|
|
6662
7316
|
import { listContextWindowModes, atomicWrite as atomicWrite5 } from "@wrongstack/core";
|
|
6663
7317
|
import * as fs11 from "fs/promises";
|
|
6664
|
-
import * as
|
|
7318
|
+
import * as path14 from "path";
|
|
6665
7319
|
var STORE_FILENAME = "custom-context-modes.json";
|
|
6666
7320
|
function storePath(wrongstackDir) {
|
|
6667
|
-
return
|
|
7321
|
+
return path14.join(wrongstackDir, STORE_FILENAME);
|
|
6668
7322
|
}
|
|
6669
7323
|
var BUILTIN_IDS = /* @__PURE__ */ new Set(["balanced", "frugal", "deep", "archival"]);
|
|
6670
7324
|
function createCustomModeStore(wrongstackDir) {
|
|
@@ -6796,12 +7450,12 @@ function createEternalSubscription(subscribe, broadcast2, clientsRef) {
|
|
|
6796
7450
|
|
|
6797
7451
|
// src/server/shell-open.ts
|
|
6798
7452
|
import * as fs12 from "fs/promises";
|
|
6799
|
-
import * as
|
|
7453
|
+
import * as path15 from "path";
|
|
6800
7454
|
import { spawn as spawn2 } from "child_process";
|
|
6801
7455
|
var METACHAR_REGEX = /[&|<>^"'`\n\r]/;
|
|
6802
7456
|
async function handleShellOpen(req, logger) {
|
|
6803
7457
|
try {
|
|
6804
|
-
const resolved =
|
|
7458
|
+
const resolved = path15.resolve(req.path);
|
|
6805
7459
|
await fs12.access(resolved);
|
|
6806
7460
|
if (METACHAR_REGEX.test(resolved)) {
|
|
6807
7461
|
return { success: false, message: "Path contains unsupported characters." };
|
|
@@ -6911,15 +7565,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
6911
7565
|
if (!m) continue;
|
|
6912
7566
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
6913
7567
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
6914
|
-
let
|
|
6915
|
-
if (
|
|
7568
|
+
let path17 = m[3] ?? "";
|
|
7569
|
+
if (path17 === "") {
|
|
6916
7570
|
i += 1;
|
|
6917
|
-
|
|
7571
|
+
path17 = parts[i + 1] ?? parts[i] ?? "";
|
|
6918
7572
|
i += 1;
|
|
6919
7573
|
}
|
|
6920
|
-
if (!
|
|
6921
|
-
const prev = counts.get(
|
|
6922
|
-
counts.set(
|
|
7574
|
+
if (!path17) continue;
|
|
7575
|
+
const prev = counts.get(path17) ?? { added: 0, deleted: 0 };
|
|
7576
|
+
counts.set(path17, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
6923
7577
|
}
|
|
6924
7578
|
};
|
|
6925
7579
|
parseNumstat(unstagedNumstat);
|
|
@@ -6931,7 +7585,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
6931
7585
|
if (!rec || rec.length < 3) continue;
|
|
6932
7586
|
const x = rec[0] ?? " ";
|
|
6933
7587
|
const y = rec[1] ?? " ";
|
|
6934
|
-
const
|
|
7588
|
+
const path17 = rec.slice(3);
|
|
6935
7589
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
6936
7590
|
if (isRename) i += 1;
|
|
6937
7591
|
let status;
|
|
@@ -6943,13 +7597,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
6943
7597
|
else if (x === "D" || y === "D") status = "D";
|
|
6944
7598
|
else status = "M";
|
|
6945
7599
|
const staged = x !== " " && x !== "?";
|
|
6946
|
-
let added = counts.get(
|
|
6947
|
-
let deleted = counts.get(
|
|
7600
|
+
let added = counts.get(path17)?.added ?? 0;
|
|
7601
|
+
let deleted = counts.get(path17)?.deleted ?? 0;
|
|
6948
7602
|
if (status === "?") {
|
|
6949
7603
|
added = 0;
|
|
6950
7604
|
deleted = 0;
|
|
6951
7605
|
}
|
|
6952
|
-
files.push({ path:
|
|
7606
|
+
files.push({ path: path17, status, added, deleted, staged });
|
|
6953
7607
|
}
|
|
6954
7608
|
send(ws, { type: "git.changes", payload: { files } });
|
|
6955
7609
|
} catch (err) {
|
|
@@ -6960,21 +7614,21 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
6960
7614
|
}
|
|
6961
7615
|
}
|
|
6962
7616
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
6963
|
-
async function handleGitDiff(ws, projectRoot,
|
|
7617
|
+
async function handleGitDiff(ws, projectRoot, path17) {
|
|
6964
7618
|
const cwd = projectRoot || void 0;
|
|
6965
|
-
const reply = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
6966
|
-
if (!
|
|
7619
|
+
const reply = (extra) => send(ws, { type: "git.diff", payload: { path: path17, ...extra } });
|
|
7620
|
+
if (!path17 || path17.includes("\0") || path17.includes("..") || nodePath.isAbsolute(path17)) {
|
|
6967
7621
|
reply({ oldText: "", newText: "", error: "invalid path" });
|
|
6968
7622
|
return;
|
|
6969
7623
|
}
|
|
6970
7624
|
try {
|
|
6971
7625
|
const git = makeGit(cwd);
|
|
6972
7626
|
const { readFile: readFile9 } = await import("fs/promises");
|
|
6973
|
-
const { join:
|
|
6974
|
-
const oldText = await git(["show", `HEAD:${
|
|
7627
|
+
const { join: join12 } = await import("path");
|
|
7628
|
+
const oldText = await git(["show", `HEAD:${path17}`]);
|
|
6975
7629
|
let newText = "";
|
|
6976
7630
|
try {
|
|
6977
|
-
const abs = cwd ?
|
|
7631
|
+
const abs = cwd ? join12(cwd, path17) : path17;
|
|
6978
7632
|
const buf = await readFile9(abs);
|
|
6979
7633
|
if (buf.includes(0)) {
|
|
6980
7634
|
reply({ oldText: "", newText: "", binary: true });
|
|
@@ -7185,6 +7839,7 @@ async function startWebUI(opts = {}) {
|
|
|
7185
7839
|
toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
|
|
7186
7840
|
toolRegistry.register(makeMailSendTool({ projectDir: wpaths.projectDir, events }));
|
|
7187
7841
|
toolRegistry.register(makeMailInboxTool({ projectDir: wpaths.projectDir, events }));
|
|
7842
|
+
applyToolDescriptionModes(toolRegistry, config.tools?.descriptionMode);
|
|
7188
7843
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
7189
7844
|
const mcpRegistry = new MCPRegistry({
|
|
7190
7845
|
toolRegistry,
|
|
@@ -7228,7 +7883,7 @@ async function startWebUI(opts = {}) {
|
|
|
7228
7883
|
sessionId: session.id,
|
|
7229
7884
|
projectSlug: wpaths.projectSlug,
|
|
7230
7885
|
projectRoot,
|
|
7231
|
-
projectName:
|
|
7886
|
+
projectName: path16.basename(projectRoot),
|
|
7232
7887
|
workingDir,
|
|
7233
7888
|
clientType: "webui",
|
|
7234
7889
|
pid: process.pid,
|
|
@@ -7248,7 +7903,7 @@ async function startWebUI(opts = {}) {
|
|
|
7248
7903
|
const hqTelemetry = createHqPublisherFromEnv({
|
|
7249
7904
|
clientKind: "webui",
|
|
7250
7905
|
projectRoot,
|
|
7251
|
-
projectName:
|
|
7906
|
+
projectName: path16.basename(projectRoot),
|
|
7252
7907
|
appConfig: config,
|
|
7253
7908
|
socketFactory: (url) => new WebSocket2(url)
|
|
7254
7909
|
});
|
|
@@ -7260,7 +7915,7 @@ async function startWebUI(opts = {}) {
|
|
|
7260
7915
|
events,
|
|
7261
7916
|
sessionId: session.id,
|
|
7262
7917
|
projectRoot,
|
|
7263
|
-
projectName:
|
|
7918
|
+
projectName: path16.basename(projectRoot),
|
|
7264
7919
|
globalRoot: wpaths.globalRoot,
|
|
7265
7920
|
initialAgents: statusTracker?.getAgents(),
|
|
7266
7921
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7316,9 +7971,9 @@ async function startWebUI(opts = {}) {
|
|
|
7316
7971
|
};
|
|
7317
7972
|
const skillLoader = config.features.skills ? new DefaultSkillLoader2({ paths: wpaths }) : void 0;
|
|
7318
7973
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
7319
|
-
manifestPath:
|
|
7320
|
-
projectSkillsDir:
|
|
7321
|
-
globalSkillsDir:
|
|
7974
|
+
manifestPath: path16.join(wstackGlobalRoot2(), "installed-skills.json"),
|
|
7975
|
+
projectSkillsDir: path16.join(projectRoot, ".wrongstack", "skills"),
|
|
7976
|
+
globalSkillsDir: path16.join(wstackGlobalRoot2(), "skills"),
|
|
7322
7977
|
projectHash: projectHash(projectRoot),
|
|
7323
7978
|
skillLoader
|
|
7324
7979
|
}) : void 0;
|
|
@@ -7422,6 +8077,8 @@ async function startWebUI(opts = {}) {
|
|
|
7422
8077
|
context.meta["enhanceDelayMs"] = autonomyCfg["enhanceDelayMs"] ?? 6e4;
|
|
7423
8078
|
context.meta["enhanceLanguage"] = autonomyCfg["enhanceLanguage"] ?? "original";
|
|
7424
8079
|
context.meta["nextPrediction"] = config.nextPrediction ?? false;
|
|
8080
|
+
context.meta["fallbackModels"] = config.fallbackModels ?? [];
|
|
8081
|
+
context.meta["fallbackAuto"] = config.fallbackAuto !== false;
|
|
7425
8082
|
context.meta["featureMcp"] = config.features.mcp !== false;
|
|
7426
8083
|
context.meta["featurePlugins"] = config.features.plugins !== false;
|
|
7427
8084
|
context.meta["featureMemory"] = config.features.memory !== false;
|
|
@@ -7479,7 +8136,9 @@ async function startWebUI(opts = {}) {
|
|
|
7479
8136
|
"reasoningMode",
|
|
7480
8137
|
"reasoningEffort",
|
|
7481
8138
|
"reasoningPreserve",
|
|
7482
|
-
"cacheTtl"
|
|
8139
|
+
"cacheTtl",
|
|
8140
|
+
"fallbackModels",
|
|
8141
|
+
"fallbackAuto"
|
|
7483
8142
|
];
|
|
7484
8143
|
const prefSnapshot = () => {
|
|
7485
8144
|
const snapshot = {};
|
|
@@ -7510,6 +8169,8 @@ async function startWebUI(opts = {}) {
|
|
|
7510
8169
|
if (typeof payload["enhanceLanguage"] === "string") setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
|
|
7511
8170
|
if (autonomyTouched) decrypted.autonomy = autonomyCfg;
|
|
7512
8171
|
if (typeof payload["nextPrediction"] === "boolean") decrypted.nextPrediction = payload["nextPrediction"];
|
|
8172
|
+
if (Array.isArray(payload["fallbackModels"])) decrypted.fallbackModels = payload["fallbackModels"];
|
|
8173
|
+
if (typeof payload["fallbackAuto"] === "boolean") decrypted.fallbackAuto = payload["fallbackAuto"];
|
|
7513
8174
|
const FEATURE_MAP = {
|
|
7514
8175
|
featureMcp: "mcp",
|
|
7515
8176
|
featurePlugins: "plugins",
|
|
@@ -7790,6 +8451,29 @@ async function startWebUI(opts = {}) {
|
|
|
7790
8451
|
events,
|
|
7791
8452
|
projectRoot
|
|
7792
8453
|
);
|
|
8454
|
+
const specsHandler = new SpecsWebSocketHandler(wpaths.projectSpecs, wpaths.projectTaskGraphs);
|
|
8455
|
+
const sddBoardHandler = new SddBoardWebSocketHandler(wpaths.projectSddBoards);
|
|
8456
|
+
const sddWizardHandler = new SddWizardWebSocketHandler(
|
|
8457
|
+
buildSddWizardDeps({
|
|
8458
|
+
agent,
|
|
8459
|
+
events,
|
|
8460
|
+
projectRoot,
|
|
8461
|
+
brain,
|
|
8462
|
+
subagentFactory: makeLightSubagentFactory({
|
|
8463
|
+
container,
|
|
8464
|
+
providerRegistry,
|
|
8465
|
+
toolRegistry,
|
|
8466
|
+
session,
|
|
8467
|
+
projectRoot
|
|
8468
|
+
}),
|
|
8469
|
+
paths: {
|
|
8470
|
+
projectSpecs: wpaths.projectSpecs,
|
|
8471
|
+
projectTaskGraphs: wpaths.projectTaskGraphs,
|
|
8472
|
+
projectSddBoards: wpaths.projectSddBoards,
|
|
8473
|
+
projectDir: wpaths.projectDir
|
|
8474
|
+
}
|
|
8475
|
+
})
|
|
8476
|
+
);
|
|
7793
8477
|
const worktreeHandler = new WorktreeWebSocketHandler(events, logger);
|
|
7794
8478
|
const terminalHandler = new TerminalWebSocketHandler(() => workingDir, logger);
|
|
7795
8479
|
const collabHandler = new CollaborationWebSocketHandler(
|
|
@@ -7829,7 +8513,7 @@ async function startWebUI(opts = {}) {
|
|
|
7829
8513
|
inputCost,
|
|
7830
8514
|
outputCost,
|
|
7831
8515
|
cacheReadCost,
|
|
7832
|
-
projectName:
|
|
8516
|
+
projectName: path16.basename(projectRoot) || projectRoot,
|
|
7833
8517
|
projectRoot,
|
|
7834
8518
|
cwd: workingDir,
|
|
7835
8519
|
mode: modeId,
|
|
@@ -7921,6 +8605,9 @@ async function startWebUI(opts = {}) {
|
|
|
7921
8605
|
}));
|
|
7922
8606
|
});
|
|
7923
8607
|
autoPhaseHandler.addClient(ws);
|
|
8608
|
+
specsHandler.addClient(ws);
|
|
8609
|
+
sddBoardHandler.addClient(ws);
|
|
8610
|
+
sddWizardHandler.addClient(ws);
|
|
7924
8611
|
worktreeHandler.addClient(ws);
|
|
7925
8612
|
collabHandler.addClient(ws);
|
|
7926
8613
|
terminalHandler.addClient(ws);
|
|
@@ -8045,21 +8732,21 @@ async function startWebUI(opts = {}) {
|
|
|
8045
8732
|
});
|
|
8046
8733
|
}
|
|
8047
8734
|
async function touchProjectEntry(root, workDir) {
|
|
8048
|
-
const resolved =
|
|
8735
|
+
const resolved = path16.resolve(root);
|
|
8049
8736
|
const manifest = await loadManifest(globalConfigPath);
|
|
8050
8737
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8051
|
-
const existing = manifest.projects.find((p) =>
|
|
8738
|
+
const existing = manifest.projects.find((p) => path16.resolve(p.root) === resolved);
|
|
8052
8739
|
if (existing) {
|
|
8053
8740
|
existing.lastSeen = now;
|
|
8054
|
-
if (workDir) existing.lastWorkingDir =
|
|
8741
|
+
if (workDir) existing.lastWorkingDir = path16.resolve(workDir);
|
|
8055
8742
|
} else {
|
|
8056
8743
|
manifest.projects.push({
|
|
8057
|
-
name:
|
|
8744
|
+
name: path16.basename(resolved),
|
|
8058
8745
|
root: resolved,
|
|
8059
8746
|
slug: generateProjectSlug(resolved),
|
|
8060
8747
|
createdAt: now,
|
|
8061
8748
|
lastSeen: now,
|
|
8062
|
-
lastWorkingDir: workDir ?
|
|
8749
|
+
lastWorkingDir: workDir ? path16.resolve(workDir) : void 0
|
|
8063
8750
|
});
|
|
8064
8751
|
}
|
|
8065
8752
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -8085,6 +8772,9 @@ async function startWebUI(opts = {}) {
|
|
|
8085
8772
|
let mailboxRoutes;
|
|
8086
8773
|
let brainRoutes;
|
|
8087
8774
|
let autoPhaseRoutes;
|
|
8775
|
+
let specsRoutes;
|
|
8776
|
+
let sddBoardRoutes;
|
|
8777
|
+
let sddWizardRoutes;
|
|
8088
8778
|
async function handleMessage(ws, _client, msg) {
|
|
8089
8779
|
if (await handleProviderRoute(ws, msg, providerRoutes)) return;
|
|
8090
8780
|
if (await handleSessionRoute(ws, msg, sessionRoutes)) return;
|
|
@@ -8094,6 +8784,9 @@ async function startWebUI(opts = {}) {
|
|
|
8094
8784
|
if (await handleMailboxRoute(ws, msg, mailboxRoutes)) return;
|
|
8095
8785
|
if (await handleBrainRoute(ws, msg, brainRoutes)) return;
|
|
8096
8786
|
if (await handleAutoPhaseRoute(ws, msg, autoPhaseRoutes)) return;
|
|
8787
|
+
if (await handleSpecsRoute(ws, msg, specsRoutes)) return;
|
|
8788
|
+
if (await handleSddBoardRoute(ws, msg, sddBoardRoutes)) return;
|
|
8789
|
+
if (await handleSddWizardRoute(ws, msg, sddWizardRoutes)) return;
|
|
8097
8790
|
switch (msg.type) {
|
|
8098
8791
|
// Collaboration messages short-circuit the user/agent flow.
|
|
8099
8792
|
// They don't touch runLock, the agent loop, or the message queue —
|
|
@@ -8390,6 +9083,10 @@ async function startWebUI(opts = {}) {
|
|
|
8390
9083
|
config.features.skills = payload["featureSkills"];
|
|
8391
9084
|
if (typeof payload["featureModelsRegistry"] === "boolean")
|
|
8392
9085
|
config.features.modelsRegistry = payload["featureModelsRegistry"];
|
|
9086
|
+
if (Array.isArray(payload["fallbackModels"]))
|
|
9087
|
+
config.fallbackModels = payload["fallbackModels"];
|
|
9088
|
+
if (typeof payload["fallbackAuto"] === "boolean")
|
|
9089
|
+
config.fallbackAuto = payload["fallbackAuto"];
|
|
8393
9090
|
if (typeof payload["contextAutoCompact"] === "boolean") {
|
|
8394
9091
|
if (payload["contextAutoCompact"] && autoCompactor) {
|
|
8395
9092
|
pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
@@ -8452,22 +9149,7 @@ async function startWebUI(opts = {}) {
|
|
|
8452
9149
|
const saved = await providerHandlers.loadConfigProviders();
|
|
8453
9150
|
send(ws, {
|
|
8454
9151
|
type: "providers.saved",
|
|
8455
|
-
payload: {
|
|
8456
|
-
providers: Object.entries(saved).map(([id, cfg]) => {
|
|
8457
|
-
const keys = normalizeKeys(cfg);
|
|
8458
|
-
return {
|
|
8459
|
-
id,
|
|
8460
|
-
family: cfg.family ?? id,
|
|
8461
|
-
baseUrl: cfg.baseUrl,
|
|
8462
|
-
apiKeys: keys.map((k) => ({
|
|
8463
|
-
label: k.label,
|
|
8464
|
-
maskedKey: maskedKey(k.apiKey),
|
|
8465
|
-
isActive: k.label === cfg.activeKey,
|
|
8466
|
-
createdAt: k.createdAt
|
|
8467
|
-
}))
|
|
8468
|
-
};
|
|
8469
|
-
})
|
|
8470
|
-
}
|
|
9152
|
+
payload: { providers: projectSavedProviders(saved) }
|
|
8471
9153
|
});
|
|
8472
9154
|
},
|
|
8473
9155
|
listProviderModels: async (ws, msg) => {
|
|
@@ -8677,7 +9359,7 @@ async function startWebUI(opts = {}) {
|
|
|
8677
9359
|
sendResult2(ws, false, parsed.message);
|
|
8678
9360
|
return;
|
|
8679
9361
|
}
|
|
8680
|
-
return handleMailboxMessages(ws, { projectRoot, globalRoot:
|
|
9362
|
+
return handleMailboxMessages(ws, { projectRoot, globalRoot: path16.dirname(globalConfigPath) }, parsed.value);
|
|
8681
9363
|
},
|
|
8682
9364
|
agents: (ws, msg) => {
|
|
8683
9365
|
const parsed = validateMailboxAgentsPayload(msg.payload);
|
|
@@ -8685,16 +9367,16 @@ async function startWebUI(opts = {}) {
|
|
|
8685
9367
|
sendResult2(ws, false, parsed.message);
|
|
8686
9368
|
return;
|
|
8687
9369
|
}
|
|
8688
|
-
return handleMailboxAgents(ws, { projectRoot, globalRoot:
|
|
9370
|
+
return handleMailboxAgents(ws, { projectRoot, globalRoot: path16.dirname(globalConfigPath) }, parsed.value);
|
|
8689
9371
|
},
|
|
8690
|
-
clear: (ws) => handleMailboxClear(ws, { projectRoot, globalRoot:
|
|
9372
|
+
clear: (ws) => handleMailboxClear(ws, { projectRoot, globalRoot: path16.dirname(globalConfigPath) }),
|
|
8691
9373
|
purge: (ws, msg) => {
|
|
8692
9374
|
const parsed = validateMailboxPurgePayload(msg.payload);
|
|
8693
9375
|
if (!parsed.ok) {
|
|
8694
9376
|
sendResult2(ws, false, parsed.message);
|
|
8695
9377
|
return;
|
|
8696
9378
|
}
|
|
8697
|
-
return handleMailboxPurge(ws, { projectRoot, globalRoot:
|
|
9379
|
+
return handleMailboxPurge(ws, { projectRoot, globalRoot: path16.dirname(globalConfigPath) }, parsed.value);
|
|
8698
9380
|
}
|
|
8699
9381
|
};
|
|
8700
9382
|
brainRoutes = {
|
|
@@ -8741,6 +9423,15 @@ async function startWebUI(opts = {}) {
|
|
|
8741
9423
|
autoPhaseRoutes = {
|
|
8742
9424
|
handleMessage: (msg) => autoPhaseHandler.handleMessage(msg)
|
|
8743
9425
|
};
|
|
9426
|
+
specsRoutes = {
|
|
9427
|
+
handleMessage: (msg) => specsHandler.handleMessage(msg)
|
|
9428
|
+
};
|
|
9429
|
+
sddBoardRoutes = {
|
|
9430
|
+
handleMessage: (msg) => sddBoardHandler.handleMessage(msg)
|
|
9431
|
+
};
|
|
9432
|
+
sddWizardRoutes = {
|
|
9433
|
+
handleMessage: (msg) => sddWizardHandler.handleMessage(msg)
|
|
9434
|
+
};
|
|
8744
9435
|
const watcherMetrics = {
|
|
8745
9436
|
fileChangesDetected: 0,
|
|
8746
9437
|
filesProcessed: 0,
|
|
@@ -8753,7 +9444,7 @@ async function startWebUI(opts = {}) {
|
|
|
8753
9444
|
};
|
|
8754
9445
|
const httpServer = createHttpServer({
|
|
8755
9446
|
host: wsHost,
|
|
8756
|
-
distDir:
|
|
9447
|
+
distDir: path16.resolve(import.meta.dirname, "../../dist"),
|
|
8757
9448
|
wsPort,
|
|
8758
9449
|
globalRoot: wpaths.globalRoot,
|
|
8759
9450
|
apiToken: wsToken,
|
|
@@ -8762,7 +9453,7 @@ async function startWebUI(opts = {}) {
|
|
|
8762
9453
|
void fleetBroadcast?.();
|
|
8763
9454
|
}
|
|
8764
9455
|
});
|
|
8765
|
-
const registryBaseDir =
|
|
9456
|
+
const registryBaseDir = path16.dirname(globalConfigPath);
|
|
8766
9457
|
httpServer.listen(httpPort, wsHost, () => {
|
|
8767
9458
|
const openUrl = `http://${wsHost}:${httpPort}`;
|
|
8768
9459
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
@@ -8774,7 +9465,7 @@ async function startWebUI(opts = {}) {
|
|
|
8774
9465
|
wsPort,
|
|
8775
9466
|
host: wsHost,
|
|
8776
9467
|
projectRoot,
|
|
8777
|
-
projectName:
|
|
9468
|
+
projectName: path16.basename(projectRoot) || projectRoot,
|
|
8778
9469
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8779
9470
|
url: `http://${wsHost}:${httpPort}`
|
|
8780
9471
|
},
|
|
@@ -8817,11 +9508,15 @@ async function startWebUI(opts = {}) {
|
|
|
8817
9508
|
}
|
|
8818
9509
|
export {
|
|
8819
9510
|
AutoPhaseWebSocketHandler,
|
|
9511
|
+
SddBoardWebSocketHandler,
|
|
9512
|
+
SddWizardWebSocketHandler,
|
|
9513
|
+
SpecsWebSocketHandler,
|
|
8820
9514
|
WorktreeWebSocketHandler,
|
|
8821
9515
|
addProvider,
|
|
8822
9516
|
broadcast,
|
|
8823
9517
|
browserOpenCommand,
|
|
8824
9518
|
buildCspHeader,
|
|
9519
|
+
buildSddWizardDeps,
|
|
8825
9520
|
createCustomModeStore,
|
|
8826
9521
|
createEternalSubscription,
|
|
8827
9522
|
createHttpServer,
|