@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/server/entry.js
CHANGED
|
@@ -42,12 +42,13 @@ function commitsSince(cwd, baseSha, branch) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
var AutoPhaseWebSocketHandler = class {
|
|
45
|
-
constructor(agent, context, logger, storeDir, events, projectRoot) {
|
|
45
|
+
constructor(agent, context, logger, storeDir, events, projectRoot, onBoardState) {
|
|
46
46
|
this.agent = agent;
|
|
47
47
|
this.context = context;
|
|
48
48
|
this.logger = logger;
|
|
49
49
|
this.events = events;
|
|
50
50
|
this.projectRoot = projectRoot;
|
|
51
|
+
this.onBoardState = onBoardState;
|
|
51
52
|
this.store = new PhaseStore({ baseDir: storeDir });
|
|
52
53
|
}
|
|
53
54
|
agent;
|
|
@@ -55,6 +56,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
55
56
|
logger;
|
|
56
57
|
events;
|
|
57
58
|
projectRoot;
|
|
59
|
+
onBoardState;
|
|
58
60
|
orchestrator = null;
|
|
59
61
|
graph = null;
|
|
60
62
|
store;
|
|
@@ -399,6 +401,15 @@ Type: ${task.type}`;
|
|
|
399
401
|
if (!this.graph) return;
|
|
400
402
|
const state = this.buildState(activePhaseId);
|
|
401
403
|
this.broadcast({ type: "autophase.state", payload: state });
|
|
404
|
+
if (this.onBoardState) {
|
|
405
|
+
try {
|
|
406
|
+
this.onBoardState(this.graph.id, state);
|
|
407
|
+
} catch (err) {
|
|
408
|
+
this.logger.error(
|
|
409
|
+
`[AutoPhase] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
402
413
|
}
|
|
403
414
|
buildState(activePhaseId) {
|
|
404
415
|
if (!this.graph) {
|
|
@@ -2614,10 +2625,7 @@ function cspSourceFromUrl(rawUrl) {
|
|
|
2614
2625
|
return void 0;
|
|
2615
2626
|
}
|
|
2616
2627
|
}
|
|
2617
|
-
var
|
|
2618
|
-
"'sha256-6PXDy0zrpXa6mvYOl11bZ8nubNUL7ushPUhGDZtaexg='",
|
|
2619
|
-
"'sha256-6sIdwbEBx7jj0drqSHHm7MqvmoYD3CQ4lp8Zp8blcb0='"
|
|
2620
|
-
];
|
|
2628
|
+
var EXTRA_SCRIPT_SOURCES = ["'wasm-unsafe-eval'"];
|
|
2621
2629
|
function buildCspHeader(wsPort, requestHost, publicWsUrl) {
|
|
2622
2630
|
const connect = /* @__PURE__ */ new Set([
|
|
2623
2631
|
"'self'",
|
|
@@ -2631,7 +2639,7 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
|
|
|
2631
2639
|
}
|
|
2632
2640
|
const publicWsSource = publicWsUrl ? cspSourceFromUrl(publicWsUrl) : void 0;
|
|
2633
2641
|
if (publicWsSource) connect.add(publicWsSource);
|
|
2634
|
-
const scriptSrc = ["'self'", ...
|
|
2642
|
+
const scriptSrc = ["'self'", ...EXTRA_SCRIPT_SOURCES].join(" ");
|
|
2635
2643
|
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'`;
|
|
2636
2644
|
}
|
|
2637
2645
|
function isInsideDist(candidate, distDir) {
|
|
@@ -2992,7 +3000,7 @@ function mapStatus(raw) {
|
|
|
2992
3000
|
return "stopped";
|
|
2993
3001
|
}
|
|
2994
3002
|
}
|
|
2995
|
-
function toView(info) {
|
|
3003
|
+
function toView(info, health) {
|
|
2996
3004
|
const view = {
|
|
2997
3005
|
name: info.name,
|
|
2998
3006
|
transport: info.transport,
|
|
@@ -3004,6 +3012,7 @@ function toView(info) {
|
|
|
3004
3012
|
};
|
|
3005
3013
|
if (info.description !== void 0) view.description = info.description;
|
|
3006
3014
|
if (info.lazy !== void 0) view.lazy = info.lazy;
|
|
3015
|
+
if (health !== void 0) view.health = health;
|
|
3007
3016
|
return view;
|
|
3008
3017
|
}
|
|
3009
3018
|
function deps(ws, globalConfigPath, registry) {
|
|
@@ -3029,7 +3038,13 @@ async function handleMcpList(ws, _msg, globalConfigPath, mcpRegistry) {
|
|
|
3029
3038
|
registry: mcpRegistry,
|
|
3030
3039
|
presets: allServers()
|
|
3031
3040
|
});
|
|
3032
|
-
|
|
3041
|
+
const health = new Map(
|
|
3042
|
+
(typeof mcpRegistry.operationalHealth === "function" ? mcpRegistry.operationalHealth() : []).map((item) => [item.name, item])
|
|
3043
|
+
);
|
|
3044
|
+
send(ws, {
|
|
3045
|
+
type: "mcp.list",
|
|
3046
|
+
payload: { servers: servers.map((server) => toView(server, health.get(server.name))) }
|
|
3047
|
+
});
|
|
3033
3048
|
}
|
|
3034
3049
|
async function handleMcpAdd(ws, msg, globalConfigPath, mcpRegistry) {
|
|
3035
3050
|
const d = deps(ws, globalConfigPath, mcpRegistry);
|
|
@@ -3179,10 +3194,119 @@ async function handleMcpDiscover(ws, msg, globalConfigPath, mcpRegistry) {
|
|
|
3179
3194
|
payload: { success: result.ok, message: result.message }
|
|
3180
3195
|
});
|
|
3181
3196
|
}
|
|
3197
|
+
async function handleMcpResources(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3198
|
+
if (!mcpRegistry) return sendContentError(ws, "resources", "", "MCP registry is not available.");
|
|
3199
|
+
const payload = payloadRecord(msg);
|
|
3200
|
+
let serverName = "";
|
|
3201
|
+
try {
|
|
3202
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3203
|
+
const refresh = payload["refresh"] === true;
|
|
3204
|
+
const [resources, resourceTemplates] = await Promise.all([
|
|
3205
|
+
mcpRegistry.listResources(serverName, { refresh }),
|
|
3206
|
+
mcpRegistry.listResourceTemplates(serverName, { refresh })
|
|
3207
|
+
]);
|
|
3208
|
+
send(ws, {
|
|
3209
|
+
type: "mcp.resources",
|
|
3210
|
+
payload: { name: serverName, resources, resourceTemplates }
|
|
3211
|
+
});
|
|
3212
|
+
} catch (err) {
|
|
3213
|
+
sendContentError(ws, "resources", serverName, errorMessage(err));
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
async function handleMcpPrompts(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3217
|
+
if (!mcpRegistry) return sendContentError(ws, "prompts", "", "MCP registry is not available.");
|
|
3218
|
+
const payload = payloadRecord(msg);
|
|
3219
|
+
let serverName = "";
|
|
3220
|
+
try {
|
|
3221
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3222
|
+
const prompts = await mcpRegistry.listPrompts(serverName, {
|
|
3223
|
+
refresh: payload["refresh"] === true
|
|
3224
|
+
});
|
|
3225
|
+
send(ws, { type: "mcp.prompts", payload: { name: serverName, prompts } });
|
|
3226
|
+
} catch (err) {
|
|
3227
|
+
sendContentError(ws, "prompts", serverName, errorMessage(err));
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
async function handleMcpResourceRead(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3231
|
+
if (!mcpRegistry)
|
|
3232
|
+
return sendContentError(ws, "resource.read", "", "MCP registry is not available.");
|
|
3233
|
+
const payload = payloadRecord(msg);
|
|
3234
|
+
let serverName = "";
|
|
3235
|
+
try {
|
|
3236
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3237
|
+
const insertion = await mcpRegistry.selectResourceForInsertion(
|
|
3238
|
+
serverName,
|
|
3239
|
+
requiredPayloadString(payload, "uri")
|
|
3240
|
+
);
|
|
3241
|
+
send(ws, { type: "mcp.content.selected", payload: insertion });
|
|
3242
|
+
} catch (err) {
|
|
3243
|
+
sendContentError(ws, "resource.read", serverName, errorMessage(err));
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
async function handleMcpPromptGet(ws, msg, _globalConfigPath, mcpRegistry) {
|
|
3247
|
+
if (!mcpRegistry) return sendContentError(ws, "prompt.get", "", "MCP registry is not available.");
|
|
3248
|
+
const payload = payloadRecord(msg);
|
|
3249
|
+
let serverName = "";
|
|
3250
|
+
try {
|
|
3251
|
+
serverName = requiredPayloadString(payload, "name");
|
|
3252
|
+
const insertion = await mcpRegistry.selectPromptForInsertion(
|
|
3253
|
+
serverName,
|
|
3254
|
+
requiredPayloadString(payload, "prompt"),
|
|
3255
|
+
promptArguments(payload["arguments"])
|
|
3256
|
+
);
|
|
3257
|
+
send(ws, { type: "mcp.content.selected", payload: insertion });
|
|
3258
|
+
} catch (err) {
|
|
3259
|
+
sendContentError(ws, "prompt.get", serverName, errorMessage(err));
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
function payloadRecord(msg) {
|
|
3263
|
+
return msg.payload && typeof msg.payload === "object" && !Array.isArray(msg.payload) ? msg.payload : {};
|
|
3264
|
+
}
|
|
3265
|
+
function requiredPayloadString(payload, field) {
|
|
3266
|
+
const value = payload[field];
|
|
3267
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
3268
|
+
throw new Error(`MCP payload field "${field}" must be a non-empty string`);
|
|
3269
|
+
}
|
|
3270
|
+
return value;
|
|
3271
|
+
}
|
|
3272
|
+
function promptArguments(value) {
|
|
3273
|
+
if (value === void 0) return void 0;
|
|
3274
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3275
|
+
throw new Error("MCP prompt arguments must be an object");
|
|
3276
|
+
}
|
|
3277
|
+
const args = {};
|
|
3278
|
+
for (const [key, item] of Object.entries(value)) {
|
|
3279
|
+
if (typeof item !== "string") throw new Error(`MCP prompt argument "${key}" must be a string`);
|
|
3280
|
+
args[key] = item;
|
|
3281
|
+
}
|
|
3282
|
+
return args;
|
|
3283
|
+
}
|
|
3284
|
+
function sendContentError(ws, action, name2, error) {
|
|
3285
|
+
send(ws, { type: "mcp.content.error", payload: { action, name: name2, error } });
|
|
3286
|
+
}
|
|
3287
|
+
function errorMessage(err) {
|
|
3288
|
+
return err instanceof Error ? err.message : String(err);
|
|
3289
|
+
}
|
|
3182
3290
|
|
|
3183
3291
|
// src/server/memory-handlers.ts
|
|
3292
|
+
function isSuperMemoryStore(store) {
|
|
3293
|
+
const s = store;
|
|
3294
|
+
return typeof s.stats === "function" && typeof s.listSuper === "function" && typeof s.getSuperMemory === "function" && typeof s.updateSuperMemory === "function" && typeof s.deleteSuperMemory === "function";
|
|
3295
|
+
}
|
|
3296
|
+
function requiresSuperMemory(command) {
|
|
3297
|
+
return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
|
|
3298
|
+
}
|
|
3184
3299
|
async function handleMemoryList(ws, memoryStore) {
|
|
3185
3300
|
try {
|
|
3301
|
+
if (isSuperMemoryStore(memoryStore)) {
|
|
3302
|
+
const [stats, memories] = await Promise.all([
|
|
3303
|
+
memoryStore.stats(),
|
|
3304
|
+
memoryStore.listSuper()
|
|
3305
|
+
]);
|
|
3306
|
+
const text2 = memories.length === 0 ? "\u{1F9E0} Super Memory is empty." : formatSuperMemoryText(stats, memories);
|
|
3307
|
+
send(ws, { type: "memory.list", payload: { text: text2 } });
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3186
3310
|
const text = await memoryStore.readAll();
|
|
3187
3311
|
send(ws, { type: "memory.list", payload: { text } });
|
|
3188
3312
|
} catch (err) {
|
|
@@ -3192,26 +3316,128 @@ async function handleMemoryList(ws, memoryStore) {
|
|
|
3192
3316
|
});
|
|
3193
3317
|
}
|
|
3194
3318
|
}
|
|
3195
|
-
|
|
3196
|
-
const
|
|
3319
|
+
function formatSuperMemoryText(stats, memories) {
|
|
3320
|
+
const active = stats.byStatus["active"] ?? 0;
|
|
3321
|
+
const stale = stats.byStatus["stale"] ?? 0;
|
|
3322
|
+
const archived = stats.byStatus["archived"] ?? 0;
|
|
3323
|
+
const lines = [
|
|
3324
|
+
"## \u{1F9E0} Super Memory",
|
|
3325
|
+
"",
|
|
3326
|
+
`**Total:** ${stats.total} \xB7 \u{1F7E2} ${active} active \xB7 \u{1F7E1} ${stale} stale \xB7 \u{1F535} ${archived} archived \xB7 **edges:** ${stats.edges}`,
|
|
3327
|
+
""
|
|
3328
|
+
];
|
|
3329
|
+
for (const m of memories) {
|
|
3330
|
+
const tags = m.tags.length > 0 ? ` \`${m.tags.slice(0, 3).join("` `")}\`` : "";
|
|
3331
|
+
const icon = m.status === "active" ? "\u{1F7E2}" : m.status === "stale" ? "\u{1F7E1}" : m.status === "archived" ? "\u{1F535}" : "\u26AA";
|
|
3332
|
+
const preview = m.text.replace(/\s+/g, " ").trim().slice(0, 140);
|
|
3333
|
+
lines.push(`- ${icon} \`${m.id.slice(0, 12)}\u2026\` [${m.kind}] ${preview}${tags}`);
|
|
3334
|
+
}
|
|
3335
|
+
return lines.join("\n");
|
|
3336
|
+
}
|
|
3337
|
+
async function handleSuperMemoryList(ws, memoryStore) {
|
|
3338
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3339
|
+
send(ws, { type: "memory.super.list", payload: { error: requiresSuperMemory("memory.super.list") } });
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3197
3342
|
try {
|
|
3198
|
-
|
|
3199
|
-
|
|
3343
|
+
const [stats, memories] = await Promise.all([
|
|
3344
|
+
memoryStore.stats(),
|
|
3345
|
+
memoryStore.listSuper()
|
|
3346
|
+
]);
|
|
3347
|
+
send(ws, { type: "memory.super.list", payload: { memories, stats } });
|
|
3200
3348
|
} catch (err) {
|
|
3201
|
-
|
|
3349
|
+
send(ws, { type: "memory.super.list", payload: { error: errMessage(err) } });
|
|
3202
3350
|
}
|
|
3203
3351
|
}
|
|
3204
|
-
async function
|
|
3205
|
-
|
|
3352
|
+
async function handleSuperMemoryGet(ws, msg, memoryStore) {
|
|
3353
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3354
|
+
send(ws, { type: "memory.super.get", payload: { error: requiresSuperMemory("memory.super.get") } });
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
const { id } = msg.payload;
|
|
3358
|
+
if (!id) {
|
|
3359
|
+
send(ws, { type: "memory.super.get", payload: { error: "id is required" } });
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3206
3362
|
try {
|
|
3207
|
-
const
|
|
3208
|
-
|
|
3209
|
-
ws,
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
);
|
|
3363
|
+
const memory = await memoryStore.getSuperMemory(id);
|
|
3364
|
+
if (!memory) {
|
|
3365
|
+
send(ws, { type: "memory.super.get", payload: { error: `Memory "${id}" not found.` } });
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
send(ws, { type: "memory.super.get", payload: { memory } });
|
|
3213
3369
|
} catch (err) {
|
|
3214
|
-
|
|
3370
|
+
send(ws, { type: "memory.super.get", payload: { error: errMessage(err) } });
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
async function handleSuperMemoryUpdate(ws, msg, memoryStore) {
|
|
3374
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3375
|
+
send(ws, { type: "memory.super.update", payload: { error: requiresSuperMemory("memory.super.update") } });
|
|
3376
|
+
return;
|
|
3377
|
+
}
|
|
3378
|
+
const payload = msg.payload;
|
|
3379
|
+
const id = payload["id"];
|
|
3380
|
+
if (!id) {
|
|
3381
|
+
send(ws, { type: "memory.super.update", payload: { error: "id is required" } });
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3384
|
+
const patch = { ...payload };
|
|
3385
|
+
delete patch["id"];
|
|
3386
|
+
if (Object.keys(patch).length === 0) {
|
|
3387
|
+
send(ws, { type: "memory.super.update", payload: { error: "No fields to update." } });
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
try {
|
|
3391
|
+
const memory = await memoryStore.updateSuperMemory(id, patch);
|
|
3392
|
+
send(ws, { type: "memory.super.update", payload: { memory } });
|
|
3393
|
+
} catch (err) {
|
|
3394
|
+
send(ws, { type: "memory.super.update", payload: { error: errMessage(err) } });
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
async function handleSuperMemoryRemember(ws, msg, memoryStore) {
|
|
3398
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3399
|
+
send(ws, { type: "memory.super.remember", payload: { error: requiresSuperMemory("memory.super.remember") } });
|
|
3400
|
+
return;
|
|
3401
|
+
}
|
|
3402
|
+
const payload = msg.payload;
|
|
3403
|
+
const text = payload["text"];
|
|
3404
|
+
if (!text || !text.trim()) {
|
|
3405
|
+
send(ws, { type: "memory.super.remember", payload: { error: "text is required" } });
|
|
3406
|
+
return;
|
|
3407
|
+
}
|
|
3408
|
+
try {
|
|
3409
|
+
const memory = await memoryStore.rememberSuper({
|
|
3410
|
+
text: text.trim(),
|
|
3411
|
+
kind: payload["kind"],
|
|
3412
|
+
scope: payload["scope"],
|
|
3413
|
+
tags: payload["tags"],
|
|
3414
|
+
anchors: payload["anchors"],
|
|
3415
|
+
importance: payload["importance"],
|
|
3416
|
+
confidence: payload["confidence"],
|
|
3417
|
+
freshness: payload["freshness"],
|
|
3418
|
+
supersedes: payload["supersedes"],
|
|
3419
|
+
contradicts: payload["contradicts"]
|
|
3420
|
+
});
|
|
3421
|
+
send(ws, { type: "memory.super.remember", payload: { memory } });
|
|
3422
|
+
} catch (err) {
|
|
3423
|
+
send(ws, { type: "memory.super.remember", payload: { error: errMessage(err) } });
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
async function handleSuperMemoryDelete(ws, msg, memoryStore) {
|
|
3427
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3428
|
+
send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
const { id, reason } = msg.payload;
|
|
3432
|
+
if (!id) {
|
|
3433
|
+
send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3436
|
+
try {
|
|
3437
|
+
await memoryStore.deleteSuperMemory(id, reason);
|
|
3438
|
+
send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
|
|
3439
|
+
} catch (err) {
|
|
3440
|
+
send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
|
|
3215
3441
|
}
|
|
3216
3442
|
}
|
|
3217
3443
|
|
|
@@ -3803,6 +4029,8 @@ var SddBoardWebSocketHandler = class {
|
|
|
3803
4029
|
import * as path8 from "node:path";
|
|
3804
4030
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3805
4031
|
import {
|
|
4032
|
+
DefaultTaskStore,
|
|
4033
|
+
TaskTracker,
|
|
3806
4034
|
WorktreeManager as WorktreeManager2,
|
|
3807
4035
|
ToolValidationError as ToolValidationError4
|
|
3808
4036
|
} from "@wrongstack/core";
|
|
@@ -3818,6 +4046,63 @@ import {
|
|
|
3818
4046
|
startSddRun,
|
|
3819
4047
|
TaskGraphStore
|
|
3820
4048
|
} from "@wrongstack/sdd";
|
|
4049
|
+
function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
4050
|
+
const runTracker = tracker ?? (() => {
|
|
4051
|
+
const t = new TaskTracker({ store: new DefaultTaskStore() });
|
|
4052
|
+
t.setGraph(graph);
|
|
4053
|
+
return t;
|
|
4054
|
+
})();
|
|
4055
|
+
const worktreesEnabled = config.worktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
|
|
4056
|
+
let worktrees;
|
|
4057
|
+
if (worktreesEnabled) {
|
|
4058
|
+
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
4059
|
+
cwd: deps2.projectRoot,
|
|
4060
|
+
encoding: "utf8",
|
|
4061
|
+
windowsHide: true
|
|
4062
|
+
}).stdout?.trim() === "true";
|
|
4063
|
+
if (inGit) {
|
|
4064
|
+
void cleanupStaleSddWorktrees({
|
|
4065
|
+
projectRoot: deps2.projectRoot,
|
|
4066
|
+
boardsDir: deps2.projectSddBoards
|
|
4067
|
+
}).catch(() => void 0);
|
|
4068
|
+
worktrees = new WorktreeManager2({
|
|
4069
|
+
projectRoot: deps2.projectRoot,
|
|
4070
|
+
events: deps2.events,
|
|
4071
|
+
sessionId: () => deps2.agent.ctx.session?.id
|
|
4072
|
+
});
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
const superviseFailure = deps2.brain && deps2.runIsolatedTurn ? new SddSupervisor({
|
|
4076
|
+
brain: deps2.brain,
|
|
4077
|
+
reassignModels: config.fallbackModels,
|
|
4078
|
+
generateSubtasks: makeLlmSubtaskGenerator({
|
|
4079
|
+
run: (prompt) => deps2.runIsolatedTurn(prompt, "Task Splitter")
|
|
4080
|
+
}),
|
|
4081
|
+
requestLlmVerdict: true
|
|
4082
|
+
}).superviseFailure : deps2.brain ? new SddSupervisor({
|
|
4083
|
+
brain: deps2.brain,
|
|
4084
|
+
reassignModels: config.fallbackModels,
|
|
4085
|
+
requestLlmVerdict: true
|
|
4086
|
+
}).superviseFailure : void 0;
|
|
4087
|
+
return startSddRun({
|
|
4088
|
+
tracker: runTracker,
|
|
4089
|
+
graph,
|
|
4090
|
+
agent: deps2.agent,
|
|
4091
|
+
projectRoot: deps2.projectRoot,
|
|
4092
|
+
events: deps2.events,
|
|
4093
|
+
sessionId: () => deps2.agent.ctx.session?.id,
|
|
4094
|
+
subagentFactory: deps2.subagentFactory,
|
|
4095
|
+
boardStore: new SddBoardStore2({ baseDir: deps2.projectSddBoards }),
|
|
4096
|
+
registry: deps2.registry ?? new SddRunRegistry(),
|
|
4097
|
+
verifyTask: makeCommandVerifier(),
|
|
4098
|
+
...superviseFailure ? { superviseFailure } : {},
|
|
4099
|
+
...config.parallelSlots !== void 0 ? { parallelSlots: config.parallelSlots } : {},
|
|
4100
|
+
...config.defaultModel !== void 0 ? { defaultModel: config.defaultModel } : {},
|
|
4101
|
+
...config.defaultProvider !== void 0 ? { defaultProvider: config.defaultProvider } : {},
|
|
4102
|
+
...config.fallbackModels !== void 0 ? { fallbackModels: config.fallbackModels } : {},
|
|
4103
|
+
...worktrees ? { worktrees } : {}
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
3821
4106
|
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";
|
|
3822
4107
|
function buildSddWizardDeps(opts) {
|
|
3823
4108
|
const registry = new SddRunRegistry();
|
|
@@ -3853,64 +4138,27 @@ function buildSddWizardDeps(opts) {
|
|
|
3853
4138
|
field: "taskGraph"
|
|
3854
4139
|
});
|
|
3855
4140
|
}
|
|
3856
|
-
const
|
|
3857
|
-
let worktrees;
|
|
3858
|
-
if (worktreesEnabled) {
|
|
3859
|
-
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3860
|
-
cwd: opts.projectRoot,
|
|
3861
|
-
encoding: "utf8",
|
|
3862
|
-
windowsHide: true
|
|
3863
|
-
}).stdout?.trim() === "true";
|
|
3864
|
-
if (inGit) {
|
|
3865
|
-
await cleanupStaleSddWorktrees({
|
|
3866
|
-
projectRoot: opts.projectRoot,
|
|
3867
|
-
boardsDir: opts.paths.projectSddBoards
|
|
3868
|
-
}).catch(() => void 0);
|
|
3869
|
-
worktrees = new WorktreeManager2({
|
|
3870
|
-
projectRoot: opts.projectRoot,
|
|
3871
|
-
events: opts.events,
|
|
3872
|
-
sessionId: () => opts.agent.ctx.session?.id
|
|
3873
|
-
});
|
|
3874
|
-
}
|
|
3875
|
-
}
|
|
3876
|
-
const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
|
|
3877
|
-
const verifyTask = makeCommandVerifier();
|
|
3878
|
-
const superviseFailure = opts.brain ? new SddSupervisor({
|
|
3879
|
-
brain: opts.brain,
|
|
3880
|
-
// The run-level fallback chain (chosen in the wizard) doubles as the
|
|
3881
|
-
// supervisor's reassign options — a `reassign` verdict rotates the
|
|
3882
|
-
// worker model on retry. Empty/undefined → reassign option dropped.
|
|
3883
|
-
reassignModels: fallbackModels,
|
|
3884
|
-
// LLM auto-split: decompose a retry-exhausted task into smaller
|
|
3885
|
-
// sub-tasks on an isolated read-only turn. Heavily validated +
|
|
3886
|
-
// bounded; an empty result degrades the split into a retry.
|
|
3887
|
-
generateSubtasks: makeLlmSubtaskGenerator({
|
|
3888
|
-
run: (prompt) => runIsolatedTurn(prompt, "Task Splitter")
|
|
3889
|
-
}),
|
|
3890
|
-
// The standalone brain is a tiered policy→LLM arbiter with NO
|
|
3891
|
-
// human-escalation wrapper (see index.ts), so it never blocks on a
|
|
3892
|
-
// human prompt — an unresolved verdict degrades to a bounded retry.
|
|
3893
|
-
// Safe to let the LLM layer actually pick reassign/split.
|
|
3894
|
-
requestLlmVerdict: true
|
|
3895
|
-
}).superviseFailure : void 0;
|
|
3896
|
-
const handle = startSddRun({
|
|
3897
|
-
tracker,
|
|
4141
|
+
const handle = startSddRunFromGraph(
|
|
3898
4142
|
graph,
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4143
|
+
{
|
|
4144
|
+
agent: opts.agent,
|
|
4145
|
+
events: opts.events,
|
|
4146
|
+
projectRoot: opts.projectRoot,
|
|
4147
|
+
subagentFactory: opts.subagentFactory,
|
|
4148
|
+
projectSddBoards: opts.paths.projectSddBoards,
|
|
4149
|
+
registry,
|
|
4150
|
+
runIsolatedTurn,
|
|
4151
|
+
...opts.brain ? { brain: opts.brain } : {}
|
|
4152
|
+
},
|
|
4153
|
+
{
|
|
4154
|
+
...parallelSlots !== void 0 ? { parallelSlots } : {},
|
|
4155
|
+
...defaultModel !== void 0 ? { defaultModel } : {},
|
|
4156
|
+
...defaultProvider !== void 0 ? { defaultProvider } : {},
|
|
4157
|
+
...fallbackModels !== void 0 ? { fallbackModels } : {},
|
|
4158
|
+
...useWorktrees !== void 0 ? { worktrees: useWorktrees } : {}
|
|
4159
|
+
},
|
|
4160
|
+
tracker
|
|
4161
|
+
);
|
|
3914
4162
|
void handle.completion.catch(() => {
|
|
3915
4163
|
});
|
|
3916
4164
|
return { runId: handle.runId };
|
|
@@ -4054,6 +4302,31 @@ var SddWizardWebSocketHandler = class {
|
|
|
4054
4302
|
}
|
|
4055
4303
|
};
|
|
4056
4304
|
|
|
4305
|
+
// src/server/session-history.ts
|
|
4306
|
+
function toSessionHistoryEntry(summary, currentSessionId) {
|
|
4307
|
+
return {
|
|
4308
|
+
id: summary.id,
|
|
4309
|
+
title: summary.title,
|
|
4310
|
+
...summary.name !== void 0 ? { name: summary.name } : {},
|
|
4311
|
+
startedAt: summary.startedAt,
|
|
4312
|
+
...summary.endedAt !== void 0 ? { endedAt: summary.endedAt } : {},
|
|
4313
|
+
model: summary.model,
|
|
4314
|
+
provider: summary.provider,
|
|
4315
|
+
tokenTotal: summary.tokenTotal,
|
|
4316
|
+
...summary.iterationCount !== void 0 ? { iterationCount: summary.iterationCount } : {},
|
|
4317
|
+
...summary.toolCallCount !== void 0 ? { toolCallCount: summary.toolCallCount } : {},
|
|
4318
|
+
...summary.toolErrorCount !== void 0 ? { toolErrorCount: summary.toolErrorCount } : {},
|
|
4319
|
+
...summary.fileChangeCount !== void 0 ? { fileChangeCount: summary.fileChangeCount } : {},
|
|
4320
|
+
...summary.toolBreakdown !== void 0 ? { toolBreakdown: summary.toolBreakdown } : {},
|
|
4321
|
+
...summary.compactionCount !== void 0 ? { compactionCount: summary.compactionCount } : {},
|
|
4322
|
+
...summary.outcome !== void 0 ? { outcome: summary.outcome } : {},
|
|
4323
|
+
isCurrent: summary.id === currentSessionId
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
function toSessionHistoryEntries(summaries, currentSessionId) {
|
|
4327
|
+
return summaries.map((summary) => toSessionHistoryEntry(summary, currentSessionId));
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4057
4330
|
// src/server/shell-open.ts
|
|
4058
4331
|
import * as fs8 from "node:fs/promises";
|
|
4059
4332
|
import * as path9 from "node:path";
|
|
@@ -4254,6 +4527,16 @@ function validateBrainAskPayload(payload) {
|
|
|
4254
4527
|
}
|
|
4255
4528
|
return { ok: true, value: { question: question.trim() } };
|
|
4256
4529
|
}
|
|
4530
|
+
function validateBrainConfigSetPayload(payload) {
|
|
4531
|
+
if (!isRecord(payload)) {
|
|
4532
|
+
return { ok: false, message: "brain.config.set payload must be an object with a patch object" };
|
|
4533
|
+
}
|
|
4534
|
+
const patch = payload["patch"];
|
|
4535
|
+
if (!isRecord(patch)) {
|
|
4536
|
+
return { ok: false, message: "brain.config.set payload.patch must be an object" };
|
|
4537
|
+
}
|
|
4538
|
+
return { ok: true, value: { patch } };
|
|
4539
|
+
}
|
|
4257
4540
|
function validateAutonomySwitchPayload(payload) {
|
|
4258
4541
|
if (!isRecord(payload)) {
|
|
4259
4542
|
return { ok: false, message: "autonomy.switch payload must be an object with string mode" };
|
|
@@ -4315,7 +4598,9 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4315
4598
|
"hqEnabled",
|
|
4316
4599
|
"hqRawContent",
|
|
4317
4600
|
"fallbackAuto",
|
|
4318
|
-
"favoriteModelsOnly"
|
|
4601
|
+
"favoriteModelsOnly",
|
|
4602
|
+
"breakerEnabled",
|
|
4603
|
+
"debugStream"
|
|
4319
4604
|
]);
|
|
4320
4605
|
var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackModels", "favoriteModels"]);
|
|
4321
4606
|
var STRING_ARRAY_RECORD_PREF_KEYS = /* @__PURE__ */ new Set(["fallbackProfiles"]);
|
|
@@ -4326,9 +4611,18 @@ var NUMBER_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4326
4611
|
"maxIterations",
|
|
4327
4612
|
"maxConcurrent",
|
|
4328
4613
|
"enhanceDelayMs",
|
|
4329
|
-
"tgLongToolMs"
|
|
4614
|
+
"tgLongToolMs",
|
|
4615
|
+
"breakerAutoKillResetMs"
|
|
4616
|
+
]);
|
|
4617
|
+
var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
4618
|
+
"hqUrl",
|
|
4619
|
+
"hqToken",
|
|
4620
|
+
"uiLocale",
|
|
4621
|
+
"thinkingWord",
|
|
4622
|
+
"refinerProvider",
|
|
4623
|
+
"refinerModel",
|
|
4624
|
+
"refinerFallbackProfile"
|
|
4330
4625
|
]);
|
|
4331
|
-
var STRING_PREF_KEYS = /* @__PURE__ */ new Set(["hqUrl", "hqToken", "uiLocale"]);
|
|
4332
4626
|
var ENUM_PREF_KEYS = {
|
|
4333
4627
|
autonomy: AUTONOMY_VALUES,
|
|
4334
4628
|
contextStrategy: CONTEXT_STRATEGY_VALUES,
|
|
@@ -4339,7 +4633,10 @@ var ENUM_PREF_KEYS = {
|
|
|
4339
4633
|
auditLevel: AUDIT_LEVEL_VALUES,
|
|
4340
4634
|
reasoningMode: REASONING_MODE_VALUES,
|
|
4341
4635
|
reasoningEffort: REASONING_EFFORT_VALUES,
|
|
4342
|
-
cacheTtl: CACHE_TTL_VALUES
|
|
4636
|
+
cacheTtl: CACHE_TTL_VALUES,
|
|
4637
|
+
statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
|
|
4638
|
+
animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
|
|
4639
|
+
fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"])
|
|
4343
4640
|
};
|
|
4344
4641
|
function validateModelRuntimeValue(modelRuntime, path22) {
|
|
4345
4642
|
const reasoning = modelRuntime["reasoning"];
|
|
@@ -5662,6 +5959,10 @@ function shouldLogWatcherStats() {
|
|
|
5662
5959
|
function setupEvents(deps2) {
|
|
5663
5960
|
const { events, broadcast: broadcast2, clients, config, context, pendingConfirms, globalConfigPath, sessionBridge, wpaths, watcherMetrics, onFleetBroadcaster } = deps2;
|
|
5664
5961
|
const disposers = [];
|
|
5962
|
+
let disposed = false;
|
|
5963
|
+
const on = (event, listener) => {
|
|
5964
|
+
disposers.push(events.on(event, listener));
|
|
5965
|
+
};
|
|
5665
5966
|
const currentSessionId = () => context.session?.id ?? "";
|
|
5666
5967
|
const sessionPayload2 = (payload) => {
|
|
5667
5968
|
const provided = payload["sessionId"];
|
|
@@ -5677,20 +5978,20 @@ function setupEvents(deps2) {
|
|
|
5677
5978
|
sessionBridge?.append(event).catch(() => {
|
|
5678
5979
|
});
|
|
5679
5980
|
};
|
|
5680
|
-
|
|
5981
|
+
on("iteration.started", (e) => {
|
|
5681
5982
|
const maxIt = typeof context.meta["maxIterations"] === "number" ? context.meta["maxIterations"] : config.tools?.maxIterations ?? 100;
|
|
5682
5983
|
broadcast2(clients, {
|
|
5683
5984
|
type: "iteration.started",
|
|
5684
5985
|
payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, maxIterations: maxIt })
|
|
5685
5986
|
});
|
|
5686
5987
|
});
|
|
5687
|
-
|
|
5988
|
+
on("iteration.completed", (e) => {
|
|
5688
5989
|
broadcast2(clients, {
|
|
5689
5990
|
type: "iteration.completed",
|
|
5690
5991
|
payload: sessionPayload2({ sessionId: e.sessionId, index: e.index, totalIterations: e.index + 1 })
|
|
5691
5992
|
});
|
|
5692
5993
|
});
|
|
5693
|
-
|
|
5994
|
+
on("iteration.limit_reached", (e) => {
|
|
5694
5995
|
broadcast2(clients, {
|
|
5695
5996
|
type: "iteration.limit_reached",
|
|
5696
5997
|
payload: sessionPayload2({
|
|
@@ -5700,19 +6001,19 @@ function setupEvents(deps2) {
|
|
|
5700
6001
|
})
|
|
5701
6002
|
});
|
|
5702
6003
|
});
|
|
5703
|
-
|
|
6004
|
+
on("provider.text_delta", (e) => {
|
|
5704
6005
|
broadcast2(clients, { type: "provider.text_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text, messageId: "current" }) });
|
|
5705
6006
|
});
|
|
5706
|
-
|
|
6007
|
+
on("provider.thinking_delta", (e) => {
|
|
5707
6008
|
broadcast2(clients, { type: "provider.thinking_delta", payload: sessionPayload2({ sessionId: e.sessionId, text: e.text }) });
|
|
5708
6009
|
});
|
|
5709
|
-
|
|
6010
|
+
on("provider.stream_error", (e) => {
|
|
5710
6011
|
broadcast2(clients, {
|
|
5711
6012
|
type: "provider.stream_error",
|
|
5712
6013
|
payload: sessionPayload2({ sessionId: e.sessionId, eventType: e.eventType, message: e.msg })
|
|
5713
6014
|
});
|
|
5714
6015
|
});
|
|
5715
|
-
|
|
6016
|
+
on("tool.started", (e) => {
|
|
5716
6017
|
broadcast2(clients, {
|
|
5717
6018
|
type: "tool.started",
|
|
5718
6019
|
payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, input: e.input, messageId: `tool_${e.id}` })
|
|
@@ -5725,7 +6026,7 @@ function setupEvents(deps2) {
|
|
|
5725
6026
|
input: e.input
|
|
5726
6027
|
});
|
|
5727
6028
|
});
|
|
5728
|
-
|
|
6029
|
+
on("tool.progress", (e) => {
|
|
5729
6030
|
broadcast2(clients, {
|
|
5730
6031
|
type: "tool.progress",
|
|
5731
6032
|
// Nested `event` shape — the client handler reads `payload.event?.text`
|
|
@@ -5746,7 +6047,7 @@ function setupEvents(deps2) {
|
|
|
5746
6047
|
}
|
|
5747
6048
|
});
|
|
5748
6049
|
});
|
|
5749
|
-
|
|
6050
|
+
on("tool.executed", (e) => {
|
|
5750
6051
|
broadcast2(clients, {
|
|
5751
6052
|
type: "tool.executed",
|
|
5752
6053
|
payload: sessionPayload2({ sessionId: e.sessionId, id: e.id, name: e.name, durationMs: e.durationMs, ok: e.ok, input: e.input, output: e.output })
|
|
@@ -5804,7 +6105,7 @@ function setupEvents(deps2) {
|
|
|
5804
6105
|
})();
|
|
5805
6106
|
}
|
|
5806
6107
|
});
|
|
5807
|
-
|
|
6108
|
+
on("tool.loop_detected", (e) => {
|
|
5808
6109
|
broadcast2(clients, {
|
|
5809
6110
|
type: "tool.loop_detected",
|
|
5810
6111
|
payload: sessionPayload2({
|
|
@@ -5816,19 +6117,19 @@ function setupEvents(deps2) {
|
|
|
5816
6117
|
})
|
|
5817
6118
|
});
|
|
5818
6119
|
});
|
|
5819
|
-
|
|
6120
|
+
on("trust.persisted", (e) => {
|
|
5820
6121
|
broadcast2(clients, {
|
|
5821
6122
|
type: "trust.persisted",
|
|
5822
6123
|
payload: sessionPayload2({ sessionId: e.sessionId, tool: e.tool, pattern: e.pattern, decision: e.decision })
|
|
5823
6124
|
});
|
|
5824
6125
|
});
|
|
5825
|
-
|
|
6126
|
+
on("delegate.started", (e) => {
|
|
5826
6127
|
broadcast2(clients, {
|
|
5827
6128
|
type: "delegate.started",
|
|
5828
6129
|
payload: sessionPayload2({ sessionId: e.sessionId, target: e.target, task: e.task })
|
|
5829
6130
|
});
|
|
5830
6131
|
});
|
|
5831
|
-
|
|
6132
|
+
on("delegate.completed", (e) => {
|
|
5832
6133
|
broadcast2(clients, {
|
|
5833
6134
|
type: "delegate.completed",
|
|
5834
6135
|
payload: sessionPayload2({
|
|
@@ -5846,7 +6147,7 @@ function setupEvents(deps2) {
|
|
|
5846
6147
|
})
|
|
5847
6148
|
});
|
|
5848
6149
|
});
|
|
5849
|
-
|
|
6150
|
+
on("provider.response", (e) => {
|
|
5850
6151
|
broadcast2(clients, {
|
|
5851
6152
|
type: "provider.response",
|
|
5852
6153
|
payload: sessionPayload2({
|
|
@@ -5858,7 +6159,7 @@ function setupEvents(deps2) {
|
|
|
5858
6159
|
})
|
|
5859
6160
|
});
|
|
5860
6161
|
});
|
|
5861
|
-
|
|
6162
|
+
on("ctx.pct", (e) => {
|
|
5862
6163
|
broadcast2(clients, {
|
|
5863
6164
|
type: "ctx.pct",
|
|
5864
6165
|
payload: sessionPayload2({ sessionId: e.sessionId, load: e.load, tokens: e.tokens, maxContext: e.maxContext })
|
|
@@ -5875,28 +6176,28 @@ function setupEvents(deps2) {
|
|
|
5875
6176
|
})
|
|
5876
6177
|
});
|
|
5877
6178
|
});
|
|
5878
|
-
|
|
6179
|
+
on("ctx.max_context", (e) => {
|
|
5879
6180
|
broadcast2(clients, {
|
|
5880
6181
|
type: "ctx.max_context",
|
|
5881
6182
|
payload: sessionPayload2({ sessionId: e.sessionId, providerId: e.providerId, modelId: e.modelId, maxContext: e.maxContext })
|
|
5882
6183
|
});
|
|
5883
6184
|
});
|
|
5884
|
-
|
|
6185
|
+
on("token.threshold", (e) => {
|
|
5885
6186
|
broadcast2(clients, {
|
|
5886
6187
|
type: "token.threshold",
|
|
5887
6188
|
payload: sessionPayload2({ sessionId: e.sessionId, used: e.used, limit: e.limit })
|
|
5888
6189
|
});
|
|
5889
6190
|
});
|
|
5890
|
-
|
|
6191
|
+
on("token.cost_estimate_unavailable", (e) => {
|
|
5891
6192
|
broadcast2(clients, {
|
|
5892
6193
|
type: "token.cost_estimate_unavailable",
|
|
5893
6194
|
payload: sessionPayload2({ sessionId: e.sessionId, model: e.model })
|
|
5894
6195
|
});
|
|
5895
6196
|
});
|
|
5896
|
-
|
|
6197
|
+
on("context.repaired", (e) => {
|
|
5897
6198
|
broadcast2(clients, { type: "context.repaired", payload: sessionPayload2({ sessionId: e.sessionId, removedToolUses: e.removedToolUses, removedToolResults: e.removedToolResults, removedMessages: e.removedMessages }) });
|
|
5898
6199
|
});
|
|
5899
|
-
|
|
6200
|
+
on("tool.confirm_needed", (e) => {
|
|
5900
6201
|
const id = e.toolUseId ?? `confirm_${Date.now()}`;
|
|
5901
6202
|
const payload = sessionPayload2({ sessionId: e.sessionId, id, toolName: e.tool?.name ?? "unknown", input: e.input, suggestedPattern: e.suggestedPattern, decisionSource: e.decisionSource, riskTier: e.riskTier });
|
|
5902
6203
|
pendingConfirms.set(id, {
|
|
@@ -5907,7 +6208,7 @@ function setupEvents(deps2) {
|
|
|
5907
6208
|
});
|
|
5908
6209
|
broadcast2(clients, { type: "tool.confirm_needed", payload });
|
|
5909
6210
|
});
|
|
5910
|
-
|
|
6211
|
+
on("error", (e) => {
|
|
5911
6212
|
broadcast2(clients, { type: "error", payload: sessionPayload2({ sessionId: e.sessionId, phase: e.phase, message: e.err instanceof Error ? e.err.message : String(e.err) }) });
|
|
5912
6213
|
appendForCurrentSession(e.sessionId, {
|
|
5913
6214
|
type: "error",
|
|
@@ -5916,13 +6217,13 @@ function setupEvents(deps2) {
|
|
|
5916
6217
|
phase: e.phase
|
|
5917
6218
|
});
|
|
5918
6219
|
});
|
|
5919
|
-
|
|
6220
|
+
on("session.damaged", (e) => {
|
|
5920
6221
|
broadcast2(clients, {
|
|
5921
6222
|
type: "session.damaged",
|
|
5922
6223
|
payload: { sessionId: e.sessionId, detail: e.detail }
|
|
5923
6224
|
});
|
|
5924
6225
|
});
|
|
5925
|
-
|
|
6226
|
+
on("session.rewound", (e) => {
|
|
5926
6227
|
broadcast2(clients, {
|
|
5927
6228
|
type: "session.rewound",
|
|
5928
6229
|
payload: sessionPayload2({
|
|
@@ -5933,7 +6234,7 @@ function setupEvents(deps2) {
|
|
|
5933
6234
|
})
|
|
5934
6235
|
});
|
|
5935
6236
|
});
|
|
5936
|
-
|
|
6237
|
+
on("checkpoint.written", (e) => {
|
|
5937
6238
|
broadcast2(clients, {
|
|
5938
6239
|
type: "checkpoint.written",
|
|
5939
6240
|
payload: sessionPayload2({
|
|
@@ -5945,19 +6246,19 @@ function setupEvents(deps2) {
|
|
|
5945
6246
|
})
|
|
5946
6247
|
});
|
|
5947
6248
|
});
|
|
5948
|
-
|
|
6249
|
+
on("in_flight.started", (e) => {
|
|
5949
6250
|
broadcast2(clients, {
|
|
5950
6251
|
type: "in_flight.started",
|
|
5951
6252
|
payload: sessionPayload2({ sessionId: e.sessionId, context: e.context, ts: e.ts })
|
|
5952
6253
|
});
|
|
5953
6254
|
});
|
|
5954
|
-
|
|
6255
|
+
on("in_flight.ended", (e) => {
|
|
5955
6256
|
broadcast2(clients, {
|
|
5956
6257
|
type: "in_flight.ended",
|
|
5957
6258
|
payload: sessionPayload2({ sessionId: e.sessionId, reason: e.reason, ts: e.ts })
|
|
5958
6259
|
});
|
|
5959
6260
|
});
|
|
5960
|
-
|
|
6261
|
+
on("provider.retry", (e) => {
|
|
5961
6262
|
broadcast2(clients, {
|
|
5962
6263
|
type: "provider.retry",
|
|
5963
6264
|
payload: sessionPayload2({
|
|
@@ -5979,7 +6280,7 @@ function setupEvents(deps2) {
|
|
|
5979
6280
|
description: e.description
|
|
5980
6281
|
});
|
|
5981
6282
|
});
|
|
5982
|
-
|
|
6283
|
+
on("provider.error", (e) => {
|
|
5983
6284
|
broadcast2(clients, {
|
|
5984
6285
|
type: "provider.error",
|
|
5985
6286
|
payload: sessionPayload2({
|
|
@@ -5999,7 +6300,7 @@ function setupEvents(deps2) {
|
|
|
5999
6300
|
retryable: e.retryable
|
|
6000
6301
|
});
|
|
6001
6302
|
});
|
|
6002
|
-
|
|
6303
|
+
on("provider.fallback", (e) => {
|
|
6003
6304
|
broadcast2(clients, {
|
|
6004
6305
|
type: "provider.fallback",
|
|
6005
6306
|
payload: sessionPayload2({
|
|
@@ -6011,7 +6312,7 @@ function setupEvents(deps2) {
|
|
|
6011
6312
|
})
|
|
6012
6313
|
});
|
|
6013
6314
|
});
|
|
6014
|
-
|
|
6315
|
+
on("compaction.fired", (e) => {
|
|
6015
6316
|
broadcast2(clients, {
|
|
6016
6317
|
type: "context.compacted",
|
|
6017
6318
|
payload: sessionPayload2({
|
|
@@ -6023,7 +6324,7 @@ function setupEvents(deps2) {
|
|
|
6023
6324
|
})
|
|
6024
6325
|
});
|
|
6025
6326
|
});
|
|
6026
|
-
|
|
6327
|
+
on("compaction.failed", (e) => {
|
|
6027
6328
|
broadcast2(clients, {
|
|
6028
6329
|
type: "compaction.failed",
|
|
6029
6330
|
payload: sessionPayload2({
|
|
@@ -6038,25 +6339,25 @@ function setupEvents(deps2) {
|
|
|
6038
6339
|
})
|
|
6039
6340
|
});
|
|
6040
6341
|
});
|
|
6041
|
-
|
|
6342
|
+
on("mcp.server.connected", (e) => {
|
|
6042
6343
|
broadcast2(clients, {
|
|
6043
6344
|
type: "mcp.server.connected",
|
|
6044
6345
|
payload: { name: e.name, toolCount: e.toolCount }
|
|
6045
6346
|
});
|
|
6046
6347
|
});
|
|
6047
|
-
|
|
6348
|
+
on("mcp.server.reconnected", (e) => {
|
|
6048
6349
|
broadcast2(clients, {
|
|
6049
6350
|
type: "mcp.server.reconnected",
|
|
6050
6351
|
payload: { name: e.name, toolCount: e.toolCount }
|
|
6051
6352
|
});
|
|
6052
6353
|
});
|
|
6053
|
-
|
|
6354
|
+
on("mcp.server.disconnected", (e) => {
|
|
6054
6355
|
broadcast2(clients, {
|
|
6055
6356
|
type: "mcp.server.disconnected",
|
|
6056
6357
|
payload: { name: e.name, reason: e.reason }
|
|
6057
6358
|
});
|
|
6058
6359
|
});
|
|
6059
|
-
|
|
6360
|
+
on("coordinator.stats", (e) => {
|
|
6060
6361
|
broadcast2(clients, {
|
|
6061
6362
|
type: "coordinator.stats",
|
|
6062
6363
|
payload: sessionPayload2({
|
|
@@ -6084,16 +6385,16 @@ function setupEvents(deps2) {
|
|
|
6084
6385
|
broadcast2(clients, { type: "mailbox.agent_registered", payload });
|
|
6085
6386
|
});
|
|
6086
6387
|
const forwardSubagent = (kind, payload) => broadcast2(clients, { type: "subagent.event", payload: sessionPayload2({ kind, ...payload }) });
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6388
|
+
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 }));
|
|
6389
|
+
on("subagent.task_started", (e) => forwardSubagent("task_started", { sessionId: e.sessionId, subagentId: e.subagentId, taskId: e.taskId, description: e.description }));
|
|
6390
|
+
on("subagent.tool_executed", (e) => forwardSubagent("tool_executed", { sessionId: e.sessionId, subagentId: e.subagentId, toolName: e.name, durationMs: e.durationMs, ok: e.ok }));
|
|
6391
|
+
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 }));
|
|
6392
|
+
on("subagent.budget_warning", (e) => forwardSubagent("budget_warning", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, used: e.used, limit: e.limit }));
|
|
6393
|
+
on("subagent.budget_extended", (e) => forwardSubagent("budget_extended", { sessionId: e.sessionId, subagentId: e.subagentId, budgetKind: e.kind, newLimit: e.newLimit, totalExtensions: e.totalExtensions }));
|
|
6394
|
+
on("subagent.ctx_pct", (e) => forwardSubagent("ctx_pct", { sessionId: e.sessionId, subagentId: e.subagentId, load: e.load, tokens: e.tokens, maxContext: e.maxContext }));
|
|
6395
|
+
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 }));
|
|
6396
|
+
on("subagent.removed", (e) => forwardSubagent("removed", { sessionId: e.sessionId, subagentId: e.subagentId, reason: e.reason }));
|
|
6397
|
+
on("agent.timeline.message", (e) => {
|
|
6097
6398
|
broadcast2(clients, {
|
|
6098
6399
|
type: "agent.timeline.message",
|
|
6099
6400
|
payload: sessionPayload2({
|
|
@@ -6109,7 +6410,7 @@ function setupEvents(deps2) {
|
|
|
6109
6410
|
})
|
|
6110
6411
|
});
|
|
6111
6412
|
});
|
|
6112
|
-
|
|
6413
|
+
on("agent.status_changed", (e) => {
|
|
6113
6414
|
broadcast2(clients, {
|
|
6114
6415
|
type: "agent.status_changed",
|
|
6115
6416
|
payload: sessionPayload2({
|
|
@@ -6124,7 +6425,7 @@ function setupEvents(deps2) {
|
|
|
6124
6425
|
});
|
|
6125
6426
|
});
|
|
6126
6427
|
let leaderSpawned = false;
|
|
6127
|
-
|
|
6428
|
+
on("iteration.started", (e) => {
|
|
6128
6429
|
if (!leaderSpawned) {
|
|
6129
6430
|
leaderSpawned = true;
|
|
6130
6431
|
const provider = context.provider?.id ?? "unknown";
|
|
@@ -6138,7 +6439,7 @@ function setupEvents(deps2) {
|
|
|
6138
6439
|
});
|
|
6139
6440
|
}
|
|
6140
6441
|
});
|
|
6141
|
-
|
|
6442
|
+
on("tool.executed", (e) => {
|
|
6142
6443
|
forwardSubagent("tool_executed", {
|
|
6143
6444
|
sessionId: e.sessionId,
|
|
6144
6445
|
subagentId: "leader",
|
|
@@ -6147,7 +6448,7 @@ function setupEvents(deps2) {
|
|
|
6147
6448
|
ok: e.ok
|
|
6148
6449
|
});
|
|
6149
6450
|
});
|
|
6150
|
-
|
|
6451
|
+
on("provider.response", (e) => {
|
|
6151
6452
|
if (e.usage?.input != null) {
|
|
6152
6453
|
const maxCtx = context.provider.capabilities.maxContext;
|
|
6153
6454
|
const rawLoad = maxCtx > 0 ? e.usage.input / maxCtx : 0;
|
|
@@ -6164,7 +6465,7 @@ function setupEvents(deps2) {
|
|
|
6164
6465
|
});
|
|
6165
6466
|
}
|
|
6166
6467
|
});
|
|
6167
|
-
|
|
6468
|
+
on("iteration.completed", (e) => {
|
|
6168
6469
|
if (!leaderSpawned) {
|
|
6169
6470
|
leaderSpawned = true;
|
|
6170
6471
|
const provider = context.provider?.id ?? "unknown";
|
|
@@ -6196,7 +6497,19 @@ function setupEvents(deps2) {
|
|
|
6196
6497
|
payload: sessionPayload2({ event: eventName, ...payload })
|
|
6197
6498
|
});
|
|
6198
6499
|
});
|
|
6199
|
-
events.
|
|
6500
|
+
events.onPattern("cron:state_snapshot", (_eventName, payload) => {
|
|
6501
|
+
broadcast2(clients, {
|
|
6502
|
+
type: "cron.snapshot",
|
|
6503
|
+
payload
|
|
6504
|
+
});
|
|
6505
|
+
});
|
|
6506
|
+
events.onPattern("cron:job_fired", (_eventName, payload) => {
|
|
6507
|
+
broadcast2(clients, {
|
|
6508
|
+
type: "cron.job_fired",
|
|
6509
|
+
payload
|
|
6510
|
+
});
|
|
6511
|
+
});
|
|
6512
|
+
on("client.status", async (e) => {
|
|
6200
6513
|
broadcast2(clients, { type: "client.status_update", payload: e });
|
|
6201
6514
|
if (wpaths?.projectStatus) {
|
|
6202
6515
|
try {
|
|
@@ -6282,6 +6595,7 @@ function setupEvents(deps2) {
|
|
|
6282
6595
|
const startWatcher = async () => {
|
|
6283
6596
|
try {
|
|
6284
6597
|
await fs10.mkdir(projectsDir, { recursive: true });
|
|
6598
|
+
if (disposed) return;
|
|
6285
6599
|
watcher = fsWatch(projectsDir, { persistent: true, recursive: true }, async (eventType, filename) => {
|
|
6286
6600
|
if (eventType !== "change" && eventType !== "rename") return;
|
|
6287
6601
|
if (filename == null) return;
|
|
@@ -6312,7 +6626,7 @@ function setupEvents(deps2) {
|
|
|
6312
6626
|
);
|
|
6313
6627
|
}
|
|
6314
6628
|
};
|
|
6315
|
-
|
|
6629
|
+
on("client.status", (e) => {
|
|
6316
6630
|
if (e.projectHash) {
|
|
6317
6631
|
const hash = String(e.projectHash);
|
|
6318
6632
|
if (!knownProjectHashes.has(hash)) {
|
|
@@ -6352,7 +6666,9 @@ function setupEvents(deps2) {
|
|
|
6352
6666
|
const registry = new SessionRegistry(globalRoot);
|
|
6353
6667
|
const sessions = await registry.list();
|
|
6354
6668
|
const mySlug = sessions.find((s) => s.pid === process.pid)?.projectSlug;
|
|
6355
|
-
const live = sessions.filter(
|
|
6669
|
+
const live = sessions.filter(
|
|
6670
|
+
(s) => s.status === "active" || s.status === "idle"
|
|
6671
|
+
).filter((s) => mySlug ? s.projectSlug === mySlug : true).map((s) => ({
|
|
6356
6672
|
sessionId: s.sessionId,
|
|
6357
6673
|
projectName: s.projectName,
|
|
6358
6674
|
projectSlug: s.projectSlug,
|
|
@@ -6406,6 +6722,8 @@ function setupEvents(deps2) {
|
|
|
6406
6722
|
void broadcastSessions();
|
|
6407
6723
|
}
|
|
6408
6724
|
return () => {
|
|
6725
|
+
if (disposed) return;
|
|
6726
|
+
disposed = true;
|
|
6409
6727
|
for (const dispose of disposers) {
|
|
6410
6728
|
try {
|
|
6411
6729
|
dispose();
|
|
@@ -6540,9 +6858,11 @@ function positiveNumber(value) {
|
|
|
6540
6858
|
// src/server/server-runtime.ts
|
|
6541
6859
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
6542
6860
|
async function resolvePorts(opts) {
|
|
6543
|
-
const
|
|
6861
|
+
const surface = opts.surface ?? "webui";
|
|
6862
|
+
const surfaceDefaults = surface === "simpleui" ? { http: 3466, ws: 3467 } : { http: 3456, ws: 3457 };
|
|
6863
|
+
const requestedWsPort = opts.wsPort ?? surfaceDefaults.ws;
|
|
6544
6864
|
const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
|
|
6545
|
-
const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ??
|
|
6865
|
+
const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? String(surfaceDefaults.http), 10);
|
|
6546
6866
|
const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
|
|
6547
6867
|
const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
|
|
6548
6868
|
const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
|
|
@@ -6632,7 +6952,7 @@ function createWsServers(ports, accessToken) {
|
|
|
6632
6952
|
allowedHostnames: publicHostnames,
|
|
6633
6953
|
allowBrowserUrlToken: Boolean(ports.publicWsUrl)
|
|
6634
6954
|
});
|
|
6635
|
-
const WS_MAX_PAYLOAD =
|
|
6955
|
+
const WS_MAX_PAYLOAD = 20 * 1024 * 1024;
|
|
6636
6956
|
const wssPrimary = new WebSocketServer({
|
|
6637
6957
|
port: ports.wsPort,
|
|
6638
6958
|
host: ports.wsHost,
|
|
@@ -6700,8 +7020,8 @@ function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
|
6700
7020
|
if (explicitDistDir) return path12.resolve(explicitDistDir);
|
|
6701
7021
|
try {
|
|
6702
7022
|
const requireFromHere2 = createRequire(fromUrl);
|
|
6703
|
-
const serverEntry = requireFromHere2.resolve("@wrongstack/webui
|
|
6704
|
-
return path12.
|
|
7023
|
+
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
7024
|
+
return path12.dirname(serverEntry);
|
|
6705
7025
|
} catch {
|
|
6706
7026
|
return path12.resolve(path12.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
6707
7027
|
}
|
|
@@ -6724,7 +7044,7 @@ function startHttpServer(opts) {
|
|
|
6724
7044
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
6725
7045
|
if (opts.openBrowser) openBrowser(openUrl);
|
|
6726
7046
|
void registerInstance(
|
|
6727
|
-
{ pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path12.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
|
|
7047
|
+
{ pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path12.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
|
|
6728
7048
|
registryBaseDir
|
|
6729
7049
|
).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
|
|
6730
7050
|
});
|
|
@@ -6740,12 +7060,10 @@ function registerShutdown(deps2) {
|
|
|
6740
7060
|
}
|
|
6741
7061
|
|
|
6742
7062
|
// src/server/pre-context-services.ts
|
|
6743
|
-
import * as
|
|
7063
|
+
import * as path15 from "node:path";
|
|
6744
7064
|
import { createRequire as createRequire2 } from "node:module";
|
|
6745
|
-
import { WebSocket as WebSocket2 } from "ws";
|
|
6746
7065
|
import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
|
|
6747
7066
|
import {
|
|
6748
|
-
AgentStatusTracker,
|
|
6749
7067
|
AnnotationsStore,
|
|
6750
7068
|
DEFAULT_SESSION_PRUNE_DAYS,
|
|
6751
7069
|
DefaultModelsRegistry,
|
|
@@ -6756,7 +7074,6 @@ import {
|
|
|
6756
7074
|
DefaultSkillLoader,
|
|
6757
7075
|
DefaultSystemPromptBuilder,
|
|
6758
7076
|
EventBus,
|
|
6759
|
-
FleetNotifier,
|
|
6760
7077
|
GlobalMailbox,
|
|
6761
7078
|
PromptUsageStore,
|
|
6762
7079
|
ProviderRegistry,
|
|
@@ -6767,8 +7084,8 @@ import {
|
|
|
6767
7084
|
applyToolDescriptionModes,
|
|
6768
7085
|
applyToolResultRenderModes,
|
|
6769
7086
|
configureChildEnvGitIdentity,
|
|
7087
|
+
getSessionRegistry as getSessionRegistry2,
|
|
6770
7088
|
resolveContextWindowPolicy,
|
|
6771
|
-
getSessionRegistry,
|
|
6772
7089
|
makeMailboxTool,
|
|
6773
7090
|
makeMailInboxTool,
|
|
6774
7091
|
makeFleetStatusTool,
|
|
@@ -6777,7 +7094,12 @@ import {
|
|
|
6777
7094
|
import {
|
|
6778
7095
|
createSuperMemoryTools
|
|
6779
7096
|
} from "@wrongstack/super-memory";
|
|
6780
|
-
import {
|
|
7097
|
+
import {
|
|
7098
|
+
MCPAuthorizationManager,
|
|
7099
|
+
MCPRegistry,
|
|
7100
|
+
MCPVaultTokenStore,
|
|
7101
|
+
createVaultBackedMcpAuthorizationProviderFactory
|
|
7102
|
+
} from "@wrongstack/mcp";
|
|
6781
7103
|
import { buildProviderFactoriesFromRegistry } from "@wrongstack/providers";
|
|
6782
7104
|
import { createDefaultContainer } from "@wrongstack/runtime";
|
|
6783
7105
|
import {
|
|
@@ -6789,7 +7111,11 @@ import {
|
|
|
6789
7111
|
rememberTool,
|
|
6790
7112
|
searchMemoryTool
|
|
6791
7113
|
} from "@wrongstack/tools";
|
|
6792
|
-
import {
|
|
7114
|
+
import {
|
|
7115
|
+
attachSessionKanbanMirror,
|
|
7116
|
+
hydrateSessionKanban
|
|
7117
|
+
} from "@wrongstack/tools/session-kanban";
|
|
7118
|
+
import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
6793
7119
|
|
|
6794
7120
|
// src/server/setup-screen.ts
|
|
6795
7121
|
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
@@ -6927,6 +7253,21 @@ function seedContextMeta(config, context) {
|
|
|
6927
7253
|
meta["reasoningPreserve"] = mr.reasoning?.preserve === true;
|
|
6928
7254
|
meta["cacheTtl"] = mr.cache?.ttl ?? "default";
|
|
6929
7255
|
}
|
|
7256
|
+
meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
|
|
7257
|
+
meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
|
|
7258
|
+
meta["refinerFallbackProfile"] = autonomyCfg["refinerFallbackProfile"] ?? "";
|
|
7259
|
+
meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
|
|
7260
|
+
meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
|
|
7261
|
+
meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
|
|
7262
|
+
meta["breakerEnabled"] = config.circuitBreaker?.enabled === true;
|
|
7263
|
+
meta["breakerAutoKillResetMs"] = config.circuitBreaker?.autoKillResetMs ?? 6e4;
|
|
7264
|
+
{
|
|
7265
|
+
const featuresAllow = config.features?.allowOutsideProjectRoot;
|
|
7266
|
+
const toolsRestrict = config.tools?.restrictToProjectRoot;
|
|
7267
|
+
const allow = featuresAllow !== void 0 ? featuresAllow : toolsRestrict !== void 0 ? !toolsRestrict : true;
|
|
7268
|
+
meta["fsAccess"] = allow ? "unrestricted" : "project";
|
|
7269
|
+
}
|
|
7270
|
+
meta["debugStream"] = config.debugStream === true;
|
|
6930
7271
|
const hqConfig = config.hq;
|
|
6931
7272
|
meta["hqEnabled"] = hqConfig?.enabled === true;
|
|
6932
7273
|
meta["hqUrl"] = hqConfig?.url ?? "";
|
|
@@ -7023,10 +7364,188 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
7023
7364
|
}
|
|
7024
7365
|
}
|
|
7025
7366
|
|
|
7367
|
+
// src/server/standalone-session-identity.ts
|
|
7368
|
+
import * as path14 from "node:path";
|
|
7369
|
+
import {
|
|
7370
|
+
AgentStatusTracker,
|
|
7371
|
+
FleetNotifier,
|
|
7372
|
+
getSessionRegistry,
|
|
7373
|
+
RecoveryLock
|
|
7374
|
+
} from "@wrongstack/core";
|
|
7375
|
+
import { WebSocket as WebSocket2 } from "ws";
|
|
7376
|
+
async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
7377
|
+
const { paths, events, logger } = opts;
|
|
7378
|
+
const registry = opts.sessionRegistry ?? getSessionRegistry(paths.globalRoot);
|
|
7379
|
+
const fleetNotifier = new FleetNotifier({
|
|
7380
|
+
baseDir: paths.globalRoot,
|
|
7381
|
+
projectRoot: paths.projectRoot,
|
|
7382
|
+
selfPid: process.pid
|
|
7383
|
+
});
|
|
7384
|
+
let activeSessionId = opts.initialSessionId;
|
|
7385
|
+
let stopped = false;
|
|
7386
|
+
let transition = Promise.resolve();
|
|
7387
|
+
const statusTracker = new AgentStatusTracker({
|
|
7388
|
+
events,
|
|
7389
|
+
registry,
|
|
7390
|
+
sessionId: () => activeSessionId,
|
|
7391
|
+
onUpdate: () => fleetNotifier.notify()
|
|
7392
|
+
});
|
|
7393
|
+
const register = async (sessionId) => {
|
|
7394
|
+
try {
|
|
7395
|
+
await registry.register({
|
|
7396
|
+
sessionId,
|
|
7397
|
+
projectSlug: paths.projectSlug,
|
|
7398
|
+
projectRoot: paths.projectRoot,
|
|
7399
|
+
projectName: path14.basename(paths.projectRoot),
|
|
7400
|
+
workingDir: opts.workingDir,
|
|
7401
|
+
clientType: "webui",
|
|
7402
|
+
pid: process.pid,
|
|
7403
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7404
|
+
agents: statusTracker.getAgents()
|
|
7405
|
+
});
|
|
7406
|
+
fleetNotifier.notify();
|
|
7407
|
+
} catch (err) {
|
|
7408
|
+
logger.debug?.(`WebUI session registry update failed: ${errorMessage2(err)}`);
|
|
7409
|
+
}
|
|
7410
|
+
};
|
|
7411
|
+
await register(activeSessionId);
|
|
7412
|
+
statusTracker.start();
|
|
7413
|
+
const recoveryLock = opts.manageRecoveryLock === false ? void 0 : new RecoveryLock({ dir: paths.projectSessions, sessionStore: opts.sessionStore });
|
|
7414
|
+
let ownsRecoveryLock = false;
|
|
7415
|
+
if (recoveryLock) {
|
|
7416
|
+
try {
|
|
7417
|
+
await recoveryLock.write(activeSessionId);
|
|
7418
|
+
ownsRecoveryLock = true;
|
|
7419
|
+
} catch {
|
|
7420
|
+
}
|
|
7421
|
+
}
|
|
7422
|
+
let stopHqBridges = () => {
|
|
7423
|
+
};
|
|
7424
|
+
let closeHqPublisher = () => {
|
|
7425
|
+
};
|
|
7426
|
+
let restartHqBridges = (_sessionId) => {
|
|
7427
|
+
};
|
|
7428
|
+
if (opts.enableHqTelemetry !== false) {
|
|
7429
|
+
try {
|
|
7430
|
+
const core = await import("@wrongstack/core");
|
|
7431
|
+
const publisher = core.createHqPublisherFromEnv({
|
|
7432
|
+
clientKind: "webui",
|
|
7433
|
+
projectRoot: paths.projectRoot,
|
|
7434
|
+
projectName: path14.basename(paths.projectRoot),
|
|
7435
|
+
appConfig: opts.config,
|
|
7436
|
+
socketFactory: (url) => new WebSocket2(url)
|
|
7437
|
+
});
|
|
7438
|
+
if (publisher) {
|
|
7439
|
+
publisher.connect();
|
|
7440
|
+
closeHqPublisher = () => publisher.close();
|
|
7441
|
+
restartHqBridges = (sessionId) => {
|
|
7442
|
+
stopHqBridges();
|
|
7443
|
+
const stops = [];
|
|
7444
|
+
const add = (start) => {
|
|
7445
|
+
try {
|
|
7446
|
+
stops.push(start());
|
|
7447
|
+
} catch {
|
|
7448
|
+
}
|
|
7449
|
+
};
|
|
7450
|
+
add(
|
|
7451
|
+
() => core.startSessionTelemetryBridge({
|
|
7452
|
+
publisher,
|
|
7453
|
+
events,
|
|
7454
|
+
sessionId,
|
|
7455
|
+
projectRoot: paths.projectRoot,
|
|
7456
|
+
projectName: path14.basename(paths.projectRoot),
|
|
7457
|
+
globalRoot: paths.globalRoot,
|
|
7458
|
+
initialAgents: statusTracker.getAgents(),
|
|
7459
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7460
|
+
})
|
|
7461
|
+
);
|
|
7462
|
+
add(
|
|
7463
|
+
() => core.startFleetTelemetryBridge({
|
|
7464
|
+
events,
|
|
7465
|
+
publisher,
|
|
7466
|
+
runId: sessionId,
|
|
7467
|
+
sessionId
|
|
7468
|
+
})
|
|
7469
|
+
);
|
|
7470
|
+
add(() => core.startBrainTelemetryBridge({ events, publisher, sessionId }));
|
|
7471
|
+
add(() => core.startWorktreeTelemetryBridge({ events, publisher, sessionId }));
|
|
7472
|
+
add(
|
|
7473
|
+
() => core.startToolTelemetryBridge({
|
|
7474
|
+
events,
|
|
7475
|
+
publisher,
|
|
7476
|
+
projectRoot: paths.projectRoot,
|
|
7477
|
+
sessionId
|
|
7478
|
+
})
|
|
7479
|
+
);
|
|
7480
|
+
add(() => core.startCostTelemetryBridge({ events, publisher, sessionId }));
|
|
7481
|
+
stopHqBridges = () => {
|
|
7482
|
+
for (const stop2 of stops.splice(0)) {
|
|
7483
|
+
try {
|
|
7484
|
+
stop2();
|
|
7485
|
+
} catch {
|
|
7486
|
+
}
|
|
7487
|
+
}
|
|
7488
|
+
};
|
|
7489
|
+
};
|
|
7490
|
+
restartHqBridges(activeSessionId);
|
|
7491
|
+
}
|
|
7492
|
+
} catch (err) {
|
|
7493
|
+
logger.debug?.(`WebUI HQ telemetry unavailable: ${errorMessage2(err)}`);
|
|
7494
|
+
}
|
|
7495
|
+
}
|
|
7496
|
+
const repointRecovery = async (sessionId) => {
|
|
7497
|
+
if (!recoveryLock) return;
|
|
7498
|
+
try {
|
|
7499
|
+
if (ownsRecoveryLock) await recoveryLock.clear();
|
|
7500
|
+
await recoveryLock.write(sessionId);
|
|
7501
|
+
ownsRecoveryLock = true;
|
|
7502
|
+
} catch {
|
|
7503
|
+
ownsRecoveryLock = false;
|
|
7504
|
+
}
|
|
7505
|
+
};
|
|
7506
|
+
const activate = async (sessionId) => {
|
|
7507
|
+
if (stopped || sessionId === activeSessionId) return;
|
|
7508
|
+
transition = transition.then(async () => {
|
|
7509
|
+
if (stopped || sessionId === activeSessionId) return;
|
|
7510
|
+
activeSessionId = sessionId;
|
|
7511
|
+
await register(sessionId);
|
|
7512
|
+
await repointRecovery(sessionId);
|
|
7513
|
+
try {
|
|
7514
|
+
restartHqBridges(sessionId);
|
|
7515
|
+
} catch (err) {
|
|
7516
|
+
logger.debug?.(`WebUI HQ session swap failed: ${errorMessage2(err)}`);
|
|
7517
|
+
}
|
|
7518
|
+
});
|
|
7519
|
+
await transition;
|
|
7520
|
+
};
|
|
7521
|
+
const stop = async () => {
|
|
7522
|
+
if (stopped) return;
|
|
7523
|
+
stopped = true;
|
|
7524
|
+
await transition.catch(() => void 0);
|
|
7525
|
+
statusTracker.stop();
|
|
7526
|
+
stopHqBridges();
|
|
7527
|
+
closeHqPublisher();
|
|
7528
|
+
fleetNotifier.dispose();
|
|
7529
|
+
try {
|
|
7530
|
+
await registry.markClosing();
|
|
7531
|
+
await registry.unregister();
|
|
7532
|
+
} catch {
|
|
7533
|
+
}
|
|
7534
|
+
if (recoveryLock && ownsRecoveryLock) {
|
|
7535
|
+
await recoveryLock.clear().catch(() => void 0);
|
|
7536
|
+
ownsRecoveryLock = false;
|
|
7537
|
+
}
|
|
7538
|
+
};
|
|
7539
|
+
return { statusTracker, activate, stop };
|
|
7540
|
+
}
|
|
7541
|
+
function errorMessage2(err) {
|
|
7542
|
+
return err instanceof Error ? err.message : String(err);
|
|
7543
|
+
}
|
|
7544
|
+
|
|
7026
7545
|
// src/server/pre-context-services.ts
|
|
7027
7546
|
var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
|
|
7028
7547
|
async function createPreContextServices(input) {
|
|
7029
|
-
const { config, wpaths, logger, opts, projectRoot, workingDir, needsProvider } = input;
|
|
7548
|
+
const { config, wpaths, logger, opts, vault, projectRoot, workingDir, needsProvider } = input;
|
|
7030
7549
|
const modelsRegistry = opts.services?.modelsRegistry ?? new DefaultModelsRegistry({
|
|
7031
7550
|
cacheFile: wpaths.modelsCache,
|
|
7032
7551
|
ttlSeconds: 0,
|
|
@@ -7045,7 +7564,7 @@ async function createPreContextServices(input) {
|
|
|
7045
7564
|
await discoverAndMergeWebuiProviders({
|
|
7046
7565
|
config,
|
|
7047
7566
|
registry: modelsRegistry,
|
|
7048
|
-
cacheDir:
|
|
7567
|
+
cacheDir: path15.dirname(wpaths.modelsCache),
|
|
7049
7568
|
logger
|
|
7050
7569
|
});
|
|
7051
7570
|
} catch (err) {
|
|
@@ -7070,12 +7589,13 @@ async function createPreContextServices(input) {
|
|
|
7070
7589
|
})();
|
|
7071
7590
|
const memoryStore = container.resolve(TOKENS.MemoryStore);
|
|
7072
7591
|
if (config.features.memory) {
|
|
7073
|
-
toolRegistry.register(rememberTool(memoryStore));
|
|
7074
|
-
toolRegistry.register(forgetTool(memoryStore));
|
|
7075
|
-
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
7076
|
-
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
7077
7592
|
if (isSuperMemoryService(memoryStore)) {
|
|
7078
7593
|
for (const tool of createSuperMemoryTools(memoryStore)) toolRegistry.register(tool);
|
|
7594
|
+
} else {
|
|
7595
|
+
toolRegistry.register(rememberTool(memoryStore));
|
|
7596
|
+
toolRegistry.register(forgetTool(memoryStore));
|
|
7597
|
+
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
7598
|
+
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
7079
7599
|
}
|
|
7080
7600
|
}
|
|
7081
7601
|
toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
|
|
@@ -7088,10 +7608,23 @@ async function createPreContextServices(input) {
|
|
|
7088
7608
|
configureDangerBypass(config.tools?.exec?.danger ?? {});
|
|
7089
7609
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
7090
7610
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
7091
|
-
const
|
|
7611
|
+
const mcpTokenStore = new MCPVaultTokenStore(
|
|
7612
|
+
path15.join(wpaths.projectDir, "mcp-auth.json"),
|
|
7613
|
+
vault
|
|
7614
|
+
);
|
|
7615
|
+
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
7616
|
+
const mcpRegistry = new MCPRegistry({
|
|
7617
|
+
toolRegistry,
|
|
7618
|
+
events,
|
|
7619
|
+
log: logger,
|
|
7620
|
+
cacheDir: wpaths.cacheDir,
|
|
7621
|
+
authorizationProviderFactory: createVaultBackedMcpAuthorizationProviderFactory({
|
|
7622
|
+
store: mcpTokenStore
|
|
7623
|
+
}),
|
|
7624
|
+
authorizationManager: mcpAuthorizationManager
|
|
7625
|
+
});
|
|
7092
7626
|
if (config.features.mcp && config.mcpServers) {
|
|
7093
7627
|
for (const [name2, cfg] of Object.entries(config.mcpServers)) {
|
|
7094
|
-
if (cfg.enabled === false) continue;
|
|
7095
7628
|
void mcpRegistry.start({ ...cfg, name: name2 }).catch((err) => {
|
|
7096
7629
|
logger.warn(`MCP server "${name2}" failed to start at boot`, err);
|
|
7097
7630
|
});
|
|
@@ -7106,7 +7639,7 @@ async function createPreContextServices(input) {
|
|
|
7106
7639
|
// surface via the SessionRegistry.
|
|
7107
7640
|
isSessionInUse: async (sessionId) => {
|
|
7108
7641
|
try {
|
|
7109
|
-
const registry =
|
|
7642
|
+
const registry = getSessionRegistry2(wpaths.globalRoot);
|
|
7110
7643
|
const live = await registry.listByProject(wpaths.projectSlug);
|
|
7111
7644
|
const hit = live.find((e) => e.sessionId === sessionId);
|
|
7112
7645
|
if (hit) {
|
|
@@ -7131,155 +7664,57 @@ async function createPreContextServices(input) {
|
|
|
7131
7664
|
await input.touchProject(projectRoot, workingDir);
|
|
7132
7665
|
} catch {
|
|
7133
7666
|
}
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7667
|
+
const sessionIdentity = await createStandaloneSessionIdentityLifecycle({
|
|
7668
|
+
config,
|
|
7669
|
+
events,
|
|
7670
|
+
logger,
|
|
7671
|
+
paths: {
|
|
7672
|
+
globalRoot: wpaths.globalRoot,
|
|
7140
7673
|
projectRoot,
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
projectRoot,
|
|
7183
|
-
projectName: path14.basename(projectRoot),
|
|
7184
|
-
globalRoot: wpaths.globalRoot,
|
|
7185
|
-
initialAgents: statusTracker?.getAgents(),
|
|
7186
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7187
|
-
});
|
|
7188
|
-
try {
|
|
7189
|
-
stopHqAuxBridges.push(
|
|
7190
|
-
startFleetTelemetryBridge({ events, publisher: hqTelemetry, runId: session.id, sessionId: session.id })
|
|
7191
|
-
);
|
|
7192
|
-
} catch {
|
|
7193
|
-
}
|
|
7194
|
-
try {
|
|
7195
|
-
stopHqAuxBridges.push(
|
|
7196
|
-
startBrainTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
|
|
7197
|
-
);
|
|
7198
|
-
} catch {
|
|
7199
|
-
}
|
|
7200
|
-
try {
|
|
7201
|
-
stopHqAuxBridges.push(
|
|
7202
|
-
startWorktreeTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
|
|
7203
|
-
);
|
|
7204
|
-
} catch {
|
|
7205
|
-
}
|
|
7206
|
-
try {
|
|
7207
|
-
stopHqAuxBridges.push(
|
|
7208
|
-
startToolTelemetryBridge({ events, publisher: hqTelemetry, projectRoot, sessionId: session.id })
|
|
7209
|
-
);
|
|
7210
|
-
} catch {
|
|
7211
|
-
}
|
|
7212
|
-
try {
|
|
7213
|
-
stopHqAuxBridges.push(
|
|
7214
|
-
startCostTelemetryBridge({ events, publisher: hqTelemetry, sessionId: session.id })
|
|
7215
|
-
);
|
|
7216
|
-
} catch {
|
|
7217
|
-
}
|
|
7218
|
-
}
|
|
7219
|
-
} catch {
|
|
7220
|
-
}
|
|
7221
|
-
const stopTracking = async () => {
|
|
7222
|
-
try {
|
|
7223
|
-
fleetNotifier.dispose();
|
|
7224
|
-
await registry.markClosing();
|
|
7225
|
-
statusTracker?.stop();
|
|
7226
|
-
stopHqSessionBridge?.();
|
|
7227
|
-
for (const stop of stopHqAuxBridges) {
|
|
7228
|
-
try {
|
|
7229
|
-
stop();
|
|
7230
|
-
} catch {
|
|
7231
|
-
}
|
|
7232
|
-
}
|
|
7233
|
-
hqTelemetryPublisher?.close();
|
|
7234
|
-
} catch {
|
|
7235
|
-
}
|
|
7236
|
-
};
|
|
7237
|
-
process.once("beforeExit", () => {
|
|
7238
|
-
void stopTracking();
|
|
7239
|
-
});
|
|
7240
|
-
process.once("SIGINT", () => {
|
|
7241
|
-
void stopTracking();
|
|
7242
|
-
});
|
|
7243
|
-
process.once("SIGTERM", () => {
|
|
7244
|
-
void stopTracking();
|
|
7245
|
-
});
|
|
7246
|
-
} catch {
|
|
7247
|
-
}
|
|
7248
|
-
let context;
|
|
7249
|
-
const tokenCounter = new DefaultTokenCounter({
|
|
7250
|
-
registry: modelsRegistry,
|
|
7251
|
-
providerId: config.provider,
|
|
7252
|
-
events,
|
|
7253
|
-
sessionId: () => context?.session?.id ?? session.id
|
|
7254
|
-
});
|
|
7255
|
-
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
7256
|
-
const activeMode = await modeStore.getActiveMode();
|
|
7257
|
-
const modeId = activeMode?.id ?? "default";
|
|
7258
|
-
const modePrompt = activeMode?.prompt ?? "";
|
|
7259
|
-
const customModeStore = createCustomModeStore(wpaths.configDir);
|
|
7260
|
-
await customModeStore.load();
|
|
7261
|
-
console.log("[WebUI] Custom context modes loaded:", customModeStore.list().filter((m) => m.custom).length, "custom");
|
|
7262
|
-
const resolvedModel = await resolveProviderModelMetadata(
|
|
7263
|
-
modelsRegistry,
|
|
7264
|
-
config.provider,
|
|
7265
|
-
config.model,
|
|
7266
|
-
config.providers?.[config.provider]
|
|
7267
|
-
);
|
|
7268
|
-
const modelCapabilities = resolvedModel?.capabilities ? { maxContextTokens: resolvedModel.capabilities.maxContext, supportsTools: resolvedModel.capabilities.tools, supportsVision: resolvedModel.capabilities.vision, supportsReasoning: resolvedModel.capabilities.reasoning } : void 0;
|
|
7269
|
-
const modelCapabilitiesRef = { current: modelCapabilities };
|
|
7270
|
-
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
7271
|
-
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
7272
|
-
manifestPath: path14.join(wpaths.globalRoot, "installed-skills.json"),
|
|
7273
|
-
projectSkillsDir: wpaths.inProjectSkills,
|
|
7274
|
-
globalSkillsDir: wpaths.globalSkills,
|
|
7275
|
-
projectHash: wpaths.projectHash,
|
|
7276
|
-
skillLoader
|
|
7277
|
-
}) : void 0;
|
|
7278
|
-
const promptsEnabled = config.features.prompts !== false;
|
|
7279
|
-
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
7674
|
+
projectSlug: wpaths.projectSlug,
|
|
7675
|
+
projectSessions: wpaths.projectSessions
|
|
7676
|
+
},
|
|
7677
|
+
workingDir,
|
|
7678
|
+
initialSessionId: session.id,
|
|
7679
|
+
sessionStore,
|
|
7680
|
+
manageRecoveryLock: !opts.services?.session
|
|
7681
|
+
});
|
|
7682
|
+
const statusTracker = sessionIdentity.statusTracker;
|
|
7683
|
+
let context;
|
|
7684
|
+
const tokenCounter = new DefaultTokenCounter({
|
|
7685
|
+
registry: modelsRegistry,
|
|
7686
|
+
providerId: config.provider,
|
|
7687
|
+
events,
|
|
7688
|
+
sessionId: () => context?.session?.id ?? session.id
|
|
7689
|
+
});
|
|
7690
|
+
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
7691
|
+
const activeMode = await modeStore.getActiveMode();
|
|
7692
|
+
const modeId = activeMode?.id ?? "default";
|
|
7693
|
+
const modePrompt = activeMode?.prompt ?? "";
|
|
7694
|
+
const customModeStore = createCustomModeStore(wpaths.configDir);
|
|
7695
|
+
await customModeStore.load();
|
|
7696
|
+
console.log("[WebUI] Custom context modes loaded:", customModeStore.list().filter((m) => m.custom).length, "custom");
|
|
7697
|
+
const resolvedModel = await resolveProviderModelMetadata(
|
|
7698
|
+
modelsRegistry,
|
|
7699
|
+
config.provider,
|
|
7700
|
+
config.model,
|
|
7701
|
+
config.providers?.[config.provider]
|
|
7702
|
+
);
|
|
7703
|
+
const modelCapabilities = resolvedModel?.capabilities ? { maxContextTokens: resolvedModel.capabilities.maxContext, supportsTools: resolvedModel.capabilities.tools, supportsVision: resolvedModel.capabilities.vision, supportsReasoning: resolvedModel.capabilities.reasoning } : void 0;
|
|
7704
|
+
const modelCapabilitiesRef = { current: modelCapabilities };
|
|
7705
|
+
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
7706
|
+
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
7707
|
+
manifestPath: path15.join(wpaths.globalRoot, "installed-skills.json"),
|
|
7708
|
+
projectSkillsDir: wpaths.inProjectSkills,
|
|
7709
|
+
globalSkillsDir: wpaths.globalSkills,
|
|
7710
|
+
projectHash: wpaths.projectHash,
|
|
7711
|
+
skillLoader
|
|
7712
|
+
}) : void 0;
|
|
7713
|
+
const promptsEnabled = config.features.prompts !== false;
|
|
7714
|
+
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
7280
7715
|
try {
|
|
7281
7716
|
const req = createRequire2(import.meta.url);
|
|
7282
|
-
return
|
|
7717
|
+
return path15.join(path15.dirname(req.resolve("@wrongstack/core/package.json")), "data", "prompts");
|
|
7283
7718
|
} catch {
|
|
7284
7719
|
return void 0;
|
|
7285
7720
|
}
|
|
@@ -7289,6 +7724,9 @@ async function createPreContextServices(input) {
|
|
|
7289
7724
|
const promptsCtx = { promptLoader, promptUsage };
|
|
7290
7725
|
const systemPromptBuilder = new DefaultSystemPromptBuilder({
|
|
7291
7726
|
memoryStore,
|
|
7727
|
+
// Super Memory's turn middleware is the single memory-injection channel;
|
|
7728
|
+
// don't also inject a static memory section here (avoids double injection).
|
|
7729
|
+
injectMemory: false,
|
|
7292
7730
|
skillLoader,
|
|
7293
7731
|
modeStore,
|
|
7294
7732
|
modeId,
|
|
@@ -7331,6 +7769,16 @@ async function createPreContextServices(input) {
|
|
|
7331
7769
|
const initialContextPolicy = resolveContextWindowPolicy(config.context);
|
|
7332
7770
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
7333
7771
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
7772
|
+
context.state.setMeta(
|
|
7773
|
+
"plan.path",
|
|
7774
|
+
sessionScopedPath(wpaths.projectSessions, session.id, ".plan.json")
|
|
7775
|
+
);
|
|
7776
|
+
context.state.setMeta(
|
|
7777
|
+
"task.path",
|
|
7778
|
+
sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
|
|
7779
|
+
);
|
|
7780
|
+
await hydrateSessionKanban(context);
|
|
7781
|
+
attachSessionKanbanMirror(context);
|
|
7334
7782
|
seedContextMeta(config, context);
|
|
7335
7783
|
return {
|
|
7336
7784
|
modelsRegistry,
|
|
@@ -7347,6 +7795,7 @@ async function createPreContextServices(input) {
|
|
|
7347
7795
|
session,
|
|
7348
7796
|
sessionStartedAt,
|
|
7349
7797
|
statusTracker,
|
|
7798
|
+
sessionIdentity,
|
|
7350
7799
|
tokenCounter,
|
|
7351
7800
|
modeStore,
|
|
7352
7801
|
modeId,
|
|
@@ -7396,6 +7845,7 @@ function patchConfig(config, updates) {
|
|
|
7396
7845
|
}
|
|
7397
7846
|
|
|
7398
7847
|
// src/server/backend-services.ts
|
|
7848
|
+
import { join as join12 } from "node:path";
|
|
7399
7849
|
import {
|
|
7400
7850
|
Agent,
|
|
7401
7851
|
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
@@ -7409,9 +7859,10 @@ import {
|
|
|
7409
7859
|
installDesignStudioMiddleware,
|
|
7410
7860
|
ObservableBrainArbiter as ObservableBrainArbiterCtor,
|
|
7411
7861
|
resolveContextWindowPolicy as resolveContextWindowPolicy2,
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7862
|
+
BrainDecisionLedger,
|
|
7863
|
+
createBrainRuntime,
|
|
7864
|
+
resolveBrainConfigDefaults,
|
|
7865
|
+
EscalationRoutingBrainArbiter,
|
|
7415
7866
|
GlobalMailbox as GlobalMailbox2,
|
|
7416
7867
|
mailboxSessionTag,
|
|
7417
7868
|
SessionMemoryConsolidator,
|
|
@@ -7425,12 +7876,13 @@ import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
|
|
|
7425
7876
|
var REPLAY_LIMIT = 50;
|
|
7426
7877
|
var PAUSE_TIMEOUT_MS = 6e4;
|
|
7427
7878
|
var CollaborationWebSocketHandler = class {
|
|
7428
|
-
constructor(events, logger, reader, annotations, bus) {
|
|
7879
|
+
constructor(events, logger, reader, annotations, bus, options = {}) {
|
|
7429
7880
|
this.events = events;
|
|
7430
7881
|
this.logger = logger;
|
|
7431
7882
|
this.reader = reader;
|
|
7432
7883
|
this.annotations = annotations;
|
|
7433
7884
|
this.bus = bus;
|
|
7885
|
+
this.options = options;
|
|
7434
7886
|
this.subscribe();
|
|
7435
7887
|
this.bus?.onInjectionConsumed((info) => this.broadcastInjectionConsumed(info));
|
|
7436
7888
|
}
|
|
@@ -7439,6 +7891,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
7439
7891
|
reader;
|
|
7440
7892
|
annotations;
|
|
7441
7893
|
bus;
|
|
7894
|
+
options;
|
|
7442
7895
|
clients = /* @__PURE__ */ new Set();
|
|
7443
7896
|
/** sessionId → participants currently watching it. */
|
|
7444
7897
|
bySession = /* @__PURE__ */ new Map();
|
|
@@ -7466,11 +7919,16 @@ var CollaborationWebSocketHandler = class {
|
|
|
7466
7919
|
handleMessage(ws, msg) {
|
|
7467
7920
|
if (msg.type === "collab.join") {
|
|
7468
7921
|
const payload = msg.payload;
|
|
7469
|
-
if (
|
|
7922
|
+
if (typeof payload?.sessionId !== "string" || payload.sessionId.trim().length === 0) {
|
|
7470
7923
|
this.send(ws, this.errorMessage("collab.join requires sessionId"));
|
|
7471
7924
|
return true;
|
|
7472
7925
|
}
|
|
7473
|
-
|
|
7926
|
+
const role = payload.role ?? "observer";
|
|
7927
|
+
if (role !== "observer" && role !== "annotator" && role !== "controller") {
|
|
7928
|
+
this.send(ws, this.errorMessage(`unknown collaboration role '${String(role)}'`));
|
|
7929
|
+
return true;
|
|
7930
|
+
}
|
|
7931
|
+
this.join(ws, payload.sessionId, role);
|
|
7474
7932
|
return true;
|
|
7475
7933
|
}
|
|
7476
7934
|
if (msg.type === "collab.leave") {
|
|
@@ -7505,6 +7963,20 @@ var CollaborationWebSocketHandler = class {
|
|
|
7505
7963
|
}
|
|
7506
7964
|
// ── Join / leave flow ──────────────────────────────────────────────────
|
|
7507
7965
|
join(ws, sessionId, role) {
|
|
7966
|
+
const activeSessionId = this.options.getActiveSessionId?.();
|
|
7967
|
+
if (activeSessionId !== void 0 && sessionId !== activeSessionId) {
|
|
7968
|
+
this.send(
|
|
7969
|
+
ws,
|
|
7970
|
+
this.errorMessage(
|
|
7971
|
+
`collab.join sessionId mismatch (active: ${activeSessionId})`
|
|
7972
|
+
)
|
|
7973
|
+
);
|
|
7974
|
+
return;
|
|
7975
|
+
}
|
|
7976
|
+
if (this.findParticipant(ws)) {
|
|
7977
|
+
this.send(ws, this.errorMessage("collab.join requires leaving the current session first"));
|
|
7978
|
+
return;
|
|
7979
|
+
}
|
|
7508
7980
|
if (role === "controller" && !this.bus) {
|
|
7509
7981
|
this.send(
|
|
7510
7982
|
ws,
|
|
@@ -7523,6 +7995,13 @@ var CollaborationWebSocketHandler = class {
|
|
|
7523
7995
|
);
|
|
7524
7996
|
return;
|
|
7525
7997
|
}
|
|
7998
|
+
if (role !== "observer" && this.options.authorizeRole?.({ ws, sessionId, requestedRole: role }) !== true) {
|
|
7999
|
+
this.send(
|
|
8000
|
+
ws,
|
|
8001
|
+
this.errorMessage(`role '${role}' requires explicit server authorization`)
|
|
8002
|
+
);
|
|
8003
|
+
return;
|
|
8004
|
+
}
|
|
7526
8005
|
const participant = {
|
|
7527
8006
|
participantId: randomUUID(),
|
|
7528
8007
|
ws,
|
|
@@ -7776,17 +8255,13 @@ var CollaborationWebSocketHandler = class {
|
|
|
7776
8255
|
type: "collab.event",
|
|
7777
8256
|
payload: { kind, payload, at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
7778
8257
|
};
|
|
7779
|
-
const
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
`collab broadcast failed: ${toErrorMessage6(err)}`
|
|
7787
|
-
);
|
|
7788
|
-
}
|
|
7789
|
-
}
|
|
8258
|
+
const activeSessionId = this.options.getActiveSessionId?.();
|
|
8259
|
+
if (activeSessionId !== void 0) {
|
|
8260
|
+
this.broadcast(activeSessionId, msg);
|
|
8261
|
+
return;
|
|
8262
|
+
}
|
|
8263
|
+
for (const sessionId of this.bySession.keys()) {
|
|
8264
|
+
this.broadcast(sessionId, msg);
|
|
7790
8265
|
}
|
|
7791
8266
|
}
|
|
7792
8267
|
/**
|
|
@@ -8110,7 +8585,8 @@ var CollaborationWebSocketHandler = class {
|
|
|
8110
8585
|
* Bus callback: a queued injection was spliced into a real tool call. Re-emit
|
|
8111
8586
|
* `collab.injection.granted` with phase `'consumed'` and the now-known tool
|
|
8112
8587
|
* name. The injection carries no sessionId, so resolve it from the author's
|
|
8113
|
-
* current
|
|
8588
|
+
* current participant. If the author has left, fail closed rather than leak
|
|
8589
|
+
* the payload to unrelated sessions.
|
|
8114
8590
|
*/
|
|
8115
8591
|
broadcastInjectionConsumed(info) {
|
|
8116
8592
|
let sessionId = null;
|
|
@@ -8139,14 +8615,16 @@ var CollaborationWebSocketHandler = class {
|
|
|
8139
8615
|
if (sessionId) {
|
|
8140
8616
|
this.broadcast(sessionId, message(sessionId));
|
|
8141
8617
|
} else {
|
|
8142
|
-
|
|
8618
|
+
this.logger.debug?.(
|
|
8619
|
+
`collab: consumed injection ${info.toolUseId} has no live author; broadcast suppressed`
|
|
8620
|
+
);
|
|
8143
8621
|
}
|
|
8144
8622
|
}
|
|
8145
8623
|
};
|
|
8146
8624
|
|
|
8147
8625
|
// src/server/codebase-indexing.ts
|
|
8148
8626
|
import * as fs12 from "node:fs";
|
|
8149
|
-
import * as
|
|
8627
|
+
import * as path16 from "node:path";
|
|
8150
8628
|
import {
|
|
8151
8629
|
cancelPendingReindexes,
|
|
8152
8630
|
enqueueReindex,
|
|
@@ -8199,7 +8677,7 @@ function setupWebUICodebaseIndexing(deps2) {
|
|
|
8199
8677
|
if (!filename) return;
|
|
8200
8678
|
const rel = filename.toString();
|
|
8201
8679
|
if (isIgnored(rel)) return;
|
|
8202
|
-
const abs =
|
|
8680
|
+
const abs = path16.resolve(deps2.projectRoot, rel);
|
|
8203
8681
|
enqueueFile(abs);
|
|
8204
8682
|
});
|
|
8205
8683
|
watcher.on("error", (err) => deps2.logger.debug(`webui codebase index watcher error: ${err}`));
|
|
@@ -8212,7 +8690,7 @@ function setupWebUICodebaseIndexing(deps2) {
|
|
|
8212
8690
|
}
|
|
8213
8691
|
function enqueueFile(filePath) {
|
|
8214
8692
|
if (!idx.onEdit && !idx.watchExternal) return;
|
|
8215
|
-
const abs =
|
|
8693
|
+
const abs = path16.isAbsolute(filePath) ? path16.normalize(filePath) : path16.resolve(deps2.projectRoot, filePath);
|
|
8216
8694
|
if (!isInside2(deps2.projectRoot, abs) || !isIndexableFile(abs)) return;
|
|
8217
8695
|
enqueueReindex({
|
|
8218
8696
|
projectRoot: deps2.projectRoot,
|
|
@@ -8249,9 +8727,9 @@ function isIgnored(rel) {
|
|
|
8249
8727
|
return rel.split(/[/\\]/).some((seg) => IGNORE_DIRS.has(seg));
|
|
8250
8728
|
}
|
|
8251
8729
|
function isInside2(root, target) {
|
|
8252
|
-
const normalizedRoot =
|
|
8253
|
-
const normalizedTarget =
|
|
8254
|
-
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot +
|
|
8730
|
+
const normalizedRoot = path16.resolve(root);
|
|
8731
|
+
const normalizedTarget = path16.resolve(target);
|
|
8732
|
+
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(normalizedRoot + path16.sep);
|
|
8255
8733
|
}
|
|
8256
8734
|
|
|
8257
8735
|
// src/server/discover-mailbox-bridge.ts
|
|
@@ -8784,21 +9262,59 @@ async function createAgentServices(input) {
|
|
|
8784
9262
|
agent.extensions.register(new SessionMemoryConsolidator({ memoryStore }));
|
|
8785
9263
|
}
|
|
8786
9264
|
console.log("[WebUI] Agent initialized");
|
|
8787
|
-
const
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
9265
|
+
const brainCfg = resolveBrainConfigDefaults(config.brain, {
|
|
9266
|
+
fallbackModels: config.fallbackModels
|
|
9267
|
+
});
|
|
9268
|
+
const brainLedgerPath = join12(wpaths.projectDir, "brain-ledger.jsonl");
|
|
9269
|
+
let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
|
|
9270
|
+
let brainLedger;
|
|
9271
|
+
const startBrainLedger = () => {
|
|
9272
|
+
if (brainLedger) return;
|
|
9273
|
+
brainLedger = new BrainDecisionLedger({ events, filePath: brainLedgerPath });
|
|
9274
|
+
void brainLedger.start();
|
|
9275
|
+
};
|
|
9276
|
+
if (brainLedgerEnabled) startBrainLedger();
|
|
9277
|
+
const brainRuntime = createBrainRuntime({
|
|
9278
|
+
initialConfig: brainCfg,
|
|
9279
|
+
defaultProviderId: config.provider,
|
|
9280
|
+
sessionProvider: () => provider,
|
|
9281
|
+
sessionModel: () => context.model,
|
|
9282
|
+
resolveProvider: (providerId) => {
|
|
9283
|
+
const savedCfg = config.providers?.[providerId] ?? {};
|
|
9284
|
+
return providerRegistry.create({
|
|
9285
|
+
...savedCfg,
|
|
9286
|
+
apiKey: savedCfg.apiKey ?? config.apiKey,
|
|
9287
|
+
baseUrl: savedCfg.baseUrl ?? config.baseUrl,
|
|
9288
|
+
type: providerId
|
|
9289
|
+
});
|
|
9290
|
+
},
|
|
9291
|
+
ledger: {
|
|
9292
|
+
getPath: () => brainLedgerEnabled ? brainLedgerPath : void 0,
|
|
9293
|
+
isEnabled: () => brainLedgerEnabled,
|
|
9294
|
+
setEnabled: (on) => {
|
|
9295
|
+
brainLedgerEnabled = on;
|
|
9296
|
+
if (on) {
|
|
9297
|
+
startBrainLedger();
|
|
9298
|
+
} else {
|
|
9299
|
+
void brainLedger?.stop();
|
|
9300
|
+
brainLedger = void 0;
|
|
9301
|
+
}
|
|
9302
|
+
},
|
|
9303
|
+
failureStreakFor: (request) => brainLedger?.failureStreakFor(request) ?? 0,
|
|
9304
|
+
getDecisionDigest: (request) => brainLedger?.digestFor(request)
|
|
9305
|
+
},
|
|
9306
|
+
persist: input.persistBrainConfig
|
|
9307
|
+
});
|
|
9308
|
+
const brainSettings = {
|
|
9309
|
+
get maxAutoRisk() {
|
|
9310
|
+
return brainRuntime.getMaxAutoRisk();
|
|
9311
|
+
},
|
|
9312
|
+
set maxAutoRisk(level) {
|
|
9313
|
+
void brainRuntime.apply({ maxAutoRisk: level }).persisted;
|
|
9314
|
+
}
|
|
8795
9315
|
};
|
|
8796
9316
|
const brain = new ObservableBrainArbiterCtor(
|
|
8797
|
-
|
|
8798
|
-
policy: new DefaultBrainArbiter(),
|
|
8799
|
-
autonomous: autonomousBrain,
|
|
8800
|
-
getMaxAutoRisk: () => brainSettings.maxAutoRisk
|
|
8801
|
-
}),
|
|
9317
|
+
new EscalationRoutingBrainArbiter(brainRuntime.arbiter, void 0, () => "headless"),
|
|
8802
9318
|
events
|
|
8803
9319
|
);
|
|
8804
9320
|
container.bind(TOKENS2.BrainArbiter, () => brain);
|
|
@@ -8838,6 +9354,12 @@ async function createAgentServices(input) {
|
|
|
8838
9354
|
const brainMonitor = new BrainMonitor({
|
|
8839
9355
|
events,
|
|
8840
9356
|
brain,
|
|
9357
|
+
toolFailureStreak: brainCfg.monitor?.toolFailureStreak,
|
|
9358
|
+
errorStormCount: brainCfg.monitor?.errorStormCount,
|
|
9359
|
+
stallMs: brainCfg.monitor?.stallMs,
|
|
9360
|
+
fileChurnThreshold: brainCfg.monitor?.fileChurnThreshold,
|
|
9361
|
+
fileChurnWindowMs: brainCfg.monitor?.fileChurnWindowMs,
|
|
9362
|
+
cooldownMs: brainCfg.monitor?.cooldownMs,
|
|
8841
9363
|
sessionId: () => context.session?.id,
|
|
8842
9364
|
intervene: async ({ subject, body }) => {
|
|
8843
9365
|
const tag = mailboxSessionTag(input.sessionGetter().id);
|
|
@@ -8902,7 +9424,10 @@ async function createAgentServices(input) {
|
|
|
8902
9424
|
logger,
|
|
8903
9425
|
input.sessionReader,
|
|
8904
9426
|
input.annotationsStore,
|
|
8905
|
-
collabBus
|
|
9427
|
+
collabBus,
|
|
9428
|
+
{
|
|
9429
|
+
getActiveSessionId: () => context.session.id
|
|
9430
|
+
}
|
|
8906
9431
|
);
|
|
8907
9432
|
return {
|
|
8908
9433
|
collabBus,
|
|
@@ -8914,8 +9439,13 @@ async function createAgentServices(input) {
|
|
|
8914
9439
|
pipelines,
|
|
8915
9440
|
brain,
|
|
8916
9441
|
brainSettings,
|
|
9442
|
+
brainRuntime,
|
|
8917
9443
|
brainLog,
|
|
8918
9444
|
brainMonitor,
|
|
9445
|
+
// Getter: ledger toggles swap the instance, shutdown must stop the LIVE one.
|
|
9446
|
+
get brainLedger() {
|
|
9447
|
+
return brainLedger;
|
|
9448
|
+
},
|
|
8919
9449
|
codebaseIndexing,
|
|
8920
9450
|
autoPhaseHandler,
|
|
8921
9451
|
specsHandler,
|
|
@@ -9091,7 +9621,18 @@ function createConnectionHandler(opts) {
|
|
|
9091
9621
|
}
|
|
9092
9622
|
|
|
9093
9623
|
// src/server/message-dispatcher.ts
|
|
9094
|
-
import
|
|
9624
|
+
import path17 from "node:path";
|
|
9625
|
+
import {
|
|
9626
|
+
buildUserContentBlocks,
|
|
9627
|
+
IncomingImageError,
|
|
9628
|
+
parseIncomingImages
|
|
9629
|
+
} from "@wrongstack/core/utils";
|
|
9630
|
+
import {
|
|
9631
|
+
createToolVisionAdapters,
|
|
9632
|
+
ImageInputUnsupportedError,
|
|
9633
|
+
routeImagesForModel,
|
|
9634
|
+
VisionUrlBlockedError
|
|
9635
|
+
} from "@wrongstack/runtime/vision";
|
|
9095
9636
|
|
|
9096
9637
|
// src/server/autophase-routes.ts
|
|
9097
9638
|
async function handleAutoPhaseRoute(_ws, msg, handlers) {
|
|
@@ -9112,6 +9653,12 @@ async function handleBrainRoute(ws, msg, handlers) {
|
|
|
9112
9653
|
case "brain.ask":
|
|
9113
9654
|
await handlers.ask(ws, msg);
|
|
9114
9655
|
return true;
|
|
9656
|
+
case "brain.config.get":
|
|
9657
|
+
await handlers.configGet(ws, msg);
|
|
9658
|
+
return true;
|
|
9659
|
+
case "brain.config.set":
|
|
9660
|
+
await handlers.configSet(ws, msg);
|
|
9661
|
+
return true;
|
|
9115
9662
|
default:
|
|
9116
9663
|
return false;
|
|
9117
9664
|
}
|
|
@@ -9365,8 +9912,10 @@ import {
|
|
|
9365
9912
|
serializeTaskGraph
|
|
9366
9913
|
} from "@wrongstack/core";
|
|
9367
9914
|
import {
|
|
9915
|
+
addCheckToTask,
|
|
9368
9916
|
addColumn,
|
|
9369
9917
|
addGoalMetricToTask,
|
|
9918
|
+
addNoteToTask,
|
|
9370
9919
|
addTask,
|
|
9371
9920
|
assignTask,
|
|
9372
9921
|
claimReadyTask,
|
|
@@ -9380,29 +9929,47 @@ import {
|
|
|
9380
9929
|
getKanbanQueueHealth,
|
|
9381
9930
|
getTask,
|
|
9382
9931
|
getTaskChain,
|
|
9383
|
-
listReadyTasks,
|
|
9384
9932
|
listBoards,
|
|
9933
|
+
listReadyTasks,
|
|
9385
9934
|
mergeTasks,
|
|
9386
9935
|
moveTask,
|
|
9387
9936
|
parseLinesIntoTasks,
|
|
9937
|
+
reconcileKanbanBoard,
|
|
9938
|
+
recoverStaleTaskAssignments,
|
|
9939
|
+
releaseTaskClaim,
|
|
9388
9940
|
removeBoard,
|
|
9389
9941
|
removeColumn,
|
|
9390
9942
|
removeTask,
|
|
9391
|
-
releaseTaskClaim,
|
|
9392
9943
|
setTaskChain,
|
|
9393
9944
|
splitTask,
|
|
9394
9945
|
syncBoardFromTaskGraph,
|
|
9395
9946
|
transferTaskToBoard,
|
|
9396
9947
|
updateBoard,
|
|
9948
|
+
updateCheckOnTask,
|
|
9397
9949
|
updateGoalMetricOnTask,
|
|
9398
9950
|
updateTask
|
|
9399
9951
|
} from "@wrongstack/kanban";
|
|
9952
|
+
import { applySessionKanbanTaskToSource } from "@wrongstack/tools/session-kanban";
|
|
9953
|
+
async function syncSessionSource(ctx, task, remove = false) {
|
|
9954
|
+
if (!ctx.context) return;
|
|
9955
|
+
const update = await applySessionKanbanTaskToSource(ctx.context, task, { remove });
|
|
9956
|
+
const sessionId = ctx.context.session?.id ?? "";
|
|
9957
|
+
if (update.todos)
|
|
9958
|
+
ctx.broadcast?.({ type: "todos.updated", payload: { sessionId, todos: update.todos } });
|
|
9959
|
+
if (update.tasks)
|
|
9960
|
+
ctx.broadcast?.({ type: "tasks.updated", payload: { sessionId, tasks: update.tasks.tasks } });
|
|
9961
|
+
if (update.plan)
|
|
9962
|
+
ctx.broadcast?.({ type: "plan.updated", payload: { sessionId, plan: update.plan } });
|
|
9963
|
+
}
|
|
9400
9964
|
function ok(ws, type, data) {
|
|
9401
9965
|
send(ws, { type, payload: { success: true, data: data ?? null } });
|
|
9402
9966
|
}
|
|
9403
9967
|
function fail(ws, type, message) {
|
|
9404
9968
|
send(ws, { type, payload: { success: false, error: message } });
|
|
9405
9969
|
}
|
|
9970
|
+
function has(payload, key) {
|
|
9971
|
+
return payload !== void 0 && Object.hasOwn(payload, key);
|
|
9972
|
+
}
|
|
9406
9973
|
async function handleKanbanRoute(ws, msg, ctx) {
|
|
9407
9974
|
if (!msg.type.startsWith("kanban.")) return false;
|
|
9408
9975
|
const payload = msg.payload;
|
|
@@ -9431,6 +9998,38 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9431
9998
|
ok(ws, type, await getKanbanQueueHealth(ctx.projectRoot, { boardId: hBoardId }));
|
|
9432
9999
|
return true;
|
|
9433
10000
|
}
|
|
10001
|
+
case "kanban.supervisor.status":
|
|
10002
|
+
case "kanban.supervisor.audit": {
|
|
10003
|
+
const boardId = payload?.boardId;
|
|
10004
|
+
if (!boardId) {
|
|
10005
|
+
fail(ws, type, "boardId required");
|
|
10006
|
+
return true;
|
|
10007
|
+
}
|
|
10008
|
+
const board = await getBoard(ctx.projectRoot, boardId);
|
|
10009
|
+
if (!board) {
|
|
10010
|
+
fail(ws, type, `Board not found: ${boardId}`);
|
|
10011
|
+
return true;
|
|
10012
|
+
}
|
|
10013
|
+
const reconciled = await reconcileKanbanBoard(ctx.projectRoot, boardId);
|
|
10014
|
+
let health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
10015
|
+
const recovered = health.staleAssignments.count ? await recoverStaleTaskAssignments(ctx.projectRoot, boardId, {
|
|
10016
|
+
mode: board.supervisor?.recoveryMode ?? "auto",
|
|
10017
|
+
reason: "On-demand standalone Kanban supervisor audit."
|
|
10018
|
+
}) : null;
|
|
10019
|
+
if (recovered) health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
10020
|
+
const anomalyCount = health.staleAssignments.count + health.dependencyBlocked.count + health.failedRetryable.count + health.counts.failed + health.counts.blocked;
|
|
10021
|
+
ok(ws, type, {
|
|
10022
|
+
boardId,
|
|
10023
|
+
status: board.supervisor?.enabled === false ? "disabled" : anomalyCount ? "attention" : "healthy",
|
|
10024
|
+
mode: board.supervisor?.mode ?? "deterministic",
|
|
10025
|
+
lastAuditAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10026
|
+
reconciledTaskIds: reconciled?.tasks.map((task) => task.id) ?? [],
|
|
10027
|
+
staleRecoveredTaskIds: recovered?.tasks.map((task) => task.id) ?? [],
|
|
10028
|
+
anomalyCount,
|
|
10029
|
+
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`
|
|
10030
|
+
});
|
|
10031
|
+
return true;
|
|
10032
|
+
}
|
|
9434
10033
|
case "kanban.create": {
|
|
9435
10034
|
const title = payload?.title;
|
|
9436
10035
|
if (!title) {
|
|
@@ -9459,7 +10058,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9459
10058
|
...payload?.title ? { title: payload.title } : {},
|
|
9460
10059
|
...payload?.description ? { description: payload.description } : {},
|
|
9461
10060
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
9462
|
-
...payload?.columns ? { columns: payload.columns } : {}
|
|
10061
|
+
...payload?.columns ? { columns: payload.columns } : {},
|
|
10062
|
+
...has(payload, "supervisor") ? {
|
|
10063
|
+
supervisor: payload?.supervisor ?? null
|
|
10064
|
+
} : {}
|
|
9463
10065
|
});
|
|
9464
10066
|
board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
|
|
9465
10067
|
return true;
|
|
@@ -9486,6 +10088,11 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9486
10088
|
return true;
|
|
9487
10089
|
}
|
|
9488
10090
|
const board = await getBoard(ctx.projectRoot, boardId);
|
|
10091
|
+
const activeSessionId = ctx.context?.session?.id;
|
|
10092
|
+
if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
|
|
10093
|
+
fail(ws, type, "The active session Kanban board cannot be deleted.");
|
|
10094
|
+
return true;
|
|
10095
|
+
}
|
|
9489
10096
|
ok(ws, type, {
|
|
9490
10097
|
removed: board ? await removeBoard(ctx.projectRoot, board.id) : false,
|
|
9491
10098
|
boardId: board?.id ?? boardId
|
|
@@ -9666,15 +10273,31 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9666
10273
|
return true;
|
|
9667
10274
|
}
|
|
9668
10275
|
const board = await updateTask(ctx.projectRoot, boardId, taskId, {
|
|
9669
|
-
...payload
|
|
9670
|
-
...payload
|
|
9671
|
-
...payload
|
|
9672
|
-
...payload
|
|
9673
|
-
...payload
|
|
9674
|
-
...payload
|
|
9675
|
-
...payload
|
|
10276
|
+
...has(payload, "title") ? { title: payload?.title } : {},
|
|
10277
|
+
...has(payload, "description") ? { description: payload?.description ?? "" } : {},
|
|
10278
|
+
...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
|
|
10279
|
+
...has(payload, "priority") ? { priority: payload?.priority } : {},
|
|
10280
|
+
...has(payload, "type") ? { type: payload?.type } : {},
|
|
10281
|
+
...has(payload, "status") ? { status: payload?.status } : {},
|
|
10282
|
+
...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
|
|
10283
|
+
...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
|
|
10284
|
+
...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
|
|
10285
|
+
...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
|
|
10286
|
+
...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
|
|
10287
|
+
...has(payload, "retryPolicy") ? {
|
|
10288
|
+
retryPolicy: payload?.retryPolicy ?? null
|
|
10289
|
+
} : {},
|
|
10290
|
+
...has(payload, "costCeilingUsd") ? {
|
|
10291
|
+
costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
|
|
10292
|
+
} : {}
|
|
9676
10293
|
});
|
|
9677
|
-
|
|
10294
|
+
if (!board) {
|
|
10295
|
+
fail(ws, type, "Board or task not found");
|
|
10296
|
+
return true;
|
|
10297
|
+
}
|
|
10298
|
+
const task = findTask(board.tasks, taskId);
|
|
10299
|
+
if (task) await syncSessionSource(ctx, task);
|
|
10300
|
+
ok(ws, type, task);
|
|
9678
10301
|
return true;
|
|
9679
10302
|
}
|
|
9680
10303
|
case "kanban.task.move": {
|
|
@@ -9692,7 +10315,13 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9692
10315
|
columnId,
|
|
9693
10316
|
payload?.order
|
|
9694
10317
|
);
|
|
9695
|
-
|
|
10318
|
+
if (!board) {
|
|
10319
|
+
fail(ws, type, "Move failed");
|
|
10320
|
+
return true;
|
|
10321
|
+
}
|
|
10322
|
+
const task = findTask(board.tasks, taskId);
|
|
10323
|
+
if (task) await syncSessionSource(ctx, task);
|
|
10324
|
+
ok(ws, type, task);
|
|
9696
10325
|
return true;
|
|
9697
10326
|
}
|
|
9698
10327
|
case "kanban.task.copy":
|
|
@@ -9751,8 +10380,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9751
10380
|
...payload?.role ? { role: payload.role } : {},
|
|
9752
10381
|
...payload?.provider ? { provider: payload.provider } : {},
|
|
9753
10382
|
...payload?.model ? { model: payload.model } : {},
|
|
10383
|
+
...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
|
|
9754
10384
|
...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
|
|
9755
10385
|
...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
|
|
10386
|
+
...payload?.skills ? { skills: payload.skills } : {},
|
|
9756
10387
|
...payload?.tools ? { tools: payload.tools } : {},
|
|
9757
10388
|
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
|
|
9758
10389
|
...payload?.assignee ? { assignee: payload.assignee } : {},
|
|
@@ -9771,7 +10402,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9771
10402
|
const board = await releaseTaskClaim(ctx.projectRoot, boardId, taskId, {
|
|
9772
10403
|
...payload?.status ? { status: payload.status } : {},
|
|
9773
10404
|
...payload?.reason ? { reason: payload.reason } : {},
|
|
9774
|
-
...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {}
|
|
10405
|
+
...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {},
|
|
10406
|
+
...typeof payload?.maxAttempts === "number" ? { maxAttempts: payload.maxAttempts } : {},
|
|
10407
|
+
...typeof payload?.costCeilingUsd === "number" ? { costCeilingUsd: payload.costCeilingUsd } : {},
|
|
10408
|
+
...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
|
|
9775
10409
|
});
|
|
9776
10410
|
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
9777
10411
|
return true;
|
|
@@ -9827,14 +10461,66 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9827
10461
|
...payload?.role ? { role: payload.role } : {},
|
|
9828
10462
|
...payload?.provider ? { provider: payload.provider } : {},
|
|
9829
10463
|
...payload?.model ? { model: payload.model } : {},
|
|
10464
|
+
...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
|
|
9830
10465
|
...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
|
|
9831
10466
|
...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
|
|
10467
|
+
...payload?.skills ? { skills: payload.skills } : {},
|
|
9832
10468
|
...payload?.tools ? { tools: payload.tools } : {},
|
|
9833
|
-
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {}
|
|
10469
|
+
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
|
|
10470
|
+
...payload?.assignee ? { assignee: payload.assignee } : {},
|
|
10471
|
+
...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
|
|
10472
|
+
...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
|
|
10473
|
+
...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
|
|
9834
10474
|
});
|
|
9835
10475
|
board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
|
|
9836
10476
|
return true;
|
|
9837
10477
|
}
|
|
10478
|
+
case "kanban.task.check.add": {
|
|
10479
|
+
const boardId = payload?.boardId;
|
|
10480
|
+
const taskId = payload?.taskId;
|
|
10481
|
+
const description = payload?.description;
|
|
10482
|
+
if (!boardId || !taskId || !description) {
|
|
10483
|
+
fail(ws, type, "boardId, taskId, and description required");
|
|
10484
|
+
return true;
|
|
10485
|
+
}
|
|
10486
|
+
const board = await addCheckToTask(ctx.projectRoot, boardId, taskId, {
|
|
10487
|
+
description,
|
|
10488
|
+
type: payload?.checkType ?? "manual",
|
|
10489
|
+
status: payload?.status ?? "pending"
|
|
10490
|
+
});
|
|
10491
|
+
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10492
|
+
return true;
|
|
10493
|
+
}
|
|
10494
|
+
case "kanban.task.check.update": {
|
|
10495
|
+
const boardId = payload?.boardId;
|
|
10496
|
+
const taskId = payload?.taskId;
|
|
10497
|
+
const checkId = payload?.checkId;
|
|
10498
|
+
if (!boardId || !taskId || !checkId) {
|
|
10499
|
+
fail(ws, type, "boardId, taskId, and checkId required");
|
|
10500
|
+
return true;
|
|
10501
|
+
}
|
|
10502
|
+
const board = await updateCheckOnTask(ctx.projectRoot, boardId, taskId, checkId, {
|
|
10503
|
+
...has(payload, "status") ? { status: payload?.status } : {}
|
|
10504
|
+
});
|
|
10505
|
+
if (!board) fail(ws, type, "Check not found");
|
|
10506
|
+
else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
|
|
10507
|
+
return true;
|
|
10508
|
+
}
|
|
10509
|
+
case "kanban.task.note.add": {
|
|
10510
|
+
const boardId = payload?.boardId;
|
|
10511
|
+
const taskId = payload?.taskId;
|
|
10512
|
+
const content = payload?.content;
|
|
10513
|
+
if (!boardId || !taskId || !content) {
|
|
10514
|
+
fail(ws, type, "boardId, taskId, and content required");
|
|
10515
|
+
return true;
|
|
10516
|
+
}
|
|
10517
|
+
const board = await addNoteToTask(ctx.projectRoot, boardId, taskId, {
|
|
10518
|
+
author: payload?.author ?? "webui",
|
|
10519
|
+
content
|
|
10520
|
+
});
|
|
10521
|
+
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10522
|
+
return true;
|
|
10523
|
+
}
|
|
9838
10524
|
case "kanban.task.dispatch":
|
|
9839
10525
|
fail(
|
|
9840
10526
|
ws,
|
|
@@ -9842,6 +10528,9 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9842
10528
|
"Kanban agent dispatch is only available from the CLI-hosted WebUI runtime."
|
|
9843
10529
|
);
|
|
9844
10530
|
return true;
|
|
10531
|
+
case "kanban.capabilities":
|
|
10532
|
+
ok(ws, type, { dispatchSupported: false });
|
|
10533
|
+
return true;
|
|
9845
10534
|
case "kanban.task.remove": {
|
|
9846
10535
|
const boardId = payload?.boardId;
|
|
9847
10536
|
const taskId = payload?.taskId;
|
|
@@ -9855,7 +10544,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9855
10544
|
return true;
|
|
9856
10545
|
}
|
|
9857
10546
|
const board = await removeTask(ctx.projectRoot, boardId, taskId);
|
|
9858
|
-
|
|
10547
|
+
if (!board) {
|
|
10548
|
+
fail(ws, type, "Board or task not found");
|
|
10549
|
+
return true;
|
|
10550
|
+
}
|
|
10551
|
+
await syncSessionSource(ctx, task, true);
|
|
10552
|
+
ok(ws, type, { removed: true, boardId: board.id, taskId: task.id, board });
|
|
9859
10553
|
return true;
|
|
9860
10554
|
}
|
|
9861
10555
|
case "kanban.task.get": {
|
|
@@ -9921,6 +10615,9 @@ async function handleMailboxRoute(ws, msg, handlers) {
|
|
|
9921
10615
|
case "mailbox.purge":
|
|
9922
10616
|
await handlers.purge(ws, msg);
|
|
9923
10617
|
return true;
|
|
10618
|
+
case "mailbox.compact":
|
|
10619
|
+
await handlers.compact(ws, msg);
|
|
10620
|
+
return true;
|
|
9924
10621
|
default:
|
|
9925
10622
|
return false;
|
|
9926
10623
|
}
|
|
@@ -9959,6 +10656,18 @@ async function handleMcpRoute(ws, msg, handlers) {
|
|
|
9959
10656
|
case "mcp.discover":
|
|
9960
10657
|
await handlers.discover(ws, msg);
|
|
9961
10658
|
return true;
|
|
10659
|
+
case "mcp.resources":
|
|
10660
|
+
await handlers.resources(ws, msg);
|
|
10661
|
+
return true;
|
|
10662
|
+
case "mcp.prompts":
|
|
10663
|
+
await handlers.prompts(ws, msg);
|
|
10664
|
+
return true;
|
|
10665
|
+
case "mcp.resource.read":
|
|
10666
|
+
await handlers.resourceRead(ws, msg);
|
|
10667
|
+
return true;
|
|
10668
|
+
case "mcp.prompt.get":
|
|
10669
|
+
await handlers.promptGet(ws, msg);
|
|
10670
|
+
return true;
|
|
9962
10671
|
default:
|
|
9963
10672
|
return false;
|
|
9964
10673
|
}
|
|
@@ -10152,6 +10861,7 @@ async function handleProviderRoute(ws, msg, routes) {
|
|
|
10152
10861
|
baseUrl,
|
|
10153
10862
|
apiKey
|
|
10154
10863
|
});
|
|
10864
|
+
await routes.adoptDefaultProviderIfUnset(id);
|
|
10155
10865
|
return true;
|
|
10156
10866
|
}
|
|
10157
10867
|
case "provider.remove": {
|
|
@@ -10348,7 +11058,7 @@ function createMessageDispatcher(opts) {
|
|
|
10348
11058
|
skillLoader: deps2.skillLoader,
|
|
10349
11059
|
skillInstaller: deps2.skillInstaller,
|
|
10350
11060
|
projectRoot,
|
|
10351
|
-
projectSkillsDir:
|
|
11061
|
+
projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
|
|
10352
11062
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
10353
11063
|
};
|
|
10354
11064
|
}
|
|
@@ -10390,7 +11100,12 @@ function createMessageDispatcher(opts) {
|
|
|
10390
11100
|
if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
|
|
10391
11101
|
if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
|
|
10392
11102
|
if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
|
|
10393
|
-
if (await handleKanbanRoute(ws, msg, {
|
|
11103
|
+
if (await handleKanbanRoute(ws, msg, {
|
|
11104
|
+
projectRoot: state.getProjectRoot(),
|
|
11105
|
+
context: deps2.context,
|
|
11106
|
+
broadcast: (message) => broadcast(state.getClients(), message)
|
|
11107
|
+
}))
|
|
11108
|
+
return;
|
|
10394
11109
|
if (msg.type.startsWith("worktree.") && await deps2.worktreeHandler.handleMessage(
|
|
10395
11110
|
msg
|
|
10396
11111
|
))
|
|
@@ -10421,7 +11136,8 @@ function createMessageDispatcher(opts) {
|
|
|
10421
11136
|
}
|
|
10422
11137
|
case "user_message": {
|
|
10423
11138
|
if (!ensureCurrentSession(ws, msg, "user_message")) return;
|
|
10424
|
-
const
|
|
11139
|
+
const userPayload = msg.payload;
|
|
11140
|
+
const content = userPayload.content;
|
|
10425
11141
|
if (runLock.get()) {
|
|
10426
11142
|
send(ws, {
|
|
10427
11143
|
type: "error",
|
|
@@ -10435,8 +11151,21 @@ function createMessageDispatcher(opts) {
|
|
|
10435
11151
|
const thisRun = new AbortController();
|
|
10436
11152
|
runLock.set(thisRun);
|
|
10437
11153
|
try {
|
|
11154
|
+
let input = content;
|
|
11155
|
+
const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
|
|
11156
|
+
if (imageBlocks.length > 0) {
|
|
11157
|
+
const routed = await routeImagesForModel(buildUserContentBlocks(content, imageBlocks), {
|
|
11158
|
+
supportsVision: deps2.agent.ctx.provider.capabilities.vision,
|
|
11159
|
+
adapters: () => createToolVisionAdapters(deps2.agent.tools),
|
|
11160
|
+
ctx: deps2.agent.ctx,
|
|
11161
|
+
signal: thisRun.signal,
|
|
11162
|
+
providerId: deps2.agent.ctx.provider.id,
|
|
11163
|
+
model: deps2.agent.ctx.model
|
|
11164
|
+
});
|
|
11165
|
+
input = routed.blocks;
|
|
11166
|
+
}
|
|
10438
11167
|
const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
|
|
10439
|
-
const result = await deps2.agent.run(
|
|
11168
|
+
const result = await deps2.agent.run(input, {
|
|
10440
11169
|
signal: thisRun.signal,
|
|
10441
11170
|
maxIterations: maxIt
|
|
10442
11171
|
});
|
|
@@ -10454,13 +11183,24 @@ function createMessageDispatcher(opts) {
|
|
|
10454
11183
|
})
|
|
10455
11184
|
});
|
|
10456
11185
|
} catch (err) {
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
11186
|
+
if (err instanceof IncomingImageError || err instanceof ImageInputUnsupportedError || err instanceof VisionUrlBlockedError) {
|
|
11187
|
+
send(ws, {
|
|
11188
|
+
type: "error",
|
|
11189
|
+
payload: sessionPayload2({
|
|
11190
|
+
phase: "user_message",
|
|
11191
|
+
...err instanceof ImageInputUnsupportedError ? { code: "vision_unsupported" } : {},
|
|
11192
|
+
message: err.message
|
|
11193
|
+
})
|
|
11194
|
+
});
|
|
11195
|
+
} else {
|
|
11196
|
+
send(ws, {
|
|
11197
|
+
type: "error",
|
|
11198
|
+
payload: sessionPayload2({
|
|
11199
|
+
phase: "agent.run",
|
|
11200
|
+
message: errMessage(err)
|
|
11201
|
+
})
|
|
11202
|
+
});
|
|
11203
|
+
}
|
|
10464
11204
|
} finally {
|
|
10465
11205
|
if (runLock.get() === thisRun) {
|
|
10466
11206
|
runLock.set(null);
|
|
@@ -10508,7 +11248,7 @@ function createMessageDispatcher(opts) {
|
|
|
10508
11248
|
break;
|
|
10509
11249
|
}
|
|
10510
11250
|
case "tool.disable": {
|
|
10511
|
-
const name2 =
|
|
11251
|
+
const name2 = msg.payload?.name;
|
|
10512
11252
|
if (!name2) {
|
|
10513
11253
|
send(ws, { type: "error", payload: { message: "tool.disable requires a name" } });
|
|
10514
11254
|
break;
|
|
@@ -10517,13 +11257,15 @@ function createMessageDispatcher(opts) {
|
|
|
10517
11257
|
const currentCfg = deps2.configStore.get().tools ?? {};
|
|
10518
11258
|
const currentDisabled = currentCfg.disabledTools ?? [];
|
|
10519
11259
|
if (ok2 && !currentDisabled.includes(name2)) {
|
|
10520
|
-
deps2.configStore.update({
|
|
11260
|
+
deps2.configStore.update({
|
|
11261
|
+
tools: { ...currentCfg, disabledTools: [...currentDisabled, name2] }
|
|
11262
|
+
});
|
|
10521
11263
|
}
|
|
10522
11264
|
send(ws, { type: "tool.disabled", payload: { name: name2, ok: ok2 } });
|
|
10523
11265
|
break;
|
|
10524
11266
|
}
|
|
10525
11267
|
case "tool.enable": {
|
|
10526
|
-
const name2 =
|
|
11268
|
+
const name2 = msg.payload?.name;
|
|
10527
11269
|
if (!name2) {
|
|
10528
11270
|
send(ws, { type: "error", payload: { message: "tool.enable requires a name" } });
|
|
10529
11271
|
break;
|
|
@@ -10533,19 +11275,31 @@ function createMessageDispatcher(opts) {
|
|
|
10533
11275
|
const currentCfg = deps2.configStore.get().tools ?? {};
|
|
10534
11276
|
const currentDisabled = currentCfg.disabledTools ?? [];
|
|
10535
11277
|
deps2.configStore.update({
|
|
10536
|
-
tools: {
|
|
11278
|
+
tools: {
|
|
11279
|
+
...currentCfg,
|
|
11280
|
+
disabledTools: currentDisabled.filter((n) => n !== name2)
|
|
11281
|
+
}
|
|
10537
11282
|
});
|
|
10538
11283
|
}
|
|
10539
11284
|
send(ws, { type: "tool.enabled", payload: { name: name2, ok: ok2 } });
|
|
10540
11285
|
break;
|
|
10541
11286
|
}
|
|
10542
11287
|
// ── Memory operations — delegated to shared handlers (memory-handlers.ts) ──
|
|
11288
|
+
// `memory.list` renders the single Super Memory store; all writes go
|
|
11289
|
+
// through the structured `memory.super.*` operations below.
|
|
10543
11290
|
case "memory.list":
|
|
10544
11291
|
return handleMemoryList(ws, deps2.memoryStore);
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
11292
|
+
// ── SuperMemory operations ──
|
|
11293
|
+
case "memory.super.list":
|
|
11294
|
+
return handleSuperMemoryList(ws, deps2.memoryStore);
|
|
11295
|
+
case "memory.super.get":
|
|
11296
|
+
return handleSuperMemoryGet(ws, msg, deps2.memoryStore);
|
|
11297
|
+
case "memory.super.update":
|
|
11298
|
+
return handleSuperMemoryUpdate(ws, msg, deps2.memoryStore);
|
|
11299
|
+
case "memory.super.delete":
|
|
11300
|
+
return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
|
|
11301
|
+
case "memory.super.remember":
|
|
11302
|
+
return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
|
|
10549
11303
|
// ── MCP tripwires — handleMcpRoute claims these upstream. ──
|
|
10550
11304
|
case "mcp.list":
|
|
10551
11305
|
throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
|
|
@@ -10567,6 +11321,14 @@ function createMessageDispatcher(opts) {
|
|
|
10567
11321
|
throw new Error("handleMcpRoute did not claim mcp.restart \u2014 check chain order");
|
|
10568
11322
|
case "mcp.discover":
|
|
10569
11323
|
throw new Error("handleMcpRoute did not claim mcp.discover \u2014 check chain order");
|
|
11324
|
+
case "mcp.resources":
|
|
11325
|
+
throw new Error("handleMcpRoute did not claim mcp.resources \u2014 check chain order");
|
|
11326
|
+
case "mcp.prompts":
|
|
11327
|
+
throw new Error("handleMcpRoute did not claim mcp.prompts \u2014 check chain order");
|
|
11328
|
+
case "mcp.resource.read":
|
|
11329
|
+
throw new Error("handleMcpRoute did not claim mcp.resource.read \u2014 check chain order");
|
|
11330
|
+
case "mcp.prompt.get":
|
|
11331
|
+
throw new Error("handleMcpRoute did not claim mcp.prompt.get \u2014 check chain order");
|
|
10570
11332
|
// Skills — full request→response cycle lives in skills-handlers.ts.
|
|
10571
11333
|
case "skills.list":
|
|
10572
11334
|
await handleSkillsList(ws, makeSkillsContext());
|
|
@@ -10867,7 +11629,20 @@ var PREF_KEYS = [
|
|
|
10867
11629
|
"favoriteModels",
|
|
10868
11630
|
"favoriteModelsOnly",
|
|
10869
11631
|
"modelMatrix",
|
|
10870
|
-
"fallbackAuto"
|
|
11632
|
+
"fallbackAuto",
|
|
11633
|
+
// Refiner + TUI visual prefs (parity with the CLI's embedded server —
|
|
11634
|
+
// these were browser-editable there but rejected as unknown keys here).
|
|
11635
|
+
"refinerProvider",
|
|
11636
|
+
"refinerModel",
|
|
11637
|
+
"refinerFallbackProfile",
|
|
11638
|
+
"thinkingWord",
|
|
11639
|
+
"statuslineMode",
|
|
11640
|
+
"animationStyle",
|
|
11641
|
+
// Safety / system prefs (parity with /settings breaker, fs-access, debug-stream).
|
|
11642
|
+
"breakerEnabled",
|
|
11643
|
+
"breakerAutoKillResetMs",
|
|
11644
|
+
"fsAccess",
|
|
11645
|
+
"debugStream"
|
|
10871
11646
|
];
|
|
10872
11647
|
function prefSnapshot(contextMeta) {
|
|
10873
11648
|
const snapshot = {};
|
|
@@ -10938,6 +11713,18 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
10938
11713
|
setAutonomy("enhanceDelayMs", payload["enhanceDelayMs"]);
|
|
10939
11714
|
if (typeof payload["enhanceLanguage"] === "string")
|
|
10940
11715
|
setAutonomy("enhanceLanguage", payload["enhanceLanguage"]);
|
|
11716
|
+
if (typeof payload["refinerProvider"] === "string")
|
|
11717
|
+
setAutonomy("refinerProvider", payload["refinerProvider"]);
|
|
11718
|
+
if (typeof payload["refinerModel"] === "string")
|
|
11719
|
+
setAutonomy("refinerModel", payload["refinerModel"]);
|
|
11720
|
+
if (typeof payload["refinerFallbackProfile"] === "string")
|
|
11721
|
+
setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
|
|
11722
|
+
if (typeof payload["thinkingWord"] === "string")
|
|
11723
|
+
setAutonomy("thinkingWord", payload["thinkingWord"]);
|
|
11724
|
+
if (typeof payload["statuslineMode"] === "string")
|
|
11725
|
+
setAutonomy("statuslineMode", payload["statuslineMode"]);
|
|
11726
|
+
if (typeof payload["animationStyle"] === "string")
|
|
11727
|
+
setAutonomy("animationStyle", payload["animationStyle"]);
|
|
10941
11728
|
if (autonomyTouched) decrypted.autonomy = autonomyCfg;
|
|
10942
11729
|
if (typeof payload["nextPrediction"] === "boolean")
|
|
10943
11730
|
decrypted.nextPrediction = payload["nextPrediction"];
|
|
@@ -11055,16 +11842,34 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
11055
11842
|
}
|
|
11056
11843
|
decrypted.modelRuntime = mr;
|
|
11057
11844
|
}
|
|
11845
|
+
if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
|
|
11846
|
+
const cb = decrypted.circuitBreaker ?? {};
|
|
11847
|
+
if (typeof payload["breakerEnabled"] === "boolean") cb.enabled = payload["breakerEnabled"];
|
|
11848
|
+
if (typeof payload["breakerAutoKillResetMs"] === "number")
|
|
11849
|
+
cb.autoKillResetMs = payload["breakerAutoKillResetMs"];
|
|
11850
|
+
decrypted.circuitBreaker = cb;
|
|
11851
|
+
}
|
|
11852
|
+
if (payload["fsAccess"] === "unrestricted" || payload["fsAccess"] === "project") {
|
|
11853
|
+
const restrict = payload["fsAccess"] === "project";
|
|
11854
|
+
const toolsCfg = decrypted.tools ?? {};
|
|
11855
|
+
toolsCfg.restrictToProjectRoot = restrict;
|
|
11856
|
+
decrypted.tools = toolsCfg;
|
|
11857
|
+
const featsCfg = decrypted.features ?? {};
|
|
11858
|
+
featsCfg.allowOutsideProjectRoot = !restrict;
|
|
11859
|
+
decrypted.features = featsCfg;
|
|
11860
|
+
}
|
|
11861
|
+
if (typeof payload["debugStream"] === "boolean")
|
|
11862
|
+
decrypted.debugStream = payload["debugStream"];
|
|
11058
11863
|
}, "prefs");
|
|
11059
11864
|
}
|
|
11060
11865
|
|
|
11061
11866
|
// src/server/projects-manifest.ts
|
|
11062
11867
|
import * as fs14 from "node:fs/promises";
|
|
11063
|
-
import * as
|
|
11868
|
+
import * as path18 from "node:path";
|
|
11064
11869
|
import { projectSlug } from "@wrongstack/core";
|
|
11065
11870
|
function projectsJsonPath(globalConfigPath) {
|
|
11066
|
-
const base =
|
|
11067
|
-
return
|
|
11871
|
+
const base = path18.dirname(globalConfigPath);
|
|
11872
|
+
return path18.join(base, "projects.json");
|
|
11068
11873
|
}
|
|
11069
11874
|
async function loadManifest(globalConfigPath) {
|
|
11070
11875
|
try {
|
|
@@ -11077,15 +11882,15 @@ async function loadManifest(globalConfigPath) {
|
|
|
11077
11882
|
}
|
|
11078
11883
|
async function saveManifest(manifest, globalConfigPath) {
|
|
11079
11884
|
const file = projectsJsonPath(globalConfigPath);
|
|
11080
|
-
await fs14.mkdir(
|
|
11885
|
+
await fs14.mkdir(path18.dirname(file), { recursive: true });
|
|
11081
11886
|
await fs14.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
11082
11887
|
}
|
|
11083
11888
|
function generateProjectSlug(rootPath) {
|
|
11084
11889
|
return projectSlug(rootPath);
|
|
11085
11890
|
}
|
|
11086
11891
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
11087
|
-
const base =
|
|
11088
|
-
const dir =
|
|
11892
|
+
const base = path18.dirname(globalConfigPath);
|
|
11893
|
+
const dir = path18.join(base, "projects", slug);
|
|
11089
11894
|
await fs14.mkdir(dir, { recursive: true });
|
|
11090
11895
|
return dir;
|
|
11091
11896
|
}
|
|
@@ -11119,6 +11924,23 @@ function projectSavedProviders(providers) {
|
|
|
11119
11924
|
});
|
|
11120
11925
|
}
|
|
11121
11926
|
var probeScrubber = new DefaultSecretScrubber();
|
|
11927
|
+
async function probeModelDescriptors(cfg) {
|
|
11928
|
+
if (!cfg.baseUrl) return [];
|
|
11929
|
+
try {
|
|
11930
|
+
const keys = normalizeKeys(cfg);
|
|
11931
|
+
const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
|
|
11932
|
+
const result = await probeLocalLlm({
|
|
11933
|
+
baseUrl: cfg.baseUrl,
|
|
11934
|
+
apiKey: active?.apiKey,
|
|
11935
|
+
noAuth: false,
|
|
11936
|
+
scrubber: probeScrubber
|
|
11937
|
+
});
|
|
11938
|
+
if (!result.ok || !result.modelIds) return [];
|
|
11939
|
+
return result.modelIds.map((id) => ({ id, name: id, capabilities: [] }));
|
|
11940
|
+
} catch {
|
|
11941
|
+
return [];
|
|
11942
|
+
}
|
|
11943
|
+
}
|
|
11122
11944
|
function createProviderHandlers(deps2) {
|
|
11123
11945
|
const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
|
|
11124
11946
|
let configWriteLock = deps2.getConfigWriteLock();
|
|
@@ -11405,7 +12227,9 @@ import path20 from "node:path";
|
|
|
11405
12227
|
import {
|
|
11406
12228
|
enhanceUserPrompt,
|
|
11407
12229
|
gatedEnhancerReasoning,
|
|
12230
|
+
nextEnhanceTimeout,
|
|
11408
12231
|
recentTextTurns,
|
|
12232
|
+
resolveEnhanceFallbackRef,
|
|
11409
12233
|
resolveProviderModelList
|
|
11410
12234
|
} from "@wrongstack/core";
|
|
11411
12235
|
import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
|
|
@@ -11502,6 +12326,16 @@ async function handleMailboxPurge(ws, deps2, opts) {
|
|
|
11502
12326
|
send(ws, { type: "mailbox.purged", payload: { error: errMessage(err) } });
|
|
11503
12327
|
}
|
|
11504
12328
|
}
|
|
12329
|
+
async function handleMailboxCompact(ws, deps2, opts) {
|
|
12330
|
+
try {
|
|
12331
|
+
const dir = resolveProjectDir2(deps2.projectRoot, deps2.globalRoot);
|
|
12332
|
+
const mb = new GlobalMailbox3(dir);
|
|
12333
|
+
const result = await mb.autoCompact(opts);
|
|
12334
|
+
send(ws, { type: "mailbox.compacted", payload: result });
|
|
12335
|
+
} catch (err) {
|
|
12336
|
+
send(ws, { type: "mailbox.compacted", payload: { error: errMessage(err) } });
|
|
12337
|
+
}
|
|
12338
|
+
}
|
|
11505
12339
|
|
|
11506
12340
|
// src/server/mode-handlers.ts
|
|
11507
12341
|
import {
|
|
@@ -11560,6 +12394,8 @@ function createModeHandlers(ctx) {
|
|
|
11560
12394
|
const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
|
|
11561
12395
|
const freshBuilder = new DefaultSystemPromptBuilder2({
|
|
11562
12396
|
memoryStore: ctx.memoryStore,
|
|
12397
|
+
// Single injection channel: Super Memory turn middleware, not a static section.
|
|
12398
|
+
injectMemory: false,
|
|
11563
12399
|
skillLoader: ctx.skillLoader,
|
|
11564
12400
|
modeStore: ctx.modeStore,
|
|
11565
12401
|
modeId: id,
|
|
@@ -11590,7 +12426,7 @@ function createModeHandlers(ctx) {
|
|
|
11590
12426
|
}
|
|
11591
12427
|
|
|
11592
12428
|
// src/server/project-handlers.ts
|
|
11593
|
-
import * as
|
|
12429
|
+
import * as path19 from "node:path";
|
|
11594
12430
|
function createProjectHandlers(ctx) {
|
|
11595
12431
|
return {
|
|
11596
12432
|
listProjects: async (ws) => {
|
|
@@ -11616,7 +12452,7 @@ function createProjectHandlers(ctx) {
|
|
|
11616
12452
|
selectProject: async (ws, msg) => {
|
|
11617
12453
|
const payload = msg.payload;
|
|
11618
12454
|
const root = typeof payload?.root === "string" ? payload.root : "";
|
|
11619
|
-
const name2 = typeof payload?.name === "string" ? payload.name : root ?
|
|
12455
|
+
const name2 = typeof payload?.name === "string" ? payload.name : root ? path19.basename(root) : "";
|
|
11620
12456
|
send(ws, {
|
|
11621
12457
|
type: "projects.selected",
|
|
11622
12458
|
payload: {
|
|
@@ -11651,12 +12487,12 @@ function createProjectHandlers(ctx) {
|
|
|
11651
12487
|
}
|
|
11652
12488
|
|
|
11653
12489
|
// src/server/session-handlers.ts
|
|
11654
|
-
import * as path19 from "node:path";
|
|
11655
12490
|
import {
|
|
11656
12491
|
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
11657
12492
|
repairToolUseAdjacency,
|
|
11658
12493
|
resolveContextWindowPolicy as resolveContextWindowPolicy3
|
|
11659
12494
|
} from "@wrongstack/core";
|
|
12495
|
+
import { sessionScopedPath as sessionScopedPath2 } from "@wrongstack/core/utils";
|
|
11660
12496
|
function createSessionHandlers(ctx) {
|
|
11661
12497
|
const currentSessionId = () => ctx.getSession().id;
|
|
11662
12498
|
const sessionPayload2 = (payload) => {
|
|
@@ -11682,33 +12518,46 @@ function createSessionHandlers(ctx) {
|
|
|
11682
12518
|
});
|
|
11683
12519
|
return false;
|
|
11684
12520
|
};
|
|
12521
|
+
const finalizeSession = async (writer) => {
|
|
12522
|
+
await writer.append({
|
|
12523
|
+
type: "session_end",
|
|
12524
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12525
|
+
usage: ctx.tokenCounter.total()
|
|
12526
|
+
}).catch(() => void 0);
|
|
12527
|
+
await writer.close().catch(() => void 0);
|
|
12528
|
+
};
|
|
12529
|
+
const activateSession = async (next, messages, usage) => {
|
|
12530
|
+
const current = ctx.getSession();
|
|
12531
|
+
if (current !== next) await finalizeSession(current);
|
|
12532
|
+
ctx.setSession(next);
|
|
12533
|
+
ctx.context.session = next;
|
|
12534
|
+
ctx.context.state.replaceMessages(messages);
|
|
12535
|
+
ctx.context.state.replaceTodos([]);
|
|
12536
|
+
ctx.context.readFiles.clear();
|
|
12537
|
+
ctx.context.fileMtimes.clear();
|
|
12538
|
+
ctx.context.state.setMeta(
|
|
12539
|
+
"plan.path",
|
|
12540
|
+
sessionScopedPath2(ctx.sessionsDir, next.id, ".plan.json")
|
|
12541
|
+
);
|
|
12542
|
+
ctx.context.state.setMeta(
|
|
12543
|
+
"task.path",
|
|
12544
|
+
sessionScopedPath2(ctx.sessionsDir, next.id, ".tasks.json")
|
|
12545
|
+
);
|
|
12546
|
+
ctx.tokenCounter.reset();
|
|
12547
|
+
if (usage) ctx.tokenCounter.account(usage, ctx.config.model);
|
|
12548
|
+
ctx.setSessionStartedAt(Date.now());
|
|
12549
|
+
await ctx.onSessionSwapped(next.id);
|
|
12550
|
+
};
|
|
11685
12551
|
return {
|
|
11686
12552
|
newSession: async (ws, msg) => {
|
|
11687
12553
|
if (!ensureCurrentSession(ws, msg, "session.new")) return;
|
|
11688
|
-
const session = ctx.getSession();
|
|
11689
|
-
try {
|
|
11690
|
-
await session.append({
|
|
11691
|
-
type: "session_end",
|
|
11692
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11693
|
-
usage: ctx.tokenCounter.total()
|
|
11694
|
-
});
|
|
11695
|
-
await session.close();
|
|
11696
|
-
} catch {
|
|
11697
|
-
}
|
|
11698
12554
|
const next = await ctx.getSessionStore().create({
|
|
11699
12555
|
id: "",
|
|
11700
12556
|
title: "",
|
|
11701
12557
|
model: ctx.config.model,
|
|
11702
12558
|
provider: ctx.config.provider
|
|
11703
12559
|
});
|
|
11704
|
-
|
|
11705
|
-
ctx.context.session = next;
|
|
11706
|
-
ctx.context.state.replaceMessages([]);
|
|
11707
|
-
ctx.context.state.replaceTodos([]);
|
|
11708
|
-
ctx.context.readFiles.clear();
|
|
11709
|
-
ctx.context.fileMtimes.clear();
|
|
11710
|
-
ctx.tokenCounter.reset();
|
|
11711
|
-
ctx.setSessionStartedAt(Date.now());
|
|
12560
|
+
await activateSession(next, []);
|
|
11712
12561
|
broadcast(ctx.clients, { type: "session.start", payload: await ctx.sessionStartPayload() });
|
|
11713
12562
|
},
|
|
11714
12563
|
clearContext: async (ws, msg) => {
|
|
@@ -11789,7 +12638,9 @@ function createSessionHandlers(ctx) {
|
|
|
11789
12638
|
},
|
|
11790
12639
|
listContextModes: async (ws, msg) => {
|
|
11791
12640
|
if (!ensureCurrentSession(ws, msg, "context.modes.list")) return;
|
|
11792
|
-
const active = String(
|
|
12641
|
+
const active = String(
|
|
12642
|
+
ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
12643
|
+
);
|
|
11793
12644
|
const allModes = ctx.customModeStore.list().map((m) => ({
|
|
11794
12645
|
id: m.id,
|
|
11795
12646
|
name: m.name,
|
|
@@ -11800,7 +12651,10 @@ function createSessionHandlers(ctx) {
|
|
|
11800
12651
|
eliseThreshold: m.eliseThreshold,
|
|
11801
12652
|
custom: m.custom === true
|
|
11802
12653
|
}));
|
|
11803
|
-
send(ws, {
|
|
12654
|
+
send(ws, {
|
|
12655
|
+
type: "context.modes.list",
|
|
12656
|
+
payload: sessionPayload2({ activeId: active, modes: allModes })
|
|
12657
|
+
});
|
|
11804
12658
|
},
|
|
11805
12659
|
switchContextMode: async (ws, msg) => {
|
|
11806
12660
|
if (!ensureCurrentSession(ws, msg, "context.mode.switch")) return;
|
|
@@ -11882,7 +12736,10 @@ function createSessionHandlers(ctx) {
|
|
|
11882
12736
|
const { id } = parsed.value;
|
|
11883
12737
|
if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
|
|
11884
12738
|
ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
|
|
11885
|
-
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
|
|
12739
|
+
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
|
|
12740
|
+
{},
|
|
12741
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
12742
|
+
);
|
|
11886
12743
|
}
|
|
11887
12744
|
const result = ctx.customModeStore.remove(id);
|
|
11888
12745
|
sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
|
|
@@ -11895,15 +12752,7 @@ function createSessionHandlers(ctx) {
|
|
|
11895
12752
|
send(ws, {
|
|
11896
12753
|
type: "sessions.list",
|
|
11897
12754
|
payload: {
|
|
11898
|
-
sessions: list
|
|
11899
|
-
id: s.id,
|
|
11900
|
-
title: s.title,
|
|
11901
|
-
startedAt: s.startedAt,
|
|
11902
|
-
model: s.model,
|
|
11903
|
-
provider: s.provider,
|
|
11904
|
-
tokenTotal: s.tokenTotal,
|
|
11905
|
-
isCurrent: s.id === currentId
|
|
11906
|
-
}))
|
|
12755
|
+
sessions: toSessionHistoryEntries(list, currentId)
|
|
11907
12756
|
}
|
|
11908
12757
|
});
|
|
11909
12758
|
} catch (err) {
|
|
@@ -11917,8 +12766,17 @@ function createSessionHandlers(ctx) {
|
|
|
11917
12766
|
sendResult(ws, false, "Cannot delete the active session");
|
|
11918
12767
|
return;
|
|
11919
12768
|
}
|
|
11920
|
-
|
|
12769
|
+
const store = ctx.getSessionStore();
|
|
12770
|
+
await store.delete(id);
|
|
11921
12771
|
sendResult(ws, true, `Session ${id} deleted`);
|
|
12772
|
+
try {
|
|
12773
|
+
const list = await store.list(200);
|
|
12774
|
+
broadcast(ctx.clients, {
|
|
12775
|
+
type: "sessions.list",
|
|
12776
|
+
payload: { sessions: toSessionHistoryEntries(list, ctx.getSession().id) }
|
|
12777
|
+
});
|
|
12778
|
+
} catch {
|
|
12779
|
+
}
|
|
11922
12780
|
} catch (err) {
|
|
11923
12781
|
sendResult(ws, false, errMessage(err));
|
|
11924
12782
|
}
|
|
@@ -11934,23 +12792,17 @@ function createSessionHandlers(ctx) {
|
|
|
11934
12792
|
try {
|
|
11935
12793
|
await ctx.getSessionStore().rename(id, name2);
|
|
11936
12794
|
sendResult(ws, true, name2 ? `Renamed session to "${name2}"` : `Cleared session name`);
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
provider: s.provider,
|
|
11949
|
-
tokenTotal: s.tokenTotal,
|
|
11950
|
-
isCurrent: s.id === currentId
|
|
11951
|
-
}))
|
|
11952
|
-
}
|
|
11953
|
-
});
|
|
12795
|
+
try {
|
|
12796
|
+
const list = await ctx.getSessionStore().list(200);
|
|
12797
|
+
const currentId = ctx.getSession().id;
|
|
12798
|
+
broadcast(ctx.clients, {
|
|
12799
|
+
type: "sessions.list",
|
|
12800
|
+
payload: {
|
|
12801
|
+
sessions: toSessionHistoryEntries(list, currentId)
|
|
12802
|
+
}
|
|
12803
|
+
});
|
|
12804
|
+
} catch {
|
|
12805
|
+
}
|
|
11954
12806
|
} catch (err) {
|
|
11955
12807
|
sendResult(ws, false, errMessage(err));
|
|
11956
12808
|
}
|
|
@@ -11964,23 +12816,7 @@ function createSessionHandlers(ctx) {
|
|
|
11964
12816
|
return;
|
|
11965
12817
|
}
|
|
11966
12818
|
const resumed = await ctx.getSessionStore().resume(id);
|
|
11967
|
-
|
|
11968
|
-
await current.append({
|
|
11969
|
-
type: "session_end",
|
|
11970
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11971
|
-
usage: ctx.tokenCounter.total()
|
|
11972
|
-
});
|
|
11973
|
-
await current.close();
|
|
11974
|
-
} catch {
|
|
11975
|
-
}
|
|
11976
|
-
ctx.setSession(resumed.writer);
|
|
11977
|
-
ctx.context.session = resumed.writer;
|
|
11978
|
-
ctx.context.state.replaceMessages(resumed.data.messages);
|
|
11979
|
-
ctx.context.readFiles.clear();
|
|
11980
|
-
ctx.context.fileMtimes.clear();
|
|
11981
|
-
ctx.tokenCounter.reset();
|
|
11982
|
-
ctx.tokenCounter.account(resumed.data.usage, ctx.config.model);
|
|
11983
|
-
ctx.setSessionStartedAt(Date.now());
|
|
12819
|
+
await activateSession(resumed.writer, resumed.data.messages, resumed.data.usage);
|
|
11984
12820
|
broadcast(ctx.clients, {
|
|
11985
12821
|
type: "session.start",
|
|
11986
12822
|
payload: {
|
|
@@ -12004,10 +12840,7 @@ function createSessionHandlers(ctx) {
|
|
|
12004
12840
|
try {
|
|
12005
12841
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
12006
12842
|
const projectRoot = ctx.getProjectRoot();
|
|
12007
|
-
const rewinder = new DefaultSessionRewinder(
|
|
12008
|
-
path19.join(projectRoot, ".wrongstack", "sessions"),
|
|
12009
|
-
projectRoot
|
|
12010
|
-
);
|
|
12843
|
+
const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
|
|
12011
12844
|
const checkpoints = await rewinder.listCheckpoints(ctx.getSession().id);
|
|
12012
12845
|
send(ws, { type: "session.checkpoints", payload: sessionPayload2({ checkpoints }) });
|
|
12013
12846
|
} catch {
|
|
@@ -12020,10 +12853,7 @@ function createSessionHandlers(ctx) {
|
|
|
12020
12853
|
try {
|
|
12021
12854
|
const { DefaultSessionRewinder } = await import("@wrongstack/core");
|
|
12022
12855
|
const projectRoot = ctx.getProjectRoot();
|
|
12023
|
-
const rewinder = new DefaultSessionRewinder(
|
|
12024
|
-
path19.join(projectRoot, ".wrongstack", "sessions"),
|
|
12025
|
-
projectRoot
|
|
12026
|
-
);
|
|
12856
|
+
const rewinder = new DefaultSessionRewinder(ctx.sessionsDir, projectRoot);
|
|
12027
12857
|
await rewinder.rewindToCheckpoint(ctx.getSession().id, checkpointIndex);
|
|
12028
12858
|
await ctx.context.session.truncateToCheckpoint(checkpointIndex);
|
|
12029
12859
|
sendResult(ws, true, `Rewound to checkpoint ${checkpointIndex}`);
|
|
@@ -12072,6 +12902,25 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12072
12902
|
clients: state.getClients(),
|
|
12073
12903
|
modelsRegistry: deps2.modelsRegistry
|
|
12074
12904
|
});
|
|
12905
|
+
async function applyModelSwitchCore(newProvider, newModel) {
|
|
12906
|
+
const cur = state.getConfig();
|
|
12907
|
+
state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
|
|
12908
|
+
deps2.configStore.update({ provider: newProvider, model: newModel });
|
|
12909
|
+
deps2.context.model = newModel;
|
|
12910
|
+
const newCfg = state.getConfig();
|
|
12911
|
+
const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
|
|
12912
|
+
const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
|
|
12913
|
+
deps2.context.provider = newProv;
|
|
12914
|
+
await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
|
|
12915
|
+
await cb.updateGlobalConfig((config) => {
|
|
12916
|
+
config.provider = newProvider;
|
|
12917
|
+
config.model = newModel;
|
|
12918
|
+
}, "model.switch");
|
|
12919
|
+
broadcast(state.getClients(), {
|
|
12920
|
+
type: "session.start",
|
|
12921
|
+
payload: await cb.sessionStartPayload()
|
|
12922
|
+
});
|
|
12923
|
+
}
|
|
12075
12924
|
const providerRoutes = {
|
|
12076
12925
|
providerHandlers,
|
|
12077
12926
|
listProviders: async (ws) => {
|
|
@@ -12108,11 +12957,20 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12108
12957
|
const provider = await resolveProviderCatalogForModels(deps2.modelsRegistry, providerId, cfg);
|
|
12109
12958
|
const siblingId = SIBLING_CATALOG[providerId];
|
|
12110
12959
|
const siblingCatalog = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
|
|
12960
|
+
let resolved = resolveProviderModelList(
|
|
12961
|
+
cfg?.models,
|
|
12962
|
+
provider,
|
|
12963
|
+
cfg?.type ?? providerId,
|
|
12964
|
+
siblingCatalog
|
|
12965
|
+
);
|
|
12966
|
+
if (resolved.length === 0 && cfg?.baseUrl) {
|
|
12967
|
+
resolved = await probeModelDescriptors(cfg);
|
|
12968
|
+
}
|
|
12111
12969
|
const models = await enrichProviderModelDescriptors(
|
|
12112
12970
|
deps2.modelsRegistry,
|
|
12113
12971
|
providerId,
|
|
12114
12972
|
cfg,
|
|
12115
|
-
|
|
12973
|
+
resolved
|
|
12116
12974
|
);
|
|
12117
12975
|
send(ws, {
|
|
12118
12976
|
type: "provider.models",
|
|
@@ -12130,21 +12988,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12130
12988
|
}
|
|
12131
12989
|
const { provider: newProvider, model: newModel } = parsed.value;
|
|
12132
12990
|
try {
|
|
12133
|
-
|
|
12134
|
-
state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
|
|
12135
|
-
deps2.configStore.update({ provider: newProvider, model: newModel });
|
|
12136
|
-
deps2.context.model = newModel;
|
|
12137
|
-
const newCfg = state.getConfig();
|
|
12138
|
-
const providerCfg = newCfg.providers?.[newProvider] ?? {
|
|
12139
|
-
type: newProvider
|
|
12140
|
-
};
|
|
12141
|
-
const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
|
|
12142
|
-
deps2.context.provider = newProv;
|
|
12143
|
-
await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
|
|
12144
|
-
await cb.updateGlobalConfig((config) => {
|
|
12145
|
-
config.provider = newProvider;
|
|
12146
|
-
config.model = newModel;
|
|
12147
|
-
}, "model.switch");
|
|
12991
|
+
await applyModelSwitchCore(newProvider, newModel);
|
|
12148
12992
|
send(ws, {
|
|
12149
12993
|
type: "key.operation_result",
|
|
12150
12994
|
payload: { success: true, message: `Switched to ${newProvider} / ${newModel}` }
|
|
@@ -12157,45 +13001,100 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12157
13001
|
message: `Switch failed: ${errMessage(err)}`
|
|
12158
13002
|
}
|
|
12159
13003
|
});
|
|
12160
|
-
return;
|
|
12161
13004
|
}
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
13005
|
+
},
|
|
13006
|
+
adoptDefaultProviderIfUnset: async (providerId) => {
|
|
13007
|
+
if (state.getConfig().model) return;
|
|
13008
|
+
const saved = await providerHandlers.loadConfigProviders();
|
|
13009
|
+
state.setConfig(patchConfig(state.getConfig(), { providers: saved }));
|
|
13010
|
+
const cfg = saved[providerId];
|
|
13011
|
+
if (!cfg) return;
|
|
13012
|
+
let model = cfg.models?.[0];
|
|
13013
|
+
if (!model) {
|
|
13014
|
+
const catalogId = cfg.type && cfg.type !== providerId ? cfg.type : providerId;
|
|
13015
|
+
const catalog = await deps2.modelsRegistry.getProvider(catalogId).catch(() => void 0);
|
|
13016
|
+
model = catalog?.models?.[0]?.id;
|
|
13017
|
+
}
|
|
13018
|
+
if (!model) {
|
|
13019
|
+
const probed = await probeModelDescriptors(cfg);
|
|
13020
|
+
model = probed[0]?.id;
|
|
13021
|
+
}
|
|
13022
|
+
if (!model) return;
|
|
13023
|
+
try {
|
|
13024
|
+
await applyModelSwitchCore(providerId, model);
|
|
13025
|
+
} catch {
|
|
13026
|
+
}
|
|
12166
13027
|
},
|
|
12167
13028
|
refineModel: async (ws, msg) => {
|
|
12168
|
-
const
|
|
13029
|
+
const payload = msg.payload;
|
|
13030
|
+
const { text } = payload;
|
|
12169
13031
|
if (!text?.trim()) {
|
|
12170
13032
|
send(ws, {
|
|
12171
13033
|
type: "model.refine_result",
|
|
12172
|
-
payload: { refined: "", english: "", error: "Empty text" }
|
|
13034
|
+
payload: { refined: "", english: "", error: "Empty text", errorKind: "provider_error" }
|
|
12173
13035
|
});
|
|
12174
13036
|
return;
|
|
12175
13037
|
}
|
|
13038
|
+
const cfg = state.getConfig();
|
|
13039
|
+
const fallbackRef = resolveEnhanceFallbackRef({
|
|
13040
|
+
...cfg,
|
|
13041
|
+
provider: cfg.provider ?? "",
|
|
13042
|
+
model: cfg.model ?? ""
|
|
13043
|
+
});
|
|
13044
|
+
let provider = deps2.context.provider;
|
|
13045
|
+
let providerId = cfg.provider ?? "";
|
|
13046
|
+
let model = deps2.context.model;
|
|
13047
|
+
if (payload.provider && payload.model) {
|
|
13048
|
+
try {
|
|
13049
|
+
const providerCfg = cfg.providers?.[payload.provider] ?? {
|
|
13050
|
+
type: payload.provider
|
|
13051
|
+
};
|
|
13052
|
+
provider = deps2.providerRegistry.has(payload.provider) ? deps2.providerRegistry.create({ ...providerCfg, type: payload.provider }) : makeProviderFromConfig2(payload.provider, providerCfg);
|
|
13053
|
+
providerId = payload.provider;
|
|
13054
|
+
model = payload.model;
|
|
13055
|
+
} catch (err) {
|
|
13056
|
+
send(ws, {
|
|
13057
|
+
type: "model.refine_result",
|
|
13058
|
+
payload: {
|
|
13059
|
+
refined: text,
|
|
13060
|
+
english: text,
|
|
13061
|
+
error: `Cannot use ${payload.provider}/${payload.model}: ${errMessage(err)}`,
|
|
13062
|
+
errorKind: "provider_error",
|
|
13063
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13064
|
+
}
|
|
13065
|
+
});
|
|
13066
|
+
return;
|
|
13067
|
+
}
|
|
13068
|
+
}
|
|
13069
|
+
const baseTimeout = 9e4;
|
|
13070
|
+
const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
|
|
12176
13071
|
try {
|
|
12177
13072
|
const history = recentTextTurns(deps2.context.messages);
|
|
12178
|
-
const cfg = state.getConfig();
|
|
12179
13073
|
const resolved = await resolveProviderModelMetadata(
|
|
12180
13074
|
deps2.modelsRegistry,
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
cfg.providers?.[
|
|
13075
|
+
providerId,
|
|
13076
|
+
model,
|
|
13077
|
+
cfg.providers?.[providerId]
|
|
12184
13078
|
).catch(() => void 0);
|
|
12185
13079
|
const reasoning = gatedEnhancerReasoning(resolved?.capabilities?.reasoningConfig);
|
|
13080
|
+
let failureKind;
|
|
12186
13081
|
const result = await enhanceUserPrompt({
|
|
12187
|
-
provider
|
|
12188
|
-
model
|
|
13082
|
+
provider,
|
|
13083
|
+
model,
|
|
12189
13084
|
text,
|
|
12190
13085
|
history,
|
|
12191
|
-
timeoutMs
|
|
13086
|
+
timeoutMs,
|
|
12192
13087
|
...reasoning ? { reasoning } : {},
|
|
12193
|
-
onError: (reason) => {
|
|
13088
|
+
onError: (reason, kind) => {
|
|
13089
|
+
failureKind = kind;
|
|
12194
13090
|
console.warn(
|
|
12195
13091
|
JSON.stringify({
|
|
12196
13092
|
level: "warn",
|
|
12197
13093
|
event: "model.refine_failed",
|
|
12198
13094
|
reason,
|
|
13095
|
+
kind,
|
|
13096
|
+
provider: providerId,
|
|
13097
|
+
model,
|
|
12199
13098
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
12200
13099
|
})
|
|
12201
13100
|
);
|
|
@@ -12204,12 +13103,23 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12204
13103
|
if (result) {
|
|
12205
13104
|
send(ws, {
|
|
12206
13105
|
type: "model.refine_result",
|
|
12207
|
-
payload: {
|
|
13106
|
+
payload: {
|
|
13107
|
+
refined: result.refined,
|
|
13108
|
+
english: result.english,
|
|
13109
|
+
refinedWith: { provider: providerId, model }
|
|
13110
|
+
}
|
|
12208
13111
|
});
|
|
12209
13112
|
} else {
|
|
12210
13113
|
send(ws, {
|
|
12211
13114
|
type: "model.refine_result",
|
|
12212
|
-
payload: {
|
|
13115
|
+
payload: {
|
|
13116
|
+
refined: text,
|
|
13117
|
+
english: text,
|
|
13118
|
+
error: "Refinement returned no result",
|
|
13119
|
+
errorKind: failureKind ?? "empty",
|
|
13120
|
+
...failureKind === "timeout" ? { retryTimeoutMs: nextEnhanceTimeout(timeoutMs, cfg.autonomy) } : {},
|
|
13121
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13122
|
+
}
|
|
12213
13123
|
});
|
|
12214
13124
|
}
|
|
12215
13125
|
} catch (err) {
|
|
@@ -12223,7 +13133,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12223
13133
|
);
|
|
12224
13134
|
send(ws, {
|
|
12225
13135
|
type: "model.refine_result",
|
|
12226
|
-
payload: {
|
|
13136
|
+
payload: {
|
|
13137
|
+
refined: text,
|
|
13138
|
+
english: text,
|
|
13139
|
+
error: errMessage(err),
|
|
13140
|
+
errorKind: "provider_error",
|
|
13141
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13142
|
+
}
|
|
12227
13143
|
});
|
|
12228
13144
|
}
|
|
12229
13145
|
}
|
|
@@ -12239,8 +13155,10 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12239
13155
|
getProjectRoot: state.getProjectRoot,
|
|
12240
13156
|
getSession: state.getSession,
|
|
12241
13157
|
getSessionStore: state.getSessionStore,
|
|
13158
|
+
sessionsDir: deps2.wpaths.projectSessions,
|
|
12242
13159
|
setSession: state.setSession,
|
|
12243
13160
|
setSessionStartedAt: state.setSessionStartedAt,
|
|
13161
|
+
onSessionSwapped: cb.onSessionSwapped,
|
|
12244
13162
|
sessionStartPayload: cb.sessionStartPayload
|
|
12245
13163
|
});
|
|
12246
13164
|
const projectRoutes = createProjectHandlers({
|
|
@@ -12335,6 +13253,17 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12335
13253
|
deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
12336
13254
|
}
|
|
12337
13255
|
}
|
|
13256
|
+
if (typeof payload["breakerEnabled"] === "boolean" || typeof payload["breakerAutoKillResetMs"] === "number") {
|
|
13257
|
+
const { getProcessRegistry } = await import("@wrongstack/tools");
|
|
13258
|
+
getProcessRegistry().setBreakerConfig({
|
|
13259
|
+
...typeof payload["breakerEnabled"] === "boolean" ? { enabled: payload["breakerEnabled"] } : {},
|
|
13260
|
+
...typeof payload["breakerAutoKillResetMs"] === "number" ? { autoKillResetMs: payload["breakerAutoKillResetMs"] } : {}
|
|
13261
|
+
});
|
|
13262
|
+
}
|
|
13263
|
+
if (typeof payload["debugStream"] === "boolean") {
|
|
13264
|
+
const { setDebugStreamEnabled } = await import("@wrongstack/providers");
|
|
13265
|
+
setDebugStreamEnabled(payload["debugStream"]);
|
|
13266
|
+
}
|
|
12338
13267
|
if (typeof payload["logLevel"] === "string") {
|
|
12339
13268
|
const valid = ["debug", "info", "warn", "error"];
|
|
12340
13269
|
if (valid.includes(payload["logLevel"])) {
|
|
@@ -12412,6 +13341,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12412
13341
|
{ projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
|
|
12413
13342
|
parsed.value
|
|
12414
13343
|
);
|
|
13344
|
+
},
|
|
13345
|
+
compact: (ws, msg) => {
|
|
13346
|
+
return handleMailboxCompact(
|
|
13347
|
+
ws,
|
|
13348
|
+
{ projectRoot: state.getProjectRoot(), globalRoot: path20.dirname(deps2.globalConfigPath) },
|
|
13349
|
+
msg.payload ?? {}
|
|
13350
|
+
);
|
|
12415
13351
|
}
|
|
12416
13352
|
};
|
|
12417
13353
|
const mcpRoutes = {
|
|
@@ -12424,14 +13360,29 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12424
13360
|
sleep: (ws, msg) => handleMcpSleep(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12425
13361
|
wake: (ws, msg) => handleMcpWake(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12426
13362
|
restart: (ws, msg) => handleMcpRestart(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
12427
|
-
discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
|
|
13363
|
+
discover: (ws, msg) => handleMcpDiscover(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
13364
|
+
resources: (ws, msg) => handleMcpResources(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
13365
|
+
prompts: (ws, msg) => handleMcpPrompts(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
13366
|
+
resourceRead: (ws, msg) => handleMcpResourceRead(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry),
|
|
13367
|
+
promptGet: (ws, msg) => handleMcpPromptGet(ws, msg, deps2.globalConfigPath, deps2.mcpRegistry)
|
|
13368
|
+
};
|
|
13369
|
+
const sendBrainStatus = (ws) => {
|
|
13370
|
+
const snapshot = deps2.brainRuntime.getSnapshot();
|
|
13371
|
+
send(ws, {
|
|
13372
|
+
type: "brain.status",
|
|
13373
|
+
payload: {
|
|
13374
|
+
maxAutoRisk: deps2.brainSettings.maxAutoRisk,
|
|
13375
|
+
log: deps2.brainLog,
|
|
13376
|
+
mode: snapshot.mode,
|
|
13377
|
+
poolLabels: snapshot.poolLabels,
|
|
13378
|
+
councilLabels: snapshot.councilLabels,
|
|
13379
|
+
ledgerPath: snapshot.ledger.path
|
|
13380
|
+
}
|
|
13381
|
+
});
|
|
12428
13382
|
};
|
|
12429
13383
|
const brainRoutes = {
|
|
12430
13384
|
status: (ws) => {
|
|
12431
|
-
|
|
12432
|
-
type: "brain.status",
|
|
12433
|
-
payload: { maxAutoRisk: deps2.brainSettings.maxAutoRisk, log: deps2.brainLog }
|
|
12434
|
-
});
|
|
13385
|
+
sendBrainStatus(ws);
|
|
12435
13386
|
},
|
|
12436
13387
|
risk: (ws, msg) => {
|
|
12437
13388
|
const parsed = validateBrainRiskPayload(msg.payload);
|
|
@@ -12441,11 +13392,43 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12441
13392
|
}
|
|
12442
13393
|
const { level } = parsed.value;
|
|
12443
13394
|
deps2.brainSettings.maxAutoRisk = level;
|
|
13395
|
+
sendBrainStatus(ws);
|
|
13396
|
+
},
|
|
13397
|
+
configGet: (ws) => {
|
|
12444
13398
|
send(ws, {
|
|
12445
|
-
type: "brain.
|
|
12446
|
-
payload: {
|
|
13399
|
+
type: "brain.config",
|
|
13400
|
+
payload: { config: deps2.brainRuntime.getSnapshot(), persisted: true }
|
|
12447
13401
|
});
|
|
12448
13402
|
},
|
|
13403
|
+
configSet: async (ws, msg) => {
|
|
13404
|
+
const parsed = validateBrainConfigSetPayload(msg.payload);
|
|
13405
|
+
if (!parsed.ok) {
|
|
13406
|
+
sendResult(ws, false, parsed.message);
|
|
13407
|
+
return;
|
|
13408
|
+
}
|
|
13409
|
+
try {
|
|
13410
|
+
const { persisted } = deps2.brainRuntime.apply(parsed.value.patch);
|
|
13411
|
+
const result = await persisted;
|
|
13412
|
+
send(ws, {
|
|
13413
|
+
type: "brain.config",
|
|
13414
|
+
payload: {
|
|
13415
|
+
config: deps2.brainRuntime.getSnapshot(),
|
|
13416
|
+
persisted: result.ok,
|
|
13417
|
+
...result.ok ? {} : { error: result.error ?? "Persist failed." }
|
|
13418
|
+
}
|
|
13419
|
+
});
|
|
13420
|
+
sendBrainStatus(ws);
|
|
13421
|
+
} catch (err) {
|
|
13422
|
+
send(ws, {
|
|
13423
|
+
type: "brain.config",
|
|
13424
|
+
payload: {
|
|
13425
|
+
config: deps2.brainRuntime.getSnapshot(),
|
|
13426
|
+
persisted: false,
|
|
13427
|
+
error: `Invalid Brain setting: ${errMessage(err)}`
|
|
13428
|
+
}
|
|
13429
|
+
});
|
|
13430
|
+
}
|
|
13431
|
+
},
|
|
12449
13432
|
ask: async (ws, msg) => {
|
|
12450
13433
|
const parsed = validateBrainAskPayload(msg.payload);
|
|
12451
13434
|
if (!parsed.ok) {
|
|
@@ -12518,8 +13501,16 @@ async function startWebUI(opts = {}) {
|
|
|
12518
13501
|
console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
|
|
12519
13502
|
if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
|
|
12520
13503
|
const firstKey = expectDefined3(Object.keys(config.providers)[0]);
|
|
12521
|
-
|
|
12522
|
-
|
|
13504
|
+
const adoptModel = !config.model ? config.providers[firstKey]?.models?.[0] : void 0;
|
|
13505
|
+
config = patchConfig(config, {
|
|
13506
|
+
provider: firstKey,
|
|
13507
|
+
...adoptModel ? { model: adoptModel } : {}
|
|
13508
|
+
});
|
|
13509
|
+
console.log(
|
|
13510
|
+
"[WebUI] No active provider \u2014 auto-selected:",
|
|
13511
|
+
firstKey,
|
|
13512
|
+
adoptModel ? `/ ${adoptModel}` : ""
|
|
13513
|
+
);
|
|
12523
13514
|
}
|
|
12524
13515
|
const needsProvider = !config.provider || !config.model;
|
|
12525
13516
|
const preContext = await createPreContextServices({
|
|
@@ -12553,7 +13544,8 @@ async function startWebUI(opts = {}) {
|
|
|
12553
13544
|
promptsCtx,
|
|
12554
13545
|
modelCapabilitiesRef,
|
|
12555
13546
|
provider,
|
|
12556
|
-
context
|
|
13547
|
+
context,
|
|
13548
|
+
sessionIdentity
|
|
12557
13549
|
} = preContext;
|
|
12558
13550
|
let sessionStore = preContext.sessionStore;
|
|
12559
13551
|
let session = preContext.session;
|
|
@@ -12586,7 +13578,12 @@ async function startWebUI(opts = {}) {
|
|
|
12586
13578
|
modelCapabilitiesRef,
|
|
12587
13579
|
sessionGetter: () => session,
|
|
12588
13580
|
sessionReader,
|
|
12589
|
-
annotationsStore
|
|
13581
|
+
annotationsStore,
|
|
13582
|
+
// Brain settings persist to the GLOBAL config only (config.brain is on
|
|
13583
|
+
// the in-project deny list), serialized behind the shared write lock.
|
|
13584
|
+
persistBrainConfig: (brainConfig) => updateGlobalConfig2((decrypted) => {
|
|
13585
|
+
decrypted["brain"] = brainConfig;
|
|
13586
|
+
}, "brain.config")
|
|
12590
13587
|
});
|
|
12591
13588
|
const {
|
|
12592
13589
|
compactor,
|
|
@@ -12596,6 +13593,7 @@ async function startWebUI(opts = {}) {
|
|
|
12596
13593
|
pipelines,
|
|
12597
13594
|
brain,
|
|
12598
13595
|
brainSettings,
|
|
13596
|
+
brainRuntime,
|
|
12599
13597
|
brainLog,
|
|
12600
13598
|
brainMonitor,
|
|
12601
13599
|
codebaseIndexing,
|
|
@@ -12766,10 +13764,16 @@ async function startWebUI(opts = {}) {
|
|
|
12766
13764
|
terminalHandler,
|
|
12767
13765
|
brain,
|
|
12768
13766
|
brainSettings,
|
|
13767
|
+
brainRuntime,
|
|
12769
13768
|
brainLog
|
|
12770
13769
|
};
|
|
12771
13770
|
const cb = {
|
|
12772
13771
|
sessionStartPayload,
|
|
13772
|
+
onSessionSwapped: async (sessionId) => {
|
|
13773
|
+
await sessionIdentity.activate(sessionId);
|
|
13774
|
+
const { hydrateSessionKanban: hydrateSessionKanban2 } = await import("@wrongstack/tools/session-kanban");
|
|
13775
|
+
await hydrateSessionKanban2(deps2.context);
|
|
13776
|
+
},
|
|
12773
13777
|
updateAutoCompactionMaxContext,
|
|
12774
13778
|
updateGlobalConfig: updateGlobalConfig2,
|
|
12775
13779
|
persistPrefsToConfig: persistPrefsToConfig2,
|
|
@@ -12895,7 +13899,10 @@ async function startWebUI(opts = {}) {
|
|
|
12895
13899
|
onShutdown: async () => {
|
|
12896
13900
|
credentialWatcherClose?.();
|
|
12897
13901
|
brainMonitor.stop();
|
|
13902
|
+
await agentServices.brainLedger?.stop().catch(() => {
|
|
13903
|
+
});
|
|
12898
13904
|
await mcpRegistry.stopAll().catch(() => void 0);
|
|
13905
|
+
await sessionIdentity.stop();
|
|
12899
13906
|
eventArming.getDispose()?.();
|
|
12900
13907
|
if (eternalSubscription) {
|
|
12901
13908
|
eternalSubscription.dispose();
|