@wrongstack/webui-server 0.286.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 +693 -189
- 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/context-meta.d.ts.map +1 -1
- package/dist/server/entry.js +672 -187
- package/dist/server/entry.js.map +4 -4
- package/dist/server/handlers.js.map +1 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -3
- 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/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.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 +18 -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 +4 -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.map +1 -1
- package/dist/server/start-webui.d.ts.map +1 -1
- package/dist/server/ws-payload-validation.d.ts.map +1 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -38,12 +38,13 @@ function commitsSince(cwd, baseSha, branch) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
var AutoPhaseWebSocketHandler = class {
|
|
41
|
-
constructor(agent, context, logger, storeDir, events, projectRoot) {
|
|
41
|
+
constructor(agent, context, logger, storeDir, events, projectRoot, onBoardState) {
|
|
42
42
|
this.agent = agent;
|
|
43
43
|
this.context = context;
|
|
44
44
|
this.logger = logger;
|
|
45
45
|
this.events = events;
|
|
46
46
|
this.projectRoot = projectRoot;
|
|
47
|
+
this.onBoardState = onBoardState;
|
|
47
48
|
this.store = new PhaseStore({ baseDir: storeDir });
|
|
48
49
|
}
|
|
49
50
|
agent;
|
|
@@ -51,6 +52,7 @@ var AutoPhaseWebSocketHandler = class {
|
|
|
51
52
|
logger;
|
|
52
53
|
events;
|
|
53
54
|
projectRoot;
|
|
55
|
+
onBoardState;
|
|
54
56
|
orchestrator = null;
|
|
55
57
|
graph = null;
|
|
56
58
|
store;
|
|
@@ -395,6 +397,15 @@ Type: ${task.type}`;
|
|
|
395
397
|
if (!this.graph) return;
|
|
396
398
|
const state = this.buildState(activePhaseId);
|
|
397
399
|
this.broadcast({ type: "autophase.state", payload: state });
|
|
400
|
+
if (this.onBoardState) {
|
|
401
|
+
try {
|
|
402
|
+
this.onBoardState(this.graph.id, state);
|
|
403
|
+
} catch (err) {
|
|
404
|
+
this.logger.error(
|
|
405
|
+
`[AutoPhase] board-state tap failed: ${err instanceof Error ? err.message : String(err)}`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
398
409
|
}
|
|
399
410
|
buildState(activePhaseId) {
|
|
400
411
|
if (!this.graph) {
|
|
@@ -2618,10 +2629,7 @@ function cspSourceFromUrl(rawUrl) {
|
|
|
2618
2629
|
return void 0;
|
|
2619
2630
|
}
|
|
2620
2631
|
}
|
|
2621
|
-
var
|
|
2622
|
-
"'sha256-6PXDy0zrpXa6mvYOl11bZ8nubNUL7ushPUhGDZtaexg='",
|
|
2623
|
-
"'sha256-6sIdwbEBx7jj0drqSHHm7MqvmoYD3CQ4lp8Zp8blcb0='"
|
|
2624
|
-
];
|
|
2632
|
+
var EXTRA_SCRIPT_SOURCES = ["'wasm-unsafe-eval'"];
|
|
2625
2633
|
function buildCspHeader(wsPort, requestHost, publicWsUrl) {
|
|
2626
2634
|
const connect = /* @__PURE__ */ new Set([
|
|
2627
2635
|
"'self'",
|
|
@@ -2635,7 +2643,7 @@ function buildCspHeader(wsPort, requestHost, publicWsUrl) {
|
|
|
2635
2643
|
}
|
|
2636
2644
|
const publicWsSource = publicWsUrl ? cspSourceFromUrl(publicWsUrl) : void 0;
|
|
2637
2645
|
if (publicWsSource) connect.add(publicWsSource);
|
|
2638
|
-
const scriptSrc = ["'self'", ...
|
|
2646
|
+
const scriptSrc = ["'self'", ...EXTRA_SCRIPT_SOURCES].join(" ");
|
|
2639
2647
|
return `default-src 'self'; script-src ${scriptSrc}; style-src 'self' 'unsafe-inline'; connect-src ${Array.from(connect).join(" ")}; img-src 'self' data:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`;
|
|
2640
2648
|
}
|
|
2641
2649
|
function isInsideDist(candidate, distDir) {
|
|
@@ -3285,8 +3293,24 @@ function errorMessage(err) {
|
|
|
3285
3293
|
}
|
|
3286
3294
|
|
|
3287
3295
|
// src/server/memory-handlers.ts
|
|
3296
|
+
function isSuperMemoryStore(store) {
|
|
3297
|
+
const s = store;
|
|
3298
|
+
return typeof s.stats === "function" && typeof s.listSuper === "function" && typeof s.getSuperMemory === "function" && typeof s.updateSuperMemory === "function" && typeof s.deleteSuperMemory === "function";
|
|
3299
|
+
}
|
|
3300
|
+
function requiresSuperMemory(command) {
|
|
3301
|
+
return `\`${command}\` requires the Super Memory backend (superMemory.enabled).`;
|
|
3302
|
+
}
|
|
3288
3303
|
async function handleMemoryList(ws, memoryStore) {
|
|
3289
3304
|
try {
|
|
3305
|
+
if (isSuperMemoryStore(memoryStore)) {
|
|
3306
|
+
const [stats, memories] = await Promise.all([
|
|
3307
|
+
memoryStore.stats(),
|
|
3308
|
+
memoryStore.listSuper()
|
|
3309
|
+
]);
|
|
3310
|
+
const text2 = memories.length === 0 ? "\u{1F9E0} Super Memory is empty." : formatSuperMemoryText(stats, memories);
|
|
3311
|
+
send(ws, { type: "memory.list", payload: { text: text2 } });
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3290
3314
|
const text = await memoryStore.readAll();
|
|
3291
3315
|
send(ws, { type: "memory.list", payload: { text } });
|
|
3292
3316
|
} catch (err) {
|
|
@@ -3296,26 +3320,128 @@ async function handleMemoryList(ws, memoryStore) {
|
|
|
3296
3320
|
});
|
|
3297
3321
|
}
|
|
3298
3322
|
}
|
|
3299
|
-
|
|
3300
|
-
const
|
|
3323
|
+
function formatSuperMemoryText(stats, memories) {
|
|
3324
|
+
const active = stats.byStatus["active"] ?? 0;
|
|
3325
|
+
const stale = stats.byStatus["stale"] ?? 0;
|
|
3326
|
+
const archived = stats.byStatus["archived"] ?? 0;
|
|
3327
|
+
const lines = [
|
|
3328
|
+
"## \u{1F9E0} Super Memory",
|
|
3329
|
+
"",
|
|
3330
|
+
`**Total:** ${stats.total} \xB7 \u{1F7E2} ${active} active \xB7 \u{1F7E1} ${stale} stale \xB7 \u{1F535} ${archived} archived \xB7 **edges:** ${stats.edges}`,
|
|
3331
|
+
""
|
|
3332
|
+
];
|
|
3333
|
+
for (const m of memories) {
|
|
3334
|
+
const tags = m.tags.length > 0 ? ` \`${m.tags.slice(0, 3).join("` `")}\`` : "";
|
|
3335
|
+
const icon = m.status === "active" ? "\u{1F7E2}" : m.status === "stale" ? "\u{1F7E1}" : m.status === "archived" ? "\u{1F535}" : "\u26AA";
|
|
3336
|
+
const preview = m.text.replace(/\s+/g, " ").trim().slice(0, 140);
|
|
3337
|
+
lines.push(`- ${icon} \`${m.id.slice(0, 12)}\u2026\` [${m.kind}] ${preview}${tags}`);
|
|
3338
|
+
}
|
|
3339
|
+
return lines.join("\n");
|
|
3340
|
+
}
|
|
3341
|
+
async function handleSuperMemoryList(ws, memoryStore) {
|
|
3342
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3343
|
+
send(ws, { type: "memory.super.list", payload: { error: requiresSuperMemory("memory.super.list") } });
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3301
3346
|
try {
|
|
3302
|
-
|
|
3303
|
-
|
|
3347
|
+
const [stats, memories] = await Promise.all([
|
|
3348
|
+
memoryStore.stats(),
|
|
3349
|
+
memoryStore.listSuper()
|
|
3350
|
+
]);
|
|
3351
|
+
send(ws, { type: "memory.super.list", payload: { memories, stats } });
|
|
3304
3352
|
} catch (err) {
|
|
3305
|
-
|
|
3353
|
+
send(ws, { type: "memory.super.list", payload: { error: errMessage(err) } });
|
|
3306
3354
|
}
|
|
3307
3355
|
}
|
|
3308
|
-
async function
|
|
3309
|
-
|
|
3356
|
+
async function handleSuperMemoryGet(ws, msg, memoryStore) {
|
|
3357
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3358
|
+
send(ws, { type: "memory.super.get", payload: { error: requiresSuperMemory("memory.super.get") } });
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3361
|
+
const { id } = msg.payload;
|
|
3362
|
+
if (!id) {
|
|
3363
|
+
send(ws, { type: "memory.super.get", payload: { error: "id is required" } });
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3310
3366
|
try {
|
|
3311
|
-
const
|
|
3312
|
-
|
|
3313
|
-
ws,
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
);
|
|
3367
|
+
const memory = await memoryStore.getSuperMemory(id);
|
|
3368
|
+
if (!memory) {
|
|
3369
|
+
send(ws, { type: "memory.super.get", payload: { error: `Memory "${id}" not found.` } });
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
send(ws, { type: "memory.super.get", payload: { memory } });
|
|
3317
3373
|
} catch (err) {
|
|
3318
|
-
|
|
3374
|
+
send(ws, { type: "memory.super.get", payload: { error: errMessage(err) } });
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
async function handleSuperMemoryUpdate(ws, msg, memoryStore) {
|
|
3378
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3379
|
+
send(ws, { type: "memory.super.update", payload: { error: requiresSuperMemory("memory.super.update") } });
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
const payload = msg.payload;
|
|
3383
|
+
const id = payload["id"];
|
|
3384
|
+
if (!id) {
|
|
3385
|
+
send(ws, { type: "memory.super.update", payload: { error: "id is required" } });
|
|
3386
|
+
return;
|
|
3387
|
+
}
|
|
3388
|
+
const patch = { ...payload };
|
|
3389
|
+
delete patch["id"];
|
|
3390
|
+
if (Object.keys(patch).length === 0) {
|
|
3391
|
+
send(ws, { type: "memory.super.update", payload: { error: "No fields to update." } });
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
try {
|
|
3395
|
+
const memory = await memoryStore.updateSuperMemory(id, patch);
|
|
3396
|
+
send(ws, { type: "memory.super.update", payload: { memory } });
|
|
3397
|
+
} catch (err) {
|
|
3398
|
+
send(ws, { type: "memory.super.update", payload: { error: errMessage(err) } });
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
async function handleSuperMemoryRemember(ws, msg, memoryStore) {
|
|
3402
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3403
|
+
send(ws, { type: "memory.super.remember", payload: { error: requiresSuperMemory("memory.super.remember") } });
|
|
3404
|
+
return;
|
|
3405
|
+
}
|
|
3406
|
+
const payload = msg.payload;
|
|
3407
|
+
const text = payload["text"];
|
|
3408
|
+
if (!text || !text.trim()) {
|
|
3409
|
+
send(ws, { type: "memory.super.remember", payload: { error: "text is required" } });
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
try {
|
|
3413
|
+
const memory = await memoryStore.rememberSuper({
|
|
3414
|
+
text: text.trim(),
|
|
3415
|
+
kind: payload["kind"],
|
|
3416
|
+
scope: payload["scope"],
|
|
3417
|
+
tags: payload["tags"],
|
|
3418
|
+
anchors: payload["anchors"],
|
|
3419
|
+
importance: payload["importance"],
|
|
3420
|
+
confidence: payload["confidence"],
|
|
3421
|
+
freshness: payload["freshness"],
|
|
3422
|
+
supersedes: payload["supersedes"],
|
|
3423
|
+
contradicts: payload["contradicts"]
|
|
3424
|
+
});
|
|
3425
|
+
send(ws, { type: "memory.super.remember", payload: { memory } });
|
|
3426
|
+
} catch (err) {
|
|
3427
|
+
send(ws, { type: "memory.super.remember", payload: { error: errMessage(err) } });
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
async function handleSuperMemoryDelete(ws, msg, memoryStore) {
|
|
3431
|
+
if (!isSuperMemoryStore(memoryStore)) {
|
|
3432
|
+
send(ws, { type: "memory.super.delete", payload: { success: false, message: requiresSuperMemory("memory.super.delete") } });
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
const { id, reason } = msg.payload;
|
|
3436
|
+
if (!id) {
|
|
3437
|
+
send(ws, { type: "memory.super.delete", payload: { success: false, message: "id is required" } });
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
try {
|
|
3441
|
+
await memoryStore.deleteSuperMemory(id, reason);
|
|
3442
|
+
send(ws, { type: "memory.super.delete", payload: { success: true, message: `Deleted memory "${id}".` } });
|
|
3443
|
+
} catch (err) {
|
|
3444
|
+
send(ws, { type: "memory.super.delete", payload: { success: false, message: errMessage(err) } });
|
|
3319
3445
|
}
|
|
3320
3446
|
}
|
|
3321
3447
|
|
|
@@ -3365,6 +3491,16 @@ function openBrowser(url, platform = process.platform) {
|
|
|
3365
3491
|
// src/server/port-utils.ts
|
|
3366
3492
|
import * as net from "node:net";
|
|
3367
3493
|
import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core";
|
|
3494
|
+
var SURFACE_DEFAULT_PORTS = {
|
|
3495
|
+
webui: { http: 3456, ws: 3457 },
|
|
3496
|
+
simpleui: { http: 3466, ws: 3467 }
|
|
3497
|
+
};
|
|
3498
|
+
function surfaceLabel(surface) {
|
|
3499
|
+
return surface === "webui" ? "WebUI" : "SimpleUI";
|
|
3500
|
+
}
|
|
3501
|
+
function getSurfaceDefaultPorts(surface) {
|
|
3502
|
+
return { ...SURFACE_DEFAULT_PORTS[surface] };
|
|
3503
|
+
}
|
|
3368
3504
|
function isPortFree(host, port) {
|
|
3369
3505
|
return new Promise((resolve10) => {
|
|
3370
3506
|
const srv = net.createServer();
|
|
@@ -3921,6 +4057,8 @@ var SddBoardWebSocketHandler = class {
|
|
|
3921
4057
|
import * as path9 from "node:path";
|
|
3922
4058
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3923
4059
|
import {
|
|
4060
|
+
DefaultTaskStore,
|
|
4061
|
+
TaskTracker,
|
|
3924
4062
|
WorktreeManager as WorktreeManager2,
|
|
3925
4063
|
ToolValidationError as ToolValidationError4
|
|
3926
4064
|
} from "@wrongstack/core";
|
|
@@ -3936,6 +4074,63 @@ import {
|
|
|
3936
4074
|
startSddRun,
|
|
3937
4075
|
TaskGraphStore
|
|
3938
4076
|
} from "@wrongstack/sdd";
|
|
4077
|
+
function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
4078
|
+
const runTracker = tracker ?? (() => {
|
|
4079
|
+
const t = new TaskTracker({ store: new DefaultTaskStore() });
|
|
4080
|
+
t.setGraph(graph);
|
|
4081
|
+
return t;
|
|
4082
|
+
})();
|
|
4083
|
+
const worktreesEnabled = config.worktrees ?? process.env["WRONGSTACK_SDD_WORKTREES"] !== "0";
|
|
4084
|
+
let worktrees;
|
|
4085
|
+
if (worktreesEnabled) {
|
|
4086
|
+
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
4087
|
+
cwd: deps2.projectRoot,
|
|
4088
|
+
encoding: "utf8",
|
|
4089
|
+
windowsHide: true
|
|
4090
|
+
}).stdout?.trim() === "true";
|
|
4091
|
+
if (inGit) {
|
|
4092
|
+
void cleanupStaleSddWorktrees({
|
|
4093
|
+
projectRoot: deps2.projectRoot,
|
|
4094
|
+
boardsDir: deps2.projectSddBoards
|
|
4095
|
+
}).catch(() => void 0);
|
|
4096
|
+
worktrees = new WorktreeManager2({
|
|
4097
|
+
projectRoot: deps2.projectRoot,
|
|
4098
|
+
events: deps2.events,
|
|
4099
|
+
sessionId: () => deps2.agent.ctx.session?.id
|
|
4100
|
+
});
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
const superviseFailure = deps2.brain && deps2.runIsolatedTurn ? new SddSupervisor({
|
|
4104
|
+
brain: deps2.brain,
|
|
4105
|
+
reassignModels: config.fallbackModels,
|
|
4106
|
+
generateSubtasks: makeLlmSubtaskGenerator({
|
|
4107
|
+
run: (prompt) => deps2.runIsolatedTurn(prompt, "Task Splitter")
|
|
4108
|
+
}),
|
|
4109
|
+
requestLlmVerdict: true
|
|
4110
|
+
}).superviseFailure : deps2.brain ? new SddSupervisor({
|
|
4111
|
+
brain: deps2.brain,
|
|
4112
|
+
reassignModels: config.fallbackModels,
|
|
4113
|
+
requestLlmVerdict: true
|
|
4114
|
+
}).superviseFailure : void 0;
|
|
4115
|
+
return startSddRun({
|
|
4116
|
+
tracker: runTracker,
|
|
4117
|
+
graph,
|
|
4118
|
+
agent: deps2.agent,
|
|
4119
|
+
projectRoot: deps2.projectRoot,
|
|
4120
|
+
events: deps2.events,
|
|
4121
|
+
sessionId: () => deps2.agent.ctx.session?.id,
|
|
4122
|
+
subagentFactory: deps2.subagentFactory,
|
|
4123
|
+
boardStore: new SddBoardStore2({ baseDir: deps2.projectSddBoards }),
|
|
4124
|
+
registry: deps2.registry ?? new SddRunRegistry(),
|
|
4125
|
+
verifyTask: makeCommandVerifier(),
|
|
4126
|
+
...superviseFailure ? { superviseFailure } : {},
|
|
4127
|
+
...config.parallelSlots !== void 0 ? { parallelSlots: config.parallelSlots } : {},
|
|
4128
|
+
...config.defaultModel !== void 0 ? { defaultModel: config.defaultModel } : {},
|
|
4129
|
+
...config.defaultProvider !== void 0 ? { defaultProvider: config.defaultProvider } : {},
|
|
4130
|
+
...config.fallbackModels !== void 0 ? { fallbackModels: config.fallbackModels } : {},
|
|
4131
|
+
...worktrees ? { worktrees } : {}
|
|
4132
|
+
});
|
|
4133
|
+
}
|
|
3939
4134
|
var PLANNING_ONLY_GUARD = "SYSTEM: You are running a PLANNING-ONLY specification interview. Do NOT write, create, or edit any files, and do NOT run shell/terminal commands or use any code-editing tools \u2014 they are disabled here and any attempt will fail and waste the turn. Respond with TEXT ONLY: ask your question, or emit the requested spec / plan / task JSON. All code is written later, automatically, once the plan is approved and the multi-agent run starts.\n\n---\n\n";
|
|
3940
4135
|
function buildSddWizardDeps(opts) {
|
|
3941
4136
|
const registry = new SddRunRegistry();
|
|
@@ -3971,64 +4166,27 @@ function buildSddWizardDeps(opts) {
|
|
|
3971
4166
|
field: "taskGraph"
|
|
3972
4167
|
});
|
|
3973
4168
|
}
|
|
3974
|
-
const
|
|
3975
|
-
let worktrees;
|
|
3976
|
-
if (worktreesEnabled) {
|
|
3977
|
-
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3978
|
-
cwd: opts.projectRoot,
|
|
3979
|
-
encoding: "utf8",
|
|
3980
|
-
windowsHide: true
|
|
3981
|
-
}).stdout?.trim() === "true";
|
|
3982
|
-
if (inGit) {
|
|
3983
|
-
await cleanupStaleSddWorktrees({
|
|
3984
|
-
projectRoot: opts.projectRoot,
|
|
3985
|
-
boardsDir: opts.paths.projectSddBoards
|
|
3986
|
-
}).catch(() => void 0);
|
|
3987
|
-
worktrees = new WorktreeManager2({
|
|
3988
|
-
projectRoot: opts.projectRoot,
|
|
3989
|
-
events: opts.events,
|
|
3990
|
-
sessionId: () => opts.agent.ctx.session?.id
|
|
3991
|
-
});
|
|
3992
|
-
}
|
|
3993
|
-
}
|
|
3994
|
-
const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
|
|
3995
|
-
const verifyTask = makeCommandVerifier();
|
|
3996
|
-
const superviseFailure = opts.brain ? new SddSupervisor({
|
|
3997
|
-
brain: opts.brain,
|
|
3998
|
-
// The run-level fallback chain (chosen in the wizard) doubles as the
|
|
3999
|
-
// supervisor's reassign options — a `reassign` verdict rotates the
|
|
4000
|
-
// worker model on retry. Empty/undefined → reassign option dropped.
|
|
4001
|
-
reassignModels: fallbackModels,
|
|
4002
|
-
// LLM auto-split: decompose a retry-exhausted task into smaller
|
|
4003
|
-
// sub-tasks on an isolated read-only turn. Heavily validated +
|
|
4004
|
-
// bounded; an empty result degrades the split into a retry.
|
|
4005
|
-
generateSubtasks: makeLlmSubtaskGenerator({
|
|
4006
|
-
run: (prompt) => runIsolatedTurn(prompt, "Task Splitter")
|
|
4007
|
-
}),
|
|
4008
|
-
// The standalone brain is a tiered policy→LLM arbiter with NO
|
|
4009
|
-
// human-escalation wrapper (see index.ts), so it never blocks on a
|
|
4010
|
-
// human prompt — an unresolved verdict degrades to a bounded retry.
|
|
4011
|
-
// Safe to let the LLM layer actually pick reassign/split.
|
|
4012
|
-
requestLlmVerdict: true
|
|
4013
|
-
}).superviseFailure : void 0;
|
|
4014
|
-
const handle = startSddRun({
|
|
4015
|
-
tracker,
|
|
4169
|
+
const handle = startSddRunFromGraph(
|
|
4016
4170
|
graph,
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4171
|
+
{
|
|
4172
|
+
agent: opts.agent,
|
|
4173
|
+
events: opts.events,
|
|
4174
|
+
projectRoot: opts.projectRoot,
|
|
4175
|
+
subagentFactory: opts.subagentFactory,
|
|
4176
|
+
projectSddBoards: opts.paths.projectSddBoards,
|
|
4177
|
+
registry,
|
|
4178
|
+
runIsolatedTurn,
|
|
4179
|
+
...opts.brain ? { brain: opts.brain } : {}
|
|
4180
|
+
},
|
|
4181
|
+
{
|
|
4182
|
+
...parallelSlots !== void 0 ? { parallelSlots } : {},
|
|
4183
|
+
...defaultModel !== void 0 ? { defaultModel } : {},
|
|
4184
|
+
...defaultProvider !== void 0 ? { defaultProvider } : {},
|
|
4185
|
+
...fallbackModels !== void 0 ? { fallbackModels } : {},
|
|
4186
|
+
...useWorktrees !== void 0 ? { worktrees: useWorktrees } : {}
|
|
4187
|
+
},
|
|
4188
|
+
tracker
|
|
4189
|
+
);
|
|
4032
4190
|
void handle.completion.catch(() => {
|
|
4033
4191
|
});
|
|
4034
4192
|
return { runId: handle.runId };
|
|
@@ -4172,6 +4330,31 @@ var SddWizardWebSocketHandler = class {
|
|
|
4172
4330
|
}
|
|
4173
4331
|
};
|
|
4174
4332
|
|
|
4333
|
+
// src/server/session-history.ts
|
|
4334
|
+
function toSessionHistoryEntry(summary, currentSessionId) {
|
|
4335
|
+
return {
|
|
4336
|
+
id: summary.id,
|
|
4337
|
+
title: summary.title,
|
|
4338
|
+
...summary.name !== void 0 ? { name: summary.name } : {},
|
|
4339
|
+
startedAt: summary.startedAt,
|
|
4340
|
+
...summary.endedAt !== void 0 ? { endedAt: summary.endedAt } : {},
|
|
4341
|
+
model: summary.model,
|
|
4342
|
+
provider: summary.provider,
|
|
4343
|
+
tokenTotal: summary.tokenTotal,
|
|
4344
|
+
...summary.iterationCount !== void 0 ? { iterationCount: summary.iterationCount } : {},
|
|
4345
|
+
...summary.toolCallCount !== void 0 ? { toolCallCount: summary.toolCallCount } : {},
|
|
4346
|
+
...summary.toolErrorCount !== void 0 ? { toolErrorCount: summary.toolErrorCount } : {},
|
|
4347
|
+
...summary.fileChangeCount !== void 0 ? { fileChangeCount: summary.fileChangeCount } : {},
|
|
4348
|
+
...summary.toolBreakdown !== void 0 ? { toolBreakdown: summary.toolBreakdown } : {},
|
|
4349
|
+
...summary.compactionCount !== void 0 ? { compactionCount: summary.compactionCount } : {},
|
|
4350
|
+
...summary.outcome !== void 0 ? { outcome: summary.outcome } : {},
|
|
4351
|
+
isCurrent: summary.id === currentSessionId
|
|
4352
|
+
};
|
|
4353
|
+
}
|
|
4354
|
+
function toSessionHistoryEntries(summaries, currentSessionId) {
|
|
4355
|
+
return summaries.map((summary) => toSessionHistoryEntry(summary, currentSessionId));
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4175
4358
|
// src/server/shell-open.ts
|
|
4176
4359
|
import * as fs8 from "node:fs/promises";
|
|
4177
4360
|
import * as path10 from "node:path";
|
|
@@ -4465,7 +4648,8 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4465
4648
|
"uiLocale",
|
|
4466
4649
|
"thinkingWord",
|
|
4467
4650
|
"refinerProvider",
|
|
4468
|
-
"refinerModel"
|
|
4651
|
+
"refinerModel",
|
|
4652
|
+
"refinerFallbackProfile"
|
|
4469
4653
|
]);
|
|
4470
4654
|
var ENUM_PREF_KEYS = {
|
|
4471
4655
|
autonomy: AUTONOMY_VALUES,
|
|
@@ -6417,6 +6601,18 @@ function setupEvents(deps2) {
|
|
|
6417
6601
|
payload: sessionPayload2({ event: eventName, ...payload })
|
|
6418
6602
|
});
|
|
6419
6603
|
});
|
|
6604
|
+
events.onPattern("cron:state_snapshot", (_eventName, payload) => {
|
|
6605
|
+
broadcast2(clients, {
|
|
6606
|
+
type: "cron.snapshot",
|
|
6607
|
+
payload
|
|
6608
|
+
});
|
|
6609
|
+
});
|
|
6610
|
+
events.onPattern("cron:job_fired", (_eventName, payload) => {
|
|
6611
|
+
broadcast2(clients, {
|
|
6612
|
+
type: "cron.job_fired",
|
|
6613
|
+
payload
|
|
6614
|
+
});
|
|
6615
|
+
});
|
|
6420
6616
|
on("client.status", async (e) => {
|
|
6421
6617
|
broadcast2(clients, { type: "client.status_update", payload: e });
|
|
6422
6618
|
if (wpaths?.projectStatus) {
|
|
@@ -6766,9 +6962,11 @@ function positiveNumber(value) {
|
|
|
6766
6962
|
// src/server/server-runtime.ts
|
|
6767
6963
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
6768
6964
|
async function resolvePorts(opts) {
|
|
6769
|
-
const
|
|
6965
|
+
const surface = opts.surface ?? "webui";
|
|
6966
|
+
const surfaceDefaults = surface === "simpleui" ? { http: 3466, ws: 3467 } : { http: 3456, ws: 3457 };
|
|
6967
|
+
const requestedWsPort = opts.wsPort ?? surfaceDefaults.ws;
|
|
6770
6968
|
const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
|
|
6771
|
-
const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ??
|
|
6969
|
+
const requestedHttpPort = opts.httpPort ?? opts.webuiPort ?? opts.port ?? Number.parseInt(process.env["WEBUI_PORT"] ?? process.env["PORT"] ?? String(surfaceDefaults.http), 10);
|
|
6772
6970
|
const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
|
|
6773
6971
|
const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
|
|
6774
6972
|
const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
|
|
@@ -6926,8 +7124,8 @@ function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
|
6926
7124
|
if (explicitDistDir) return path13.resolve(explicitDistDir);
|
|
6927
7125
|
try {
|
|
6928
7126
|
const requireFromHere2 = createRequire(fromUrl);
|
|
6929
|
-
const serverEntry = requireFromHere2.resolve("@wrongstack/webui
|
|
6930
|
-
return path13.
|
|
7127
|
+
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
7128
|
+
return path13.dirname(serverEntry);
|
|
6931
7129
|
} catch {
|
|
6932
7130
|
return path13.resolve(path13.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
6933
7131
|
}
|
|
@@ -6950,7 +7148,7 @@ function startHttpServer(opts) {
|
|
|
6950
7148
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
6951
7149
|
if (opts.openBrowser) openBrowser(openUrl);
|
|
6952
7150
|
void registerInstance(
|
|
6953
|
-
{ pid: process.pid, httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path13.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
|
|
7151
|
+
{ pid: process.pid, surface: "webui", httpPort: opts.httpPort, wsPort: opts.wsPort, host: opts.wsHost, projectRoot: opts.projectRoot, projectName: path13.basename(opts.projectRoot) || opts.projectRoot, startedAt: (/* @__PURE__ */ new Date()).toISOString(), url: buildWebUIAccessUrl({ host: opts.wsHost, port: opts.httpPort, publicUrl: opts.publicUrl }) },
|
|
6954
7152
|
registryBaseDir
|
|
6955
7153
|
).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
|
|
6956
7154
|
});
|
|
@@ -7017,6 +7215,10 @@ import {
|
|
|
7017
7215
|
rememberTool,
|
|
7018
7216
|
searchMemoryTool
|
|
7019
7217
|
} from "@wrongstack/tools";
|
|
7218
|
+
import {
|
|
7219
|
+
attachSessionKanbanMirror,
|
|
7220
|
+
hydrateSessionKanban
|
|
7221
|
+
} from "@wrongstack/tools/session-kanban";
|
|
7020
7222
|
import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
7021
7223
|
|
|
7022
7224
|
// src/server/setup-screen.ts
|
|
@@ -7157,6 +7359,7 @@ function seedContextMeta(config, context) {
|
|
|
7157
7359
|
}
|
|
7158
7360
|
meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
|
|
7159
7361
|
meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
|
|
7362
|
+
meta["refinerFallbackProfile"] = autonomyCfg["refinerFallbackProfile"] ?? "";
|
|
7160
7363
|
meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
|
|
7161
7364
|
meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
|
|
7162
7365
|
meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
|
|
@@ -7490,12 +7693,13 @@ async function createPreContextServices(input) {
|
|
|
7490
7693
|
})();
|
|
7491
7694
|
const memoryStore = container.resolve(TOKENS.MemoryStore);
|
|
7492
7695
|
if (config.features.memory) {
|
|
7493
|
-
toolRegistry.register(rememberTool(memoryStore));
|
|
7494
|
-
toolRegistry.register(forgetTool(memoryStore));
|
|
7495
|
-
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
7496
|
-
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
7497
7696
|
if (isSuperMemoryService(memoryStore)) {
|
|
7498
7697
|
for (const tool of createSuperMemoryTools(memoryStore)) toolRegistry.register(tool);
|
|
7698
|
+
} else {
|
|
7699
|
+
toolRegistry.register(rememberTool(memoryStore));
|
|
7700
|
+
toolRegistry.register(forgetTool(memoryStore));
|
|
7701
|
+
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
7702
|
+
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
7499
7703
|
}
|
|
7500
7704
|
}
|
|
7501
7705
|
toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
|
|
@@ -7624,6 +7828,9 @@ async function createPreContextServices(input) {
|
|
|
7624
7828
|
const promptsCtx = { promptLoader, promptUsage };
|
|
7625
7829
|
const systemPromptBuilder = new DefaultSystemPromptBuilder({
|
|
7626
7830
|
memoryStore,
|
|
7831
|
+
// Super Memory's turn middleware is the single memory-injection channel;
|
|
7832
|
+
// don't also inject a static memory section here (avoids double injection).
|
|
7833
|
+
injectMemory: false,
|
|
7627
7834
|
skillLoader,
|
|
7628
7835
|
modeStore,
|
|
7629
7836
|
modeId,
|
|
@@ -7674,6 +7881,8 @@ async function createPreContextServices(input) {
|
|
|
7674
7881
|
"task.path",
|
|
7675
7882
|
sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
|
|
7676
7883
|
);
|
|
7884
|
+
await hydrateSessionKanban(context);
|
|
7885
|
+
attachSessionKanbanMirror(context);
|
|
7677
7886
|
seedContextMeta(config, context);
|
|
7678
7887
|
return {
|
|
7679
7888
|
modelsRegistry,
|
|
@@ -9807,8 +10016,10 @@ import {
|
|
|
9807
10016
|
serializeTaskGraph
|
|
9808
10017
|
} from "@wrongstack/core";
|
|
9809
10018
|
import {
|
|
10019
|
+
addCheckToTask,
|
|
9810
10020
|
addColumn,
|
|
9811
10021
|
addGoalMetricToTask,
|
|
10022
|
+
addNoteToTask,
|
|
9812
10023
|
addTask,
|
|
9813
10024
|
assignTask,
|
|
9814
10025
|
claimReadyTask,
|
|
@@ -9822,29 +10033,47 @@ import {
|
|
|
9822
10033
|
getKanbanQueueHealth,
|
|
9823
10034
|
getTask,
|
|
9824
10035
|
getTaskChain,
|
|
9825
|
-
listReadyTasks,
|
|
9826
10036
|
listBoards,
|
|
10037
|
+
listReadyTasks,
|
|
9827
10038
|
mergeTasks,
|
|
9828
10039
|
moveTask,
|
|
9829
10040
|
parseLinesIntoTasks,
|
|
10041
|
+
reconcileKanbanBoard,
|
|
10042
|
+
recoverStaleTaskAssignments,
|
|
10043
|
+
releaseTaskClaim,
|
|
9830
10044
|
removeBoard,
|
|
9831
10045
|
removeColumn,
|
|
9832
10046
|
removeTask,
|
|
9833
|
-
releaseTaskClaim,
|
|
9834
10047
|
setTaskChain,
|
|
9835
10048
|
splitTask,
|
|
9836
10049
|
syncBoardFromTaskGraph,
|
|
9837
10050
|
transferTaskToBoard,
|
|
9838
10051
|
updateBoard,
|
|
10052
|
+
updateCheckOnTask,
|
|
9839
10053
|
updateGoalMetricOnTask,
|
|
9840
10054
|
updateTask
|
|
9841
10055
|
} from "@wrongstack/kanban";
|
|
10056
|
+
import { applySessionKanbanTaskToSource } from "@wrongstack/tools/session-kanban";
|
|
10057
|
+
async function syncSessionSource(ctx, task, remove = false) {
|
|
10058
|
+
if (!ctx.context) return;
|
|
10059
|
+
const update = await applySessionKanbanTaskToSource(ctx.context, task, { remove });
|
|
10060
|
+
const sessionId = ctx.context.session?.id ?? "";
|
|
10061
|
+
if (update.todos)
|
|
10062
|
+
ctx.broadcast?.({ type: "todos.updated", payload: { sessionId, todos: update.todos } });
|
|
10063
|
+
if (update.tasks)
|
|
10064
|
+
ctx.broadcast?.({ type: "tasks.updated", payload: { sessionId, tasks: update.tasks.tasks } });
|
|
10065
|
+
if (update.plan)
|
|
10066
|
+
ctx.broadcast?.({ type: "plan.updated", payload: { sessionId, plan: update.plan } });
|
|
10067
|
+
}
|
|
9842
10068
|
function ok(ws, type, data) {
|
|
9843
10069
|
send(ws, { type, payload: { success: true, data: data ?? null } });
|
|
9844
10070
|
}
|
|
9845
10071
|
function fail(ws, type, message) {
|
|
9846
10072
|
send(ws, { type, payload: { success: false, error: message } });
|
|
9847
10073
|
}
|
|
10074
|
+
function has(payload, key) {
|
|
10075
|
+
return payload !== void 0 && Object.hasOwn(payload, key);
|
|
10076
|
+
}
|
|
9848
10077
|
async function handleKanbanRoute(ws, msg, ctx) {
|
|
9849
10078
|
if (!msg.type.startsWith("kanban.")) return false;
|
|
9850
10079
|
const payload = msg.payload;
|
|
@@ -9873,6 +10102,38 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9873
10102
|
ok(ws, type, await getKanbanQueueHealth(ctx.projectRoot, { boardId: hBoardId }));
|
|
9874
10103
|
return true;
|
|
9875
10104
|
}
|
|
10105
|
+
case "kanban.supervisor.status":
|
|
10106
|
+
case "kanban.supervisor.audit": {
|
|
10107
|
+
const boardId = payload?.boardId;
|
|
10108
|
+
if (!boardId) {
|
|
10109
|
+
fail(ws, type, "boardId required");
|
|
10110
|
+
return true;
|
|
10111
|
+
}
|
|
10112
|
+
const board = await getBoard(ctx.projectRoot, boardId);
|
|
10113
|
+
if (!board) {
|
|
10114
|
+
fail(ws, type, `Board not found: ${boardId}`);
|
|
10115
|
+
return true;
|
|
10116
|
+
}
|
|
10117
|
+
const reconciled = await reconcileKanbanBoard(ctx.projectRoot, boardId);
|
|
10118
|
+
let health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
10119
|
+
const recovered = health.staleAssignments.count ? await recoverStaleTaskAssignments(ctx.projectRoot, boardId, {
|
|
10120
|
+
mode: board.supervisor?.recoveryMode ?? "auto",
|
|
10121
|
+
reason: "On-demand standalone Kanban supervisor audit."
|
|
10122
|
+
}) : null;
|
|
10123
|
+
if (recovered) health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
10124
|
+
const anomalyCount = health.staleAssignments.count + health.dependencyBlocked.count + health.failedRetryable.count + health.counts.failed + health.counts.blocked;
|
|
10125
|
+
ok(ws, type, {
|
|
10126
|
+
boardId,
|
|
10127
|
+
status: board.supervisor?.enabled === false ? "disabled" : anomalyCount ? "attention" : "healthy",
|
|
10128
|
+
mode: board.supervisor?.mode ?? "deterministic",
|
|
10129
|
+
lastAuditAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10130
|
+
reconciledTaskIds: reconciled?.tasks.map((task) => task.id) ?? [],
|
|
10131
|
+
staleRecoveredTaskIds: recovered?.tasks.map((task) => task.id) ?? [],
|
|
10132
|
+
anomalyCount,
|
|
10133
|
+
summary: `${health.counts.running} running \xB7 ${health.counts.ready} ready \xB7 ${health.counts.review} review \xB7 ${health.counts.blocked} blocked \xB7 ${health.counts.failed} failed`
|
|
10134
|
+
});
|
|
10135
|
+
return true;
|
|
10136
|
+
}
|
|
9876
10137
|
case "kanban.create": {
|
|
9877
10138
|
const title = payload?.title;
|
|
9878
10139
|
if (!title) {
|
|
@@ -9901,7 +10162,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9901
10162
|
...payload?.title ? { title: payload.title } : {},
|
|
9902
10163
|
...payload?.description ? { description: payload.description } : {},
|
|
9903
10164
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
9904
|
-
...payload?.columns ? { columns: payload.columns } : {}
|
|
10165
|
+
...payload?.columns ? { columns: payload.columns } : {},
|
|
10166
|
+
...has(payload, "supervisor") ? {
|
|
10167
|
+
supervisor: payload?.supervisor ?? null
|
|
10168
|
+
} : {}
|
|
9905
10169
|
});
|
|
9906
10170
|
board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
|
|
9907
10171
|
return true;
|
|
@@ -9928,6 +10192,11 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9928
10192
|
return true;
|
|
9929
10193
|
}
|
|
9930
10194
|
const board = await getBoard(ctx.projectRoot, boardId);
|
|
10195
|
+
const activeSessionId = ctx.context?.session?.id;
|
|
10196
|
+
if (activeSessionId && board?.tags?.includes(`session:${activeSessionId}`)) {
|
|
10197
|
+
fail(ws, type, "The active session Kanban board cannot be deleted.");
|
|
10198
|
+
return true;
|
|
10199
|
+
}
|
|
9931
10200
|
ok(ws, type, {
|
|
9932
10201
|
removed: board ? await removeBoard(ctx.projectRoot, board.id) : false,
|
|
9933
10202
|
boardId: board?.id ?? boardId
|
|
@@ -10108,15 +10377,31 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10108
10377
|
return true;
|
|
10109
10378
|
}
|
|
10110
10379
|
const board = await updateTask(ctx.projectRoot, boardId, taskId, {
|
|
10111
|
-
...payload
|
|
10112
|
-
...payload
|
|
10113
|
-
...payload
|
|
10114
|
-
...payload
|
|
10115
|
-
...payload
|
|
10116
|
-
...payload
|
|
10117
|
-
...payload
|
|
10380
|
+
...has(payload, "title") ? { title: payload?.title } : {},
|
|
10381
|
+
...has(payload, "description") ? { description: payload?.description ?? "" } : {},
|
|
10382
|
+
...has(payload, "columnId") ? { columnId: payload?.columnId } : {},
|
|
10383
|
+
...has(payload, "priority") ? { priority: payload?.priority } : {},
|
|
10384
|
+
...has(payload, "type") ? { type: payload?.type } : {},
|
|
10385
|
+
...has(payload, "status") ? { status: payload?.status } : {},
|
|
10386
|
+
...has(payload, "dependsOn") ? { dependsOn: payload?.dependsOn ?? [] } : {},
|
|
10387
|
+
...has(payload, "chain") ? { chain: payload?.chain ?? null } : {},
|
|
10388
|
+
...has(payload, "labels") ? { labels: payload?.labels ?? [] } : {},
|
|
10389
|
+
...has(payload, "estimatedHours") ? { estimatedHours: Number(payload?.estimatedHours ?? 0) } : {},
|
|
10390
|
+
...has(payload, "actualHours") ? { actualHours: Number(payload?.actualHours ?? 0) } : {},
|
|
10391
|
+
...has(payload, "retryPolicy") ? {
|
|
10392
|
+
retryPolicy: payload?.retryPolicy ?? null
|
|
10393
|
+
} : {},
|
|
10394
|
+
...has(payload, "costCeilingUsd") ? {
|
|
10395
|
+
costCeilingUsd: payload?.costCeilingUsd === null || payload?.costCeilingUsd === "" ? null : Number(payload?.costCeilingUsd)
|
|
10396
|
+
} : {}
|
|
10118
10397
|
});
|
|
10119
|
-
|
|
10398
|
+
if (!board) {
|
|
10399
|
+
fail(ws, type, "Board or task not found");
|
|
10400
|
+
return true;
|
|
10401
|
+
}
|
|
10402
|
+
const task = findTask(board.tasks, taskId);
|
|
10403
|
+
if (task) await syncSessionSource(ctx, task);
|
|
10404
|
+
ok(ws, type, task);
|
|
10120
10405
|
return true;
|
|
10121
10406
|
}
|
|
10122
10407
|
case "kanban.task.move": {
|
|
@@ -10134,7 +10419,13 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10134
10419
|
columnId,
|
|
10135
10420
|
payload?.order
|
|
10136
10421
|
);
|
|
10137
|
-
|
|
10422
|
+
if (!board) {
|
|
10423
|
+
fail(ws, type, "Move failed");
|
|
10424
|
+
return true;
|
|
10425
|
+
}
|
|
10426
|
+
const task = findTask(board.tasks, taskId);
|
|
10427
|
+
if (task) await syncSessionSource(ctx, task);
|
|
10428
|
+
ok(ws, type, task);
|
|
10138
10429
|
return true;
|
|
10139
10430
|
}
|
|
10140
10431
|
case "kanban.task.copy":
|
|
@@ -10193,8 +10484,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10193
10484
|
...payload?.role ? { role: payload.role } : {},
|
|
10194
10485
|
...payload?.provider ? { provider: payload.provider } : {},
|
|
10195
10486
|
...payload?.model ? { model: payload.model } : {},
|
|
10487
|
+
...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
|
|
10196
10488
|
...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
|
|
10197
10489
|
...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
|
|
10490
|
+
...payload?.skills ? { skills: payload.skills } : {},
|
|
10198
10491
|
...payload?.tools ? { tools: payload.tools } : {},
|
|
10199
10492
|
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
|
|
10200
10493
|
...payload?.assignee ? { assignee: payload.assignee } : {},
|
|
@@ -10213,7 +10506,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10213
10506
|
const board = await releaseTaskClaim(ctx.projectRoot, boardId, taskId, {
|
|
10214
10507
|
...payload?.status ? { status: payload.status } : {},
|
|
10215
10508
|
...payload?.reason ? { reason: payload.reason } : {},
|
|
10216
|
-
...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {}
|
|
10509
|
+
...typeof payload?.clearAssignee === "boolean" ? { clearAssignee: payload.clearAssignee } : {},
|
|
10510
|
+
...typeof payload?.maxAttempts === "number" ? { maxAttempts: payload.maxAttempts } : {},
|
|
10511
|
+
...typeof payload?.costCeilingUsd === "number" ? { costCeilingUsd: payload.costCeilingUsd } : {},
|
|
10512
|
+
...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
|
|
10217
10513
|
});
|
|
10218
10514
|
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10219
10515
|
return true;
|
|
@@ -10269,14 +10565,66 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10269
10565
|
...payload?.role ? { role: payload.role } : {},
|
|
10270
10566
|
...payload?.provider ? { provider: payload.provider } : {},
|
|
10271
10567
|
...payload?.model ? { model: payload.model } : {},
|
|
10568
|
+
...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
|
|
10272
10569
|
...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
|
|
10273
10570
|
...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
|
|
10571
|
+
...payload?.skills ? { skills: payload.skills } : {},
|
|
10274
10572
|
...payload?.tools ? { tools: payload.tools } : {},
|
|
10275
|
-
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {}
|
|
10573
|
+
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
|
|
10574
|
+
...payload?.assignee ? { assignee: payload.assignee } : {},
|
|
10575
|
+
...payload?.maxAttempts !== void 0 ? { maxAttempts: Number(payload.maxAttempts) } : {},
|
|
10576
|
+
...payload?.costCeilingUsd !== void 0 ? { costCeilingUsd: Number(payload.costCeilingUsd) } : {},
|
|
10577
|
+
...payload?.retryPolicy ? { retryPolicy: payload.retryPolicy } : {}
|
|
10276
10578
|
});
|
|
10277
10579
|
board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
|
|
10278
10580
|
return true;
|
|
10279
10581
|
}
|
|
10582
|
+
case "kanban.task.check.add": {
|
|
10583
|
+
const boardId = payload?.boardId;
|
|
10584
|
+
const taskId = payload?.taskId;
|
|
10585
|
+
const description = payload?.description;
|
|
10586
|
+
if (!boardId || !taskId || !description) {
|
|
10587
|
+
fail(ws, type, "boardId, taskId, and description required");
|
|
10588
|
+
return true;
|
|
10589
|
+
}
|
|
10590
|
+
const board = await addCheckToTask(ctx.projectRoot, boardId, taskId, {
|
|
10591
|
+
description,
|
|
10592
|
+
type: payload?.checkType ?? "manual",
|
|
10593
|
+
status: payload?.status ?? "pending"
|
|
10594
|
+
});
|
|
10595
|
+
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10596
|
+
return true;
|
|
10597
|
+
}
|
|
10598
|
+
case "kanban.task.check.update": {
|
|
10599
|
+
const boardId = payload?.boardId;
|
|
10600
|
+
const taskId = payload?.taskId;
|
|
10601
|
+
const checkId = payload?.checkId;
|
|
10602
|
+
if (!boardId || !taskId || !checkId) {
|
|
10603
|
+
fail(ws, type, "boardId, taskId, and checkId required");
|
|
10604
|
+
return true;
|
|
10605
|
+
}
|
|
10606
|
+
const board = await updateCheckOnTask(ctx.projectRoot, boardId, taskId, checkId, {
|
|
10607
|
+
...has(payload, "status") ? { status: payload?.status } : {}
|
|
10608
|
+
});
|
|
10609
|
+
if (!board) fail(ws, type, "Check not found");
|
|
10610
|
+
else ok(ws, type, (await reconcileKanbanBoard(ctx.projectRoot, boardId))?.board ?? board);
|
|
10611
|
+
return true;
|
|
10612
|
+
}
|
|
10613
|
+
case "kanban.task.note.add": {
|
|
10614
|
+
const boardId = payload?.boardId;
|
|
10615
|
+
const taskId = payload?.taskId;
|
|
10616
|
+
const content = payload?.content;
|
|
10617
|
+
if (!boardId || !taskId || !content) {
|
|
10618
|
+
fail(ws, type, "boardId, taskId, and content required");
|
|
10619
|
+
return true;
|
|
10620
|
+
}
|
|
10621
|
+
const board = await addNoteToTask(ctx.projectRoot, boardId, taskId, {
|
|
10622
|
+
author: payload?.author ?? "webui",
|
|
10623
|
+
content
|
|
10624
|
+
});
|
|
10625
|
+
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10626
|
+
return true;
|
|
10627
|
+
}
|
|
10280
10628
|
case "kanban.task.dispatch":
|
|
10281
10629
|
fail(
|
|
10282
10630
|
ws,
|
|
@@ -10300,7 +10648,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10300
10648
|
return true;
|
|
10301
10649
|
}
|
|
10302
10650
|
const board = await removeTask(ctx.projectRoot, boardId, taskId);
|
|
10303
|
-
|
|
10651
|
+
if (!board) {
|
|
10652
|
+
fail(ws, type, "Board or task not found");
|
|
10653
|
+
return true;
|
|
10654
|
+
}
|
|
10655
|
+
await syncSessionSource(ctx, task, true);
|
|
10656
|
+
ok(ws, type, { removed: true, boardId: board.id, taskId: task.id, board });
|
|
10304
10657
|
return true;
|
|
10305
10658
|
}
|
|
10306
10659
|
case "kanban.task.get": {
|
|
@@ -10612,6 +10965,7 @@ async function handleProviderRoute(ws, msg, routes) {
|
|
|
10612
10965
|
baseUrl,
|
|
10613
10966
|
apiKey
|
|
10614
10967
|
});
|
|
10968
|
+
await routes.adoptDefaultProviderIfUnset(id);
|
|
10615
10969
|
return true;
|
|
10616
10970
|
}
|
|
10617
10971
|
case "provider.remove": {
|
|
@@ -10850,7 +11204,12 @@ function createMessageDispatcher(opts) {
|
|
|
10850
11204
|
if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
|
|
10851
11205
|
if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
|
|
10852
11206
|
if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
|
|
10853
|
-
if (await handleKanbanRoute(ws, msg, {
|
|
11207
|
+
if (await handleKanbanRoute(ws, msg, {
|
|
11208
|
+
projectRoot: state.getProjectRoot(),
|
|
11209
|
+
context: deps2.context,
|
|
11210
|
+
broadcast: (message) => broadcast(state.getClients(), message)
|
|
11211
|
+
}))
|
|
11212
|
+
return;
|
|
10854
11213
|
if (msg.type.startsWith("worktree.") && await deps2.worktreeHandler.handleMessage(
|
|
10855
11214
|
msg
|
|
10856
11215
|
))
|
|
@@ -10899,17 +11258,14 @@ function createMessageDispatcher(opts) {
|
|
|
10899
11258
|
let input = content;
|
|
10900
11259
|
const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
|
|
10901
11260
|
if (imageBlocks.length > 0) {
|
|
10902
|
-
const routed = await routeImagesForModel(
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
model: deps2.agent.ctx.model
|
|
10911
|
-
}
|
|
10912
|
-
);
|
|
11261
|
+
const routed = await routeImagesForModel(buildUserContentBlocks(content, imageBlocks), {
|
|
11262
|
+
supportsVision: deps2.agent.ctx.provider.capabilities.vision,
|
|
11263
|
+
adapters: () => createToolVisionAdapters(deps2.agent.tools),
|
|
11264
|
+
ctx: deps2.agent.ctx,
|
|
11265
|
+
signal: thisRun.signal,
|
|
11266
|
+
providerId: deps2.agent.ctx.provider.id,
|
|
11267
|
+
model: deps2.agent.ctx.model
|
|
11268
|
+
});
|
|
10913
11269
|
input = routed.blocks;
|
|
10914
11270
|
}
|
|
10915
11271
|
const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
|
|
@@ -11005,7 +11361,9 @@ function createMessageDispatcher(opts) {
|
|
|
11005
11361
|
const currentCfg = deps2.configStore.get().tools ?? {};
|
|
11006
11362
|
const currentDisabled = currentCfg.disabledTools ?? [];
|
|
11007
11363
|
if (ok2 && !currentDisabled.includes(name2)) {
|
|
11008
|
-
deps2.configStore.update({
|
|
11364
|
+
deps2.configStore.update({
|
|
11365
|
+
tools: { ...currentCfg, disabledTools: [...currentDisabled, name2] }
|
|
11366
|
+
});
|
|
11009
11367
|
}
|
|
11010
11368
|
send(ws, { type: "tool.disabled", payload: { name: name2, ok: ok2 } });
|
|
11011
11369
|
break;
|
|
@@ -11021,19 +11379,31 @@ function createMessageDispatcher(opts) {
|
|
|
11021
11379
|
const currentCfg = deps2.configStore.get().tools ?? {};
|
|
11022
11380
|
const currentDisabled = currentCfg.disabledTools ?? [];
|
|
11023
11381
|
deps2.configStore.update({
|
|
11024
|
-
tools: {
|
|
11382
|
+
tools: {
|
|
11383
|
+
...currentCfg,
|
|
11384
|
+
disabledTools: currentDisabled.filter((n) => n !== name2)
|
|
11385
|
+
}
|
|
11025
11386
|
});
|
|
11026
11387
|
}
|
|
11027
11388
|
send(ws, { type: "tool.enabled", payload: { name: name2, ok: ok2 } });
|
|
11028
11389
|
break;
|
|
11029
11390
|
}
|
|
11030
11391
|
// ── Memory operations — delegated to shared handlers (memory-handlers.ts) ──
|
|
11392
|
+
// `memory.list` renders the single Super Memory store; all writes go
|
|
11393
|
+
// through the structured `memory.super.*` operations below.
|
|
11031
11394
|
case "memory.list":
|
|
11032
11395
|
return handleMemoryList(ws, deps2.memoryStore);
|
|
11033
|
-
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
|
|
11396
|
+
// ── SuperMemory operations ──
|
|
11397
|
+
case "memory.super.list":
|
|
11398
|
+
return handleSuperMemoryList(ws, deps2.memoryStore);
|
|
11399
|
+
case "memory.super.get":
|
|
11400
|
+
return handleSuperMemoryGet(ws, msg, deps2.memoryStore);
|
|
11401
|
+
case "memory.super.update":
|
|
11402
|
+
return handleSuperMemoryUpdate(ws, msg, deps2.memoryStore);
|
|
11403
|
+
case "memory.super.delete":
|
|
11404
|
+
return handleSuperMemoryDelete(ws, msg, deps2.memoryStore);
|
|
11405
|
+
case "memory.super.remember":
|
|
11406
|
+
return handleSuperMemoryRemember(ws, msg, deps2.memoryStore);
|
|
11037
11407
|
// ── MCP tripwires — handleMcpRoute claims these upstream. ──
|
|
11038
11408
|
case "mcp.list":
|
|
11039
11409
|
throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
|
|
@@ -11368,6 +11738,7 @@ var PREF_KEYS = [
|
|
|
11368
11738
|
// these were browser-editable there but rejected as unknown keys here).
|
|
11369
11739
|
"refinerProvider",
|
|
11370
11740
|
"refinerModel",
|
|
11741
|
+
"refinerFallbackProfile",
|
|
11371
11742
|
"thinkingWord",
|
|
11372
11743
|
"statuslineMode",
|
|
11373
11744
|
"animationStyle",
|
|
@@ -11450,6 +11821,8 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
11450
11821
|
setAutonomy("refinerProvider", payload["refinerProvider"]);
|
|
11451
11822
|
if (typeof payload["refinerModel"] === "string")
|
|
11452
11823
|
setAutonomy("refinerModel", payload["refinerModel"]);
|
|
11824
|
+
if (typeof payload["refinerFallbackProfile"] === "string")
|
|
11825
|
+
setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
|
|
11453
11826
|
if (typeof payload["thinkingWord"] === "string")
|
|
11454
11827
|
setAutonomy("thinkingWord", payload["thinkingWord"]);
|
|
11455
11828
|
if (typeof payload["statuslineMode"] === "string")
|
|
@@ -11655,6 +12028,23 @@ function projectSavedProviders(providers) {
|
|
|
11655
12028
|
});
|
|
11656
12029
|
}
|
|
11657
12030
|
var probeScrubber = new DefaultSecretScrubber();
|
|
12031
|
+
async function probeModelDescriptors(cfg) {
|
|
12032
|
+
if (!cfg.baseUrl) return [];
|
|
12033
|
+
try {
|
|
12034
|
+
const keys = normalizeKeys(cfg);
|
|
12035
|
+
const active = keys.find((k) => k.label === cfg.activeKey) ?? keys[0];
|
|
12036
|
+
const result = await probeLocalLlm({
|
|
12037
|
+
baseUrl: cfg.baseUrl,
|
|
12038
|
+
apiKey: active?.apiKey,
|
|
12039
|
+
noAuth: false,
|
|
12040
|
+
scrubber: probeScrubber
|
|
12041
|
+
});
|
|
12042
|
+
if (!result.ok || !result.modelIds) return [];
|
|
12043
|
+
return result.modelIds.map((id) => ({ id, name: id, capabilities: [] }));
|
|
12044
|
+
} catch {
|
|
12045
|
+
return [];
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
11658
12048
|
function createProviderHandlers(deps2) {
|
|
11659
12049
|
const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
|
|
11660
12050
|
let configWriteLock = deps2.getConfigWriteLock();
|
|
@@ -11941,7 +12331,9 @@ import path21 from "node:path";
|
|
|
11941
12331
|
import {
|
|
11942
12332
|
enhanceUserPrompt,
|
|
11943
12333
|
gatedEnhancerReasoning,
|
|
12334
|
+
nextEnhanceTimeout,
|
|
11944
12335
|
recentTextTurns,
|
|
12336
|
+
resolveEnhanceFallbackRef,
|
|
11945
12337
|
resolveProviderModelList
|
|
11946
12338
|
} from "@wrongstack/core";
|
|
11947
12339
|
import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
|
|
@@ -12106,6 +12498,8 @@ function createModeHandlers(ctx) {
|
|
|
12106
12498
|
const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
|
|
12107
12499
|
const freshBuilder = new DefaultSystemPromptBuilder2({
|
|
12108
12500
|
memoryStore: ctx.memoryStore,
|
|
12501
|
+
// Single injection channel: Super Memory turn middleware, not a static section.
|
|
12502
|
+
injectMemory: false,
|
|
12109
12503
|
skillLoader: ctx.skillLoader,
|
|
12110
12504
|
modeStore: ctx.modeStore,
|
|
12111
12505
|
modeId: id,
|
|
@@ -12348,7 +12742,9 @@ function createSessionHandlers(ctx) {
|
|
|
12348
12742
|
},
|
|
12349
12743
|
listContextModes: async (ws, msg) => {
|
|
12350
12744
|
if (!ensureCurrentSession(ws, msg, "context.modes.list")) return;
|
|
12351
|
-
const active = String(
|
|
12745
|
+
const active = String(
|
|
12746
|
+
ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
12747
|
+
);
|
|
12352
12748
|
const allModes = ctx.customModeStore.list().map((m) => ({
|
|
12353
12749
|
id: m.id,
|
|
12354
12750
|
name: m.name,
|
|
@@ -12359,7 +12755,10 @@ function createSessionHandlers(ctx) {
|
|
|
12359
12755
|
eliseThreshold: m.eliseThreshold,
|
|
12360
12756
|
custom: m.custom === true
|
|
12361
12757
|
}));
|
|
12362
|
-
send(ws, {
|
|
12758
|
+
send(ws, {
|
|
12759
|
+
type: "context.modes.list",
|
|
12760
|
+
payload: sessionPayload2({ activeId: active, modes: allModes })
|
|
12761
|
+
});
|
|
12363
12762
|
},
|
|
12364
12763
|
switchContextMode: async (ws, msg) => {
|
|
12365
12764
|
if (!ensureCurrentSession(ws, msg, "context.mode.switch")) return;
|
|
@@ -12441,7 +12840,10 @@ function createSessionHandlers(ctx) {
|
|
|
12441
12840
|
const { id } = parsed.value;
|
|
12442
12841
|
if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
|
|
12443
12842
|
ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
|
|
12444
|
-
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
|
|
12843
|
+
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
|
|
12844
|
+
{},
|
|
12845
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
12846
|
+
);
|
|
12445
12847
|
}
|
|
12446
12848
|
const result = ctx.customModeStore.remove(id);
|
|
12447
12849
|
sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
|
|
@@ -12454,15 +12856,7 @@ function createSessionHandlers(ctx) {
|
|
|
12454
12856
|
send(ws, {
|
|
12455
12857
|
type: "sessions.list",
|
|
12456
12858
|
payload: {
|
|
12457
|
-
sessions: list
|
|
12458
|
-
id: s.id,
|
|
12459
|
-
title: s.title,
|
|
12460
|
-
startedAt: s.startedAt,
|
|
12461
|
-
model: s.model,
|
|
12462
|
-
provider: s.provider,
|
|
12463
|
-
tokenTotal: s.tokenTotal,
|
|
12464
|
-
isCurrent: s.id === currentId
|
|
12465
|
-
}))
|
|
12859
|
+
sessions: toSessionHistoryEntries(list, currentId)
|
|
12466
12860
|
}
|
|
12467
12861
|
});
|
|
12468
12862
|
} catch (err) {
|
|
@@ -12476,8 +12870,17 @@ function createSessionHandlers(ctx) {
|
|
|
12476
12870
|
sendResult(ws, false, "Cannot delete the active session");
|
|
12477
12871
|
return;
|
|
12478
12872
|
}
|
|
12479
|
-
|
|
12873
|
+
const store = ctx.getSessionStore();
|
|
12874
|
+
await store.delete(id);
|
|
12480
12875
|
sendResult(ws, true, `Session ${id} deleted`);
|
|
12876
|
+
try {
|
|
12877
|
+
const list = await store.list(200);
|
|
12878
|
+
broadcast(ctx.clients, {
|
|
12879
|
+
type: "sessions.list",
|
|
12880
|
+
payload: { sessions: toSessionHistoryEntries(list, ctx.getSession().id) }
|
|
12881
|
+
});
|
|
12882
|
+
} catch {
|
|
12883
|
+
}
|
|
12481
12884
|
} catch (err) {
|
|
12482
12885
|
sendResult(ws, false, errMessage(err));
|
|
12483
12886
|
}
|
|
@@ -12493,23 +12896,17 @@ function createSessionHandlers(ctx) {
|
|
|
12493
12896
|
try {
|
|
12494
12897
|
await ctx.getSessionStore().rename(id, name2);
|
|
12495
12898
|
sendResult(ws, true, name2 ? `Renamed session to "${name2}"` : `Cleared session name`);
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
provider: s.provider,
|
|
12508
|
-
tokenTotal: s.tokenTotal,
|
|
12509
|
-
isCurrent: s.id === currentId
|
|
12510
|
-
}))
|
|
12511
|
-
}
|
|
12512
|
-
});
|
|
12899
|
+
try {
|
|
12900
|
+
const list = await ctx.getSessionStore().list(200);
|
|
12901
|
+
const currentId = ctx.getSession().id;
|
|
12902
|
+
broadcast(ctx.clients, {
|
|
12903
|
+
type: "sessions.list",
|
|
12904
|
+
payload: {
|
|
12905
|
+
sessions: toSessionHistoryEntries(list, currentId)
|
|
12906
|
+
}
|
|
12907
|
+
});
|
|
12908
|
+
} catch {
|
|
12909
|
+
}
|
|
12513
12910
|
} catch (err) {
|
|
12514
12911
|
sendResult(ws, false, errMessage(err));
|
|
12515
12912
|
}
|
|
@@ -12609,6 +13006,25 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12609
13006
|
clients: state.getClients(),
|
|
12610
13007
|
modelsRegistry: deps2.modelsRegistry
|
|
12611
13008
|
});
|
|
13009
|
+
async function applyModelSwitchCore(newProvider, newModel) {
|
|
13010
|
+
const cur = state.getConfig();
|
|
13011
|
+
state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
|
|
13012
|
+
deps2.configStore.update({ provider: newProvider, model: newModel });
|
|
13013
|
+
deps2.context.model = newModel;
|
|
13014
|
+
const newCfg = state.getConfig();
|
|
13015
|
+
const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
|
|
13016
|
+
const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
|
|
13017
|
+
deps2.context.provider = newProv;
|
|
13018
|
+
await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
|
|
13019
|
+
await cb.updateGlobalConfig((config) => {
|
|
13020
|
+
config.provider = newProvider;
|
|
13021
|
+
config.model = newModel;
|
|
13022
|
+
}, "model.switch");
|
|
13023
|
+
broadcast(state.getClients(), {
|
|
13024
|
+
type: "session.start",
|
|
13025
|
+
payload: await cb.sessionStartPayload()
|
|
13026
|
+
});
|
|
13027
|
+
}
|
|
12612
13028
|
const providerRoutes = {
|
|
12613
13029
|
providerHandlers,
|
|
12614
13030
|
listProviders: async (ws) => {
|
|
@@ -12645,11 +13061,20 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12645
13061
|
const provider = await resolveProviderCatalogForModels(deps2.modelsRegistry, providerId, cfg);
|
|
12646
13062
|
const siblingId = SIBLING_CATALOG[providerId];
|
|
12647
13063
|
const siblingCatalog = siblingId && siblingId !== providerId ? await deps2.modelsRegistry.getProvider(siblingId).catch(() => void 0) : void 0;
|
|
13064
|
+
let resolved = resolveProviderModelList(
|
|
13065
|
+
cfg?.models,
|
|
13066
|
+
provider,
|
|
13067
|
+
cfg?.type ?? providerId,
|
|
13068
|
+
siblingCatalog
|
|
13069
|
+
);
|
|
13070
|
+
if (resolved.length === 0 && cfg?.baseUrl) {
|
|
13071
|
+
resolved = await probeModelDescriptors(cfg);
|
|
13072
|
+
}
|
|
12648
13073
|
const models = await enrichProviderModelDescriptors(
|
|
12649
13074
|
deps2.modelsRegistry,
|
|
12650
13075
|
providerId,
|
|
12651
13076
|
cfg,
|
|
12652
|
-
|
|
13077
|
+
resolved
|
|
12653
13078
|
);
|
|
12654
13079
|
send(ws, {
|
|
12655
13080
|
type: "provider.models",
|
|
@@ -12667,21 +13092,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12667
13092
|
}
|
|
12668
13093
|
const { provider: newProvider, model: newModel } = parsed.value;
|
|
12669
13094
|
try {
|
|
12670
|
-
|
|
12671
|
-
state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
|
|
12672
|
-
deps2.configStore.update({ provider: newProvider, model: newModel });
|
|
12673
|
-
deps2.context.model = newModel;
|
|
12674
|
-
const newCfg = state.getConfig();
|
|
12675
|
-
const providerCfg = newCfg.providers?.[newProvider] ?? {
|
|
12676
|
-
type: newProvider
|
|
12677
|
-
};
|
|
12678
|
-
const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
|
|
12679
|
-
deps2.context.provider = newProv;
|
|
12680
|
-
await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
|
|
12681
|
-
await cb.updateGlobalConfig((config) => {
|
|
12682
|
-
config.provider = newProvider;
|
|
12683
|
-
config.model = newModel;
|
|
12684
|
-
}, "model.switch");
|
|
13095
|
+
await applyModelSwitchCore(newProvider, newModel);
|
|
12685
13096
|
send(ws, {
|
|
12686
13097
|
type: "key.operation_result",
|
|
12687
13098
|
payload: { success: true, message: `Switched to ${newProvider} / ${newModel}` }
|
|
@@ -12694,45 +13105,100 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12694
13105
|
message: `Switch failed: ${errMessage(err)}`
|
|
12695
13106
|
}
|
|
12696
13107
|
});
|
|
12697
|
-
return;
|
|
12698
13108
|
}
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
13109
|
+
},
|
|
13110
|
+
adoptDefaultProviderIfUnset: async (providerId) => {
|
|
13111
|
+
if (state.getConfig().model) return;
|
|
13112
|
+
const saved = await providerHandlers.loadConfigProviders();
|
|
13113
|
+
state.setConfig(patchConfig(state.getConfig(), { providers: saved }));
|
|
13114
|
+
const cfg = saved[providerId];
|
|
13115
|
+
if (!cfg) return;
|
|
13116
|
+
let model = cfg.models?.[0];
|
|
13117
|
+
if (!model) {
|
|
13118
|
+
const catalogId = cfg.type && cfg.type !== providerId ? cfg.type : providerId;
|
|
13119
|
+
const catalog = await deps2.modelsRegistry.getProvider(catalogId).catch(() => void 0);
|
|
13120
|
+
model = catalog?.models?.[0]?.id;
|
|
13121
|
+
}
|
|
13122
|
+
if (!model) {
|
|
13123
|
+
const probed = await probeModelDescriptors(cfg);
|
|
13124
|
+
model = probed[0]?.id;
|
|
13125
|
+
}
|
|
13126
|
+
if (!model) return;
|
|
13127
|
+
try {
|
|
13128
|
+
await applyModelSwitchCore(providerId, model);
|
|
13129
|
+
} catch {
|
|
13130
|
+
}
|
|
12703
13131
|
},
|
|
12704
13132
|
refineModel: async (ws, msg) => {
|
|
12705
|
-
const
|
|
13133
|
+
const payload = msg.payload;
|
|
13134
|
+
const { text } = payload;
|
|
12706
13135
|
if (!text?.trim()) {
|
|
12707
13136
|
send(ws, {
|
|
12708
13137
|
type: "model.refine_result",
|
|
12709
|
-
payload: { refined: "", english: "", error: "Empty text" }
|
|
13138
|
+
payload: { refined: "", english: "", error: "Empty text", errorKind: "provider_error" }
|
|
12710
13139
|
});
|
|
12711
13140
|
return;
|
|
12712
13141
|
}
|
|
13142
|
+
const cfg = state.getConfig();
|
|
13143
|
+
const fallbackRef = resolveEnhanceFallbackRef({
|
|
13144
|
+
...cfg,
|
|
13145
|
+
provider: cfg.provider ?? "",
|
|
13146
|
+
model: cfg.model ?? ""
|
|
13147
|
+
});
|
|
13148
|
+
let provider = deps2.context.provider;
|
|
13149
|
+
let providerId = cfg.provider ?? "";
|
|
13150
|
+
let model = deps2.context.model;
|
|
13151
|
+
if (payload.provider && payload.model) {
|
|
13152
|
+
try {
|
|
13153
|
+
const providerCfg = cfg.providers?.[payload.provider] ?? {
|
|
13154
|
+
type: payload.provider
|
|
13155
|
+
};
|
|
13156
|
+
provider = deps2.providerRegistry.has(payload.provider) ? deps2.providerRegistry.create({ ...providerCfg, type: payload.provider }) : makeProviderFromConfig2(payload.provider, providerCfg);
|
|
13157
|
+
providerId = payload.provider;
|
|
13158
|
+
model = payload.model;
|
|
13159
|
+
} catch (err) {
|
|
13160
|
+
send(ws, {
|
|
13161
|
+
type: "model.refine_result",
|
|
13162
|
+
payload: {
|
|
13163
|
+
refined: text,
|
|
13164
|
+
english: text,
|
|
13165
|
+
error: `Cannot use ${payload.provider}/${payload.model}: ${errMessage(err)}`,
|
|
13166
|
+
errorKind: "provider_error",
|
|
13167
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13168
|
+
}
|
|
13169
|
+
});
|
|
13170
|
+
return;
|
|
13171
|
+
}
|
|
13172
|
+
}
|
|
13173
|
+
const baseTimeout = 9e4;
|
|
13174
|
+
const timeoutMs = typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : baseTimeout;
|
|
12713
13175
|
try {
|
|
12714
13176
|
const history = recentTextTurns(deps2.context.messages);
|
|
12715
|
-
const cfg = state.getConfig();
|
|
12716
13177
|
const resolved = await resolveProviderModelMetadata(
|
|
12717
13178
|
deps2.modelsRegistry,
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
cfg.providers?.[
|
|
13179
|
+
providerId,
|
|
13180
|
+
model,
|
|
13181
|
+
cfg.providers?.[providerId]
|
|
12721
13182
|
).catch(() => void 0);
|
|
12722
13183
|
const reasoning = gatedEnhancerReasoning(resolved?.capabilities?.reasoningConfig);
|
|
13184
|
+
let failureKind;
|
|
12723
13185
|
const result = await enhanceUserPrompt({
|
|
12724
|
-
provider
|
|
12725
|
-
model
|
|
13186
|
+
provider,
|
|
13187
|
+
model,
|
|
12726
13188
|
text,
|
|
12727
13189
|
history,
|
|
12728
|
-
timeoutMs
|
|
13190
|
+
timeoutMs,
|
|
12729
13191
|
...reasoning ? { reasoning } : {},
|
|
12730
|
-
onError: (reason) => {
|
|
13192
|
+
onError: (reason, kind) => {
|
|
13193
|
+
failureKind = kind;
|
|
12731
13194
|
console.warn(
|
|
12732
13195
|
JSON.stringify({
|
|
12733
13196
|
level: "warn",
|
|
12734
13197
|
event: "model.refine_failed",
|
|
12735
13198
|
reason,
|
|
13199
|
+
kind,
|
|
13200
|
+
provider: providerId,
|
|
13201
|
+
model,
|
|
12736
13202
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
12737
13203
|
})
|
|
12738
13204
|
);
|
|
@@ -12741,12 +13207,23 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12741
13207
|
if (result) {
|
|
12742
13208
|
send(ws, {
|
|
12743
13209
|
type: "model.refine_result",
|
|
12744
|
-
payload: {
|
|
13210
|
+
payload: {
|
|
13211
|
+
refined: result.refined,
|
|
13212
|
+
english: result.english,
|
|
13213
|
+
refinedWith: { provider: providerId, model }
|
|
13214
|
+
}
|
|
12745
13215
|
});
|
|
12746
13216
|
} else {
|
|
12747
13217
|
send(ws, {
|
|
12748
13218
|
type: "model.refine_result",
|
|
12749
|
-
payload: {
|
|
13219
|
+
payload: {
|
|
13220
|
+
refined: text,
|
|
13221
|
+
english: text,
|
|
13222
|
+
error: "Refinement returned no result",
|
|
13223
|
+
errorKind: failureKind ?? "empty",
|
|
13224
|
+
...failureKind === "timeout" ? { retryTimeoutMs: nextEnhanceTimeout(timeoutMs, cfg.autonomy) } : {},
|
|
13225
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13226
|
+
}
|
|
12750
13227
|
});
|
|
12751
13228
|
}
|
|
12752
13229
|
} catch (err) {
|
|
@@ -12760,7 +13237,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12760
13237
|
);
|
|
12761
13238
|
send(ws, {
|
|
12762
13239
|
type: "model.refine_result",
|
|
12763
|
-
payload: {
|
|
13240
|
+
payload: {
|
|
13241
|
+
refined: text,
|
|
13242
|
+
english: text,
|
|
13243
|
+
error: errMessage(err),
|
|
13244
|
+
errorKind: "provider_error",
|
|
13245
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13246
|
+
}
|
|
12764
13247
|
});
|
|
12765
13248
|
}
|
|
12766
13249
|
}
|
|
@@ -13122,8 +13605,16 @@ async function startWebUI(opts = {}) {
|
|
|
13122
13605
|
console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
|
|
13123
13606
|
if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
|
|
13124
13607
|
const firstKey = expectDefined3(Object.keys(config.providers)[0]);
|
|
13125
|
-
|
|
13126
|
-
|
|
13608
|
+
const adoptModel = !config.model ? config.providers[firstKey]?.models?.[0] : void 0;
|
|
13609
|
+
config = patchConfig(config, {
|
|
13610
|
+
provider: firstKey,
|
|
13611
|
+
...adoptModel ? { model: adoptModel } : {}
|
|
13612
|
+
});
|
|
13613
|
+
console.log(
|
|
13614
|
+
"[WebUI] No active provider \u2014 auto-selected:",
|
|
13615
|
+
firstKey,
|
|
13616
|
+
adoptModel ? `/ ${adoptModel}` : ""
|
|
13617
|
+
);
|
|
13127
13618
|
}
|
|
13128
13619
|
const needsProvider = !config.provider || !config.model;
|
|
13129
13620
|
const preContext = await createPreContextServices({
|
|
@@ -13382,7 +13873,11 @@ async function startWebUI(opts = {}) {
|
|
|
13382
13873
|
};
|
|
13383
13874
|
const cb = {
|
|
13384
13875
|
sessionStartPayload,
|
|
13385
|
-
onSessionSwapped: (sessionId) =>
|
|
13876
|
+
onSessionSwapped: async (sessionId) => {
|
|
13877
|
+
await sessionIdentity.activate(sessionId);
|
|
13878
|
+
const { hydrateSessionKanban: hydrateSessionKanban2 } = await import("@wrongstack/tools/session-kanban");
|
|
13879
|
+
await hydrateSessionKanban2(deps2.context);
|
|
13880
|
+
},
|
|
13386
13881
|
updateAutoCompactionMaxContext,
|
|
13387
13882
|
updateGlobalConfig: updateGlobalConfig2,
|
|
13388
13883
|
persistPrefsToConfig: persistPrefsToConfig2,
|
|
@@ -13622,6 +14117,7 @@ export {
|
|
|
13622
14117
|
AutoPhaseWebSocketHandler,
|
|
13623
14118
|
CollaborationWebSocketHandler,
|
|
13624
14119
|
SKIP_DIRS,
|
|
14120
|
+
SURFACE_DEFAULT_PORTS,
|
|
13625
14121
|
SddBoardWebSocketHandler,
|
|
13626
14122
|
SddWizardWebSocketHandler,
|
|
13627
14123
|
SpecsWebSocketHandler,
|
|
@@ -13661,6 +14157,7 @@ export {
|
|
|
13661
14157
|
generateAuthToken,
|
|
13662
14158
|
getAnalyticsBuffer,
|
|
13663
14159
|
getCostRates,
|
|
14160
|
+
getSurfaceDefaultPorts,
|
|
13664
14161
|
handleApiAnalyticsGet,
|
|
13665
14162
|
handleApiAnalyticsPost,
|
|
13666
14163
|
handleApiAnalyticsSummary,
|
|
@@ -13698,9 +14195,7 @@ export {
|
|
|
13698
14195
|
handleMcpSleep,
|
|
13699
14196
|
handleMcpUpdate,
|
|
13700
14197
|
handleMcpWake,
|
|
13701
|
-
handleMemoryForget,
|
|
13702
14198
|
handleMemoryList,
|
|
13703
|
-
handleMemoryRemember,
|
|
13704
14199
|
handleModeRoute,
|
|
13705
14200
|
handlePrefsRoute,
|
|
13706
14201
|
handleProcessKill,
|
|
@@ -13729,6 +14224,11 @@ export {
|
|
|
13729
14224
|
handleSkillsUninstall,
|
|
13730
14225
|
handleSkillsUpdate,
|
|
13731
14226
|
handleSpecsRoute,
|
|
14227
|
+
handleSuperMemoryDelete,
|
|
14228
|
+
handleSuperMemoryGet,
|
|
14229
|
+
handleSuperMemoryList,
|
|
14230
|
+
handleSuperMemoryRemember,
|
|
14231
|
+
handleSuperMemoryUpdate,
|
|
13732
14232
|
handleWorklistMessage,
|
|
13733
14233
|
hostForBrowserUrl,
|
|
13734
14234
|
hostHeaderOk,
|
|
@@ -13773,9 +14273,13 @@ export {
|
|
|
13773
14273
|
setActiveKey,
|
|
13774
14274
|
setupEvents,
|
|
13775
14275
|
setupWebUICodebaseIndexing,
|
|
14276
|
+
startSddRunFromGraph,
|
|
13776
14277
|
startWebUI,
|
|
13777
14278
|
statusProjectHashFromWatchFilename,
|
|
13778
14279
|
stringifyContent,
|
|
14280
|
+
surfaceLabel,
|
|
14281
|
+
toSessionHistoryEntries,
|
|
14282
|
+
toSessionHistoryEntry,
|
|
13779
14283
|
tokenMatches,
|
|
13780
14284
|
unregisterInstance,
|
|
13781
14285
|
upsertKey,
|