@wrongstack/webui-server 0.285.0 → 0.287.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1538 -508
- package/dist/index.js.map +4 -4
- package/dist/server/autophase-ws-handler.d.ts +17 -1
- package/dist/server/autophase-ws-handler.d.ts.map +1 -1
- package/dist/server/backend-services.d.ts +12 -23
- package/dist/server/backend-services.d.ts.map +1 -1
- package/dist/server/brain-routes.d.ts +2 -0
- package/dist/server/brain-routes.d.ts.map +1 -1
- package/dist/server/collaboration-ws-handler.d.ts +27 -19
- package/dist/server/collaboration-ws-handler.d.ts.map +1 -1
- package/dist/server/context-meta.d.ts.map +1 -1
- package/dist/server/entry.js +1513 -506
- package/dist/server/entry.js.map +4 -4
- package/dist/server/handlers.js.map +2 -2
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/index.d.ts +6 -5
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/instance-registry.d.ts +3 -1
- package/dist/server/instance-registry.d.ts.map +1 -1
- package/dist/server/kanban-routes.d.ts +3 -0
- package/dist/server/kanban-routes.d.ts.map +1 -1
- package/dist/server/mailbox-handlers.d.ts +12 -0
- package/dist/server/mailbox-handlers.d.ts.map +1 -1
- package/dist/server/mailbox-routes.d.ts +1 -0
- package/dist/server/mailbox-routes.d.ts.map +1 -1
- package/dist/server/mcp-handlers.d.ts +10 -1
- package/dist/server/mcp-handlers.d.ts.map +1 -1
- package/dist/server/mcp-routes.d.ts +4 -0
- package/dist/server/mcp-routes.d.ts.map +1 -1
- package/dist/server/memory-handlers.d.ts +41 -9
- package/dist/server/memory-handlers.d.ts.map +1 -1
- package/dist/server/message-dispatcher.d.ts.map +1 -1
- package/dist/server/mode-handlers.d.ts.map +1 -1
- package/dist/server/port-utils.d.ts +48 -10
- package/dist/server/port-utils.d.ts.map +1 -1
- package/dist/server/pre-context-services.d.ts +3 -1
- package/dist/server/pre-context-services.d.ts.map +1 -1
- package/dist/server/pref-helpers.d.ts +1 -1
- package/dist/server/pref-helpers.d.ts.map +1 -1
- package/dist/server/provider-handlers.d.ts +13 -0
- package/dist/server/provider-handlers.d.ts.map +1 -1
- package/dist/server/provider-routes.d.ts +2 -0
- package/dist/server/provider-routes.d.ts.map +1 -1
- package/dist/server/routes.d.ts +23 -20
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/sdd-wizard-wiring.d.ts +31 -1
- package/dist/server/sdd-wizard-wiring.d.ts.map +1 -1
- package/dist/server/server-runtime.d.ts +1 -0
- package/dist/server/server-runtime.d.ts.map +1 -1
- package/dist/server/session-handlers.d.ts +17 -4
- package/dist/server/session-handlers.d.ts.map +1 -1
- package/dist/server/session-history.d.ts +29 -0
- package/dist/server/session-history.d.ts.map +1 -0
- package/dist/server/setup-events.d.ts +1 -1
- package/dist/server/setup-events.d.ts.map +1 -1
- package/dist/server/standalone-session-identity.d.ts +35 -0
- package/dist/server/standalone-session-identity.d.ts.map +1 -0
- package/dist/server/start-webui.d.ts.map +1 -1
- package/dist/server/ws-payload-validation.d.ts +4 -0
- package/dist/server/ws-payload-validation.d.ts.map +1 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -38,12 +38,13 @@ function commitsSince(cwd, baseSha, branch) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
var AutoPhaseWebSocketHandler = class {
|
|
41
|
-
constructor(agent, context, logger, storeDir, events, projectRoot) {
|
|
41
|
+
constructor(agent, context, logger, storeDir, events, projectRoot, onBoardState) {
|
|
42
42
|
this.agent = agent;
|
|
43
43
|
this.context = context;
|
|
44
44
|
this.logger = logger;
|
|
45
45
|
this.events = events;
|
|
46
46
|
this.projectRoot = projectRoot;
|
|
47
|
+
this.onBoardState = onBoardState;
|
|
47
48
|
this.store = new PhaseStore({ baseDir: storeDir });
|
|
48
49
|
}
|
|
49
50
|
agent;
|
|
@@ -51,6 +52,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
51
52
|
logger;
|
|
52
53
|
events;
|
|
53
54
|
projectRoot;
|
|
55
|
+
onBoardState;
|
|
54
56
|
orchestrator = null;
|
|
55
57
|
graph = null;
|
|
56
58
|
store;
|
|
@@ -395,6 +397,15 @@ Type: ${task.type}`;
|
|
|
395
397
|
if (!this.graph) return;
|
|
396
398
|
const state = this.buildState(activePhaseId);
|
|
397
399
|
this.broadcast({ type: "autophase.state", payload: state });
|
|
400
|
+
if (this.onBoardState) {
|
|
401
|
+
try {
|
|
402
|
+
this.onBoardState(this.graph.id, state);
|
|
403
|
+
} catch (err) {
|
|
404
|
+
this.logger.error(
|
|
405
|
+
`[AutoPhase] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
398
409
|
}
|
|
399
410
|
buildState(activePhaseId) {
|
|
400
411
|
if (!this.graph) {
|
|
@@ -2618,10 +2629,7 @@ function cspSourceFromUrl(rawUrl) {
|
|
|
2618
2629
|
return void 0;
|
|
2619
2630
|
}
|
|
2620
2631
|
}
|
|
2621
|
-
var
|
|
2622
|
-
"'sha256-6PXDy0zrpXa6mvYOl11bZ8nubNUL7ushPUhGDZtaexg='",
|
|
2623
|
-
"'sha256-6sIdwbEBx7jj0drqSHHm7MqvmoYD3CQ4lp8Zp8blcb0='"
|
|
2624
|
-
];
|
|
2632
|
+
var EXTRA_SCRIPT_SOURCES = ["'wasm-unsafe-eval'"];
|
|
2625
2633
|
function buildCspHeader(wsPort, requestHost, publicWsUrl) {
|
|
2626
2634
|
const connect = /* @__PURE__ */ new Set([
|
|
2627
2635
|
"'self'",
|
|
@@ -2635,7 +2643,7 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
|
|
|
2635
2643
|
}
|
|
2636
2644
|
const publicWsSource = publicWsUrl ? cspSourceFromUrl(publicWsUrl) : void 0;
|
|
2637
2645
|
if (publicWsSource) connect.add(publicWsSource);
|
|
2638
|
-
const scriptSrc = ["'self'", ...
|
|
2646
|
+
const scriptSrc = ["'self'", ...EXTRA_SCRIPT_SOURCES].join(" ");
|
|
2639
2647
|
return `default-src 'self'; script-src ${scriptSrc}; style-src 'self' 'unsafe-inline'; connect-src ${Array.from(connect).join(" ")}; img-src 'self' data:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`;
|
|
2640
2648
|
}
|
|
2641
2649
|
function isInsideDist(candidate, distDir) {
|
|
@@ -2996,7 +3004,7 @@ function mapStatus(raw) {
|
|
|
2996
3004
|
return "stopped";
|
|
2997
3005
|
}
|
|
2998
3006
|
}
|
|
2999
|
-
function toView(info) {
|
|
3007
|
+
function toView(info, health) {
|
|
3000
3008
|
const view = {
|
|
3001
3009
|
name: info.name,
|
|
3002
3010
|
transport: info.transport,
|
|
@@ -3008,6 +3016,7 @@ function toView(info) {
|
|
|
3008
3016
|
};
|
|
3009
3017
|
if (info.description !== void 0) view.description = info.description;
|
|
3010
3018
|
if (info.lazy !== void 0) view.lazy = info.lazy;
|
|
3019
|
+
if (health !== void 0) view.health = health;
|
|
3011
3020
|
return view;
|
|
3012
3021
|
}
|
|
3013
3022
|
function deps(ws, globalConfigPath, registry) {
|
|
@@ -3033,7 +3042,13 @@ async function handleMcpList(ws, _msg, globalConfigPath, mcpRegistry) {
|
|
|
3033
3042
|
registry: mcpRegistry,
|
|
3034
3043
|
presets: allServers()
|
|
3035
3044
|
});
|
|
3036
|
-
|
|
3045
|
+
const health = new Map(
|
|
3046
|
+
(typeof mcpRegistry.operationalHealth === "function" ? mcpRegistry.operationalHealth() : []).map((item) => [item.name, item])
|
|
3047
|
+
);
|
|
3048
|
+
send(ws, {
|
|
3049
|
+
type: "mcp.list",
|
|
3050
|
+
payload: { servers: servers.map((server) => toView(server, health.get(server.name))) }
|
|
3051
|
+
});
|
|
3037
3052
|
}
|
|
3038
3053
|
async function handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry) {
|
|
3039
3054
|
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
@@ -3183,10 +3198,119 @@ async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
|
|
|
3183
3198
|
payload: { success: result.ok, message: result.message }
|
|
3184
3199
|
});
|
|
3185
3200
|
}
|
|
3201
|
+
async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3202
|
+
if (!mcpRegistry) return sendContentError(ws, "resources", "", "MCP registry is not available.");
|
|
3203
|
+
const payload = payloadRecord(msg);
|
|
3204
|
+
let serverName = "";
|
|
3205
|
+
try {
|
|
3206
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3207
|
+
const refresh = payload["refresh"] === true;
|
|
3208
|
+
const [resources, resourceTemplates] = await Promise.all([
|
|
3209
|
+
mcpRegistry.listResources(serverName, { refresh }),
|
|
3210
|
+
mcpRegistry.listResourceTemplates(serverName, { refresh })
|
|
3211
|
+
]);
|
|
3212
|
+
send(ws, {
|
|
3213
|
+
type: "mcp.resources",
|
|
3214
|
+
payload: { name: serverName, resources, resourceTemplates }
|
|
3215
|
+
});
|
|
3216
|
+
} catch (err) {
|
|
3217
|
+
sendContentError(ws, "resources", serverName, errorMessage(err));
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3221
|
+
if (!mcpRegistry) return sendContentError(ws, "prompts", "", "MCP registry is not available.");
|
|
3222
|
+
const payload = payloadRecord(msg);
|
|
3223
|
+
let serverName = "";
|
|
3224
|
+
try {
|
|
3225
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3226
|
+
const prompts = await mcpRegistry.listPrompts(serverName, {
|
|
3227
|
+
refresh: payload["refresh"] === true
|
|
3228
|
+
});
|
|
3229
|
+
send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
|
|
3230
|
+
} catch (err) {
|
|
3231
|
+
sendContentError(ws, "prompts", serverName, errorMessage(err));
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3235
|
+
if (!mcpRegistry)
|
|
3236
|
+
return sendContentError(ws, "resource.read", "", "MCP registry is not available.");
|
|
3237
|
+
const payload = payloadRecord(msg);
|
|
3238
|
+
let serverName = "";
|
|
3239
|
+
try {
|
|
3240
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3241
|
+
const insertion = await mcpRegistry.selectResourceForInsertion(
|
|
3242
|
+
serverName,
|
|
3243
|
+
requiredPayloadString(payload, "uri")
|
|
3244
|
+
);
|
|
3245
|
+
send(ws, { type: "mcp.content.selected", payload: insertion });
|
|
3246
|
+
} catch (err) {
|
|
3247
|
+
sendContentError(ws, "resource.read", serverName, errorMessage(err));
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3251
|
+
if (!mcpRegistry) return sendContentError(ws, "prompt.get", "", "MCP registry is not available.");
|
|
3252
|
+
const payload = payloadRecord(msg);
|
|
3253
|
+
let serverName = "";
|
|
3254
|
+
try {
|
|
3255
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3256
|
+
const insertion = await mcpRegistry.selectPromptForInsertion(
|
|
3257
|
+
serverName,
|
|
3258
|
+
requiredPayloadString(payload, "prompt"),
|
|
3259
|
+
promptArguments(payload["arguments"])
|
|
3260
|
+
);
|
|
3261
|
+
send(ws, { type: "mcp.content.selected", payload: insertion });
|
|
3262
|
+
} catch (err) {
|
|
3263
|
+
sendContentError(ws, "prompt.get", serverName, errorMessage(err));
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
function payloadRecord(msg) {
|
|
3267
|
+
return msg.payload && typeof msg.payload === "object" && !Array.isArray(msg.payload) ? msg.payload : {};
|
|
3268
|
+
}
|
|
3269
|
+
function requiredPayloadString(payload, field) {
|
|
3270
|
+
const value = payload[field];
|
|
3271
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
3272
|
+
throw new Error(`MCP payload field "${field}" must be a non-empty string`);
|
|
3273
|
+
}
|
|
3274
|
+
return value;
|
|
3275
|
+
}
|
|
3276
|
+
function promptArguments(value) {
|
|
3277
|
+
if (value === void 0) return void 0;
|
|
3278
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3279
|
+
throw new Error("MCP prompt arguments must be an object");
|
|
3280
|
+
}
|
|
3281
|
+
const args = {};
|
|
3282
|
+
for (const [key, item] of Object.entries(value)) {
|
|
3283
|
+
if (typeof item !== "string") throw new Error(`MCP prompt argument "${key}" must be a string`);
|
|
3284
|
+
args[key] = item;
|
|
3285
|
+
}
|
|
3286
|
+
return args;
|
|
3287
|
+
}
|
|
3288
|
+
function sendContentError(ws, action, name2, error) {
|
|
3289
|
+
send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
|
|
3290
|
+
}
|
|
3291
|
+
function errorMessage(err) {
|
|
3292
|
+
return err instanceof Error ? err.message : String(err);
|
|
3293
|
+
}
|
|
3186
3294
|
|
|
3187
3295
|
// src/server/memory-handlers.ts
|
|
3296
|
+
function isSuperMemoryStore(store) {
|
|
3297
|
+
const s = store;
|
|
3298
|
+
return typeof s.stats === "function" && typeof s.listSuper === "function" && typeof s.getSuperMemory === "function" && typeof s.updateSuperMemory === "function" && typeof s.deleteSuperMemory === "function";
|
|
3299
|
+
}
|
|
3300
|
+
function requiresSuperMemory(command) {
|
|
3301
|
+
return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
|
|
3302
|
+
}
|
|
3188
3303
|
async function handleMemoryList(ws, memoryStore) {
|
|
3189
3304
|
try {
|
|
3305
|
+
if (isSuperMemoryStore(memoryStore)) {
|
|
3306
|
+
const [stats, memories] = await Promise.all([
|
|
3307
|
+
memoryStore.stats(),
|
|
3308
|
+
memoryStore.listSuper()
|
|
3309
|
+
]);
|
|
3310
|
+
const text2 = memories.length === 0 ? "\u{1F9E0} Super Memory is empty." : formatSuperMemoryText(stats, memories);
|
|
3311
|
+
send(ws, { type: "memory.list", payload: { text: text2 } });
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3190
3314
|
const text = await memoryStore.readAll();
|
|
3191
3315
|
send(ws, { type: "memory.list", payload: { text } });
|
|
3192
3316
|
} catch (err) {
|
|
@@ -3196,26 +3320,128 @@ async function handleMemoryList(ws, memoryStore) {
|
|
|
3196
3320
|
});
|
|
3197
3321
|
}
|
|
3198
3322
|
}
|
|
3199
|
-
|
|
3200
|
-
const
|
|
3323
|
+
function formatSuperMemoryText(stats, memories) {
|
|
3324
|
+
const active = stats.byStatus["active"] ?? 0;
|
|
3325
|
+
const stale = stats.byStatus["stale"] ?? 0;
|
|
3326
|
+
const archived = stats.byStatus["archived"] ?? 0;
|
|
3327
|
+
const lines = [
|
|
3328
|
+
"## \u{1F9E0} Super Memory",
|
|
3329
|
+
"",
|
|
3330
|
+
`**Total:** ${stats.total} \xB7 \u{1F7E2} ${active} active \xB7 \u{1F7E1} ${stale} stale \xB7 \u{1F535} ${archived} archived \xB7 **edges:** ${stats.edges}`,
|
|
3331
|
+
""
|
|
3332
|
+
];
|
|
3333
|
+
for (const m of memories) {
|
|
3334
|
+
const tags = m.tags.length > 0 ? ` \`${m.tags.slice(0, 3).join("` `")}\`` : "";
|
|
3335
|
+
const icon = m.status === "active" ? "\u{1F7E2}" : m.status === "stale" ? "\u{1F7E1}" : m.status === "archived" ? "\u{1F535}" : "\u26AA";
|
|
3336
|
+
const preview = m.text.replace(/\s+/g, " ").trim().slice(0, 140);
|
|
3337
|
+
lines.push(`- ${icon} \`${m.id.slice(0, 12)}\u2026\` [${m.kind}] ${preview}${tags}`);
|
|
3338
|
+
}
|
|
3339
|
+
return lines.join("\n");
|
|
3340
|
+
}
|
|
3341
|
+
async function handleSuperMemoryList(ws, memoryStore) {
|
|
3342
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3343
|
+
send(ws, { type: "memory.super.list", payload: { error: requiresSuperMemory("memory.super.list") } });
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3201
3346
|
try {
|
|
3202
|
-
|
|
3203
|
-
|
|
3347
|
+
const [stats, memories] = await Promise.all([
|
|
3348
|
+
memoryStore.stats(),
|
|
3349
|
+
memoryStore.listSuper()
|
|
3350
|
+
]);
|
|
3351
|
+
send(ws, { type: "memory.super.list", payload: { memories, stats } });
|
|
3204
3352
|
} catch (err) {
|
|
3205
|
-
|
|
3353
|
+
send(ws, { type: "memory.super.list", payload: { error: errMessage(err) } });
|
|
3206
3354
|
}
|
|
3207
3355
|
}
|
|
3208
|
-
async function
|
|
3209
|
-
|
|
3356
|
+
async function handleSuperMemoryGet(ws, msg, memoryStore) {
|
|
3357
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3358
|
+
send(ws, { type: "memory.super.get", payload: { error: requiresSuperMemory("memory.super.get") } });
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3361
|
+
const { id } = msg.payload;
|
|
3362
|
+
if (!id) {
|
|
3363
|
+
send(ws, { type: "memory.super.get", payload: { error: "id is required" } });
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3210
3366
|
try {
|
|
3211
|
-
const
|
|
3212
|
-
|
|
3213
|
-
ws,
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
);
|
|
3367
|
+
const memory = await memoryStore.getSuperMemory(id);
|
|
3368
|
+
if (!memory) {
|
|
3369
|
+
send(ws, { type: "memory.super.get", payload: { error: `Memory "${id}" not found.` } });
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
send(ws, { type: "memory.super.get", payload: { memory } });
|
|
3217
3373
|
} catch (err) {
|
|
3218
|
-
|
|
3374
|
+
send(ws, { type: "memory.super.get", payload: { error: errMessage(err) } });
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
async function handleSuperMemoryUpdate(ws, msg, memoryStore) {
|
|
3378
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3379
|
+
send(ws, { type: "memory.super.update", payload: { error: requiresSuperMemory("memory.super.update") } });
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
const payload = msg.payload;
|
|
3383
|
+
const id = payload["id"];
|
|
3384
|
+
if (!id) {
|
|
3385
|
+
send(ws, { type: "memory.super.update", payload: { error: "id is required" } });
|
|
3386
|
+
return;
|
|
3387
|
+
}
|
|
3388
|
+
const patch = { ...payload };
|
|
3389
|
+
delete patch["id"];
|
|
3390
|
+
if (Object.keys(patch).length === 0) {
|
|
3391
|
+
send(ws, { type: "memory.super.update", payload: { error: "No fields to update." } });
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
try {
|
|
3395
|
+
const memory = await memoryStore.updateSuperMemory(id, patch);
|
|
3396
|
+
send(ws, { type: "memory.super.update", payload: { memory } });
|
|
3397
|
+
} catch (err) {
|
|
3398
|
+
send(ws, { type: "memory.super.update", payload: { error: errMessage(err) } });
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
async function handleSuperMemoryRemember(ws, msg, memoryStore) {
|
|
3402
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3403
|
+
send(ws, { type: "memory.super.remember", payload: { error: requiresSuperMemory("memory.super.remember") } });
|
|
3404
|
+
return;
|
|
3405
|
+
}
|
|
3406
|
+
const payload = msg.payload;
|
|
3407
|
+
const text = payload["text"];
|
|
3408
|
+
if (!text || !text.trim()) {
|
|
3409
|
+
send(ws, { type: "memory.super.remember", payload: { error: "text is required" } });
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
try {
|
|
3413
|
+
const memory = await memoryStore.rememberSuper({
|
|
3414
|
+
text: text.trim(),
|
|
3415
|
+
kind: payload["kind"],
|
|
3416
|
+
scope: payload["scope"],
|
|
3417
|
+
tags: payload["tags"],
|
|
3418
|
+
anchors: payload["anchors"],
|
|
3419
|
+
importance: payload["importance"],
|
|
3420
|
+
confidence: payload["confidence"],
|
|
3421
|
+
freshness: payload["freshness"],
|
|
3422
|
+
supersedes: payload["supersedes"],
|
|
3423
|
+
contradicts: payload["contradicts"]
|
|
3424
|
+
});
|
|
3425
|
+
send(ws, { type: "memory.super.remember", payload: { memory } });
|
|
3426
|
+
} catch (err) {
|
|
3427
|
+
send(ws, { type: "memory.super.remember", payload: { error: errMessage(err) } });
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
async function handleSuperMemoryDelete(ws, msg, memoryStore) {
|
|
3431
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3432
|
+
send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
const { id, reason } = msg.payload;
|
|
3436
|
+
if (!id) {
|
|
3437
|
+
send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
try {
|
|
3441
|
+
await memoryStore.deleteSuperMemory(id, reason);
|
|
3442
|
+
send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
|
|
3443
|
+
} catch (err) {
|
|
3444
|
+
send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
|
|
3219
3445
|
}
|
|
3220
3446
|
}
|
|
3221
3447
|
|
|
@@ -3265,6 +3491,16 @@ function openBrowser(url, platform = process.platform) {
|
|
|
3265
3491
|
// src/server/port-utils.ts
|
|
3266
3492
|
import * as net from "node:net";
|
|
3267
3493
|
import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
|
|
3494
|
+
var SURFACE_DEFAULT_PORTS = {
|
|
3495
|
+
webui: { http: 3456, ws: 3457 },
|
|
3496
|
+
simpleui: { http: 3466, ws: 3467 }
|
|
3497
|
+
};
|
|
3498
|
+
function surfaceLabel(surface) {
|
|
3499
|
+
return surface === "webui" ? "WebUI" : "SimpleUI";
|
|
3500
|
+
}
|
|
3501
|
+
function getSurfaceDefaultPorts(surface) {
|
|
3502
|
+
return { ...SURFACE_DEFAULT_PORTS[surface] };
|
|
3503
|
+
}
|
|
3268
3504
|
function isPortFree(host, port) {
|
|
3269
3505
|
return new Promise((resolve10) => {
|
|
3270
3506
|
const srv = net.createServer();
|
|
@@ -3821,6 +4057,8 @@ var SddBoardWebSocketHandler = class {
|
|
|
3821
4057
|
import * as path9 from "node:path";
|
|
3822
4058
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3823
4059
|
import {
|
|
4060
|
+
DefaultTaskStore,
|
|
4061
|
+
TaskTracker,
|
|
3824
4062
|
WorktreeManager as WorktreeManager2,
|
|
3825
4063
|
ToolValidationError as ToolValidationError4
|
|
3826
4064
|
} from "@wrongstack/core";
|
|
@@ -3836,6 +4074,63 @@ import {
|
|
|
3836
4074
|
startSddRun,
|
|
3837
4075
|
TaskGraphStore
|
|
3838
4076
|
} from "@wrongstack/sdd";
|
|
4077
|
+
function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
4078
|
+
const runTracker = tracker ?? (() => {
|
|
4079
|
+
const t = new TaskTracker({ store: new DefaultTaskStore() });
|
|
4080
|
+
t.setGraph(graph);
|
|
4081
|
+
return t;
|
|
4082
|
+
})();
|
|
4083
|
+
const worktreesEnabled = config.worktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
|
|
4084
|
+
let worktrees;
|
|
4085
|
+
if (worktreesEnabled) {
|
|
4086
|
+
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
4087
|
+
cwd: deps2.projectRoot,
|
|
4088
|
+
encoding: "utf8",
|
|
4089
|
+
windowsHide: true
|
|
4090
|
+
}).stdout?.trim() === "true";
|
|
4091
|
+
if (inGit) {
|
|
4092
|
+
void cleanupStaleSddWorktrees({
|
|
4093
|
+
projectRoot: deps2.projectRoot,
|
|
4094
|
+
boardsDir: deps2.projectSddBoards
|
|
4095
|
+
}).catch(() => void 0);
|
|
4096
|
+
worktrees = new WorktreeManager2({
|
|
4097
|
+
projectRoot: deps2.projectRoot,
|
|
4098
|
+
events: deps2.events,
|
|
4099
|
+
sessionId: () => deps2.agent.ctx.session?.id
|
|
4100
|
+
});
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
const superviseFailure = deps2.brain && deps2.runIsolatedTurn ? new SddSupervisor({
|
|
4104
|
+
brain: deps2.brain,
|
|
4105
|
+
reassignModels: config.fallbackModels,
|
|
4106
|
+
generateSubtasks: makeLlmSubtaskGenerator({
|
|
4107
|
+
run: (prompt) => deps2.runIsolatedTurn(prompt, "Task Splitter")
|
|
4108
|
+
}),
|
|
4109
|
+
requestLlmVerdict: true
|
|
4110
|
+
}).superviseFailure : deps2.brain ? new SddSupervisor({
|
|
4111
|
+
brain: deps2.brain,
|
|
4112
|
+
reassignModels: config.fallbackModels,
|
|
4113
|
+
requestLlmVerdict: true
|
|
4114
|
+
}).superviseFailure : void 0;
|
|
4115
|
+
return startSddRun({
|
|
4116
|
+
tracker: runTracker,
|
|
4117
|
+
graph,
|
|
4118
|
+
agent: deps2.agent,
|
|
4119
|
+
projectRoot: deps2.projectRoot,
|
|
4120
|
+
events: deps2.events,
|
|
4121
|
+
sessionId: () => deps2.agent.ctx.session?.id,
|
|
4122
|
+
subagentFactory: deps2.subagentFactory,
|
|
4123
|
+
boardStore: new SddBoardStore2({ baseDir: deps2.projectSddBoards }),
|
|
4124
|
+
registry: deps2.registry ?? new SddRunRegistry(),
|
|
4125
|
+
verifyTask: makeCommandVerifier(),
|
|
4126
|
+
...superviseFailure ? { superviseFailure } : {},
|
|
4127
|
+
...config.parallelSlots !== void 0 ? { parallelSlots: config.parallelSlots } : {},
|
|
4128
|
+
...config.defaultModel !== void 0 ? { defaultModel: config.defaultModel } : {},
|
|
4129
|
+
...config.defaultProvider !== void 0 ? { defaultProvider: config.defaultProvider } : {},
|
|
4130
|
+
...config.fallbackModels !== void 0 ? { fallbackModels: config.fallbackModels } : {},
|
|
4131
|
+
...worktrees ? { worktrees } : {}
|
|
4132
|
+
});
|
|
4133
|
+
}
|
|
3839
4134
|
var PLANNING_ONLY_GUARD = "SYSTEM: You are running a PLANNING-ONLY specification interview. Do NOT write, create, or edit any files, and do NOT run shell/terminal commands or use any code-editing tools \u2014 they are disabled here and any attempt will fail and waste the turn. Respond with TEXT ONLY: ask your question, or emit the requested spec / plan / task JSON. All code is written later, automatically, once the plan is approved and the multi-agent run starts.\n\n---\n\n";
|
|
3840
4135
|
function buildSddWizardDeps(opts) {
|
|
3841
4136
|
const registry = new SddRunRegistry();
|
|
@@ -3871,64 +4166,27 @@ function buildSddWizardDeps(opts) {
|
|
|
3871
4166
|
field: "taskGraph"
|
|
3872
4167
|
});
|
|
3873
4168
|
}
|
|
3874
|
-
const
|
|
3875
|
-
let worktrees;
|
|
3876
|
-
if (worktreesEnabled) {
|
|
3877
|
-
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3878
|
-
cwd: opts.projectRoot,
|
|
3879
|
-
encoding: "utf8",
|
|
3880
|
-
windowsHide: true
|
|
3881
|
-
}).stdout?.trim() === "true";
|
|
3882
|
-
if (inGit) {
|
|
3883
|
-
await cleanupStaleSddWorktrees({
|
|
3884
|
-
projectRoot: opts.projectRoot,
|
|
3885
|
-
boardsDir: opts.paths.projectSddBoards
|
|
3886
|
-
}).catch(() => void 0);
|
|
3887
|
-
worktrees = new WorktreeManager2({
|
|
3888
|
-
projectRoot: opts.projectRoot,
|
|
3889
|
-
events: opts.events,
|
|
3890
|
-
sessionId: () => opts.agent.ctx.session?.id
|
|
3891
|
-
});
|
|
3892
|
-
}
|
|
3893
|
-
}
|
|
3894
|
-
const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
|
|
3895
|
-
const verifyTask = makeCommandVerifier();
|
|
3896
|
-
const superviseFailure = opts.brain ? new SddSupervisor({
|
|
3897
|
-
brain: opts.brain,
|
|
3898
|
-
// The run-level fallback chain (chosen in the wizard) doubles as the
|
|
3899
|
-
// supervisor's reassign options — a `reassign` verdict rotates the
|
|
3900
|
-
// worker model on retry. Empty/undefined → reassign option dropped.
|
|
3901
|
-
reassignModels: fallbackModels,
|
|
3902
|
-
// LLM auto-split: decompose a retry-exhausted task into smaller
|
|
3903
|
-
// sub-tasks on an isolated read-only turn. Heavily validated +
|
|
3904
|
-
// bounded; an empty result degrades the split into a retry.
|
|
3905
|
-
generateSubtasks: makeLlmSubtaskGenerator({
|
|
3906
|
-
run: (prompt) => runIsolatedTurn(prompt, "Task Splitter")
|
|
3907
|
-
}),
|
|
3908
|
-
// The standalone brain is a tiered policy→LLM arbiter with NO
|
|
3909
|
-
// human-escalation wrapper (see index.ts), so it never blocks on a
|
|
3910
|
-
// human prompt — an unresolved verdict degrades to a bounded retry.
|
|
3911
|
-
// Safe to let the LLM layer actually pick reassign/split.
|
|
3912
|
-
requestLlmVerdict: true
|
|
3913
|
-
}).superviseFailure : void 0;
|
|
3914
|
-
const handle = startSddRun({
|
|
3915
|
-
tracker,
|
|
4169
|
+
const handle = startSddRunFromGraph(
|
|
3916
4170
|
graph,
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
4171
|
+
{
|
|
4172
|
+
agent: opts.agent,
|
|
4173
|
+
events: opts.events,
|
|
4174
|
+
projectRoot: opts.projectRoot,
|
|
4175
|
+
subagentFactory: opts.subagentFactory,
|
|
4176
|
+
projectSddBoards: opts.paths.projectSddBoards,
|
|
4177
|
+
registry,
|
|
4178
|
+
runIsolatedTurn,
|
|
4179
|
+
...opts.brain ? { brain: opts.brain } : {}
|
|
4180
|
+
},
|
|
4181
|
+
{
|
|
4182
|
+
...parallelSlots !== void 0 ? { parallelSlots } : {},
|
|
4183
|
+
...defaultModel !== void 0 ? { defaultModel } : {},
|
|
4184
|
+
...defaultProvider !== void 0 ? { defaultProvider } : {},
|
|
4185
|
+
...fallbackModels !== void 0 ? { fallbackModels } : {},
|
|
4186
|
+
...useWorktrees !== void 0 ? { worktrees: useWorktrees } : {}
|
|
4187
|
+
},
|
|
4188
|
+
tracker
|
|
4189
|
+
);
|
|
3932
4190
|
void handle.completion.catch(() => {
|
|
3933
4191
|
});
|
|
3934
4192
|
return { runId: handle.runId };
|
|
@@ -4072,6 +4330,31 @@ var SddWizardWebSocketHandler = class {
|
|
|
4072
4330
|
}
|
|
4073
4331
|
};
|
|
4074
4332
|
|
|
4333
|
+
// src/server/session-history.ts
|
|
4334
|
+
function toSessionHistoryEntry(summary, currentSessionId) {
|
|
4335
|
+
return {
|
|
4336
|
+
id: summary.id,
|
|
4337
|
+
title: summary.title,
|
|
4338
|
+
...summary.name !== void 0 ? { name: summary.name } : {},
|
|
4339
|
+
startedAt: summary.startedAt,
|
|
4340
|
+
...summary.endedAt !== void 0 ? { endedAt: summary.endedAt } : {},
|
|
4341
|
+
model: summary.model,
|
|
4342
|
+
provider: summary.provider,
|
|
4343
|
+
tokenTotal: summary.tokenTotal,
|
|
4344
|
+
...summary.iterationCount !== void 0 ? { iterationCount: summary.iterationCount } : {},
|
|
4345
|
+
...summary.toolCallCount !== void 0 ? { toolCallCount: summary.toolCallCount } : {},
|
|
4346
|
+
...summary.toolErrorCount !== void 0 ? { toolErrorCount: summary.toolErrorCount } : {},
|
|
4347
|
+
...summary.fileChangeCount !== void 0 ? { fileChangeCount: summary.fileChangeCount } : {},
|
|
4348
|
+
...summary.toolBreakdown !== void 0 ? { toolBreakdown: summary.toolBreakdown } : {},
|
|
4349
|
+
...summary.compactionCount !== void 0 ? { compactionCount: summary.compactionCount } : {},
|
|
4350
|
+
...summary.outcome !== void 0 ? { outcome: summary.outcome } : {},
|
|
4351
|
+
isCurrent: summary.id === currentSessionId
|
|
4352
|
+
};
|
|
4353
|
+
}
|
|
4354
|
+
function toSessionHistoryEntries(summaries, currentSessionId) {
|
|
4355
|
+
return summaries.map((summary) => toSessionHistoryEntry(summary, currentSessionId));
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4075
4358
|
// src/server/shell-open.ts
|
|
4076
4359
|
import * as fs8 from "node:fs/promises";
|
|
4077
4360
|
import * as path10 from "node:path";
|
|
@@ -4272,6 +4555,16 @@ function validateBrainAskPayload(payload) {
|
|
|
4272
4555
|
}
|
|
4273
4556
|
return { ok: true, value: { question: question.trim() } };
|
|
4274
4557
|
}
|
|
4558
|
+
function validateBrainConfigSetPayload(payload) {
|
|
4559
|
+
if (!isRecord(payload)) {
|
|
4560
|
+
return { ok: false, message: "brain.config.set payload must be an object with a patch object" };
|
|
4561
|
+
}
|
|
4562
|
+
const patch = payload["patch"];
|
|
4563
|
+
if (!isRecord(patch)) {
|
|
4564
|
+
return { ok: false, message: "brain.config.set payload.patch must be an object" };
|
|
4565
|
+
}
|
|
4566
|
+
return { ok: true, value: { patch } };
|
|
4567
|
+
}
|
|
4275
4568
|
function validateAutonomySwitchPayload(payload) {
|
|
4276
4569
|
if (!isRecord(payload)) {
|
|
4277
4570
|
return { ok: false, message: "autonomy.switch payload must be an object with string mode" };
|
|
@@ -4333,7 +4626,9 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4333
4626
|
"hqEnabled",
|
|
4334
4627
|
"hqRawContent",
|
|
4335
4628
|
"fallbackAuto",
|
|
4336
|
-
"favoriteModelsOnly"
|
|
4629
|
+
"favoriteModelsOnly",
|
|
4630
|
+
"breakerEnabled",
|
|
4631
|
+
"debugStream"
|
|
4337
4632
|
]);
|
|
4338
4633
|
var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
|
|
4339
4634
|
var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
|
|
@@ -4344,9 +4639,18 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4344
4639
|
"maxIterations",
|
|
4345
4640
|
"maxConcurrent",
|
|
4346
4641
|
"enhanceDelayMs",
|
|
4347
|
-
"tgLongToolMs"
|
|
4642
|
+
"tgLongToolMs",
|
|
4643
|
+
"breakerAutoKillResetMs"
|
|
4644
|
+
]);
|
|
4645
|
+
var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
4646
|
+
"hqUrl",
|
|
4647
|
+
"hqToken",
|
|
4648
|
+
"uiLocale",
|
|
4649
|
+
"thinkingWord",
|
|
4650
|
+
"refinerProvider",
|
|
4651
|
+
"refinerModel",
|
|
4652
|
+
"refinerFallbackProfile"
|
|
4348
4653
|
]);
|
|
4349
|
-
var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken", "uiLocale"]);
|
|
4350
4654
|
var ENUM_PREF_KEYS = {
|
|
4351
4655
|
autonomy: AUTONOMY_VALUES,
|
|
4352
4656
|
contextStrategy: CONTEXT_STRATEGY_VALUES,
|
|
@@ -4357,7 +4661,10 @@ var ENUM_PREF_KEYS = {
|
|
|
4357
4661
|
auditLevel: AUDIT_LEVEL_VALUES,
|
|
4358
4662
|
reasoningMode: REASONING_MODE_VALUES,
|
|
4359
4663
|
reasoningEffort: REASONING_EFFORT_VALUES,
|
|
4360
|
-
cacheTtl: CACHE_TTL_VALUES
|
|
4664
|
+
cacheTtl: CACHE_TTL_VALUES,
|
|
4665
|
+
statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
|
|
4666
|
+
animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
|
|
4667
|
+
fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
|
|
4361
4668
|
};
|
|
4362
4669
|
function validateModelRuntimeValue(modelRuntime, path23) {
|
|
4363
4670
|
const reasoning = modelRuntime["reasoning"];
|
|
@@ -5756,6 +6063,10 @@ function shouldLogWatcherStats() {
|
|
|
5756
6063
|
function setupEvents(deps2) {
|
|
5757
6064
|
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
|
|
5758
6065
|
const disposers = [];
|
|
6066
|
+
let disposed = false;
|
|
6067
|
+
const on = (event, listener) => {
|
|
6068
|
+
disposers.push(events.on(event, listener));
|
|
6069
|
+
};
|
|
5759
6070
|
const currentSessionId = () => context.session?.id ?? "";
|
|
5760
6071
|
const sessionPayload2 = (payload) => {
|
|
5761
6072
|
const provided = payload["sessionId"];
|
|
@@ -5771,20 +6082,20 @@ function setupEvents(deps2) {
|
|
|
5771
6082
|
sessionBridge?.append(event).catch(() => {
|
|
5772
6083
|
});
|
|
5773
6084
|
};
|
|
5774
|
-
|
|
6085
|
+
on("iteration.started", (e) => {
|
|
5775
6086
|
const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
|
|
5776
6087
|
broadcast2(clients, {
|
|
5777
6088
|
type: "iteration.started",
|
|
5778
6089
|
payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, maxIterations: maxIt })
|
|
5779
6090
|
});
|
|
5780
6091
|
});
|
|
5781
|
-
|
|
6092
|
+
on("iteration.completed", (e) => {
|
|
5782
6093
|
broadcast2(clients, {
|
|
5783
6094
|
type: "iteration.completed",
|
|
5784
6095
|
payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
|
|
5785
6096
|
});
|
|
5786
6097
|
});
|
|
5787
|
-
|
|
6098
|
+
on("iteration.limit_reached", (e) => {
|
|
5788
6099
|
broadcast2(clients, {
|
|
5789
6100
|
type: "iteration.limit_reached",
|
|
5790
6101
|
payload: sessionPayload2({
|
|
@@ -5794,19 +6105,19 @@ function setupEvents(deps2) {
|
|
|
5794
6105
|
})
|
|
5795
6106
|
});
|
|
5796
6107
|
});
|
|
5797
|
-
|
|
6108
|
+
on("provider.text_delta", (e) => {
|
|
5798
6109
|
broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
|
|
5799
6110
|
});
|
|
5800
|
-
|
|
6111
|
+
on("provider.thinking_delta", (e) => {
|
|
5801
6112
|
broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
|
|
5802
6113
|
});
|
|
5803
|
-
|
|
6114
|
+
on("provider.stream_error", (e) => {
|
|
5804
6115
|
broadcast2(clients, {
|
|
5805
6116
|
type: "provider.stream_error",
|
|
5806
6117
|
payload: sessionPayload2({ sessionId: e.sessionId, eventType: e.eventType, message: e.msg })
|
|
5807
6118
|
});
|
|
5808
6119
|
});
|
|
5809
|
-
|
|
6120
|
+
on("tool.started", (e) => {
|
|
5810
6121
|
broadcast2(clients, {
|
|
5811
6122
|
type: "tool.started",
|
|
5812
6123
|
payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
|
|
@@ -5819,7 +6130,7 @@ function setupEvents(deps2) {
|
|
|
5819
6130
|
input: e.input
|
|
5820
6131
|
});
|
|
5821
6132
|
});
|
|
5822
|
-
|
|
6133
|
+
on("tool.progress", (e) => {
|
|
5823
6134
|
broadcast2(clients, {
|
|
5824
6135
|
type: "tool.progress",
|
|
5825
6136
|
// Nested `event` shape — the client handler reads `payload.event?.text`
|
|
@@ -5840,7 +6151,7 @@ function setupEvents(deps2) {
|
|
|
5840
6151
|
}
|
|
5841
6152
|
});
|
|
5842
6153
|
});
|
|
5843
|
-
|
|
6154
|
+
on("tool.executed", (e) => {
|
|
5844
6155
|
broadcast2(clients, {
|
|
5845
6156
|
type: "tool.executed",
|
|
5846
6157
|
payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
|
|
@@ -5898,7 +6209,7 @@ function setupEvents(deps2) {
|
|
|
5898
6209
|
})();
|
|
5899
6210
|
}
|
|
5900
6211
|
});
|
|
5901
|
-
|
|
6212
|
+
on("tool.loop_detected", (e) => {
|
|
5902
6213
|
broadcast2(clients, {
|
|
5903
6214
|
type: "tool.loop_detected",
|
|
5904
6215
|
payload: sessionPayload2({
|
|
@@ -5910,19 +6221,19 @@ function setupEvents(deps2) {
|
|
|
5910
6221
|
})
|
|
5911
6222
|
});
|
|
5912
6223
|
});
|
|
5913
|
-
|
|
6224
|
+
on("trust.persisted", (e) => {
|
|
5914
6225
|
broadcast2(clients, {
|
|
5915
6226
|
type: "trust.persisted",
|
|
5916
6227
|
payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
|
|
5917
6228
|
});
|
|
5918
6229
|
});
|
|
5919
|
-
|
|
6230
|
+
on("delegate.started", (e) => {
|
|
5920
6231
|
broadcast2(clients, {
|
|
5921
6232
|
type: "delegate.started",
|
|
5922
6233
|
payload: sessionPayload2({ sessionId: e.sessionId, target: e.target, task: e.task })
|
|
5923
6234
|
});
|
|
5924
6235
|
});
|
|
5925
|
-
|
|
6236
|
+
on("delegate.completed", (e) => {
|
|
5926
6237
|
broadcast2(clients, {
|
|
5927
6238
|
type: "delegate.completed",
|
|
5928
6239
|
payload: sessionPayload2({
|
|
@@ -5940,7 +6251,7 @@ function setupEvents(deps2) {
|
|
|
5940
6251
|
})
|
|
5941
6252
|
});
|
|
5942
6253
|
});
|
|
5943
|
-
|
|
6254
|
+
on("provider.response", (e) => {
|
|
5944
6255
|
broadcast2(clients, {
|
|
5945
6256
|
type: "provider.response",
|
|
5946
6257
|
payload: sessionPayload2({
|
|
@@ -5952,7 +6263,7 @@ function setupEvents(deps2) {
|
|
|
5952
6263
|
})
|
|
5953
6264
|
});
|
|
5954
6265
|
});
|
|
5955
|
-
|
|
6266
|
+
on("ctx.pct", (e) => {
|
|
5956
6267
|
broadcast2(clients, {
|
|
5957
6268
|
type: "ctx.pct",
|
|
5958
6269
|
payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
|
|
@@ -5969,28 +6280,28 @@ function setupEvents(deps2) {
|
|
|
5969
6280
|
})
|
|
5970
6281
|
});
|
|
5971
6282
|
});
|
|
5972
|
-
|
|
6283
|
+
on("ctx.max_context", (e) => {
|
|
5973
6284
|
broadcast2(clients, {
|
|
5974
6285
|
type: "ctx.max_context",
|
|
5975
6286
|
payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
|
|
5976
6287
|
});
|
|
5977
6288
|
});
|
|
5978
|
-
|
|
6289
|
+
on("token.threshold", (e) => {
|
|
5979
6290
|
broadcast2(clients, {
|
|
5980
6291
|
type: "token.threshold",
|
|
5981
6292
|
payload: sessionPayload2({ sessionId: e.sessionId, used: e.used, limit: e.limit })
|
|
5982
6293
|
});
|
|
5983
6294
|
});
|
|
5984
|
-
|
|
6295
|
+
on("token.cost_estimate_unavailable", (e) => {
|
|
5985
6296
|
broadcast2(clients, {
|
|
5986
6297
|
type: "token.cost_estimate_unavailable",
|
|
5987
6298
|
payload: sessionPayload2({ sessionId: e.sessionId, model: e.model })
|
|
5988
6299
|
});
|
|
5989
6300
|
});
|
|
5990
|
-
|
|
6301
|
+
on("context.repaired", (e) => {
|
|
5991
6302
|
broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
|
|
5992
6303
|
});
|
|
5993
|
-
|
|
6304
|
+
on("tool.confirm_needed", (e) => {
|
|
5994
6305
|
const id = e.toolUseId ?? `confirm_${Date.now()}`;
|
|
5995
6306
|
const payload = sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern, decisionSource: e.decisionSource, riskTier: e.riskTier });
|
|
5996
6307
|
pendingConfirms.set(id, {
|
|
@@ -6001,7 +6312,7 @@ function setupEvents(deps2) {
|
|
|
6001
6312
|
});
|
|
6002
6313
|
broadcast2(clients, { type: "tool.confirm_needed", payload });
|
|
6003
6314
|
});
|
|
6004
|
-
|
|
6315
|
+
on("error", (e) => {
|
|
6005
6316
|
broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
|
|
6006
6317
|
appendForCurrentSession(e.sessionId, {
|
|
6007
6318
|
type: "error",
|
|
@@ -6010,13 +6321,13 @@ function setupEvents(deps2) {
|
|
|
6010
6321
|
phase: e.phase
|
|
6011
6322
|
});
|
|
6012
6323
|
});
|
|
6013
|
-
|
|
6324
|
+
on("session.damaged", (e) => {
|
|
6014
6325
|
broadcast2(clients, {
|
|
6015
6326
|
type: "session.damaged",
|
|
6016
6327
|
payload: { sessionId: e.sessionId, detail: e.detail }
|
|
6017
6328
|
});
|
|
6018
6329
|
});
|
|
6019
|
-
|
|
6330
|
+
on("session.rewound", (e) => {
|
|
6020
6331
|
broadcast2(clients, {
|
|
6021
6332
|
type: "session.rewound",
|
|
6022
6333
|
payload: sessionPayload2({
|
|
@@ -6027,7 +6338,7 @@ function setupEvents(deps2) {
|
|
|
6027
6338
|
})
|
|
6028
6339
|
});
|
|
6029
6340
|
});
|
|
6030
|
-
|
|
6341
|
+
on("checkpoint.written", (e) => {
|
|
6031
6342
|
broadcast2(clients, {
|
|
6032
6343
|
type: "checkpoint.written",
|
|
6033
6344
|
payload: sessionPayload2({
|
|
@@ -6039,19 +6350,19 @@ function setupEvents(deps2) {
|
|
|
6039
6350
|
})
|
|
6040
6351
|
});
|
|
6041
6352
|
});
|
|
6042
|
-
|
|
6353
|
+
on("in_flight.started", (e) => {
|
|
6043
6354
|
broadcast2(clients, {
|
|
6044
6355
|
type: "in_flight.started",
|
|
6045
6356
|
payload: sessionPayload2({ sessionId: e.sessionId, context: e.context, ts: e.ts })
|
|
6046
6357
|
});
|
|
6047
6358
|
});
|
|
6048
|
-
|
|
6359
|
+
on("in_flight.ended", (e) => {
|
|
6049
6360
|
broadcast2(clients, {
|
|
6050
6361
|
type: "in_flight.ended",
|
|
6051
6362
|
payload: sessionPayload2({ sessionId: e.sessionId, reason: e.reason, ts: e.ts })
|
|
6052
6363
|
});
|
|
6053
6364
|
});
|
|
6054
|
-
|
|
6365
|
+
on("provider.retry", (e) => {
|
|
6055
6366
|
broadcast2(clients, {
|
|
6056
6367
|
type: "provider.retry",
|
|
6057
6368
|
payload: sessionPayload2({
|
|
@@ -6073,7 +6384,7 @@ function setupEvents(deps2) {
|
|
|
6073
6384
|
description: e.description
|
|
6074
6385
|
});
|
|
6075
6386
|
});
|
|
6076
|
-
|
|
6387
|
+
on("provider.error", (e) => {
|
|
6077
6388
|
broadcast2(clients, {
|
|
6078
6389
|
type: "provider.error",
|
|
6079
6390
|
payload: sessionPayload2({
|
|
@@ -6093,7 +6404,7 @@ function setupEvents(deps2) {
|
|
|
6093
6404
|
retryable: e.retryable
|
|
6094
6405
|
});
|
|
6095
6406
|
});
|
|
6096
|
-
|
|
6407
|
+
on("provider.fallback", (e) => {
|
|
6097
6408
|
broadcast2(clients, {
|
|
6098
6409
|
type: "provider.fallback",
|
|
6099
6410
|
payload: sessionPayload2({
|
|
@@ -6105,7 +6416,7 @@ function setupEvents(deps2) {
|
|
|
6105
6416
|
})
|
|
6106
6417
|
});
|
|
6107
6418
|
});
|
|
6108
|
-
|
|
6419
|
+
on("compaction.fired", (e) => {
|
|
6109
6420
|
broadcast2(clients, {
|
|
6110
6421
|
type: "context.compacted",
|
|
6111
6422
|
payload: sessionPayload2({
|
|
@@ -6117,7 +6428,7 @@ function setupEvents(deps2) {
|
|
|
6117
6428
|
})
|
|
6118
6429
|
});
|
|
6119
6430
|
});
|
|
6120
|
-
|
|
6431
|
+
on("compaction.failed", (e) => {
|
|
6121
6432
|
broadcast2(clients, {
|
|
6122
6433
|
type: "compaction.failed",
|
|
6123
6434
|
payload: sessionPayload2({
|
|
@@ -6132,25 +6443,25 @@ function setupEvents(deps2) {
|
|
|
6132
6443
|
})
|
|
6133
6444
|
});
|
|
6134
6445
|
});
|
|
6135
|
-
|
|
6446
|
+
on("mcp.server.connected", (e) => {
|
|
6136
6447
|
broadcast2(clients, {
|
|
6137
6448
|
type: "mcp.server.connected",
|
|
6138
6449
|
payload: { name: e.name, toolCount: e.toolCount }
|
|
6139
6450
|
});
|
|
6140
6451
|
});
|
|
6141
|
-
|
|
6452
|
+
on("mcp.server.reconnected", (e) => {
|
|
6142
6453
|
broadcast2(clients, {
|
|
6143
6454
|
type: "mcp.server.reconnected",
|
|
6144
6455
|
payload: { name: e.name, toolCount: e.toolCount }
|
|
6145
6456
|
});
|
|
6146
6457
|
});
|
|
6147
|
-
|
|
6458
|
+
on("mcp.server.disconnected", (e) => {
|
|
6148
6459
|
broadcast2(clients, {
|
|
6149
6460
|
type: "mcp.server.disconnected",
|
|
6150
6461
|
payload: { name: e.name, reason: e.reason }
|
|
6151
6462
|
});
|
|
6152
6463
|
});
|
|
6153
|
-
|
|
6464
|
+
on("coordinator.stats", (e) => {
|
|
6154
6465
|
broadcast2(clients, {
|
|
6155
6466
|
type: "coordinator.stats",
|
|
6156
6467
|
payload: sessionPayload2({
|
|
@@ -6178,16 +6489,16 @@ function setupEvents(deps2) {
|
|
|
6178
6489
|
broadcast2(clients, { type: "mailbox.agent_registered", payload });
|
|
6179
6490
|
});
|
|
6180
6491
|
const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6492
|
+
on("subagent.spawned", (e) => forwardSubagent("spawned", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, name: e.name, provider: e.provider, model: e.model, description: e.description }));
|
|
6493
|
+
on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
|
|
6494
|
+
on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
|
|
6495
|
+
on("subagent.iteration_summary", (e) => forwardSubagent("iteration_summary", { sessionId: e.sessionId, subagentId: e.subagentId, iteration: e.iteration, toolCalls: e.toolCalls, costUsd: e.costUsd, currentTool: e.currentTool, partialText: e.partialText }));
|
|
6496
|
+
on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
|
|
6497
|
+
on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
|
|
6498
|
+
on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
|
|
6499
|
+
on("subagent.task_completed", (e) => forwardSubagent("task_completed", { sessionId: e.sessionId, subagentId: e.subagentId, status: e.status, iterations: e.iterations, toolCalls: e.toolCalls, finalText: e.finalText, failureReason: e.error?.kind, error: e.error ? { kind: e.error.kind, message: e.error.message } : void 0 }));
|
|
6500
|
+
on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
|
|
6501
|
+
on("agent.timeline.message", (e) => {
|
|
6191
6502
|
broadcast2(clients, {
|
|
6192
6503
|
type: "agent.timeline.message",
|
|
6193
6504
|
payload: sessionPayload2({
|
|
@@ -6203,7 +6514,7 @@ function setupEvents(deps2) {
|
|
|
6203
6514
|
})
|
|
6204
6515
|
});
|
|
6205
6516
|
});
|
|
6206
|
-
|
|
6517
|
+
on("agent.status_changed", (e) => {
|
|
6207
6518
|
broadcast2(clients, {
|
|
6208
6519
|
type: "agent.status_changed",
|
|
6209
6520
|
payload: sessionPayload2({
|
|
@@ -6218,7 +6529,7 @@ function setupEvents(deps2) {
|
|
|
6218
6529
|
});
|
|
6219
6530
|
});
|
|
6220
6531
|
let leaderSpawned = false;
|
|
6221
|
-
|
|
6532
|
+
on("iteration.started", (e) => {
|
|
6222
6533
|
if (!leaderSpawned) {
|
|
6223
6534
|
leaderSpawned = true;
|
|
6224
6535
|
const provider = context.provider?.id ?? "unknown";
|
|
@@ -6232,7 +6543,7 @@ function setupEvents(deps2) {
|
|
|
6232
6543
|
});
|
|
6233
6544
|
}
|
|
6234
6545
|
});
|
|
6235
|
-
|
|
6546
|
+
on("tool.executed", (e) => {
|
|
6236
6547
|
forwardSubagent("tool_executed", {
|
|
6237
6548
|
sessionId: e.sessionId,
|
|
6238
6549
|
subagentId: "leader",
|
|
@@ -6241,7 +6552,7 @@ function setupEvents(deps2) {
|
|
|
6241
6552
|
ok: e.ok
|
|
6242
6553
|
});
|
|
6243
6554
|
});
|
|
6244
|
-
|
|
6555
|
+
on("provider.response", (e) => {
|
|
6245
6556
|
if (e.usage?.input != null) {
|
|
6246
6557
|
const maxCtx = context.provider.capabilities.maxContext;
|
|
6247
6558
|
const rawLoad = maxCtx > 0 ? e.usage.input / maxCtx : 0;
|
|
@@ -6258,7 +6569,7 @@ function setupEvents(deps2) {
|
|
|
6258
6569
|
});
|
|
6259
6570
|
}
|
|
6260
6571
|
});
|
|
6261
|
-
|
|
6572
|
+
on("iteration.completed", (e) => {
|
|
6262
6573
|
if (!leaderSpawned) {
|
|
6263
6574
|
leaderSpawned = true;
|
|
6264
6575
|
const provider = context.provider?.id ?? "unknown";
|
|
@@ -6290,7 +6601,19 @@ function setupEvents(deps2) {
|
|
|
6290
6601
|
payload: sessionPayload2({ event: eventName, ...payload })
|
|
6291
6602
|
});
|
|
6292
6603
|
});
|
|
6293
|
-
events.
|
|
6604
|
+
events.onPattern("cron:state_snapshot", (_eventName, payload) => {
|
|
6605
|
+
broadcast2(clients, {
|
|
6606
|
+
type: "cron.snapshot",
|
|
6607
|
+
payload
|
|
6608
|
+
});
|
|
6609
|
+
});
|
|
6610
|
+
events.onPattern("cron:job_fired", (_eventName, payload) => {
|
|
6611
|
+
broadcast2(clients, {
|
|
6612
|
+
type: "cron.job_fired",
|
|
6613
|
+
payload
|
|
6614
|
+
});
|
|
6615
|
+
});
|
|
6616
|
+
on("client.status", async (e) => {
|
|
6294
6617
|
broadcast2(clients, { type: "client.status_update", payload: e });
|
|
6295
6618
|
if (wpaths?.projectStatus) {
|
|
6296
6619
|
try {
|
|
@@ -6376,6 +6699,7 @@ function setupEvents(deps2) {
|
|
|
6376
6699
|
const startWatcher = async () => {
|
|
6377
6700
|
try {
|
|
6378
6701
|
await fs10.mkdir(projectsDir, { recursive: true });
|
|
6702
|
+
if (disposed) return;
|
|
6379
6703
|
watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
|
|
6380
6704
|
if (eventType !== "change" && eventType !== "rename") return;
|
|
6381
6705
|
if (filename == null) return;
|
|
@@ -6406,7 +6730,7 @@ function setupEvents(deps2) {
|
|
|
6406
6730
|
);
|
|
6407
6731
|
}
|
|
6408
6732
|
};
|
|
6409
|
-
|
|
6733
|
+
on("client.status", (e) => {
|
|
6410
6734
|
if (e.projectHash) {
|
|
6411
6735
|
const hash = String(e.projectHash);
|
|
6412
6736
|
if (!knownProjectHashes.has(hash)) {
|
|
@@ -6446,7 +6770,9 @@ function setupEvents(deps2) {
|
|
|
6446
6770
|
const registry = new SessionRegistry(globalRoot);
|
|
6447
6771
|
const sessions = await registry.list();
|
|
6448
6772
|
const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
|
|
6449
|
-
const live = sessions.filter(
|
|
6773
|
+
const live = sessions.filter(
|
|
6774
|
+
(s) => s.status === "active" || s.status === "idle"
|
|
6775
|
+
).filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
|
|
6450
6776
|
sessionId: s.sessionId,
|
|
6451
6777
|
projectName: s.projectName,
|
|
6452
6778
|
projectSlug: s.projectSlug,
|
|
@@ -6500,6 +6826,8 @@ function setupEvents(deps2) {
|
|
|
6500
6826
|
void broadcastSessions();
|
|
6501
6827
|
}
|
|
6502
6828
|
return () => {
|
|
6829
|
+
if (disposed) return;
|
|
6830
|
+
disposed = true;
|
|
6503
6831
|
for (const dispose of disposers) {
|
|
6504
6832
|
try {
|
|
6505
6833
|
dispose();
|
|
@@ -6634,9 +6962,11 @@ function positiveNumber(value) {
|
|
|
6634
6962
|
// src/server/server-runtime.ts
|
|
6635
6963
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
6636
6964
|
async function resolvePorts(opts) {
|
|
6637
|
-
const
|
|
6965
|
+
const surface = opts.surface ?? "webui";
|
|
6966
|
+
const surfaceDefaults = surface === "simpleui" ? { http: 3466, ws: 3467 } : { http: 3456, ws: 3457 };
|
|
6967
|
+
const requestedWsPort = opts.wsPort ?? surfaceDefaults.ws;
|
|
6638
6968
|
const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
|
|
6639
|
-
const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ??
|
|
6969
|
+
const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? String(surfaceDefaults.http), 10);
|
|
6640
6970
|
const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
|
|
6641
6971
|
const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
|
|
6642
6972
|
const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
|
|
@@ -6726,7 +7056,7 @@ function createWsServers(ports, accessToken) {
|
|
|
6726
7056
|
allowedHostnames: publicHostnames,
|
|
6727
7057
|
allowBrowserUrlToken: Boolean(ports.publicWsUrl)
|
|
6728
7058
|
});
|
|
6729
|
-
const WS_MAX_PAYLOAD =
|
|
7059
|
+
const WS_MAX_PAYLOAD = 20 * 1024 * 1024;
|
|
6730
7060
|
const wssPrimary = new WebSocketServer({
|
|
6731
7061
|
port: ports.wsPort,
|
|
6732
7062
|
host: ports.wsHost,
|
|
@@ -6794,8 +7124,8 @@ function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
|
6794
7124
|
if (explicitDistDir) return path13.resolve(explicitDistDir);
|
|
6795
7125
|
try {
|
|
6796
7126
|
const requireFromHere2 = createRequire(fromUrl);
|
|
6797
|
-
const serverEntry = requireFromHere2.resolve("@wrongstack/webui
|
|
6798
|
-
return path13.
|
|
7127
|
+
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
7128
|
+
return path13.dirname(serverEntry);
|
|
6799
7129
|
} catch {
|
|
6800
7130
|
return path13.resolve(path13.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
6801
7131
|
}
|
|
@@ -6818,7 +7148,7 @@ function startHttpServer(opts) {
|
|
|
6818
7148
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
6819
7149
|
if (opts.openBrowser) openBrowser(openUrl);
|
|
6820
7150
|
void registerInstance(
|
|
6821
|
-
{ pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path13.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
|
|
7151
|
+
{ pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path13.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
|
|
6822
7152
|
registryBaseDir
|
|
6823
7153
|
).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
|
|
6824
7154
|
});
|
|
@@ -6834,12 +7164,10 @@ function registerShutdown(deps2) {
|
|
|
6834
7164
|
}
|
|
6835
7165
|
|
|
6836
7166
|
// src/server/pre-context-services.ts
|
|
6837
|
-
import * as
|
|
7167
|
+
import * as path16 from "node:path";
|
|
6838
7168
|
import { createRequire as createRequire2 } from "node:module";
|
|
6839
|
-
import { WebSocket as WebSocket2 } from "ws";
|
|
6840
7169
|
import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
|
|
6841
7170
|
import {
|
|
6842
|
-
AgentStatusTracker,
|
|
6843
7171
|
AnnotationsStore,
|
|
6844
7172
|
DEFAULT_SESSION_PRUNE_DAYS,
|
|
6845
7173
|
DefaultModelsRegistry,
|
|
@@ -6850,7 +7178,6 @@ import {
|
|
|
6850
7178
|
DefaultSkillLoader,
|
|
6851
7179
|
DefaultSystemPromptBuilder,
|
|
6852
7180
|
EventBus,
|
|
6853
|
-
FleetNotifier,
|
|
6854
7181
|
GlobalMailbox,
|
|
6855
7182
|
PromptUsageStore,
|
|
6856
7183
|
ProviderRegistry,
|
|
@@ -6861,8 +7188,8 @@ import {
|
|
|
6861
7188
|
applyToolDescriptionModes,
|
|
6862
7189
|
applyToolResultRenderModes,
|
|
6863
7190
|
configureChildEnvGitIdentity,
|
|
7191
|
+
getSessionRegistry as getSessionRegistry2,
|
|
6864
7192
|
resolveContextWindowPolicy,
|
|
6865
|
-
getSessionRegistry,
|
|
6866
7193
|
makeMailboxTool,
|
|
6867
7194
|
makeMailInboxTool,
|
|
6868
7195
|
makeFleetStatusTool,
|
|
@@ -6871,7 +7198,12 @@ import {
|
|
|
6871
7198
|
import {
|
|
6872
7199
|
createSuperMemoryTools
|
|
6873
7200
|
} from "@wrongstack/super-memory";
|
|
6874
|
-
import {
|
|
7201
|
+
import {
|
|
7202
|
+
MCPAuthorizationManager,
|
|
7203
|
+
MCPRegistry,
|
|
7204
|
+
MCPVaultTokenStore,
|
|
7205
|
+
createVaultBackedMcpAuthorizationProviderFactory
|
|
7206
|
+
} from "@wrongstack/mcp";
|
|
6875
7207
|
import { buildProviderFactoriesFromRegistry } from "@wrongstack/providers";
|
|
6876
7208
|
import { createDefaultContainer } from "@wrongstack/runtime";
|
|
6877
7209
|
import {
|
|
@@ -6883,7 +7215,11 @@ import {
|
|
|
6883
7215
|
rememberTool,
|
|
6884
7216
|
searchMemoryTool
|
|
6885
7217
|
} from "@wrongstack/tools";
|
|
6886
|
-
import {
|
|
7218
|
+
import {
|
|
7219
|
+
attachSessionKanbanMirror,
|
|
7220
|
+
hydrateSessionKanban
|
|
7221
|
+
} from "@wrongstack/tools/session-kanban";
|
|
7222
|
+
import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
6887
7223
|
|
|
6888
7224
|
// src/server/setup-screen.ts
|
|
6889
7225
|
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
@@ -7021,6 +7357,21 @@ function seedContextMeta(config, context) {
|
|
|
7021
7357
|
meta["reasoningPreserve"] = mr.reasoning?.preserve === true;
|
|
7022
7358
|
meta["cacheTtl"] = mr.cache?.ttl ?? "default";
|
|
7023
7359
|
}
|
|
7360
|
+
meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
|
|
7361
|
+
meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
|
|
7362
|
+
meta["refinerFallbackProfile"] = autonomyCfg["refinerFallbackProfile"] ?? "";
|
|
7363
|
+
meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
|
|
7364
|
+
meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
|
|
7365
|
+
meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
|
|
7366
|
+
meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
|
|
7367
|
+
meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
|
|
7368
|
+
{
|
|
7369
|
+
const featuresAllow = config.features?.allowOutsideProjectRoot;
|
|
7370
|
+
const toolsRestrict = config.tools?.restrictToProjectRoot;
|
|
7371
|
+
const allow = featuresAllow !== void 0 ? featuresAllow : toolsRestrict !== void 0 ? !toolsRestrict : true;
|
|
7372
|
+
meta["fsAccess"] = allow ? "unrestricted" : "project";
|
|
7373
|
+
}
|
|
7374
|
+
meta["debugStream"] = config.debugStream === true;
|
|
7024
7375
|
const hqConfig = config.hq;
|
|
7025
7376
|
meta["hqEnabled"] = hqConfig?.enabled === true;
|
|
7026
7377
|
meta["hqUrl"] = hqConfig?.url ?? "";
|
|
@@ -7117,10 +7468,188 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
7117
7468
|
}
|
|
7118
7469
|
}
|
|
7119
7470
|
|
|
7471
|
+
// src/server/standalone-session-identity.ts
|
|
7472
|
+
import * as path15 from "node:path";
|
|
7473
|
+
import {
|
|
7474
|
+
AgentStatusTracker,
|
|
7475
|
+
FleetNotifier,
|
|
7476
|
+
getSessionRegistry,
|
|
7477
|
+
RecoveryLock
|
|
7478
|
+
} from "@wrongstack/core";
|
|
7479
|
+
import { WebSocket as WebSocket2 } from "ws";
|
|
7480
|
+
async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
7481
|
+
const { paths, events, logger } = opts;
|
|
7482
|
+
const registry = opts.sessionRegistry ?? getSessionRegistry(paths.globalRoot);
|
|
7483
|
+
const fleetNotifier = new FleetNotifier({
|
|
7484
|
+
baseDir: paths.globalRoot,
|
|
7485
|
+
projectRoot: paths.projectRoot,
|
|
7486
|
+
selfPid: process.pid
|
|
7487
|
+
});
|
|
7488
|
+
let activeSessionId = opts.initialSessionId;
|
|
7489
|
+
let stopped = false;
|
|
7490
|
+
let transition = Promise.resolve();
|
|
7491
|
+
const statusTracker = new AgentStatusTracker({
|
|
7492
|
+
events,
|
|
7493
|
+
registry,
|
|
7494
|
+
sessionId: () => activeSessionId,
|
|
7495
|
+
onUpdate: () => fleetNotifier.notify()
|
|
7496
|
+
});
|
|
7497
|
+
const register = async (sessionId) => {
|
|
7498
|
+
try {
|
|
7499
|
+
await registry.register({
|
|
7500
|
+
sessionId,
|
|
7501
|
+
projectSlug: paths.projectSlug,
|
|
7502
|
+
projectRoot: paths.projectRoot,
|
|
7503
|
+
projectName: path15.basename(paths.projectRoot),
|
|
7504
|
+
workingDir: opts.workingDir,
|
|
7505
|
+
clientType: "webui",
|
|
7506
|
+
pid: process.pid,
|
|
7507
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7508
|
+
agents: statusTracker.getAgents()
|
|
7509
|
+
});
|
|
7510
|
+
fleetNotifier.notify();
|
|
7511
|
+
} catch (err) {
|
|
7512
|
+
logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
|
|
7513
|
+
}
|
|
7514
|
+
};
|
|
7515
|
+
await register(activeSessionId);
|
|
7516
|
+
statusTracker.start();
|
|
7517
|
+
const recoveryLock = opts.manageRecoveryLock === false ? void 0 : new RecoveryLock({ dir: paths.projectSessions, sessionStore: opts.sessionStore });
|
|
7518
|
+
let ownsRecoveryLock = false;
|
|
7519
|
+
if (recoveryLock) {
|
|
7520
|
+
try {
|
|
7521
|
+
await recoveryLock.write(activeSessionId);
|
|
7522
|
+
ownsRecoveryLock = true;
|
|
7523
|
+
} catch {
|
|
7524
|
+
}
|
|
7525
|
+
}
|
|
7526
|
+
let stopHqBridges = () => {
|
|
7527
|
+
};
|
|
7528
|
+
let closeHqPublisher = () => {
|
|
7529
|
+
};
|
|
7530
|
+
let restartHqBridges = (_sessionId) => {
|
|
7531
|
+
};
|
|
7532
|
+
if (opts.enableHqTelemetry !== false) {
|
|
7533
|
+
try {
|
|
7534
|
+
const core = await import("@wrongstack/core");
|
|
7535
|
+
const publisher = core.createHqPublisherFromEnv({
|
|
7536
|
+
clientKind: "webui",
|
|
7537
|
+
projectRoot: paths.projectRoot,
|
|
7538
|
+
projectName: path15.basename(paths.projectRoot),
|
|
7539
|
+
appConfig: opts.config,
|
|
7540
|
+
socketFactory: (url) => new WebSocket2(url)
|
|
7541
|
+
});
|
|
7542
|
+
if (publisher) {
|
|
7543
|
+
publisher.connect();
|
|
7544
|
+
closeHqPublisher = () => publisher.close();
|
|
7545
|
+
restartHqBridges = (sessionId) => {
|
|
7546
|
+
stopHqBridges();
|
|
7547
|
+
const stops = [];
|
|
7548
|
+
const add = (start) => {
|
|
7549
|
+
try {
|
|
7550
|
+
stops.push(start());
|
|
7551
|
+
} catch {
|
|
7552
|
+
}
|
|
7553
|
+
};
|
|
7554
|
+
add(
|
|
7555
|
+
() => core.startSessionTelemetryBridge({
|
|
7556
|
+
publisher,
|
|
7557
|
+
events,
|
|
7558
|
+
sessionId,
|
|
7559
|
+
projectRoot: paths.projectRoot,
|
|
7560
|
+
projectName: path15.basename(paths.projectRoot),
|
|
7561
|
+
globalRoot: paths.globalRoot,
|
|
7562
|
+
initialAgents: statusTracker.getAgents(),
|
|
7563
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7564
|
+
})
|
|
7565
|
+
);
|
|
7566
|
+
add(
|
|
7567
|
+
() => core.startFleetTelemetryBridge({
|
|
7568
|
+
events,
|
|
7569
|
+
publisher,
|
|
7570
|
+
runId: sessionId,
|
|
7571
|
+
sessionId
|
|
7572
|
+
})
|
|
7573
|
+
);
|
|
7574
|
+
add(() => core.startBrainTelemetryBridge({ events, publisher, sessionId }));
|
|
7575
|
+
add(() => core.startWorktreeTelemetryBridge({ events, publisher, sessionId }));
|
|
7576
|
+
add(
|
|
7577
|
+
() => core.startToolTelemetryBridge({
|
|
7578
|
+
events,
|
|
7579
|
+
publisher,
|
|
7580
|
+
projectRoot: paths.projectRoot,
|
|
7581
|
+
sessionId
|
|
7582
|
+
})
|
|
7583
|
+
);
|
|
7584
|
+
add(() => core.startCostTelemetryBridge({ events, publisher, sessionId }));
|
|
7585
|
+
stopHqBridges = () => {
|
|
7586
|
+
for (const stop2 of stops.splice(0)) {
|
|
7587
|
+
try {
|
|
7588
|
+
stop2();
|
|
7589
|
+
} catch {
|
|
7590
|
+
}
|
|
7591
|
+
}
|
|
7592
|
+
};
|
|
7593
|
+
};
|
|
7594
|
+
restartHqBridges(activeSessionId);
|
|
7595
|
+
}
|
|
7596
|
+
} catch (err) {
|
|
7597
|
+
logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
|
|
7598
|
+
}
|
|
7599
|
+
}
|
|
7600
|
+
const repointRecovery = async (sessionId) => {
|
|
7601
|
+
if (!recoveryLock) return;
|
|
7602
|
+
try {
|
|
7603
|
+
if (ownsRecoveryLock) await recoveryLock.clear();
|
|
7604
|
+
await recoveryLock.write(sessionId);
|
|
7605
|
+
ownsRecoveryLock = true;
|
|
7606
|
+
} catch {
|
|
7607
|
+
ownsRecoveryLock = false;
|
|
7608
|
+
}
|
|
7609
|
+
};
|
|
7610
|
+
const activate = async (sessionId) => {
|
|
7611
|
+
if (stopped || sessionId === activeSessionId) return;
|
|
7612
|
+
transition = transition.then(async () => {
|
|
7613
|
+
if (stopped || sessionId === activeSessionId) return;
|
|
7614
|
+
activeSessionId = sessionId;
|
|
7615
|
+
await register(sessionId);
|
|
7616
|
+
await repointRecovery(sessionId);
|
|
7617
|
+
try {
|
|
7618
|
+
restartHqBridges(sessionId);
|
|
7619
|
+
} catch (err) {
|
|
7620
|
+
logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
|
|
7621
|
+
}
|
|
7622
|
+
});
|
|
7623
|
+
await transition;
|
|
7624
|
+
};
|
|
7625
|
+
const stop = async () => {
|
|
7626
|
+
if (stopped) return;
|
|
7627
|
+
stopped = true;
|
|
7628
|
+
await transition.catch(() => void 0);
|
|
7629
|
+
statusTracker.stop();
|
|
7630
|
+
stopHqBridges();
|
|
7631
|
+
closeHqPublisher();
|
|
7632
|
+
fleetNotifier.dispose();
|
|
7633
|
+
try {
|
|
7634
|
+
await registry.markClosing();
|
|
7635
|
+
await registry.unregister();
|
|
7636
|
+
} catch {
|
|
7637
|
+
}
|
|
7638
|
+
if (recoveryLock && ownsRecoveryLock) {
|
|
7639
|
+
await recoveryLock.clear().catch(() => void 0);
|
|
7640
|
+
ownsRecoveryLock = false;
|
|
7641
|
+
}
|
|
7642
|
+
};
|
|
7643
|
+
return { statusTracker, activate, stop };
|
|
7644
|
+
}
|
|
7645
|
+
function errorMessage2(err) {
|
|
7646
|
+
return err instanceof Error ? err.message : String(err);
|
|
7647
|
+
}
|
|
7648
|
+
|
|
7120
7649
|
// src/server/pre-context-services.ts
|
|
7121
7650
|
var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
|
|
7122
7651
|
async function createPreContextServices(input) {
|
|
7123
|
-
const { config, wpaths, logger, opts, projectRoot, workingDir, needsProvider } = input;
|
|
7652
|
+
const { config, wpaths, logger, opts, vault, projectRoot, workingDir, needsProvider } = input;
|
|
7124
7653
|
const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
|
|
7125
7654
|
cacheFile: wpaths.modelsCache,
|
|
7126
7655
|
ttlSeconds: 0,
|
|
@@ -7139,7 +7668,7 @@ async function createPreContextServices(input) {
|
|
|
7139
7668
|
await discoverAndMergeWebuiProviders({
|
|
7140
7669
|
config,
|
|
7141
7670
|
registry: modelsRegistry,
|
|
7142
|
-
cacheDir:
|
|
7671
|
+
cacheDir: path16.dirname(wpaths.modelsCache),
|
|
7143
7672
|
logger
|
|
7144
7673
|
});
|
|
7145
7674
|
} catch (err) {
|
|
@@ -7164,12 +7693,13 @@ async function createPreContextServices(input) {
|
|
|
7164
7693
|
})();
|
|
7165
7694
|
const memoryStore = container.resolve(TOKENS.MemoryStore);
|
|
7166
7695
|
if (config.features.memory) {
|
|
7167
|
-
toolRegistry.register(rememberTool(memoryStore));
|
|
7168
|
-
toolRegistry.register(forgetTool(memoryStore));
|
|
7169
|
-
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
7170
|
-
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
7171
7696
|
if (isSuperMemoryService(memoryStore)) {
|
|
7172
7697
|
for (const tool of createSuperMemoryTools(memoryStore)) toolRegistry.register(tool);
|
|
7698
|
+
} else {
|
|
7699
|
+
toolRegistry.register(rememberTool(memoryStore));
|
|
7700
|
+
toolRegistry.register(forgetTool(memoryStore));
|
|
7701
|
+
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
7702
|
+
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
7173
7703
|
}
|
|
7174
7704
|
}
|
|
7175
7705
|
toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
|
|
@@ -7182,10 +7712,23 @@ async function createPreContextServices(input) {
|
|
|
7182
7712
|
configureDangerBypass(config.tools?.exec?.danger ?? {});
|
|
7183
7713
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
7184
7714
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
7185
|
-
const
|
|
7715
|
+
const mcpTokenStore = new MCPVaultTokenStore(
|
|
7716
|
+
path16.join(wpaths.projectDir, "mcp-auth.json"),
|
|
7717
|
+
vault
|
|
7718
|
+
);
|
|
7719
|
+
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
7720
|
+
const mcpRegistry = new MCPRegistry({
|
|
7721
|
+
toolRegistry,
|
|
7722
|
+
events,
|
|
7723
|
+
log: logger,
|
|
7724
|
+
cacheDir: wpaths.cacheDir,
|
|
7725
|
+
authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
|
|
7726
|
+
store: mcpTokenStore
|
|
7727
|
+
}),
|
|
7728
|
+
authorizationManager: mcpAuthorizationManager
|
|
7729
|
+
});
|
|
7186
7730
|
if (config.features.mcp && config.mcpServers) {
|
|
7187
7731
|
for (const [name2, cfg] of Object.entries(config.mcpServers)) {
|
|
7188
|
-
if (cfg.enabled === false) continue;
|
|
7189
7732
|
void mcpRegistry.start({ ...cfg, name: name2 }).catch((err) => {
|
|
7190
7733
|
logger.warn(`MCP server "${name2}" failed to start at boot`, err);
|
|
7191
7734
|
});
|
|
@@ -7200,7 +7743,7 @@ async function createPreContextServices(input) {
|
|
|
7200
7743
|
// surface via the SessionRegistry.
|
|
7201
7744
|
isSessionInUse: async (sessionId) => {
|
|
7202
7745
|
try {
|
|
7203
|
-
const registry =
|
|
7746
|
+
const registry = getSessionRegistry2(wpaths.globalRoot);
|
|
7204
7747
|
const live = await registry.listByProject(wpaths.projectSlug);
|
|
7205
7748
|
const hit = live.find((e) => e.sessionId === sessionId);
|
|
7206
7749
|
if (hit) {
|
|
@@ -7225,155 +7768,57 @@ async function createPreContextServices(input) {
|
|
|
7225
7768
|
await input.touchProject(projectRoot, workingDir);
|
|
7226
7769
|
} catch {
|
|
7227
7770
|
}
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7771
|
+
const sessionIdentity = await createStandaloneSessionIdentityLifecycle({
|
|
7772
|
+
config,
|
|
7773
|
+
events,
|
|
7774
|
+
logger,
|
|
7775
|
+
paths: {
|
|
7776
|
+
globalRoot: wpaths.globalRoot,
|
|
7234
7777
|
projectRoot,
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
projectRoot,
|
|
7277
|
-
projectName: path15.basename(projectRoot),
|
|
7278
|
-
globalRoot: wpaths.globalRoot,
|
|
7279
|
-
initialAgents: statusTracker?.getAgents(),
|
|
7280
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7281
|
-
});
|
|
7282
|
-
try {
|
|
7283
|
-
stopHqAuxBridges.push(
|
|
7284
|
-
startFleetTelemetryBridge({ events, publisher: hqTelemetry, runId: session.id, sessionId: session.id })
|
|
7285
|
-
);
|
|
7286
|
-
} catch {
|
|
7287
|
-
}
|
|
7288
|
-
try {
|
|
7289
|
-
stopHqAuxBridges.push(
|
|
7290
|
-
startBrainTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
|
|
7291
|
-
);
|
|
7292
|
-
} catch {
|
|
7293
|
-
}
|
|
7294
|
-
try {
|
|
7295
|
-
stopHqAuxBridges.push(
|
|
7296
|
-
startWorktreeTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
|
|
7297
|
-
);
|
|
7298
|
-
} catch {
|
|
7299
|
-
}
|
|
7300
|
-
try {
|
|
7301
|
-
stopHqAuxBridges.push(
|
|
7302
|
-
startToolTelemetryBridge({ events, publisher: hqTelemetry, projectRoot, sessionId: session.id })
|
|
7303
|
-
);
|
|
7304
|
-
} catch {
|
|
7305
|
-
}
|
|
7306
|
-
try {
|
|
7307
|
-
stopHqAuxBridges.push(
|
|
7308
|
-
startCostTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
|
|
7309
|
-
);
|
|
7310
|
-
} catch {
|
|
7311
|
-
}
|
|
7312
|
-
}
|
|
7313
|
-
} catch {
|
|
7314
|
-
}
|
|
7315
|
-
const stopTracking = async () => {
|
|
7316
|
-
try {
|
|
7317
|
-
fleetNotifier.dispose();
|
|
7318
|
-
await registry.markClosing();
|
|
7319
|
-
statusTracker?.stop();
|
|
7320
|
-
stopHqSessionBridge?.();
|
|
7321
|
-
for (const stop of stopHqAuxBridges) {
|
|
7322
|
-
try {
|
|
7323
|
-
stop();
|
|
7324
|
-
} catch {
|
|
7325
|
-
}
|
|
7326
|
-
}
|
|
7327
|
-
hqTelemetryPublisher?.close();
|
|
7328
|
-
} catch {
|
|
7329
|
-
}
|
|
7330
|
-
};
|
|
7331
|
-
process.once("beforeExit", () => {
|
|
7332
|
-
void stopTracking();
|
|
7333
|
-
});
|
|
7334
|
-
process.once("SIGINT", () => {
|
|
7335
|
-
void stopTracking();
|
|
7336
|
-
});
|
|
7337
|
-
process.once("SIGTERM", () => {
|
|
7338
|
-
void stopTracking();
|
|
7339
|
-
});
|
|
7340
|
-
} catch {
|
|
7341
|
-
}
|
|
7342
|
-
let context;
|
|
7343
|
-
const tokenCounter = new DefaultTokenCounter({
|
|
7344
|
-
registry: modelsRegistry,
|
|
7345
|
-
providerId: config.provider,
|
|
7346
|
-
events,
|
|
7347
|
-
sessionId: () => context?.session?.id ?? session.id
|
|
7348
|
-
});
|
|
7349
|
-
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
7350
|
-
const activeMode = await modeStore.getActiveMode();
|
|
7351
|
-
const modeId = activeMode?.id ?? "default";
|
|
7352
|
-
const modePrompt = activeMode?.prompt ?? "";
|
|
7353
|
-
const customModeStore = createCustomModeStore(wpaths.configDir);
|
|
7354
|
-
await customModeStore.load();
|
|
7355
|
-
console.log("[WebUI] Custom context modes loaded:", customModeStore.list().filter((m) => m.custom).length, "custom");
|
|
7356
|
-
const resolvedModel = await resolveProviderModelMetadata(
|
|
7357
|
-
modelsRegistry,
|
|
7358
|
-
config.provider,
|
|
7359
|
-
config.model,
|
|
7360
|
-
config.providers?.[config.provider]
|
|
7361
|
-
);
|
|
7362
|
-
const modelCapabilities = resolvedModel?.capabilities ? { maxContextTokens: resolvedModel.capabilities.maxContext, supportsTools: resolvedModel.capabilities.tools, supportsVision: resolvedModel.capabilities.vision, supportsReasoning: resolvedModel.capabilities.reasoning } : void 0;
|
|
7363
|
-
const modelCapabilitiesRef = { current: modelCapabilities };
|
|
7364
|
-
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
7365
|
-
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
7366
|
-
manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
|
|
7367
|
-
projectSkillsDir: wpaths.inProjectSkills,
|
|
7368
|
-
globalSkillsDir: wpaths.globalSkills,
|
|
7369
|
-
projectHash: wpaths.projectHash,
|
|
7370
|
-
skillLoader
|
|
7371
|
-
}) : void 0;
|
|
7372
|
-
const promptsEnabled = config.features.prompts !== false;
|
|
7373
|
-
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
7778
|
+
projectSlug: wpaths.projectSlug,
|
|
7779
|
+
projectSessions: wpaths.projectSessions
|
|
7780
|
+
},
|
|
7781
|
+
workingDir,
|
|
7782
|
+
initialSessionId: session.id,
|
|
7783
|
+
sessionStore,
|
|
7784
|
+
manageRecoveryLock: !opts.services?.session
|
|
7785
|
+
});
|
|
7786
|
+
const statusTracker = sessionIdentity.statusTracker;
|
|
7787
|
+
let context;
|
|
7788
|
+
const tokenCounter = new DefaultTokenCounter({
|
|
7789
|
+
registry: modelsRegistry,
|
|
7790
|
+
providerId: config.provider,
|
|
7791
|
+
events,
|
|
7792
|
+
sessionId: () => context?.session?.id ?? session.id
|
|
7793
|
+
});
|
|
7794
|
+
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
7795
|
+
const activeMode = await modeStore.getActiveMode();
|
|
7796
|
+
const modeId = activeMode?.id ?? "default";
|
|
7797
|
+
const modePrompt = activeMode?.prompt ?? "";
|
|
7798
|
+
const customModeStore = createCustomModeStore(wpaths.configDir);
|
|
7799
|
+
await customModeStore.load();
|
|
7800
|
+
console.log("[WebUI] Custom context modes loaded:", customModeStore.list().filter((m) => m.custom).length, "custom");
|
|
7801
|
+
const resolvedModel = await resolveProviderModelMetadata(
|
|
7802
|
+
modelsRegistry,
|
|
7803
|
+
config.provider,
|
|
7804
|
+
config.model,
|
|
7805
|
+
config.providers?.[config.provider]
|
|
7806
|
+
);
|
|
7807
|
+
const modelCapabilities = resolvedModel?.capabilities ? { maxContextTokens: resolvedModel.capabilities.maxContext, supportsTools: resolvedModel.capabilities.tools, supportsVision: resolvedModel.capabilities.vision, supportsReasoning: resolvedModel.capabilities.reasoning } : void 0;
|
|
7808
|
+
const modelCapabilitiesRef = { current: modelCapabilities };
|
|
7809
|
+
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
7810
|
+
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
7811
|
+
manifestPath: path16.join(wpaths.globalRoot, "installed-skills.json"),
|
|
7812
|
+
projectSkillsDir: wpaths.inProjectSkills,
|
|
7813
|
+
globalSkillsDir: wpaths.globalSkills,
|
|
7814
|
+
projectHash: wpaths.projectHash,
|
|
7815
|
+
skillLoader
|
|
7816
|
+
}) : void 0;
|
|
7817
|
+
const promptsEnabled = config.features.prompts !== false;
|
|
7818
|
+
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
7374
7819
|
try {
|
|
7375
7820
|
const req = createRequire2(import.meta.url);
|
|
7376
|
-
return
|
|
7821
|
+
return path16.join(path16.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
|
|
7377
7822
|
} catch {
|
|
7378
7823
|
return void 0;
|
|
7379
7824
|
}
|
|
@@ -7383,6 +7828,9 @@ async function createPreContextServices(input) {
|
|
|
7383
7828
|
const promptsCtx = { promptLoader, promptUsage };
|
|
7384
7829
|
const systemPromptBuilder = new DefaultSystemPromptBuilder({
|
|
7385
7830
|
memoryStore,
|
|
7831
|
+
// Super Memory's turn middleware is the single memory-injection channel;
|
|
7832
|
+
// don't also inject a static memory section here (avoids double injection).
|
|
7833
|
+
injectMemory: false,
|
|
7386
7834
|
skillLoader,
|
|
7387
7835
|
modeStore,
|
|
7388
7836
|
modeId,
|
|
@@ -7425,6 +7873,16 @@ async function createPreContextServices(input) {
|
|
|
7425
7873
|
const initialContextPolicy = resolveContextWindowPolicy(config.context);
|
|
7426
7874
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
7427
7875
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
7876
|
+
context.state.setMeta(
|
|
7877
|
+
"plan.path",
|
|
7878
|
+
sessionScopedPath(wpaths.projectSessions, session.id, ".plan.json")
|
|
7879
|
+
);
|
|
7880
|
+
context.state.setMeta(
|
|
7881
|
+
"task.path",
|
|
7882
|
+
sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
|
|
7883
|
+
);
|
|
7884
|
+
await hydrateSessionKanban(context);
|
|
7885
|
+
attachSessionKanbanMirror(context);
|
|
7428
7886
|
seedContextMeta(config, context);
|
|
7429
7887
|
return {
|
|
7430
7888
|
modelsRegistry,
|
|
@@ -7441,6 +7899,7 @@ async function createPreContextServices(input) {
|
|
|
7441
7899
|
session,
|
|
7442
7900
|
sessionStartedAt,
|
|
7443
7901
|
statusTracker,
|
|
7902
|
+
sessionIdentity,
|
|
7444
7903
|
tokenCounter,
|
|
7445
7904
|
modeStore,
|
|
7446
7905
|
modeId,
|
|
@@ -7490,6 +7949,7 @@ function patchConfig(config, updates) {
|
|
|
7490
7949
|
}
|
|
7491
7950
|
|
|
7492
7951
|
// src/server/backend-services.ts
|
|
7952
|
+
import { join as join13 } from "node:path";
|
|
7493
7953
|
import {
|
|
7494
7954
|
Agent,
|
|
7495
7955
|
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
@@ -7503,9 +7963,10 @@ import {
|
|
|
7503
7963
|
installDesignStudioMiddleware,
|
|
7504
7964
|
ObservableBrainArbiter as ObservableBrainArbiterCtor,
|
|
7505
7965
|
resolveContextWindowPolicy as resolveContextWindowPolicy2,
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7966
|
+
BrainDecisionLedger,
|
|
7967
|
+
createBrainRuntime,
|
|
7968
|
+
resolveBrainConfigDefaults,
|
|
7969
|
+
EscalationRoutingBrainArbiter,
|
|
7509
7970
|
GlobalMailbox as GlobalMailbox2,
|
|
7510
7971
|
mailboxSessionTag,
|
|
7511
7972
|
SessionMemoryConsolidator,
|
|
@@ -7519,12 +7980,13 @@ import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
|
|
|
7519
7980
|
var REPLAY_LIMIT = 50;
|
|
7520
7981
|
var PAUSE_TIMEOUT_MS = 6e4;
|
|
7521
7982
|
var CollaborationWebSocketHandler = class {
|
|
7522
|
-
constructor(events, logger, reader, annotations, bus) {
|
|
7983
|
+
constructor(events, logger, reader, annotations, bus, options = {}) {
|
|
7523
7984
|
this.events = events;
|
|
7524
7985
|
this.logger = logger;
|
|
7525
7986
|
this.reader = reader;
|
|
7526
7987
|
this.annotations = annotations;
|
|
7527
7988
|
this.bus = bus;
|
|
7989
|
+
this.options = options;
|
|
7528
7990
|
this.subscribe();
|
|
7529
7991
|
this.bus?.onInjectionConsumed((info) => this.broadcastInjectionConsumed(info));
|
|
7530
7992
|
}
|
|
@@ -7533,6 +7995,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
7533
7995
|
reader;
|
|
7534
7996
|
annotations;
|
|
7535
7997
|
bus;
|
|
7998
|
+
options;
|
|
7536
7999
|
clients = /* @__PURE__ */ new Set();
|
|
7537
8000
|
/** sessionId → participants currently watching it. */
|
|
7538
8001
|
bySession = /* @__PURE__ */ new Map();
|
|
@@ -7560,11 +8023,16 @@ var CollaborationWebSocketHandler = class {
|
|
|
7560
8023
|
handleMessage(ws, msg) {
|
|
7561
8024
|
if (msg.type === "collab.join") {
|
|
7562
8025
|
const payload = msg.payload;
|
|
7563
|
-
if (
|
|
8026
|
+
if (typeof payload?.sessionId !== "string" || payload.sessionId.trim().length === 0) {
|
|
7564
8027
|
this.send(ws, this.errorMessage("collab.join requires sessionId"));
|
|
7565
8028
|
return true;
|
|
7566
8029
|
}
|
|
7567
|
-
|
|
8030
|
+
const role = payload.role ?? "observer";
|
|
8031
|
+
if (role !== "observer" && role !== "annotator" && role !== "controller") {
|
|
8032
|
+
this.send(ws, this.errorMessage(`unknown collaboration role '${String(role)}'`));
|
|
8033
|
+
return true;
|
|
8034
|
+
}
|
|
8035
|
+
this.join(ws, payload.sessionId, role);
|
|
7568
8036
|
return true;
|
|
7569
8037
|
}
|
|
7570
8038
|
if (msg.type === "collab.leave") {
|
|
@@ -7599,6 +8067,20 @@ var CollaborationWebSocketHandler = class {
|
|
|
7599
8067
|
}
|
|
7600
8068
|
// ── Join / leave flow ──────────────────────────────────────────────────
|
|
7601
8069
|
join(ws, sessionId, role) {
|
|
8070
|
+
const activeSessionId = this.options.getActiveSessionId?.();
|
|
8071
|
+
if (activeSessionId !== void 0 && sessionId !== activeSessionId) {
|
|
8072
|
+
this.send(
|
|
8073
|
+
ws,
|
|
8074
|
+
this.errorMessage(
|
|
8075
|
+
`collab.join sessionId mismatch (active: ${activeSessionId})`
|
|
8076
|
+
)
|
|
8077
|
+
);
|
|
8078
|
+
return;
|
|
8079
|
+
}
|
|
8080
|
+
if (this.findParticipant(ws)) {
|
|
8081
|
+
this.send(ws, this.errorMessage("collab.join requires leaving the current session first"));
|
|
8082
|
+
return;
|
|
8083
|
+
}
|
|
7602
8084
|
if (role === "controller" && !this.bus) {
|
|
7603
8085
|
this.send(
|
|
7604
8086
|
ws,
|
|
@@ -7617,6 +8099,13 @@ var CollaborationWebSocketHandler = class {
|
|
|
7617
8099
|
);
|
|
7618
8100
|
return;
|
|
7619
8101
|
}
|
|
8102
|
+
if (role !== "observer" && this.options.authorizeRole?.({ ws, sessionId, requestedRole: role }) !== true) {
|
|
8103
|
+
this.send(
|
|
8104
|
+
ws,
|
|
8105
|
+
this.errorMessage(`role '${role}' requires explicit server authorization`)
|
|
8106
|
+
);
|
|
8107
|
+
return;
|
|
8108
|
+
}
|
|
7620
8109
|
const participant = {
|
|
7621
8110
|
participantId: randomUUID(),
|
|
7622
8111
|
ws,
|
|
@@ -7870,17 +8359,13 @@ var CollaborationWebSocketHandler = class {
|
|
|
7870
8359
|
type: "collab.event",
|
|
7871
8360
|
payload: { kind, payload, at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
7872
8361
|
};
|
|
7873
|
-
const
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
`collab broadcast failed: ${toErrorMessage6(err)}`
|
|
7881
|
-
);
|
|
7882
|
-
}
|
|
7883
|
-
}
|
|
8362
|
+
const activeSessionId = this.options.getActiveSessionId?.();
|
|
8363
|
+
if (activeSessionId !== void 0) {
|
|
8364
|
+
this.broadcast(activeSessionId, msg);
|
|
8365
|
+
return;
|
|
8366
|
+
}
|
|
8367
|
+
for (const sessionId of this.bySession.keys()) {
|
|
8368
|
+
this.broadcast(sessionId, msg);
|
|
7884
8369
|
}
|
|
7885
8370
|
}
|
|
7886
8371
|
/**
|
|
@@ -8204,7 +8689,8 @@ var CollaborationWebSocketHandler = class {
|
|
|
8204
8689
|
* Bus callback: a queued injection was spliced into a real tool call. Re-emit
|
|
8205
8690
|
* `collab.injection.granted` with phase `'consumed'` and the now-known tool
|
|
8206
8691
|
* name. The injection carries no sessionId, so resolve it from the author's
|
|
8207
|
-
* current
|
|
8692
|
+
* current participant. If the author has left, fail closed rather than leak
|
|
8693
|
+
* the payload to unrelated sessions.
|
|
8208
8694
|
*/
|
|
8209
8695
|
broadcastInjectionConsumed(info) {
|
|
8210
8696
|
let sessionId = null;
|
|
@@ -8233,14 +8719,16 @@ var CollaborationWebSocketHandler = class {
|
|
|
8233
8719
|
if (sessionId) {
|
|
8234
8720
|
this.broadcast(sessionId, message(sessionId));
|
|
8235
8721
|
} else {
|
|
8236
|
-
|
|
8722
|
+
this.logger.debug?.(
|
|
8723
|
+
`collab: consumed injection ${info.toolUseId} has no live author; broadcast suppressed`
|
|
8724
|
+
);
|
|
8237
8725
|
}
|
|
8238
8726
|
}
|
|
8239
8727
|
};
|
|
8240
8728
|
|
|
8241
8729
|
// src/server/codebase-indexing.ts
|
|
8242
8730
|
import * as fs12 from "node:fs";
|
|
8243
|
-
import * as
|
|
8731
|
+
import * as path17 from "node:path";
|
|
8244
8732
|
import {
|
|
8245
8733
|
cancelPendingReindexes,
|
|
8246
8734
|
enqueueReindex,
|
|
@@ -8293,7 +8781,7 @@ function setupWebUICodebaseIndexing(deps2) {
|
|
|
8293
8781
|
if (!filename) return;
|
|
8294
8782
|
const rel = filename.toString();
|
|
8295
8783
|
if (isIgnored(rel)) return;
|
|
8296
|
-
const abs =
|
|
8784
|
+
const abs = path17.resolve(deps2.projectRoot, rel);
|
|
8297
8785
|
enqueueFile(abs);
|
|
8298
8786
|
});
|
|
8299
8787
|
watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
|
|
@@ -8306,7 +8794,7 @@ function setupWebUICodebaseIndexing(deps2) {
|
|
|
8306
8794
|
}
|
|
8307
8795
|
function enqueueFile(filePath) {
|
|
8308
8796
|
if (!idx.onEdit && !idx.watchExternal) return;
|
|
8309
|
-
const abs =
|
|
8797
|
+
const abs = path17.isAbsolute(filePath) ? path17.normalize(filePath) : path17.resolve(deps2.projectRoot, filePath);
|
|
8310
8798
|
if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
|
|
8311
8799
|
enqueueReindex({
|
|
8312
8800
|
projectRoot: deps2.projectRoot,
|
|
@@ -8343,9 +8831,9 @@ function isIgnored(rel) {
|
|
|
8343
8831
|
return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
|
|
8344
8832
|
}
|
|
8345
8833
|
function isInside2(root, target) {
|
|
8346
|
-
const normalizedRoot =
|
|
8347
|
-
const normalizedTarget =
|
|
8348
|
-
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot +
|
|
8834
|
+
const normalizedRoot = path17.resolve(root);
|
|
8835
|
+
const normalizedTarget = path17.resolve(target);
|
|
8836
|
+
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path17.sep);
|
|
8349
8837
|
}
|
|
8350
8838
|
|
|
8351
8839
|
// src/server/discover-mailbox-bridge.ts
|
|
@@ -8878,21 +9366,59 @@ async function createAgentServices(input) {
|
|
|
8878
9366
|
agent.extensions.register(new SessionMemoryConsolidator({ memoryStore }));
|
|
8879
9367
|
}
|
|
8880
9368
|
console.log("[WebUI] Agent initialized");
|
|
8881
|
-
const
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
9369
|
+
const brainCfg = resolveBrainConfigDefaults(config.brain, {
|
|
9370
|
+
fallbackModels: config.fallbackModels
|
|
9371
|
+
});
|
|
9372
|
+
const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
|
|
9373
|
+
let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
|
|
9374
|
+
let brainLedger;
|
|
9375
|
+
const startBrainLedger = () => {
|
|
9376
|
+
if (brainLedger) return;
|
|
9377
|
+
brainLedger = new BrainDecisionLedger({ events, filePath: brainLedgerPath });
|
|
9378
|
+
void brainLedger.start();
|
|
9379
|
+
};
|
|
9380
|
+
if (brainLedgerEnabled) startBrainLedger();
|
|
9381
|
+
const brainRuntime = createBrainRuntime({
|
|
9382
|
+
initialConfig: brainCfg,
|
|
9383
|
+
defaultProviderId: config.provider,
|
|
9384
|
+
sessionProvider: () => provider,
|
|
9385
|
+
sessionModel: () => context.model,
|
|
9386
|
+
resolveProvider: (providerId) => {
|
|
9387
|
+
const savedCfg = config.providers?.[providerId] ?? {};
|
|
9388
|
+
return providerRegistry.create({
|
|
9389
|
+
...savedCfg,
|
|
9390
|
+
apiKey: savedCfg.apiKey ?? config.apiKey,
|
|
9391
|
+
baseUrl: savedCfg.baseUrl ?? config.baseUrl,
|
|
9392
|
+
type: providerId
|
|
9393
|
+
});
|
|
9394
|
+
},
|
|
9395
|
+
ledger: {
|
|
9396
|
+
getPath: () => brainLedgerEnabled ? brainLedgerPath : void 0,
|
|
9397
|
+
isEnabled: () => brainLedgerEnabled,
|
|
9398
|
+
setEnabled: (on) => {
|
|
9399
|
+
brainLedgerEnabled = on;
|
|
9400
|
+
if (on) {
|
|
9401
|
+
startBrainLedger();
|
|
9402
|
+
} else {
|
|
9403
|
+
void brainLedger?.stop();
|
|
9404
|
+
brainLedger = void 0;
|
|
9405
|
+
}
|
|
9406
|
+
},
|
|
9407
|
+
failureStreakFor: (request) => brainLedger?.failureStreakFor(request) ?? 0,
|
|
9408
|
+
getDecisionDigest: (request) => brainLedger?.digestFor(request)
|
|
9409
|
+
},
|
|
9410
|
+
persist: input.persistBrainConfig
|
|
9411
|
+
});
|
|
9412
|
+
const brainSettings = {
|
|
9413
|
+
get maxAutoRisk() {
|
|
9414
|
+
return brainRuntime.getMaxAutoRisk();
|
|
9415
|
+
},
|
|
9416
|
+
set maxAutoRisk(level) {
|
|
9417
|
+
void brainRuntime.apply({ maxAutoRisk: level }).persisted;
|
|
9418
|
+
}
|
|
8889
9419
|
};
|
|
8890
9420
|
const brain = new ObservableBrainArbiterCtor(
|
|
8891
|
-
|
|
8892
|
-
policy: new DefaultBrainArbiter(),
|
|
8893
|
-
autonomous: autonomousBrain,
|
|
8894
|
-
getMaxAutoRisk: () => brainSettings.maxAutoRisk
|
|
8895
|
-
}),
|
|
9421
|
+
new EscalationRoutingBrainArbiter(brainRuntime.arbiter, void 0, () => "headless"),
|
|
8896
9422
|
events
|
|
8897
9423
|
);
|
|
8898
9424
|
container.bind(TOKENS2.BrainArbiter, () => brain);
|
|
@@ -8932,6 +9458,12 @@ async function createAgentServices(input) {
|
|
|
8932
9458
|
const brainMonitor = new BrainMonitor({
|
|
8933
9459
|
events,
|
|
8934
9460
|
brain,
|
|
9461
|
+
toolFailureStreak: brainCfg.monitor?.toolFailureStreak,
|
|
9462
|
+
errorStormCount: brainCfg.monitor?.errorStormCount,
|
|
9463
|
+
stallMs: brainCfg.monitor?.stallMs,
|
|
9464
|
+
fileChurnThreshold: brainCfg.monitor?.fileChurnThreshold,
|
|
9465
|
+
fileChurnWindowMs: brainCfg.monitor?.fileChurnWindowMs,
|
|
9466
|
+
cooldownMs: brainCfg.monitor?.cooldownMs,
|
|
8935
9467
|
sessionId: () => context.session?.id,
|
|
8936
9468
|
intervene: async ({ subject, body }) => {
|
|
8937
9469
|
const tag = mailboxSessionTag(input.sessionGetter().id);
|
|
@@ -8996,7 +9528,10 @@ async function createAgentServices(input) {
|
|
|
8996
9528
|
logger,
|
|
8997
9529
|
input.sessionReader,
|
|
8998
9530
|
input.annotationsStore,
|
|
8999
|
-
collabBus
|
|
9531
|
+
collabBus,
|
|
9532
|
+
{
|
|
9533
|
+
getActiveSessionId: () => context.session.id
|
|
9534
|
+
}
|
|
9000
9535
|
);
|
|
9001
9536
|
return {
|
|
9002
9537
|
collabBus,
|
|
@@ -9008,8 +9543,13 @@ async function createAgentServices(input) {
|
|
|
9008
9543
|
pipelines,
|
|
9009
9544
|
brain,
|
|
9010
9545
|
brainSettings,
|
|
9546
|
+
brainRuntime,
|
|
9011
9547
|
brainLog,
|
|
9012
9548
|
brainMonitor,
|
|
9549
|
+
// Getter: ledger toggles swap the instance, shutdown must stop the LIVE one.
|
|
9550
|
+
get brainLedger() {
|
|
9551
|
+
return brainLedger;
|
|
9552
|
+
},
|
|
9013
9553
|
codebaseIndexing,
|
|
9014
9554
|
autoPhaseHandler,
|
|
9015
9555
|
specsHandler,
|
|
@@ -9185,7 +9725,18 @@ function createConnectionHandler(opts) {
|
|
|
9185
9725
|
}
|
|
9186
9726
|
|
|
9187
9727
|
// src/server/message-dispatcher.ts
|
|
9188
|
-
import
|
|
9728
|
+
import path18 from "node:path";
|
|
9729
|
+
import {
|
|
9730
|
+
buildUserContentBlocks,
|
|
9731
|
+
IncomingImageError,
|
|
9732
|
+
parseIncomingImages
|
|
9733
|
+
} from "@wrongstack/core/utils";
|
|
9734
|
+
import {
|
|
9735
|
+
createToolVisionAdapters,
|
|
9736
|
+
ImageInputUnsupportedError,
|
|
9737
|
+
routeImagesForModel,
|
|
9738
|
+
VisionUrlBlockedError
|
|
9739
|
+
} from "@wrongstack/runtime/vision";
|
|
9189
9740
|
|
|
9190
9741
|
// src/server/autophase-routes.ts
|
|
9191
9742
|
async function handleAutoPhaseRoute(_ws, msg, handlers) {
|
|
@@ -9206,6 +9757,12 @@ async function handleBrainRoute(ws, msg, handlers) {
|
|
|
9206
9757
|
case "brain.ask":
|
|
9207
9758
|
await handlers.ask(ws, msg);
|
|
9208
9759
|
return true;
|
|
9760
|
+
case "brain.config.get":
|
|
9761
|
+
await handlers.configGet(ws, msg);
|
|
9762
|
+
return true;
|
|
9763
|
+
case "brain.config.set":
|
|
9764
|
+
await handlers.configSet(ws, msg);
|
|
9765
|
+
return true;
|
|
9209
9766
|
default:
|
|
9210
9767
|
return false;
|
|
9211
9768
|
}
|
|
@@ -9459,8 +10016,10 @@ import {
|
|
|
9459
10016
|
serializeTaskGraph
|
|
9460
10017
|
} from "@wrongstack/core";
|
|
9461
10018
|
import {
|
|
10019
|
+
addCheckToTask,
|
|
9462
10020
|
addColumn,
|
|
9463
10021
|
addGoalMetricToTask,
|
|
10022
|
+
addNoteToTask,
|
|
9464
10023
|
addTask,
|
|
9465
10024
|
assignTask,
|
|
9466
10025
|
claimReadyTask,
|
|
@@ -9474,29 +10033,47 @@ import {
|
|
|
9474
10033
|
getKanbanQueueHealth,
|
|
9475
10034
|
getTask,
|
|
9476
10035
|
getTaskChain,
|
|
9477
|
-
listReadyTasks,
|
|
9478
10036
|
listBoards,
|
|
10037
|
+
listReadyTasks,
|
|
9479
10038
|
mergeTasks,
|
|
9480
10039
|
moveTask,
|
|
9481
10040
|
parseLinesIntoTasks,
|
|
10041
|
+
reconcileKanbanBoard,
|
|
10042
|
+
recoverStaleTaskAssignments,
|
|
10043
|
+
releaseTaskClaim,
|
|
9482
10044
|
removeBoard,
|
|
9483
10045
|
removeColumn,
|
|
9484
10046
|
removeTask,
|
|
9485
|
-
releaseTaskClaim,
|
|
9486
10047
|
setTaskChain,
|
|
9487
10048
|
splitTask,
|
|
9488
10049
|
syncBoardFromTaskGraph,
|
|
9489
10050
|
transferTaskToBoard,
|
|
9490
10051
|
updateBoard,
|
|
10052
|
+
updateCheckOnTask,
|
|
9491
10053
|
updateGoalMetricOnTask,
|
|
9492
10054
|
updateTask
|
|
9493
10055
|
} from "@wrongstack/kanban";
|
|
10056
|
+
import { applySessionKanbanTaskToSource } from "@wrongstack/tools/session-kanban";
|
|
10057
|
+
async function syncSessionSource(ctx, task, remove = false) {
|
|
10058
|
+
if (!ctx.context) return;
|
|
10059
|
+
const update = await applySessionKanbanTaskToSource(ctx.context, task, { remove });
|
|
10060
|
+
const sessionId = ctx.context.session?.id ?? "";
|
|
10061
|
+
if (update.todos)
|
|
10062
|
+
ctx.broadcast?.({ type: "todos.updated", payload: { sessionId, todos: update.todos } });
|
|
10063
|
+
if (update.tasks)
|
|
10064
|
+
ctx.broadcast?.({ type: "tasks.updated", payload: { sessionId, tasks: update.tasks.tasks } });
|
|
10065
|
+
if (update.plan)
|
|
10066
|
+
ctx.broadcast?.({ type: "plan.updated", payload: { sessionId, plan: update.plan } });
|
|
10067
|
+
}
|
|
9494
10068
|
function ok(ws, type, data) {
|
|
9495
10069
|
send(ws, { type, payload: { success: true, data: data ?? null } });
|
|
9496
10070
|
}
|
|
9497
10071
|
function fail(ws, type, message) {
|
|
9498
10072
|
send(ws, { type, payload: { success: false, error: message } });
|
|
9499
10073
|
}
|
|
10074
|
+
function has(payload, key) {
|
|
10075
|
+
return payload !== void 0 && Object.hasOwn(payload, key);
|
|
10076
|
+
}
|
|
9500
10077
|
async function handleKanbanRoute(ws, msg, ctx) {
|
|
9501
10078
|
if (!msg.type.startsWith("kanban.")) return false;
|
|
9502
10079
|
const payload = msg.payload;
|
|
@@ -9525,6 +10102,38 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9525
10102
|
ok(ws, type, await getKanbanQueueHealth(ctx.projectRoot, { boardId: hBoardId }));
|
|
9526
10103
|
return true;
|
|
9527
10104
|
}
|
|
10105
|
+
case "kanban.supervisor.status":
|
|
10106
|
+
case "kanban.supervisor.audit": {
|
|
10107
|
+
const boardId = payload?.boardId;
|
|
10108
|
+
if (!boardId) {
|
|
10109
|
+
fail(ws, type, "boardId required");
|
|
10110
|
+
return true;
|
|
10111
|
+
}
|
|
10112
|
+
const board = await getBoard(ctx.projectRoot, boardId);
|
|
10113
|
+
if (!board) {
|
|
10114
|
+
fail(ws, type, `Board not found: ${boardId}`);
|
|
10115
|
+
return true;
|
|
10116
|
+
}
|
|
10117
|
+
const reconciled = await reconcileKanbanBoard(ctx.projectRoot, boardId);
|
|
10118
|
+
let health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
10119
|
+
const recovered = health.staleAssignments.count ? await recoverStaleTaskAssignments(ctx.projectRoot, boardId, {
|
|
10120
|
+
mode: board.supervisor?.recoveryMode ?? "auto",
|
|
10121
|
+
reason: "On-demand standalone Kanban supervisor audit."
|
|
10122
|
+
}) : null;
|
|
10123
|
+
if (recovered) health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
10124
|
+
const anomalyCount = health.staleAssignments.count + health.dependencyBlocked.count + health.failedRetryable.count + health.counts.failed + health.counts.blocked;
|
|
10125
|
+
ok(ws, type, {
|
|
10126
|
+
boardId,
|
|
10127
|
+
status: board.supervisor?.enabled === false ? "disabled" : anomalyCount ? "attention" : "healthy",
|
|
10128
|
+
mode: board.supervisor?.mode ?? "deterministic",
|
|
10129
|
+
lastAuditAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10130
|
+
reconciledTaskIds: reconciled?.tasks.map((task) => task.id) ?? [],
|
|
10131
|
+
staleRecoveredTaskIds: recovered?.tasks.map((task) => task.id) ?? [],
|
|
10132
|
+
anomalyCount,
|
|
10133
|
+
summary: `${health.counts.running} running \xB7 ${health.counts.ready} ready \xB7 ${health.counts.review} review \xB7 ${health.counts.blocked} blocked \xB7 ${health.counts.failed} failed`
|
|
10134
|
+
});
|
|
10135
|
+
return true;
|
|
10136
|
+
}
|
|
9528
10137
|
case "kanban.create": {
|
|
9529
10138
|
const title = payload?.title;
|
|
9530
10139
|
if (!title) {
|
|
@@ -9553,7 +10162,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9553
10162
|
...payload?.title ? { title: payload.title } : {},
|
|
9554
10163
|
...payload?.description ? { description: payload.description } : {},
|
|
9555
10164
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
9556
|
-
...payload?.columns ? { columns: payload.columns } : {}
|
|
10165
|
+
...payload?.columns ? { columns: payload.columns } : {},
|
|
10166
|
+
...has(payload, "supervisor") ? {
|
|
10167
|
+
supervisor: payload?.supervisor ?? null
|
|
10168
|
+
} : {}
|
|
9557
10169
|
});
|
|
9558
10170
|
board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
|
|
9559
10171
|
return true;
|
|
@@ -9580,6 +10192,11 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9580
10192
|
return true;
|
|
9581
10193
|
}
|
|
9582
10194
|
const board = await getBoard(ctx.projectRoot, boardId);
|
|
10195
|
+
const activeSessionId = ctx.context?.session?.id;
|
|
10196
|
+
if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
|
|
10197
|
+
fail(ws, type, "The active session Kanban board cannot be deleted.");
|
|
10198
|
+
return true;
|
|
10199
|
+
}
|
|
9583
10200
|
ok(ws, type, {
|
|
9584
10201
|
removed: board ? await removeBoard(ctx.projectRoot, board.id) : false,
|
|
9585
10202
|
boardId: board?.id ?? boardId
|
|
@@ -9760,15 +10377,31 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9760
10377
|
return true;
|
|
9761
10378
|
}
|
|
9762
10379
|
const board = await updateTask(ctx.projectRoot, boardId, taskId, {
|
|
9763
|
-
...payload
|
|
9764
|
-
...payload
|
|
9765
|
-
...payload
|
|
9766
|
-
...payload
|
|
9767
|
-
...payload
|
|
9768
|
-
...payload
|
|
9769
|
-
...payload
|
|
10380
|
+
...has(payload, "title") ? { title: payload?.title } : {},
|
|
10381
|
+
...has(payload, "description") ? { description: payload?.description ?? "" } : {},
|
|
10382
|
+
...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
|
|
10383
|
+
...has(payload, "priority") ? { priority: payload?.priority } : {},
|
|
10384
|
+
...has(payload, "type") ? { type: payload?.type } : {},
|
|
10385
|
+
...has(payload, "status") ? { status: payload?.status } : {},
|
|
10386
|
+
...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
|
|
10387
|
+
...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
|
|
10388
|
+
...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
|
|
10389
|
+
...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
|
|
10390
|
+
...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
|
|
10391
|
+
...has(payload, "retryPolicy") ? {
|
|
10392
|
+
retryPolicy: payload?.retryPolicy ?? null
|
|
10393
|
+
} : {},
|
|
10394
|
+
...has(payload, "costCeilingUsd") ? {
|
|
10395
|
+
costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
|
|
10396
|
+
} : {}
|
|
9770
10397
|
});
|
|
9771
|
-
|
|
10398
|
+
if (!board) {
|
|
10399
|
+
fail(ws, type, "Board or task not found");
|
|
10400
|
+
return true;
|
|
10401
|
+
}
|
|
10402
|
+
const task = findTask(board.tasks, taskId);
|
|
10403
|
+
if (task) await syncSessionSource(ctx, task);
|
|
10404
|
+
ok(ws, type, task);
|
|
9772
10405
|
return true;
|
|
9773
10406
|
}
|
|
9774
10407
|
case "kanban.task.move": {
|
|
@@ -9786,7 +10419,13 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9786
10419
|
columnId,
|
|
9787
10420
|
payload?.order
|
|
9788
10421
|
);
|
|
9789
|
-
|
|
10422
|
+
if (!board) {
|
|
10423
|
+
fail(ws, type, "Move failed");
|
|
10424
|
+
return true;
|
|
10425
|
+
}
|
|
10426
|
+
const task = findTask(board.tasks, taskId);
|
|
10427
|
+
if (task) await syncSessionSource(ctx, task);
|
|
10428
|
+
ok(ws, type, task);
|
|
9790
10429
|
return true;
|
|
9791
10430
|
}
|
|
9792
10431
|
case "kanban.task.copy":
|
|
@@ -9845,8 +10484,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9845
10484
|
...payload?.role ? { role: payload.role } : {},
|
|
9846
10485
|
...payload?.provider ? { provider: payload.provider } : {},
|
|
9847
10486
|
...payload?.model ? { model: payload.model } : {},
|
|
10487
|
+
...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
|
|
9848
10488
|
...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
|
|
9849
10489
|
...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
|
|
10490
|
+
...payload?.skills ? { skills: payload.skills } : {},
|
|
9850
10491
|
...payload?.tools ? { tools: payload.tools } : {},
|
|
9851
10492
|
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
|
|
9852
10493
|
...payload?.assignee ? { assignee: payload.assignee } : {},
|
|
@@ -9865,7 +10506,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9865
10506
|
const board = await releaseTaskClaim(ctx.projectRoot, boardId, taskId, {
|
|
9866
10507
|
...payload?.status ? { status: payload.status } : {},
|
|
9867
10508
|
...payload?.reason ? { reason: payload.reason } : {},
|
|
9868
|
-
...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {}
|
|
10509
|
+
...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {},
|
|
10510
|
+
...typeof payload?.maxAttempts === "number" ? { maxAttempts: payload.maxAttempts } : {},
|
|
10511
|
+
...typeof payload?.costCeilingUsd === "number" ? { costCeilingUsd: payload.costCeilingUsd } : {},
|
|
10512
|
+
...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
|
|
9869
10513
|
});
|
|
9870
10514
|
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
9871
10515
|
return true;
|
|
@@ -9921,14 +10565,66 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9921
10565
|
...payload?.role ? { role: payload.role } : {},
|
|
9922
10566
|
...payload?.provider ? { provider: payload.provider } : {},
|
|
9923
10567
|
...payload?.model ? { model: payload.model } : {},
|
|
10568
|
+
...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
|
|
9924
10569
|
...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
|
|
9925
10570
|
...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
|
|
10571
|
+
...payload?.skills ? { skills: payload.skills } : {},
|
|
9926
10572
|
...payload?.tools ? { tools: payload.tools } : {},
|
|
9927
|
-
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {}
|
|
10573
|
+
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
|
|
10574
|
+
...payload?.assignee ? { assignee: payload.assignee } : {},
|
|
10575
|
+
...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
|
|
10576
|
+
...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
|
|
10577
|
+
...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
|
|
9928
10578
|
});
|
|
9929
10579
|
board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
|
|
9930
10580
|
return true;
|
|
9931
10581
|
}
|
|
10582
|
+
case "kanban.task.check.add": {
|
|
10583
|
+
const boardId = payload?.boardId;
|
|
10584
|
+
const taskId = payload?.taskId;
|
|
10585
|
+
const description = payload?.description;
|
|
10586
|
+
if (!boardId || !taskId || !description) {
|
|
10587
|
+
fail(ws, type, "boardId, taskId, and description required");
|
|
10588
|
+
return true;
|
|
10589
|
+
}
|
|
10590
|
+
const board = await addCheckToTask(ctx.projectRoot, boardId, taskId, {
|
|
10591
|
+
description,
|
|
10592
|
+
type: payload?.checkType ?? "manual",
|
|
10593
|
+
status: payload?.status ?? "pending"
|
|
10594
|
+
});
|
|
10595
|
+
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10596
|
+
return true;
|
|
10597
|
+
}
|
|
10598
|
+
case "kanban.task.check.update": {
|
|
10599
|
+
const boardId = payload?.boardId;
|
|
10600
|
+
const taskId = payload?.taskId;
|
|
10601
|
+
const checkId = payload?.checkId;
|
|
10602
|
+
if (!boardId || !taskId || !checkId) {
|
|
10603
|
+
fail(ws, type, "boardId, taskId, and checkId required");
|
|
10604
|
+
return true;
|
|
10605
|
+
}
|
|
10606
|
+
const board = await updateCheckOnTask(ctx.projectRoot, boardId, taskId, checkId, {
|
|
10607
|
+
...has(payload, "status") ? { status: payload?.status } : {}
|
|
10608
|
+
});
|
|
10609
|
+
if (!board) fail(ws, type, "Check not found");
|
|
10610
|
+
else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
|
|
10611
|
+
return true;
|
|
10612
|
+
}
|
|
10613
|
+
case "kanban.task.note.add": {
|
|
10614
|
+
const boardId = payload?.boardId;
|
|
10615
|
+
const taskId = payload?.taskId;
|
|
10616
|
+
const content = payload?.content;
|
|
10617
|
+
if (!boardId || !taskId || !content) {
|
|
10618
|
+
fail(ws, type, "boardId, taskId, and content required");
|
|
10619
|
+
return true;
|
|
10620
|
+
}
|
|
10621
|
+
const board = await addNoteToTask(ctx.projectRoot, boardId, taskId, {
|
|
10622
|
+
author: payload?.author ?? "webui",
|
|
10623
|
+
content
|
|
10624
|
+
});
|
|
10625
|
+
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10626
|
+
return true;
|
|
10627
|
+
}
|
|
9932
10628
|
case "kanban.task.dispatch":
|
|
9933
10629
|
fail(
|
|
9934
10630
|
ws,
|
|
@@ -9936,6 +10632,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9936
10632
|
"Kanban agent dispatch is only available from the CLI-hosted WebUI runtime."
|
|
9937
10633
|
);
|
|
9938
10634
|
return true;
|
|
10635
|
+
case "kanban.capabilities":
|
|
10636
|
+
ok(ws, type, { dispatchSupported: false });
|
|
10637
|
+
return true;
|
|
9939
10638
|
case "kanban.task.remove": {
|
|
9940
10639
|
const boardId = payload?.boardId;
|
|
9941
10640
|
const taskId = payload?.taskId;
|
|
@@ -9949,7 +10648,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9949
10648
|
return true;
|
|
9950
10649
|
}
|
|
9951
10650
|
const board = await removeTask(ctx.projectRoot, boardId, taskId);
|
|
9952
|
-
|
|
10651
|
+
if (!board) {
|
|
10652
|
+
fail(ws, type, "Board or task not found");
|
|
10653
|
+
return true;
|
|
10654
|
+
}
|
|
10655
|
+
await syncSessionSource(ctx, task, true);
|
|
10656
|
+
ok(ws, type, { removed: true, boardId: board.id, taskId: task.id, board });
|
|
9953
10657
|
return true;
|
|
9954
10658
|
}
|
|
9955
10659
|
case "kanban.task.get": {
|
|
@@ -10015,6 +10719,9 @@ async function handleMailboxRoute(ws, msg, handlers) {
|
|
|
10015
10719
|
case "mailbox.purge":
|
|
10016
10720
|
await handlers.purge(ws, msg);
|
|
10017
10721
|
return true;
|
|
10722
|
+
case "mailbox.compact":
|
|
10723
|
+
await handlers.compact(ws, msg);
|
|
10724
|
+
return true;
|
|
10018
10725
|
default:
|
|
10019
10726
|
return false;
|
|
10020
10727
|
}
|
|
@@ -10053,6 +10760,18 @@ async function handleMcpRoute(ws, msg, handlers) {
|
|
|
10053
10760
|
case "mcp.discover":
|
|
10054
10761
|
await handlers.discover(ws, msg);
|
|
10055
10762
|
return true;
|
|
10763
|
+
case "mcp.resources":
|
|
10764
|
+
await handlers.resources(ws, msg);
|
|
10765
|
+
return true;
|
|
10766
|
+
case "mcp.prompts":
|
|
10767
|
+
await handlers.prompts(ws, msg);
|
|
10768
|
+
return true;
|
|
10769
|
+
case "mcp.resource.read":
|
|
10770
|
+
await handlers.resourceRead(ws, msg);
|
|
10771
|
+
return true;
|
|
10772
|
+
case "mcp.prompt.get":
|
|
10773
|
+
await handlers.promptGet(ws, msg);
|
|
10774
|
+
return true;
|
|
10056
10775
|
default:
|
|
10057
10776
|
return false;
|
|
10058
10777
|
}
|
|
@@ -10246,6 +10965,7 @@ async function handleProviderRoute(ws, msg, routes) {
|
|
|
10246
10965
|
baseUrl,
|
|
10247
10966
|
apiKey
|
|
10248
10967
|
});
|
|
10968
|
+
await routes.adoptDefaultProviderIfUnset(id);
|
|
10249
10969
|
return true;
|
|
10250
10970
|
}
|
|
10251
10971
|
case "provider.remove": {
|
|
@@ -10442,7 +11162,7 @@ function createMessageDispatcher(opts) {
|
|
|
10442
11162
|
skillLoader: deps2.skillLoader,
|
|
10443
11163
|
skillInstaller: deps2.skillInstaller,
|
|
10444
11164
|
projectRoot,
|
|
10445
|
-
projectSkillsDir:
|
|
11165
|
+
projectSkillsDir: path18.join(projectRoot, ".wrongstack", "skills"),
|
|
10446
11166
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
10447
11167
|
};
|
|
10448
11168
|
}
|
|
@@ -10484,7 +11204,12 @@ function createMessageDispatcher(opts) {
|
|
|
10484
11204
|
if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
|
|
10485
11205
|
if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
|
|
10486
11206
|
if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
|
|
10487
|
-
if (await handleKanbanRoute(ws, msg, {
|
|
11207
|
+
if (await handleKanbanRoute(ws, msg, {
|
|
11208
|
+
projectRoot: state.getProjectRoot(),
|
|
11209
|
+
context: deps2.context,
|
|
11210
|
+
broadcast: (message) => broadcast(state.getClients(), message)
|
|
11211
|
+
}))
|
|
11212
|
+
return;
|
|
10488
11213
|
if (msg.type.startsWith("worktree.") && await deps2.worktreeHandler.handleMessage(
|
|
10489
11214
|
msg
|
|
10490
11215
|
))
|
|
@@ -10515,7 +11240,8 @@ function createMessageDispatcher(opts) {
|
|
|
10515
11240
|
}
|
|
10516
11241
|
case "user_message": {
|
|
10517
11242
|
if (!ensureCurrentSession(ws, msg, "user_message")) return;
|
|
10518
|
-
const
|
|
11243
|
+
const userPayload = msg.payload;
|
|
11244
|
+
const content = userPayload.content;
|
|
10519
11245
|
if (runLock.get()) {
|
|
10520
11246
|
send(ws, {
|
|
10521
11247
|
type: "error",
|
|
@@ -10529,8 +11255,21 @@ function createMessageDispatcher(opts) {
|
|
|
10529
11255
|
const thisRun = new AbortController();
|
|
10530
11256
|
runLock.set(thisRun);
|
|
10531
11257
|
try {
|
|
11258
|
+
let input = content;
|
|
11259
|
+
const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
|
|
11260
|
+
if (imageBlocks.length > 0) {
|
|
11261
|
+
const routed = await routeImagesForModel(buildUserContentBlocks(content, imageBlocks), {
|
|
11262
|
+
supportsVision: deps2.agent.ctx.provider.capabilities.vision,
|
|
11263
|
+
adapters: () => createToolVisionAdapters(deps2.agent.tools),
|
|
11264
|
+
ctx: deps2.agent.ctx,
|
|
11265
|
+
signal: thisRun.signal,
|
|
11266
|
+
providerId: deps2.agent.ctx.provider.id,
|
|
11267
|
+
model: deps2.agent.ctx.model
|
|
11268
|
+
});
|
|
11269
|
+
input = routed.blocks;
|
|
11270
|
+
}
|
|
10532
11271
|
const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
|
|
10533
|
-
const result = await deps2.agent.run(
|
|
11272
|
+
const result = await deps2.agent.run(input, {
|
|
10534
11273
|
signal: thisRun.signal,
|
|
10535
11274
|
maxIterations: maxIt
|
|
10536
11275
|
});
|
|
@@ -10548,13 +11287,24 @@ function createMessageDispatcher(opts) {
|
|
|
10548
11287
|
})
|
|
10549
11288
|
});
|
|
10550
11289
|
} catch (err) {
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
11290
|
+
if (err instanceof IncomingImageError || err instanceof ImageInputUnsupportedError || err instanceof VisionUrlBlockedError) {
|
|
11291
|
+
send(ws, {
|
|
11292
|
+
type: "error",
|
|
11293
|
+
payload: sessionPayload2({
|
|
11294
|
+
phase: "user_message",
|
|
11295
|
+
...err instanceof ImageInputUnsupportedError ? { code: "vision_unsupported" } : {},
|
|
11296
|
+
message: err.message
|
|
11297
|
+
})
|
|
11298
|
+
});
|
|
11299
|
+
} else {
|
|
11300
|
+
send(ws, {
|
|
11301
|
+
type: "error",
|
|
11302
|
+
payload: sessionPayload2({
|
|
11303
|
+
phase: "agent.run",
|
|
11304
|
+
message: errMessage(err)
|
|
11305
|
+
})
|
|
11306
|
+
});
|
|
11307
|
+
}
|
|
10558
11308
|
} finally {
|
|
10559
11309
|
if (runLock.get() === thisRun) {
|
|
10560
11310
|
runLock.set(null);
|
|
@@ -10602,7 +11352,7 @@ function createMessageDispatcher(opts) {
|
|
|
10602
11352
|
break;
|
|
10603
11353
|
}
|
|
10604
11354
|
case "tool.disable": {
|
|
10605
|
-
const name2 =
|
|
11355
|
+
const name2 = msg.payload?.name;
|
|
10606
11356
|
if (!name2) {
|
|
10607
11357
|
send(ws, { type: "error", payload: { message: "tool.disable requires a name" } });
|
|
10608
11358
|
break;
|
|
@@ -10611,13 +11361,15 @@ function createMessageDispatcher(opts) {
|
|
|
10611
11361
|
const currentCfg = deps2.configStore.get().tools ?? {};
|
|
10612
11362
|
const currentDisabled = currentCfg.disabledTools ?? [];
|
|
10613
11363
|
if (ok2 && !currentDisabled.includes(name2)) {
|
|
10614
|
-
deps2.configStore.update({
|
|
11364
|
+
deps2.configStore.update({
|
|
11365
|
+
tools: { ...currentCfg, disabledTools: [...currentDisabled, name2] }
|
|
11366
|
+
});
|
|
10615
11367
|
}
|
|
10616
11368
|
send(ws, { type: "tool.disabled", payload: { name: name2, ok: ok2 } });
|
|
10617
11369
|
break;
|
|
10618
11370
|
}
|
|
10619
11371
|
case "tool.enable": {
|
|
10620
|
-
const name2 =
|
|
11372
|
+
const name2 = msg.payload?.name;
|
|
10621
11373
|
if (!name2) {
|
|
10622
11374
|
send(ws, { type: "error", payload: { message: "tool.enable requires a name" } });
|
|
10623
11375
|
break;
|
|
@@ -10627,19 +11379,31 @@ function createMessageDispatcher(opts) {
|
|
|
10627
11379
|
const currentCfg = deps2.configStore.get().tools ?? {};
|
|
10628
11380
|
const currentDisabled = currentCfg.disabledTools ?? [];
|
|
10629
11381
|
deps2.configStore.update({
|
|
10630
|
-
tools: {
|
|
11382
|
+
tools: {
|
|
11383
|
+
...currentCfg,
|
|
11384
|
+
disabledTools: currentDisabled.filter((n) => n !== name2)
|
|
11385
|
+
}
|
|
10631
11386
|
});
|
|
10632
11387
|
}
|
|
10633
11388
|
send(ws, { type: "tool.enabled", payload: { name: name2, ok: ok2 } });
|
|
10634
11389
|
break;
|
|
10635
11390
|
}
|
|
10636
11391
|
// ── Memory operations — delegated to shared handlers (memory-handlers.ts) ──
|
|
11392
|
+
// `memory.list` renders the single Super Memory store; all writes go
|
|
11393
|
+
// through the structured `memory.super.*` operations below.
|
|
10637
11394
|
case "memory.list":
|
|
10638
11395
|
return handleMemoryList(ws, deps2.memoryStore);
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
11396
|
+
// ── SuperMemory operations ──
|
|
11397
|
+
case "memory.super.list":
|
|
11398
|
+
return handleSuperMemoryList(ws, deps2.memoryStore);
|
|
11399
|
+
case "memory.super.get":
|
|
11400
|
+
return handleSuperMemoryGet(ws, msg, deps2.memoryStore);
|
|
11401
|
+
case "memory.super.update":
|
|
11402
|
+
return handleSuperMemoryUpdate(ws, msg, deps2.memoryStore);
|
|
11403
|
+
case "memory.super.delete":
|
|
11404
|
+
return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
|
|
11405
|
+
case "memory.super.remember":
|
|
11406
|
+
return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
|
|
10643
11407
|
// ── MCP tripwires — handleMcpRoute claims these upstream. ──
|
|
10644
11408
|
case "mcp.list":
|
|
10645
11409
|
throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
|
|
@@ -10661,6 +11425,14 @@ function createMessageDispatcher(opts) {
|
|
|
10661
11425
|
throw new Error("handleMcpRoute did not claim mcp.restart \u2014 check chain order");
|
|
10662
11426
|
case "mcp.discover":
|
|
10663
11427
|
throw new Error("handleMcpRoute did not claim mcp.discover \u2014 check chain order");
|
|
11428
|
+
case "mcp.resources":
|
|
11429
|
+
throw new Error("handleMcpRoute did not claim mcp.resources \u2014 check chain order");
|
|
11430
|
+
case "mcp.prompts":
|
|
11431
|
+
throw new Error("handleMcpRoute did not claim mcp.prompts \u2014 check chain order");
|
|
11432
|
+
case "mcp.resource.read":
|
|
11433
|
+
throw new Error("handleMcpRoute did not claim mcp.resource.read \u2014 check chain order");
|
|
11434
|
+
case "mcp.prompt.get":
|
|
11435
|
+
throw new Error("handleMcpRoute did not claim mcp.prompt.get \u2014 check chain order");
|
|
10664
11436
|
// Skills — full request→response cycle lives in skills-handlers.ts.
|
|
10665
11437
|
case "skills.list":
|
|
10666
11438
|
await handleSkillsList(ws, makeSkillsContext());
|
|
@@ -10961,7 +11733,20 @@ var PREF_KEYS = [
|
|
|
10961
11733
|
"favoriteModels",
|
|
10962
11734
|
"favoriteModelsOnly",
|
|
10963
11735
|
"modelMatrix",
|
|
10964
|
-
"fallbackAuto"
|
|
11736
|
+
"fallbackAuto",
|
|
11737
|
+
// Refiner + TUI visual prefs (parity with the CLI's embedded server —
|
|
11738
|
+
// these were browser-editable there but rejected as unknown keys here).
|
|
11739
|
+
"refinerProvider",
|
|
11740
|
+
"refinerModel",
|
|
11741
|
+
"refinerFallbackProfile",
|
|
11742
|
+
"thinkingWord",
|
|
11743
|
+
"statuslineMode",
|
|
11744
|
+
"animationStyle",
|
|
11745
|
+
// Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
|
|
11746
|
+
"breakerEnabled",
|
|
11747
|
+
"breakerAutoKillResetMs",
|
|
11748
|
+
"fsAccess",
|
|
11749
|
+
"debugStream"
|
|
10965
11750
|
];
|
|
10966
11751
|
function prefSnapshot(contextMeta) {
|
|
10967
11752
|
const snapshot = {};
|
|
@@ -11032,6 +11817,18 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
11032
11817
|
setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
|
|
11033
11818
|
if (typeof payload["enhanceLanguage"] === "string")
|
|
11034
11819
|
setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
|
|
11820
|
+
if (typeof payload["refinerProvider"] === "string")
|
|
11821
|
+
setAutonomy("refinerProvider", payload["refinerProvider"]);
|
|
11822
|
+
if (typeof payload["refinerModel"] === "string")
|
|
11823
|
+
setAutonomy("refinerModel", payload["refinerModel"]);
|
|
11824
|
+
if (typeof payload["refinerFallbackProfile"] === "string")
|
|
11825
|
+
setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
|
|
11826
|
+
if (typeof payload["thinkingWord"] === "string")
|
|
11827
|
+
setAutonomy("thinkingWord", payload["thinkingWord"]);
|
|
11828
|
+
if (typeof payload["statuslineMode"] === "string")
|
|
11829
|
+
setAutonomy("statuslineMode", payload["statuslineMode"]);
|
|
11830
|
+
if (typeof payload["animationStyle"] === "string")
|
|
11831
|
+
setAutonomy("animationStyle", payload["animationStyle"]);
|
|
11035
11832
|
if (autonomyTouched) decrypted.autonomy = autonomyCfg;
|
|
11036
11833
|
if (typeof payload["nextPrediction"] === "boolean")
|
|
11037
11834
|
decrypted.nextPrediction = payload["nextPrediction"];
|
|
@@ -11149,16 +11946,34 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
11149
11946
|
}
|
|
11150
11947
|
decrypted.modelRuntime = mr;
|
|
11151
11948
|
}
|
|
11949
|
+
if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
|
|
11950
|
+
const cb = decrypted.circuitBreaker ?? {};
|
|
11951
|
+
if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
|
|
11952
|
+
if (typeof payload["breakerAutoKillResetMs"] === "number")
|
|
11953
|
+
cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
|
|
11954
|
+
decrypted.circuitBreaker = cb;
|
|
11955
|
+
}
|
|
11956
|
+
if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
|
|
11957
|
+
const restrict = payload["fsAccess"] === "project";
|
|
11958
|
+
const toolsCfg = decrypted.tools ?? {};
|
|
11959
|
+
toolsCfg.restrictToProjectRoot = restrict;
|
|
11960
|
+
decrypted.tools = toolsCfg;
|
|
11961
|
+
const featsCfg = decrypted.features ?? {};
|
|
11962
|
+
featsCfg.allowOutsideProjectRoot = !restrict;
|
|
11963
|
+
decrypted.features = featsCfg;
|
|
11964
|
+
}
|
|
11965
|
+
if (typeof payload["debugStream"] === "boolean")
|
|
11966
|
+
decrypted.debugStream = payload["debugStream"];
|
|
11152
11967
|
}, "prefs");
|
|
11153
11968
|
}
|
|
11154
11969
|
|
|
11155
11970
|
// src/server/projects-manifest.ts
|
|
11156
11971
|
import * as fs14 from "node:fs/promises";
|
|
11157
|
-
import * as
|
|
11972
|
+
import * as path19 from "node:path";
|
|
11158
11973
|
import { projectSlug } from "@wrongstack/core";
|
|
11159
11974
|
function projectsJsonPath(globalConfigPath) {
|
|
11160
|
-
const base =
|
|
11161
|
-
return
|
|
11975
|
+
const base = path19.dirname(globalConfigPath);
|
|
11976
|
+
return path19.join(base, "projects.json");
|
|
11162
11977
|
}
|
|
11163
11978
|
async function loadManifest(globalConfigPath) {
|
|
11164
11979
|
try {
|
|
@@ -11171,15 +11986,15 @@ async function loadManifest(globalConfigPath) {
|
|
|
11171
11986
|
}
|
|
11172
11987
|
async function saveManifest(manifest, globalConfigPath) {
|
|
11173
11988
|
const file = projectsJsonPath(globalConfigPath);
|
|
11174
|
-
await fs14.mkdir(
|
|
11989
|
+
await fs14.mkdir(path19.dirname(file), { recursive: true });
|
|
11175
11990
|
await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
11176
11991
|
}
|
|
11177
11992
|
function generateProjectSlug(rootPath) {
|
|
11178
11993
|
return projectSlug(rootPath);
|
|
11179
11994
|
}
|
|
11180
11995
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
11181
|
-
const base =
|
|
11182
|
-
const dir =
|
|
11996
|
+
const base = path19.dirname(globalConfigPath);
|
|
11997
|
+
const dir = path19.join(base, "projects", slug);
|
|
11183
11998
|
await fs14.mkdir(dir, { recursive: true });
|
|
11184
11999
|
return dir;
|
|
11185
12000
|
}
|
|
@@ -11213,6 +12028,23 @@ function projectSavedProviders(providers) {
|
|
|
11213
12028
|
});
|
|
11214
12029
|
}
|
|
11215
12030
|
var probeScrubber = new DefaultSecretScrubber();
|
|
12031
|
+
async function probeModelDescriptors(cfg) {
|
|
12032
|
+
if (!cfg.baseUrl) return [];
|
|
12033
|
+
try {
|
|
12034
|
+
const keys = normalizeKeys(cfg);
|
|
12035
|
+
const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
|
|
12036
|
+
const result = await probeLocalLlm({
|
|
12037
|
+
baseUrl: cfg.baseUrl,
|
|
12038
|
+
apiKey: active?.apiKey,
|
|
12039
|
+
noAuth: false,
|
|
12040
|
+
scrubber: probeScrubber
|
|
12041
|
+
});
|
|
12042
|
+
if (!result.ok || !result.modelIds) return [];
|
|
12043
|
+
return result.modelIds.map((id) => ({ id, name: id, capabilities: [] }));
|
|
12044
|
+
} catch {
|
|
12045
|
+
return [];
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
11216
12048
|
function createProviderHandlers(deps2) {
|
|
11217
12049
|
const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
|
|
11218
12050
|
let configWriteLock = deps2.getConfigWriteLock();
|
|
@@ -11499,7 +12331,9 @@ import path21 from "node:path";
|
|
|
11499
12331
|
import {
|
|
11500
12332
|
enhanceUserPrompt,
|
|
11501
12333
|
gatedEnhancerReasoning,
|
|
12334
|
+
nextEnhanceTimeout,
|
|
11502
12335
|
recentTextTurns,
|
|
12336
|
+
resolveEnhanceFallbackRef,
|
|
11503
12337
|
resolveProviderModelList
|
|
11504
12338
|
} from "@wrongstack/core";
|
|
11505
12339
|
import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
|
|
@@ -11596,6 +12430,16 @@ async function handleMailboxPurge(ws, deps2, opts) {
|
|
|
11596
12430
|
send(ws, { type: "mailbox.purged", payload: { error: errMessage(err) } });
|
|
11597
12431
|
}
|
|
11598
12432
|
}
|
|
12433
|
+
async function handleMailboxCompact(ws, deps2, opts) {
|
|
12434
|
+
try {
|
|
12435
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
12436
|
+
const mb = new GlobalMailbox3(dir);
|
|
12437
|
+
const result = await mb.autoCompact(opts);
|
|
12438
|
+
send(ws, { type: "mailbox.compacted", payload: result });
|
|
12439
|
+
} catch (err) {
|
|
12440
|
+
send(ws, { type: "mailbox.compacted", payload: { error: errMessage(err) } });
|
|
12441
|
+
}
|
|
12442
|
+
}
|
|
11599
12443
|
|
|
11600
12444
|
// src/server/mode-handlers.ts
|
|
11601
12445
|
import {
|
|
@@ -11654,6 +12498,8 @@ function createModeHandlers(ctx) {
|
|
|
11654
12498
|
const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
|
|
11655
12499
|
const freshBuilder = new DefaultSystemPromptBuilder2({
|
|
11656
12500
|
memoryStore: ctx.memoryStore,
|
|
12501
|
+
// Single injection channel: Super Memory turn middleware, not a static section.
|
|
12502
|
+
injectMemory: false,
|
|
11657
12503
|
skillLoader: ctx.skillLoader,
|
|
11658
12504
|
modeStore: ctx.modeStore,
|
|
11659
12505
|
modeId: id,
|
|
@@ -11684,7 +12530,7 @@ function createModeHandlers(ctx) {
|
|
|
11684
12530
|
}
|
|
11685
12531
|
|
|
11686
12532
|
// src/server/project-handlers.ts
|
|
11687
|
-
import * as
|
|
12533
|
+
import * as path20 from "node:path";
|
|
11688
12534
|
function createProjectHandlers(ctx) {
|
|
11689
12535
|
return {
|
|
11690
12536
|
listProjects: async (ws) => {
|
|
@@ -11710,7 +12556,7 @@ function createProjectHandlers(ctx) {
|
|
|
11710
12556
|
selectProject: async (ws, msg) => {
|
|
11711
12557
|
const payload = msg.payload;
|
|
11712
12558
|
const root = typeof payload?.root === "string" ? payload.root : "";
|
|
11713
|
-
const name2 = typeof payload?.name === "string" ? payload.name : root ?
|
|
12559
|
+
const name2 = typeof payload?.name === "string" ? payload.name : root ? path20.basename(root) : "";
|
|
11714
12560
|
send(ws, {
|
|
11715
12561
|
type: "projects.selected",
|
|
11716
12562
|
payload: {
|
|
@@ -11745,12 +12591,12 @@ function createProjectHandlers(ctx) {
|
|
|
11745
12591
|
}
|
|
11746
12592
|
|
|
11747
12593
|
// src/server/session-handlers.ts
|
|
11748
|
-
import * as path20 from "node:path";
|
|
11749
12594
|
import {
|
|
11750
12595
|
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
11751
12596
|
repairToolUseAdjacency,
|
|
11752
12597
|
resolveContextWindowPolicy as resolveContextWindowPolicy3
|
|
11753
12598
|
} from "@wrongstack/core";
|
|
12599
|
+
import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
|
|
11754
12600
|
function createSessionHandlers(ctx) {
|
|
11755
12601
|
const currentSessionId = () => ctx.getSession().id;
|
|
11756
12602
|
const sessionPayload2 = (payload) => {
|
|
@@ -11776,33 +12622,46 @@ function createSessionHandlers(ctx) {
|
|
|
11776
12622
|
});
|
|
11777
12623
|
return false;
|
|
11778
12624
|
};
|
|
12625
|
+
const finalizeSession = async (writer) => {
|
|
12626
|
+
await writer.append({
|
|
12627
|
+
type: "session_end",
|
|
12628
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12629
|
+
usage: ctx.tokenCounter.total()
|
|
12630
|
+
}).catch(() => void 0);
|
|
12631
|
+
await writer.close().catch(() => void 0);
|
|
12632
|
+
};
|
|
12633
|
+
const activateSession = async (next, messages, usage) => {
|
|
12634
|
+
const current = ctx.getSession();
|
|
12635
|
+
if (current !== next) await finalizeSession(current);
|
|
12636
|
+
ctx.setSession(next);
|
|
12637
|
+
ctx.context.session = next;
|
|
12638
|
+
ctx.context.state.replaceMessages(messages);
|
|
12639
|
+
ctx.context.state.replaceTodos([]);
|
|
12640
|
+
ctx.context.readFiles.clear();
|
|
12641
|
+
ctx.context.fileMtimes.clear();
|
|
12642
|
+
ctx.context.state.setMeta(
|
|
12643
|
+
"plan.path",
|
|
12644
|
+
sessionScopedPath2(ctx.sessionsDir, next.id, ".plan.json")
|
|
12645
|
+
);
|
|
12646
|
+
ctx.context.state.setMeta(
|
|
12647
|
+
"task.path",
|
|
12648
|
+
sessionScopedPath2(ctx.sessionsDir, next.id, ".tasks.json")
|
|
12649
|
+
);
|
|
12650
|
+
ctx.tokenCounter.reset();
|
|
12651
|
+
if (usage) ctx.tokenCounter.account(usage, ctx.config.model);
|
|
12652
|
+
ctx.setSessionStartedAt(Date.now());
|
|
12653
|
+
await ctx.onSessionSwapped(next.id);
|
|
12654
|
+
};
|
|
11779
12655
|
return {
|
|
11780
12656
|
newSession: async (ws, msg) => {
|
|
11781
12657
|
if (!ensureCurrentSession(ws, msg, "session.new")) return;
|
|
11782
|
-
const session = ctx.getSession();
|
|
11783
|
-
try {
|
|
11784
|
-
await session.append({
|
|
11785
|
-
type: "session_end",
|
|
11786
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11787
|
-
usage: ctx.tokenCounter.total()
|
|
11788
|
-
});
|
|
11789
|
-
await session.close();
|
|
11790
|
-
} catch {
|
|
11791
|
-
}
|
|
11792
12658
|
const next = await ctx.getSessionStore().create({
|
|
11793
12659
|
id: "",
|
|
11794
12660
|
title: "",
|
|
11795
12661
|
model: ctx.config.model,
|
|
11796
12662
|
provider: ctx.config.provider
|
|
11797
12663
|
});
|
|
11798
|
-
|
|
11799
|
-
ctx.context.session = next;
|
|
11800
|
-
ctx.context.state.replaceMessages([]);
|
|
11801
|
-
ctx.context.state.replaceTodos([]);
|
|
11802
|
-
ctx.context.readFiles.clear();
|
|
11803
|
-
ctx.context.fileMtimes.clear();
|
|
11804
|
-
ctx.tokenCounter.reset();
|
|
11805
|
-
ctx.setSessionStartedAt(Date.now());
|
|
12664
|
+
await activateSession(next, []);
|
|
11806
12665
|
broadcast(ctx.clients, { type: "session.start", payload: await ctx.sessionStartPayload() });
|
|
11807
12666
|
},
|
|
11808
12667
|
clearContext: async (ws, msg) => {
|
|
@@ -11883,7 +12742,9 @@ function createSessionHandlers(ctx) {
|
|
|
11883
12742
|
},
|
|
11884
12743
|
listContextModes: async (ws, msg) => {
|
|
11885
12744
|
if (!ensureCurrentSession(ws, msg, "context.modes.list")) return;
|
|
11886
|
-
const active = String(
|
|
12745
|
+
const active = String(
|
|
12746
|
+
ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
12747
|
+
);
|
|
11887
12748
|
const allModes = ctx.customModeStore.list().map((m) => ({
|
|
11888
12749
|
id: m.id,
|
|
11889
12750
|
name: m.name,
|
|
@@ -11894,7 +12755,10 @@ function createSessionHandlers(ctx) {
|
|
|
11894
12755
|
eliseThreshold: m.eliseThreshold,
|
|
11895
12756
|
custom: m.custom === true
|
|
11896
12757
|
}));
|
|
11897
|
-
send(ws, {
|
|
12758
|
+
send(ws, {
|
|
12759
|
+
type: "context.modes.list",
|
|
12760
|
+
payload: sessionPayload2({ activeId: active, modes: allModes })
|
|
12761
|
+
});
|
|
11898
12762
|
},
|
|
11899
12763
|
switchContextMode: async (ws, msg) => {
|
|
11900
12764
|
if (!ensureCurrentSession(ws, msg, "context.mode.switch")) return;
|
|
@@ -11976,7 +12840,10 @@ function createSessionHandlers(ctx) {
|
|
|
11976
12840
|
const { id } = parsed.value;
|
|
11977
12841
|
if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
|
|
11978
12842
|
ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
|
|
11979
|
-
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
|
|
12843
|
+
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
|
|
12844
|
+
{},
|
|
12845
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
12846
|
+
);
|
|
11980
12847
|
}
|
|
11981
12848
|
const result = ctx.customModeStore.remove(id);
|
|
11982
12849
|
sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
|
|
@@ -11989,15 +12856,7 @@ function createSessionHandlers(ctx) {
|
|
|
11989
12856
|
send(ws, {
|
|
11990
12857
|
type: "sessions.list",
|
|
11991
12858
|
payload: {
|
|
11992
|
-
sessions: list
|
|
11993
|
-
id: s.id,
|
|
11994
|
-
title: s.title,
|
|
11995
|
-
startedAt: s.startedAt,
|
|
11996
|
-
model: s.model,
|
|
11997
|
-
provider: s.provider,
|
|
11998
|
-
tokenTotal: s.tokenTotal,
|
|
11999
|
-
isCurrent: s.id === currentId
|
|
12000
|
-
}))
|
|
12859
|
+
sessions: toSessionHistoryEntries(list, currentId)
|
|
12001
12860
|
}
|
|
12002
12861
|
});
|
|
12003
12862
|
} catch (err) {
|
|
@@ -12011,8 +12870,17 @@ function createSessionHandlers(ctx) {
|
|
|
12011
12870
|
sendResult(ws, false, "Cannot delete the active session");
|
|
12012
12871
|
return;
|
|
12013
12872
|
}
|
|
12014
|
-
|
|
12873
|
+
const store = ctx.getSessionStore();
|
|
12874
|
+
await store.delete(id);
|
|
12015
12875
|
sendResult(ws, true, `Session ${id} deleted`);
|
|
12876
|
+
try {
|
|
12877
|
+
const list = await store.list(200);
|
|
12878
|
+
broadcast(ctx.clients, {
|
|
12879
|
+
type: "sessions.list",
|
|
12880
|
+
payload: { sessions: toSessionHistoryEntries(list, ctx.getSession().id) }
|
|
12881
|
+
});
|
|
12882
|
+
} catch {
|
|
12883
|
+
}
|
|
12016
12884
|
} catch (err) {
|
|
12017
12885
|
sendResult(ws, false, errMessage(err));
|
|
12018
12886
|
}
|
|
@@ -12028,23 +12896,17 @@ function createSessionHandlers(ctx) {
|
|
|
12028
12896
|
try {
|
|
12029
12897
|
await ctx.getSessionStore().rename(id, name2);
|
|
12030
12898
|
sendResult(ws, true, name2 ? `Renamed session to "${name2}"` : `Cleared session name`);
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
provider: s.provider,
|
|
12043
|
-
tokenTotal: s.tokenTotal,
|
|
12044
|
-
isCurrent: s.id === currentId
|
|
12045
|
-
}))
|
|
12046
|
-
}
|
|
12047
|
-
});
|
|
12899
|
+
try {
|
|
12900
|
+
const list = await ctx.getSessionStore().list(200);
|
|
12901
|
+
const currentId = ctx.getSession().id;
|
|
12902
|
+
broadcast(ctx.clients, {
|
|
12903
|
+
type: "sessions.list",
|
|
12904
|
+
payload: {
|
|
12905
|
+
sessions: toSessionHistoryEntries(list, currentId)
|
|
12906
|
+
}
|
|
12907
|
+
});
|
|
12908
|
+
} catch {
|
|
12909
|
+
}
|
|
12048
12910
|
} catch (err) {
|
|
12049
12911
|
sendResult(ws, false, errMessage(err));
|
|
12050
12912
|
}
|
|
@@ -12058,23 +12920,7 @@ function createSessionHandlers(ctx) {
|
|
|
12058
12920
|
return;
|
|
12059
12921
|
}
|
|
12060
12922
|
const resumed = await ctx.getSessionStore().resume(id);
|
|
12061
|
-
|
|
12062
|
-
await current.append({
|
|
12063
|
-
type: "session_end",
|
|
12064
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12065
|
-
usage: ctx.tokenCounter.total()
|
|
12066
|
-
});
|
|
12067
|
-
await current.close();
|
|
12068
|
-
} catch {
|
|
12069
|
-
}
|
|
12070
|
-
ctx.setSession(resumed.writer);
|
|
12071
|
-
ctx.context.session = resumed.writer;
|
|
12072
|
-
ctx.context.state.replaceMessages(resumed.data.messages);
|
|
12073
|
-
ctx.context.readFiles.clear();
|
|
12074
|
-
ctx.context.fileMtimes.clear();
|
|
12075
|
-
ctx.tokenCounter.reset();
|
|
12076
|
-
ctx.tokenCounter.account(resumed.data.usage, ctx.config.model);
|
|
12077
|
-
ctx.setSessionStartedAt(Date.now());
|
|
12923
|
+
await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
|
|
12078
12924
|
broadcast(ctx.clients, {
|
|
12079
12925
|
type: "session.start",
|
|
12080
12926
|
payload: {
|
|
@@ -12098,10 +12944,7 @@ function createSessionHandlers(ctx) {
|
|
|
12098
12944
|
try {
|
|
12099
12945
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
12100
12946
|
const projectRoot = ctx.getProjectRoot();
|
|
12101
|
-
const rewinder = new DefaultSessionRewinder(
|
|
12102
|
-
path20.join(projectRoot, ".wrongstack", "sessions"),
|
|
12103
|
-
projectRoot
|
|
12104
|
-
);
|
|
12947
|
+
const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
|
|
12105
12948
|
const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
|
|
12106
12949
|
send(ws, { type: "session.checkpoints", payload: sessionPayload2({ checkpoints }) });
|
|
12107
12950
|
} catch {
|
|
@@ -12114,10 +12957,7 @@ function createSessionHandlers(ctx) {
|
|
|
12114
12957
|
try {
|
|
12115
12958
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
12116
12959
|
const projectRoot = ctx.getProjectRoot();
|
|
12117
|
-
const rewinder = new DefaultSessionRewinder(
|
|
12118
|
-
path20.join(projectRoot, ".wrongstack", "sessions"),
|
|
12119
|
-
projectRoot
|
|
12120
|
-
);
|
|
12960
|
+
const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
|
|
12121
12961
|
await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
|
|
12122
12962
|
await ctx.context.session.truncateToCheckpoint(checkpointIndex);
|
|
12123
12963
|
sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
|
|
@@ -12166,6 +13006,25 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12166
13006
|
clients: state.getClients(),
|
|
12167
13007
|
modelsRegistry: deps2.modelsRegistry
|
|
12168
13008
|
});
|
|
13009
|
+
async function applyModelSwitchCore(newProvider, newModel) {
|
|
13010
|
+
const cur = state.getConfig();
|
|
13011
|
+
state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
|
|
13012
|
+
deps2.configStore.update({ provider: newProvider, model: newModel });
|
|
13013
|
+
deps2.context.model = newModel;
|
|
13014
|
+
const newCfg = state.getConfig();
|
|
13015
|
+
const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
|
|
13016
|
+
const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
|
|
13017
|
+
deps2.context.provider = newProv;
|
|
13018
|
+
await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
|
|
13019
|
+
await cb.updateGlobalConfig((config) => {
|
|
13020
|
+
config.provider = newProvider;
|
|
13021
|
+
config.model = newModel;
|
|
13022
|
+
}, "model.switch");
|
|
13023
|
+
broadcast(state.getClients(), {
|
|
13024
|
+
type: "session.start",
|
|
13025
|
+
payload: await cb.sessionStartPayload()
|
|
13026
|
+
});
|
|
13027
|
+
}
|
|
12169
13028
|
const providerRoutes = {
|
|
12170
13029
|
providerHandlers,
|
|
12171
13030
|
listProviders: async (ws) => {
|
|
@@ -12202,11 +13061,20 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12202
13061
|
const provider = await resolveProviderCatalogForModels(deps2.modelsRegistry, providerId, cfg);
|
|
12203
13062
|
const siblingId = SIBLING_CATALOG[providerId];
|
|
12204
13063
|
const siblingCatalog = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
|
|
13064
|
+
let resolved = resolveProviderModelList(
|
|
13065
|
+
cfg?.models,
|
|
13066
|
+
provider,
|
|
13067
|
+
cfg?.type ?? providerId,
|
|
13068
|
+
siblingCatalog
|
|
13069
|
+
);
|
|
13070
|
+
if (resolved.length === 0 && cfg?.baseUrl) {
|
|
13071
|
+
resolved = await probeModelDescriptors(cfg);
|
|
13072
|
+
}
|
|
12205
13073
|
const models = await enrichProviderModelDescriptors(
|
|
12206
13074
|
deps2.modelsRegistry,
|
|
12207
13075
|
providerId,
|
|
12208
13076
|
cfg,
|
|
12209
|
-
|
|
13077
|
+
resolved
|
|
12210
13078
|
);
|
|
12211
13079
|
send(ws, {
|
|
12212
13080
|
type: "provider.models",
|
|
@@ -12224,21 +13092,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12224
13092
|
}
|
|
12225
13093
|
const { provider: newProvider, model: newModel } = parsed.value;
|
|
12226
13094
|
try {
|
|
12227
|
-
|
|
12228
|
-
state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
|
|
12229
|
-
deps2.configStore.update({ provider: newProvider, model: newModel });
|
|
12230
|
-
deps2.context.model = newModel;
|
|
12231
|
-
const newCfg = state.getConfig();
|
|
12232
|
-
const providerCfg = newCfg.providers?.[newProvider] ?? {
|
|
12233
|
-
type: newProvider
|
|
12234
|
-
};
|
|
12235
|
-
const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
|
|
12236
|
-
deps2.context.provider = newProv;
|
|
12237
|
-
await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
|
|
12238
|
-
await cb.updateGlobalConfig((config) => {
|
|
12239
|
-
config.provider = newProvider;
|
|
12240
|
-
config.model = newModel;
|
|
12241
|
-
}, "model.switch");
|
|
13095
|
+
await applyModelSwitchCore(newProvider, newModel);
|
|
12242
13096
|
send(ws, {
|
|
12243
13097
|
type: "key.operation_result",
|
|
12244
13098
|
payload: { success: true, message: `Switched to ${newProvider} / ${newModel}` }
|
|
@@ -12251,45 +13105,100 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12251
13105
|
message: `Switch failed: ${errMessage(err)}`
|
|
12252
13106
|
}
|
|
12253
13107
|
});
|
|
12254
|
-
return;
|
|
12255
13108
|
}
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
13109
|
+
},
|
|
13110
|
+
adoptDefaultProviderIfUnset: async (providerId) => {
|
|
13111
|
+
if (state.getConfig().model) return;
|
|
13112
|
+
const saved = await providerHandlers.loadConfigProviders();
|
|
13113
|
+
state.setConfig(patchConfig(state.getConfig(), { providers: saved }));
|
|
13114
|
+
const cfg = saved[providerId];
|
|
13115
|
+
if (!cfg) return;
|
|
13116
|
+
let model = cfg.models?.[0];
|
|
13117
|
+
if (!model) {
|
|
13118
|
+
const catalogId = cfg.type && cfg.type !== providerId ? cfg.type : providerId;
|
|
13119
|
+
const catalog = await deps2.modelsRegistry.getProvider(catalogId).catch(() => void 0);
|
|
13120
|
+
model = catalog?.models?.[0]?.id;
|
|
13121
|
+
}
|
|
13122
|
+
if (!model) {
|
|
13123
|
+
const probed = await probeModelDescriptors(cfg);
|
|
13124
|
+
model = probed[0]?.id;
|
|
13125
|
+
}
|
|
13126
|
+
if (!model) return;
|
|
13127
|
+
try {
|
|
13128
|
+
await applyModelSwitchCore(providerId, model);
|
|
13129
|
+
} catch {
|
|
13130
|
+
}
|
|
12260
13131
|
},
|
|
12261
13132
|
refineModel: async (ws, msg) => {
|
|
12262
|
-
const
|
|
13133
|
+
const payload = msg.payload;
|
|
13134
|
+
const { text } = payload;
|
|
12263
13135
|
if (!text?.trim()) {
|
|
12264
13136
|
send(ws, {
|
|
12265
13137
|
type: "model.refine_result",
|
|
12266
|
-
payload: { refined: "", english: "", error: "Empty text" }
|
|
13138
|
+
payload: { refined: "", english: "", error: "Empty text", errorKind: "provider_error" }
|
|
12267
13139
|
});
|
|
12268
13140
|
return;
|
|
12269
13141
|
}
|
|
13142
|
+
const cfg = state.getConfig();
|
|
13143
|
+
const fallbackRef = resolveEnhanceFallbackRef({
|
|
13144
|
+
...cfg,
|
|
13145
|
+
provider: cfg.provider ?? "",
|
|
13146
|
+
model: cfg.model ?? ""
|
|
13147
|
+
});
|
|
13148
|
+
let provider = deps2.context.provider;
|
|
13149
|
+
let providerId = cfg.provider ?? "";
|
|
13150
|
+
let model = deps2.context.model;
|
|
13151
|
+
if (payload.provider && payload.model) {
|
|
13152
|
+
try {
|
|
13153
|
+
const providerCfg = cfg.providers?.[payload.provider] ?? {
|
|
13154
|
+
type: payload.provider
|
|
13155
|
+
};
|
|
13156
|
+
provider = deps2.providerRegistry.has(payload.provider) ? deps2.providerRegistry.create({ ...providerCfg, type: payload.provider }) : makeProviderFromConfig2(payload.provider, providerCfg);
|
|
13157
|
+
providerId = payload.provider;
|
|
13158
|
+
model = payload.model;
|
|
13159
|
+
} catch (err) {
|
|
13160
|
+
send(ws, {
|
|
13161
|
+
type: "model.refine_result",
|
|
13162
|
+
payload: {
|
|
13163
|
+
refined: text,
|
|
13164
|
+
english: text,
|
|
13165
|
+
error: `Cannot use ${payload.provider}/${payload.model}: ${errMessage(err)}`,
|
|
13166
|
+
errorKind: "provider_error",
|
|
13167
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13168
|
+
}
|
|
13169
|
+
});
|
|
13170
|
+
return;
|
|
13171
|
+
}
|
|
13172
|
+
}
|
|
13173
|
+
const baseTimeout = 9e4;
|
|
13174
|
+
const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
|
|
12270
13175
|
try {
|
|
12271
13176
|
const history = recentTextTurns(deps2.context.messages);
|
|
12272
|
-
const cfg = state.getConfig();
|
|
12273
13177
|
const resolved = await resolveProviderModelMetadata(
|
|
12274
13178
|
deps2.modelsRegistry,
|
|
12275
|
-
|
|
12276
|
-
|
|
12277
|
-
cfg.providers?.[
|
|
13179
|
+
providerId,
|
|
13180
|
+
model,
|
|
13181
|
+
cfg.providers?.[providerId]
|
|
12278
13182
|
).catch(() => void 0);
|
|
12279
13183
|
const reasoning = gatedEnhancerReasoning(resolved?.capabilities?.reasoningConfig);
|
|
13184
|
+
let failureKind;
|
|
12280
13185
|
const result = await enhanceUserPrompt({
|
|
12281
|
-
provider
|
|
12282
|
-
model
|
|
13186
|
+
provider,
|
|
13187
|
+
model,
|
|
12283
13188
|
text,
|
|
12284
13189
|
history,
|
|
12285
|
-
timeoutMs
|
|
13190
|
+
timeoutMs,
|
|
12286
13191
|
...reasoning ? { reasoning } : {},
|
|
12287
|
-
onError: (reason) => {
|
|
13192
|
+
onError: (reason, kind) => {
|
|
13193
|
+
failureKind = kind;
|
|
12288
13194
|
console.warn(
|
|
12289
13195
|
JSON.stringify({
|
|
12290
13196
|
level: "warn",
|
|
12291
13197
|
event: "model.refine_failed",
|
|
12292
13198
|
reason,
|
|
13199
|
+
kind,
|
|
13200
|
+
provider: providerId,
|
|
13201
|
+
model,
|
|
12293
13202
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
12294
13203
|
})
|
|
12295
13204
|
);
|
|
@@ -12298,12 +13207,23 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12298
13207
|
if (result) {
|
|
12299
13208
|
send(ws, {
|
|
12300
13209
|
type: "model.refine_result",
|
|
12301
|
-
payload: {
|
|
13210
|
+
payload: {
|
|
13211
|
+
refined: result.refined,
|
|
13212
|
+
english: result.english,
|
|
13213
|
+
refinedWith: { provider: providerId, model }
|
|
13214
|
+
}
|
|
12302
13215
|
});
|
|
12303
13216
|
} else {
|
|
12304
13217
|
send(ws, {
|
|
12305
13218
|
type: "model.refine_result",
|
|
12306
|
-
payload: {
|
|
13219
|
+
payload: {
|
|
13220
|
+
refined: text,
|
|
13221
|
+
english: text,
|
|
13222
|
+
error: "Refinement returned no result",
|
|
13223
|
+
errorKind: failureKind ?? "empty",
|
|
13224
|
+
...failureKind === "timeout" ? { retryTimeoutMs: nextEnhanceTimeout(timeoutMs, cfg.autonomy) } : {},
|
|
13225
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13226
|
+
}
|
|
12307
13227
|
});
|
|
12308
13228
|
}
|
|
12309
13229
|
} catch (err) {
|
|
@@ -12317,7 +13237,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12317
13237
|
);
|
|
12318
13238
|
send(ws, {
|
|
12319
13239
|
type: "model.refine_result",
|
|
12320
|
-
payload: {
|
|
13240
|
+
payload: {
|
|
13241
|
+
refined: text,
|
|
13242
|
+
english: text,
|
|
13243
|
+
error: errMessage(err),
|
|
13244
|
+
errorKind: "provider_error",
|
|
13245
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13246
|
+
}
|
|
12321
13247
|
});
|
|
12322
13248
|
}
|
|
12323
13249
|
}
|
|
@@ -12333,8 +13259,10 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12333
13259
|
getProjectRoot: state.getProjectRoot,
|
|
12334
13260
|
getSession: state.getSession,
|
|
12335
13261
|
getSessionStore: state.getSessionStore,
|
|
13262
|
+
sessionsDir: deps2.wpaths.projectSessions,
|
|
12336
13263
|
setSession: state.setSession,
|
|
12337
13264
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
13265
|
+
onSessionSwapped: cb.onSessionSwapped,
|
|
12338
13266
|
sessionStartPayload: cb.sessionStartPayload
|
|
12339
13267
|
});
|
|
12340
13268
|
const projectRoutes = createProjectHandlers({
|
|
@@ -12429,6 +13357,17 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12429
13357
|
deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
12430
13358
|
}
|
|
12431
13359
|
}
|
|
13360
|
+
if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
|
|
13361
|
+
const { getProcessRegistry } = await import("@wrongstack/tools");
|
|
13362
|
+
getProcessRegistry().setBreakerConfig({
|
|
13363
|
+
...typeof payload["breakerEnabled"] === "boolean" ? { enabled: payload["breakerEnabled"] } : {},
|
|
13364
|
+
...typeof payload["breakerAutoKillResetMs"] === "number" ? { autoKillResetMs: payload["breakerAutoKillResetMs"] } : {}
|
|
13365
|
+
});
|
|
13366
|
+
}
|
|
13367
|
+
if (typeof payload["debugStream"] === "boolean") {
|
|
13368
|
+
const { setDebugStreamEnabled } = await import("@wrongstack/providers");
|
|
13369
|
+
setDebugStreamEnabled(payload["debugStream"]);
|
|
13370
|
+
}
|
|
12432
13371
|
if (typeof payload["logLevel"] === "string") {
|
|
12433
13372
|
const valid = ["debug", "info", "warn", "error"];
|
|
12434
13373
|
if (valid.includes(payload["logLevel"])) {
|
|
@@ -12506,6 +13445,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12506
13445
|
{ projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
|
|
12507
13446
|
parsed.value
|
|
12508
13447
|
);
|
|
13448
|
+
},
|
|
13449
|
+
compact: (ws, msg) => {
|
|
13450
|
+
return handleMailboxCompact(
|
|
13451
|
+
ws,
|
|
13452
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path21.dirname(deps2.globalConfigPath) },
|
|
13453
|
+
msg.payload ?? {}
|
|
13454
|
+
);
|
|
12509
13455
|
}
|
|
12510
13456
|
};
|
|
12511
13457
|
const mcpRoutes = {
|
|
@@ -12518,14 +13464,29 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12518
13464
|
sleep: (ws, msg) => handleMcpSleep(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12519
13465
|
wake: (ws, msg) => handleMcpWake(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12520
13466
|
restart: (ws, msg) => handleMcpRestart(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12521
|
-
discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
|
|
13467
|
+
discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
13468
|
+
resources: (ws, msg) => handleMcpResources(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
13469
|
+
prompts: (ws, msg) => handleMcpPrompts(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
13470
|
+
resourceRead: (ws, msg) => handleMcpResourceRead(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
13471
|
+
promptGet: (ws, msg) => handleMcpPromptGet(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
|
|
13472
|
+
};
|
|
13473
|
+
const sendBrainStatus = (ws) => {
|
|
13474
|
+
const snapshot = deps2.brainRuntime.getSnapshot();
|
|
13475
|
+
send(ws, {
|
|
13476
|
+
type: "brain.status",
|
|
13477
|
+
payload: {
|
|
13478
|
+
maxAutoRisk: deps2.brainSettings.maxAutoRisk,
|
|
13479
|
+
log: deps2.brainLog,
|
|
13480
|
+
mode: snapshot.mode,
|
|
13481
|
+
poolLabels: snapshot.poolLabels,
|
|
13482
|
+
councilLabels: snapshot.councilLabels,
|
|
13483
|
+
ledgerPath: snapshot.ledger.path
|
|
13484
|
+
}
|
|
13485
|
+
});
|
|
12522
13486
|
};
|
|
12523
13487
|
const brainRoutes = {
|
|
12524
13488
|
status: (ws) => {
|
|
12525
|
-
|
|
12526
|
-
type: "brain.status",
|
|
12527
|
-
payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
|
|
12528
|
-
});
|
|
13489
|
+
sendBrainStatus(ws);
|
|
12529
13490
|
},
|
|
12530
13491
|
risk: (ws, msg) => {
|
|
12531
13492
|
const parsed = validateBrainRiskPayload(msg.payload);
|
|
@@ -12535,11 +13496,43 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12535
13496
|
}
|
|
12536
13497
|
const { level } = parsed.value;
|
|
12537
13498
|
deps2.brainSettings.maxAutoRisk = level;
|
|
13499
|
+
sendBrainStatus(ws);
|
|
13500
|
+
},
|
|
13501
|
+
configGet: (ws) => {
|
|
12538
13502
|
send(ws, {
|
|
12539
|
-
type: "brain.
|
|
12540
|
-
payload: {
|
|
13503
|
+
type: "brain.config",
|
|
13504
|
+
payload: { config: deps2.brainRuntime.getSnapshot(), persisted: true }
|
|
12541
13505
|
});
|
|
12542
13506
|
},
|
|
13507
|
+
configSet: async (ws, msg) => {
|
|
13508
|
+
const parsed = validateBrainConfigSetPayload(msg.payload);
|
|
13509
|
+
if (!parsed.ok) {
|
|
13510
|
+
sendResult(ws, false, parsed.message);
|
|
13511
|
+
return;
|
|
13512
|
+
}
|
|
13513
|
+
try {
|
|
13514
|
+
const { persisted } = deps2.brainRuntime.apply(parsed.value.patch);
|
|
13515
|
+
const result = await persisted;
|
|
13516
|
+
send(ws, {
|
|
13517
|
+
type: "brain.config",
|
|
13518
|
+
payload: {
|
|
13519
|
+
config: deps2.brainRuntime.getSnapshot(),
|
|
13520
|
+
persisted: result.ok,
|
|
13521
|
+
...result.ok ? {} : { error: result.error ?? "Persist failed." }
|
|
13522
|
+
}
|
|
13523
|
+
});
|
|
13524
|
+
sendBrainStatus(ws);
|
|
13525
|
+
} catch (err) {
|
|
13526
|
+
send(ws, {
|
|
13527
|
+
type: "brain.config",
|
|
13528
|
+
payload: {
|
|
13529
|
+
config: deps2.brainRuntime.getSnapshot(),
|
|
13530
|
+
persisted: false,
|
|
13531
|
+
error: `Invalid Brain setting: ${errMessage(err)}`
|
|
13532
|
+
}
|
|
13533
|
+
});
|
|
13534
|
+
}
|
|
13535
|
+
},
|
|
12543
13536
|
ask: async (ws, msg) => {
|
|
12544
13537
|
const parsed = validateBrainAskPayload(msg.payload);
|
|
12545
13538
|
if (!parsed.ok) {
|
|
@@ -12612,8 +13605,16 @@ async function startWebUI(opts = {}) {
|
|
|
12612
13605
|
console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
|
|
12613
13606
|
if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
|
|
12614
13607
|
const firstKey = expectDefined3(Object.keys(config.providers)[0]);
|
|
12615
|
-
|
|
12616
|
-
|
|
13608
|
+
const adoptModel = !config.model ? config.providers[firstKey]?.models?.[0] : void 0;
|
|
13609
|
+
config = patchConfig(config, {
|
|
13610
|
+
provider: firstKey,
|
|
13611
|
+
...adoptModel ? { model: adoptModel } : {}
|
|
13612
|
+
});
|
|
13613
|
+
console.log(
|
|
13614
|
+
"[WebUI] No active provider \u2014 auto-selected:",
|
|
13615
|
+
firstKey,
|
|
13616
|
+
adoptModel ? `/ ${adoptModel}` : ""
|
|
13617
|
+
);
|
|
12617
13618
|
}
|
|
12618
13619
|
const needsProvider = !config.provider || !config.model;
|
|
12619
13620
|
const preContext = await createPreContextServices({
|
|
@@ -12647,7 +13648,8 @@ async function startWebUI(opts = {}) {
|
|
|
12647
13648
|
promptsCtx,
|
|
12648
13649
|
modelCapabilitiesRef,
|
|
12649
13650
|
provider,
|
|
12650
|
-
context
|
|
13651
|
+
context,
|
|
13652
|
+
sessionIdentity
|
|
12651
13653
|
} = preContext;
|
|
12652
13654
|
let sessionStore = preContext.sessionStore;
|
|
12653
13655
|
let session = preContext.session;
|
|
@@ -12680,7 +13682,12 @@ async function startWebUI(opts = {}) {
|
|
|
12680
13682
|
modelCapabilitiesRef,
|
|
12681
13683
|
sessionGetter: () => session,
|
|
12682
13684
|
sessionReader,
|
|
12683
|
-
annotationsStore
|
|
13685
|
+
annotationsStore,
|
|
13686
|
+
// Brain settings persist to the GLOBAL config only (config.brain is on
|
|
13687
|
+
// the in-project deny list), serialized behind the shared write lock.
|
|
13688
|
+
persistBrainConfig: (brainConfig) => updateGlobalConfig2((decrypted) => {
|
|
13689
|
+
decrypted["brain"] = brainConfig;
|
|
13690
|
+
}, "brain.config")
|
|
12684
13691
|
});
|
|
12685
13692
|
const {
|
|
12686
13693
|
compactor,
|
|
@@ -12690,6 +13697,7 @@ async function startWebUI(opts = {}) {
|
|
|
12690
13697
|
pipelines,
|
|
12691
13698
|
brain,
|
|
12692
13699
|
brainSettings,
|
|
13700
|
+
brainRuntime,
|
|
12693
13701
|
brainLog,
|
|
12694
13702
|
brainMonitor,
|
|
12695
13703
|
codebaseIndexing,
|
|
@@ -12860,10 +13868,16 @@ async function startWebUI(opts = {}) {
|
|
|
12860
13868
|
terminalHandler,
|
|
12861
13869
|
brain,
|
|
12862
13870
|
brainSettings,
|
|
13871
|
+
brainRuntime,
|
|
12863
13872
|
brainLog
|
|
12864
13873
|
};
|
|
12865
13874
|
const cb = {
|
|
12866
13875
|
sessionStartPayload,
|
|
13876
|
+
onSessionSwapped: async (sessionId) => {
|
|
13877
|
+
await sessionIdentity.activate(sessionId);
|
|
13878
|
+
const { hydrateSessionKanban: hydrateSessionKanban2 } = await import("@wrongstack/tools/session-kanban");
|
|
13879
|
+
await hydrateSessionKanban2(deps2.context);
|
|
13880
|
+
},
|
|
12867
13881
|
updateAutoCompactionMaxContext,
|
|
12868
13882
|
updateGlobalConfig: updateGlobalConfig2,
|
|
12869
13883
|
persistPrefsToConfig: persistPrefsToConfig2,
|
|
@@ -12989,7 +14003,10 @@ async function startWebUI(opts = {}) {
|
|
|
12989
14003
|
onShutdown: async () => {
|
|
12990
14004
|
credentialWatcherClose?.();
|
|
12991
14005
|
brainMonitor.stop();
|
|
14006
|
+
await agentServices.brainLedger?.stop().catch(() => {
|
|
14007
|
+
});
|
|
12992
14008
|
await mcpRegistry.stopAll().catch(() => void 0);
|
|
14009
|
+
await sessionIdentity.stop();
|
|
12993
14010
|
eventArming.getDispose()?.();
|
|
12994
14011
|
if (eternalSubscription) {
|
|
12995
14012
|
eternalSubscription.dispose();
|
|
@@ -13100,6 +14117,7 @@ export {
|
|
|
13100
14117
|
AutoPhaseWebSocketHandler,
|
|
13101
14118
|
CollaborationWebSocketHandler,
|
|
13102
14119
|
SKIP_DIRS,
|
|
14120
|
+
SURFACE_DEFAULT_PORTS,
|
|
13103
14121
|
SddBoardWebSocketHandler,
|
|
13104
14122
|
SddWizardWebSocketHandler,
|
|
13105
14123
|
SpecsWebSocketHandler,
|
|
@@ -13139,6 +14157,7 @@ export {
|
|
|
13139
14157
|
generateAuthToken,
|
|
13140
14158
|
getAnalyticsBuffer,
|
|
13141
14159
|
getCostRates,
|
|
14160
|
+
getSurfaceDefaultPorts,
|
|
13142
14161
|
handleApiAnalyticsGet,
|
|
13143
14162
|
handleApiAnalyticsPost,
|
|
13144
14163
|
handleApiAnalyticsSummary,
|
|
@@ -13166,15 +14185,17 @@ export {
|
|
|
13166
14185
|
handleMcpDiscover,
|
|
13167
14186
|
handleMcpEnable,
|
|
13168
14187
|
handleMcpList,
|
|
14188
|
+
handleMcpPromptGet,
|
|
14189
|
+
handleMcpPrompts,
|
|
13169
14190
|
handleMcpRemove,
|
|
14191
|
+
handleMcpResourceRead,
|
|
14192
|
+
handleMcpResources,
|
|
13170
14193
|
handleMcpRestart,
|
|
13171
14194
|
handleMcpRoute,
|
|
13172
14195
|
handleMcpSleep,
|
|
13173
14196
|
handleMcpUpdate,
|
|
13174
14197
|
handleMcpWake,
|
|
13175
|
-
handleMemoryForget,
|
|
13176
14198
|
handleMemoryList,
|
|
13177
|
-
handleMemoryRemember,
|
|
13178
14199
|
handleModeRoute,
|
|
13179
14200
|
handlePrefsRoute,
|
|
13180
14201
|
handleProcessKill,
|
|
@@ -13203,6 +14224,11 @@ export {
|
|
|
13203
14224
|
handleSkillsUninstall,
|
|
13204
14225
|
handleSkillsUpdate,
|
|
13205
14226
|
handleSpecsRoute,
|
|
14227
|
+
handleSuperMemoryDelete,
|
|
14228
|
+
handleSuperMemoryGet,
|
|
14229
|
+
handleSuperMemoryList,
|
|
14230
|
+
handleSuperMemoryRemember,
|
|
14231
|
+
handleSuperMemoryUpdate,
|
|
13206
14232
|
handleWorklistMessage,
|
|
13207
14233
|
hostForBrowserUrl,
|
|
13208
14234
|
hostHeaderOk,
|
|
@@ -13247,9 +14273,13 @@ export {
|
|
|
13247
14273
|
setActiveKey,
|
|
13248
14274
|
setupEvents,
|
|
13249
14275
|
setupWebUICodebaseIndexing,
|
|
14276
|
+
startSddRunFromGraph,
|
|
13250
14277
|
startWebUI,
|
|
13251
14278
|
statusProjectHashFromWatchFilename,
|
|
13252
14279
|
stringifyContent,
|
|
14280
|
+
surfaceLabel,
|
|
14281
|
+
toSessionHistoryEntries,
|
|
14282
|
+
toSessionHistoryEntry,
|
|
13253
14283
|
tokenMatches,
|
|
13254
14284
|
unregisterInstance,
|
|
13255
14285
|
upsertKey,
|