@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/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) {
|
|
@@ -3281,8 +3289,24 @@ function errorMessage(err) {
|
|
|
3281
3289
|
}
|
|
3282
3290
|
|
|
3283
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
|
+
}
|
|
3284
3299
|
async function handleMemoryList(ws, memoryStore) {
|
|
3285
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
|
+
}
|
|
3286
3310
|
const text = await memoryStore.readAll();
|
|
3287
3311
|
send(ws, { type: "memory.list", payload: { text } });
|
|
3288
3312
|
} catch (err) {
|
|
@@ -3292,26 +3316,128 @@ async function handleMemoryList(ws, memoryStore) {
|
|
|
3292
3316
|
});
|
|
3293
3317
|
}
|
|
3294
3318
|
}
|
|
3295
|
-
|
|
3296
|
-
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
|
+
}
|
|
3297
3342
|
try {
|
|
3298
|
-
|
|
3299
|
-
|
|
3343
|
+
const [stats, memories] = await Promise.all([
|
|
3344
|
+
memoryStore.stats(),
|
|
3345
|
+
memoryStore.listSuper()
|
|
3346
|
+
]);
|
|
3347
|
+
send(ws, { type: "memory.super.list", payload: { memories, stats } });
|
|
3300
3348
|
} catch (err) {
|
|
3301
|
-
|
|
3349
|
+
send(ws, { type: "memory.super.list", payload: { error: errMessage(err) } });
|
|
3302
3350
|
}
|
|
3303
3351
|
}
|
|
3304
|
-
async function
|
|
3305
|
-
|
|
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
|
+
}
|
|
3306
3362
|
try {
|
|
3307
|
-
const
|
|
3308
|
-
|
|
3309
|
-
ws,
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
);
|
|
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 } });
|
|
3313
3369
|
} catch (err) {
|
|
3314
|
-
|
|
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) } });
|
|
3315
3441
|
}
|
|
3316
3442
|
}
|
|
3317
3443
|
|
|
@@ -3903,6 +4029,8 @@ var SddBoardWebSocketHandler = class {
|
|
|
3903
4029
|
import * as path8 from "node:path";
|
|
3904
4030
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3905
4031
|
import {
|
|
4032
|
+
DefaultTaskStore,
|
|
4033
|
+
TaskTracker,
|
|
3906
4034
|
WorktreeManager as WorktreeManager2,
|
|
3907
4035
|
ToolValidationError as ToolValidationError4
|
|
3908
4036
|
} from "@wrongstack/core";
|
|
@@ -3918,6 +4046,63 @@ import {
|
|
|
3918
4046
|
startSddRun,
|
|
3919
4047
|
TaskGraphStore
|
|
3920
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
|
+
}
|
|
3921
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";
|
|
3922
4107
|
function buildSddWizardDeps(opts) {
|
|
3923
4108
|
const registry = new SddRunRegistry();
|
|
@@ -3953,64 +4138,27 @@ function buildSddWizardDeps(opts) {
|
|
|
3953
4138
|
field: "taskGraph"
|
|
3954
4139
|
});
|
|
3955
4140
|
}
|
|
3956
|
-
const
|
|
3957
|
-
let worktrees;
|
|
3958
|
-
if (worktreesEnabled) {
|
|
3959
|
-
const inGit = spawnSync2("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3960
|
-
cwd: opts.projectRoot,
|
|
3961
|
-
encoding: "utf8",
|
|
3962
|
-
windowsHide: true
|
|
3963
|
-
}).stdout?.trim() === "true";
|
|
3964
|
-
if (inGit) {
|
|
3965
|
-
await cleanupStaleSddWorktrees({
|
|
3966
|
-
projectRoot: opts.projectRoot,
|
|
3967
|
-
boardsDir: opts.paths.projectSddBoards
|
|
3968
|
-
}).catch(() => void 0);
|
|
3969
|
-
worktrees = new WorktreeManager2({
|
|
3970
|
-
projectRoot: opts.projectRoot,
|
|
3971
|
-
events: opts.events,
|
|
3972
|
-
sessionId: () => opts.agent.ctx.session?.id
|
|
3973
|
-
});
|
|
3974
|
-
}
|
|
3975
|
-
}
|
|
3976
|
-
const boardStore = new SddBoardStore2({ baseDir: opts.paths.projectSddBoards });
|
|
3977
|
-
const verifyTask = makeCommandVerifier();
|
|
3978
|
-
const superviseFailure = opts.brain ? new SddSupervisor({
|
|
3979
|
-
brain: opts.brain,
|
|
3980
|
-
// The run-level fallback chain (chosen in the wizard) doubles as the
|
|
3981
|
-
// supervisor's reassign options — a `reassign` verdict rotates the
|
|
3982
|
-
// worker model on retry. Empty/undefined → reassign option dropped.
|
|
3983
|
-
reassignModels: fallbackModels,
|
|
3984
|
-
// LLM auto-split: decompose a retry-exhausted task into smaller
|
|
3985
|
-
// sub-tasks on an isolated read-only turn. Heavily validated +
|
|
3986
|
-
// bounded; an empty result degrades the split into a retry.
|
|
3987
|
-
generateSubtasks: makeLlmSubtaskGenerator({
|
|
3988
|
-
run: (prompt) => runIsolatedTurn(prompt, "Task Splitter")
|
|
3989
|
-
}),
|
|
3990
|
-
// The standalone brain is a tiered policy→LLM arbiter with NO
|
|
3991
|
-
// human-escalation wrapper (see index.ts), so it never blocks on a
|
|
3992
|
-
// human prompt — an unresolved verdict degrades to a bounded retry.
|
|
3993
|
-
// Safe to let the LLM layer actually pick reassign/split.
|
|
3994
|
-
requestLlmVerdict: true
|
|
3995
|
-
}).superviseFailure : void 0;
|
|
3996
|
-
const handle = startSddRun({
|
|
3997
|
-
tracker,
|
|
4141
|
+
const handle = startSddRunFromGraph(
|
|
3998
4142
|
graph,
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
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
|
+
);
|
|
4014
4162
|
void handle.completion.catch(() => {
|
|
4015
4163
|
});
|
|
4016
4164
|
return { runId: handle.runId };
|
|
@@ -4154,6 +4302,31 @@ var SddWizardWebSocketHandler = class {
|
|
|
4154
4302
|
}
|
|
4155
4303
|
};
|
|
4156
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
|
+
|
|
4157
4330
|
// src/server/shell-open.ts
|
|
4158
4331
|
import * as fs8 from "node:fs/promises";
|
|
4159
4332
|
import * as path9 from "node:path";
|
|
@@ -4447,7 +4620,8 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
|
|
|
4447
4620
|
"uiLocale",
|
|
4448
4621
|
"thinkingWord",
|
|
4449
4622
|
"refinerProvider",
|
|
4450
|
-
"refinerModel"
|
|
4623
|
+
"refinerModel",
|
|
4624
|
+
"refinerFallbackProfile"
|
|
4451
4625
|
]);
|
|
4452
4626
|
var ENUM_PREF_KEYS = {
|
|
4453
4627
|
autonomy: AUTONOMY_VALUES,
|
|
@@ -6323,6 +6497,18 @@ function setupEvents(deps2) {
|
|
|
6323
6497
|
payload: sessionPayload2({ event: eventName, ...payload })
|
|
6324
6498
|
});
|
|
6325
6499
|
});
|
|
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
|
+
});
|
|
6326
6512
|
on("client.status", async (e) => {
|
|
6327
6513
|
broadcast2(clients, { type: "client.status_update", payload: e });
|
|
6328
6514
|
if (wpaths?.projectStatus) {
|
|
@@ -6672,9 +6858,11 @@ function positiveNumber(value) {
|
|
|
6672
6858
|
// src/server/server-runtime.ts
|
|
6673
6859
|
import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
|
|
6674
6860
|
async function resolvePorts(opts) {
|
|
6675
|
-
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;
|
|
6676
6864
|
const wsHost = opts.wsHost ?? process.env["WEBUI_HOST"] ?? process.env["WS_HOST"] ?? "127.0.0.1";
|
|
6677
|
-
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);
|
|
6678
6866
|
const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
|
|
6679
6867
|
const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
|
|
6680
6868
|
const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
|
|
@@ -6832,8 +7020,8 @@ function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
|
6832
7020
|
if (explicitDistDir) return path12.resolve(explicitDistDir);
|
|
6833
7021
|
try {
|
|
6834
7022
|
const requireFromHere2 = createRequire(fromUrl);
|
|
6835
|
-
const serverEntry = requireFromHere2.resolve("@wrongstack/webui
|
|
6836
|
-
return path12.
|
|
7023
|
+
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
7024
|
+
return path12.dirname(serverEntry);
|
|
6837
7025
|
} catch {
|
|
6838
7026
|
return path12.resolve(path12.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
6839
7027
|
}
|
|
@@ -6856,7 +7044,7 @@ function startHttpServer(opts) {
|
|
|
6856
7044
|
console.log(`[WebUI] HTTP server running on ${openUrl}`);
|
|
6857
7045
|
if (opts.openBrowser) openBrowser(openUrl);
|
|
6858
7046
|
void registerInstance(
|
|
6859
|
-
{ 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 }) },
|
|
6860
7048
|
registryBaseDir
|
|
6861
7049
|
).catch((err) => console.warn(JSON.stringify({ level: "warn", event: "webui.instance_record_failed", message: errMessage(err), timestamp: (/* @__PURE__ */ new Date()).toISOString() })));
|
|
6862
7050
|
});
|
|
@@ -6923,6 +7111,10 @@ import {
|
|
|
6923
7111
|
rememberTool,
|
|
6924
7112
|
searchMemoryTool
|
|
6925
7113
|
} from "@wrongstack/tools";
|
|
7114
|
+
import {
|
|
7115
|
+
attachSessionKanbanMirror,
|
|
7116
|
+
hydrateSessionKanban
|
|
7117
|
+
} from "@wrongstack/tools/session-kanban";
|
|
6926
7118
|
import { sessionScopedPath, toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
6927
7119
|
|
|
6928
7120
|
// src/server/setup-screen.ts
|
|
@@ -7063,6 +7255,7 @@ function seedContextMeta(config, context) {
|
|
|
7063
7255
|
}
|
|
7064
7256
|
meta["refinerProvider"] = autonomyCfg["refinerProvider"] ?? "";
|
|
7065
7257
|
meta["refinerModel"] = autonomyCfg["refinerModel"] ?? "";
|
|
7258
|
+
meta["refinerFallbackProfile"] = autonomyCfg["refinerFallbackProfile"] ?? "";
|
|
7066
7259
|
meta["thinkingWord"] = autonomyCfg["thinkingWord"] ?? "thinking";
|
|
7067
7260
|
meta["statuslineMode"] = autonomyCfg["statuslineMode"] ?? "detailed";
|
|
7068
7261
|
meta["animationStyle"] = autonomyCfg["animationStyle"] ?? "rainbow";
|
|
@@ -7396,12 +7589,13 @@ async function createPreContextServices(input) {
|
|
|
7396
7589
|
})();
|
|
7397
7590
|
const memoryStore = container.resolve(TOKENS.MemoryStore);
|
|
7398
7591
|
if (config.features.memory) {
|
|
7399
|
-
toolRegistry.register(rememberTool(memoryStore));
|
|
7400
|
-
toolRegistry.register(forgetTool(memoryStore));
|
|
7401
|
-
toolRegistry.register(searchMemoryTool(memoryStore));
|
|
7402
|
-
toolRegistry.register(relatedMemoryTool(memoryStore));
|
|
7403
7592
|
if (isSuperMemoryService(memoryStore)) {
|
|
7404
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));
|
|
7405
7599
|
}
|
|
7406
7600
|
}
|
|
7407
7601
|
toolRegistry.register(makeMailboxTool({ projectDir: wpaths.projectDir, events }));
|
|
@@ -7530,6 +7724,9 @@ async function createPreContextServices(input) {
|
|
|
7530
7724
|
const promptsCtx = { promptLoader, promptUsage };
|
|
7531
7725
|
const systemPromptBuilder = new DefaultSystemPromptBuilder({
|
|
7532
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,
|
|
7533
7730
|
skillLoader,
|
|
7534
7731
|
modeStore,
|
|
7535
7732
|
modeId,
|
|
@@ -7580,6 +7777,8 @@ async function createPreContextServices(input) {
|
|
|
7580
7777
|
"task.path",
|
|
7581
7778
|
sessionScopedPath(wpaths.projectSessions, session.id, ".tasks.json")
|
|
7582
7779
|
);
|
|
7780
|
+
await hydrateSessionKanban(context);
|
|
7781
|
+
attachSessionKanbanMirror(context);
|
|
7583
7782
|
seedContextMeta(config, context);
|
|
7584
7783
|
return {
|
|
7585
7784
|
modelsRegistry,
|
|
@@ -9713,8 +9912,10 @@ import {
|
|
|
9713
9912
|
serializeTaskGraph
|
|
9714
9913
|
} from "@wrongstack/core";
|
|
9715
9914
|
import {
|
|
9915
|
+
addCheckToTask,
|
|
9716
9916
|
addColumn,
|
|
9717
9917
|
addGoalMetricToTask,
|
|
9918
|
+
addNoteToTask,
|
|
9718
9919
|
addTask,
|
|
9719
9920
|
assignTask,
|
|
9720
9921
|
claimReadyTask,
|
|
@@ -9728,29 +9929,47 @@ import {
|
|
|
9728
9929
|
getKanbanQueueHealth,
|
|
9729
9930
|
getTask,
|
|
9730
9931
|
getTaskChain,
|
|
9731
|
-
listReadyTasks,
|
|
9732
9932
|
listBoards,
|
|
9933
|
+
listReadyTasks,
|
|
9733
9934
|
mergeTasks,
|
|
9734
9935
|
moveTask,
|
|
9735
9936
|
parseLinesIntoTasks,
|
|
9937
|
+
reconcileKanbanBoard,
|
|
9938
|
+
recoverStaleTaskAssignments,
|
|
9939
|
+
releaseTaskClaim,
|
|
9736
9940
|
removeBoard,
|
|
9737
9941
|
removeColumn,
|
|
9738
9942
|
removeTask,
|
|
9739
|
-
releaseTaskClaim,
|
|
9740
9943
|
setTaskChain,
|
|
9741
9944
|
splitTask,
|
|
9742
9945
|
syncBoardFromTaskGraph,
|
|
9743
9946
|
transferTaskToBoard,
|
|
9744
9947
|
updateBoard,
|
|
9948
|
+
updateCheckOnTask,
|
|
9745
9949
|
updateGoalMetricOnTask,
|
|
9746
9950
|
updateTask
|
|
9747
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
|
+
}
|
|
9748
9964
|
function ok(ws, type, data) {
|
|
9749
9965
|
send(ws, { type, payload: { success: true, data: data ?? null } });
|
|
9750
9966
|
}
|
|
9751
9967
|
function fail(ws, type, message) {
|
|
9752
9968
|
send(ws, { type, payload: { success: false, error: message } });
|
|
9753
9969
|
}
|
|
9970
|
+
function has(payload, key) {
|
|
9971
|
+
return payload !== void 0 && Object.hasOwn(payload, key);
|
|
9972
|
+
}
|
|
9754
9973
|
async function handleKanbanRoute(ws, msg, ctx) {
|
|
9755
9974
|
if (!msg.type.startsWith("kanban.")) return false;
|
|
9756
9975
|
const payload = msg.payload;
|
|
@@ -9779,6 +9998,38 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9779
9998
|
ok(ws, type, await getKanbanQueueHealth(ctx.projectRoot, { boardId: hBoardId }));
|
|
9780
9999
|
return true;
|
|
9781
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
|
+
}
|
|
9782
10033
|
case "kanban.create": {
|
|
9783
10034
|
const title = payload?.title;
|
|
9784
10035
|
if (!title) {
|
|
@@ -9807,7 +10058,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9807
10058
|
...payload?.title ? { title: payload.title } : {},
|
|
9808
10059
|
...payload?.description ? { description: payload.description } : {},
|
|
9809
10060
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
9810
|
-
...payload?.columns ? { columns: payload.columns } : {}
|
|
10061
|
+
...payload?.columns ? { columns: payload.columns } : {},
|
|
10062
|
+
...has(payload, "supervisor") ? {
|
|
10063
|
+
supervisor: payload?.supervisor ?? null
|
|
10064
|
+
} : {}
|
|
9811
10065
|
});
|
|
9812
10066
|
board ? ok(ws, type, board) : fail(ws, type, `Board not found: ${boardId}`);
|
|
9813
10067
|
return true;
|
|
@@ -9834,6 +10088,11 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
9834
10088
|
return true;
|
|
9835
10089
|
}
|
|
9836
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
|
+
}
|
|
9837
10096
|
ok(ws, type, {
|
|
9838
10097
|
removed: board ? await removeBoard(ctx.projectRoot, board.id) : false,
|
|
9839
10098
|
boardId: board?.id ?? boardId
|
|
@@ -10014,15 +10273,31 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10014
10273
|
return true;
|
|
10015
10274
|
}
|
|
10016
10275
|
const board = await updateTask(ctx.projectRoot, boardId, taskId, {
|
|
10017
|
-
...payload
|
|
10018
|
-
...payload
|
|
10019
|
-
...payload
|
|
10020
|
-
...payload
|
|
10021
|
-
...payload
|
|
10022
|
-
...payload
|
|
10023
|
-
...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
|
+
} : {}
|
|
10024
10293
|
});
|
|
10025
|
-
|
|
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);
|
|
10026
10301
|
return true;
|
|
10027
10302
|
}
|
|
10028
10303
|
case "kanban.task.move": {
|
|
@@ -10040,7 +10315,13 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10040
10315
|
columnId,
|
|
10041
10316
|
payload?.order
|
|
10042
10317
|
);
|
|
10043
|
-
|
|
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);
|
|
10044
10325
|
return true;
|
|
10045
10326
|
}
|
|
10046
10327
|
case "kanban.task.copy":
|
|
@@ -10099,8 +10380,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10099
10380
|
...payload?.role ? { role: payload.role } : {},
|
|
10100
10381
|
...payload?.provider ? { provider: payload.provider } : {},
|
|
10101
10382
|
...payload?.model ? { model: payload.model } : {},
|
|
10383
|
+
...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
|
|
10102
10384
|
...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
|
|
10103
10385
|
...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
|
|
10386
|
+
...payload?.skills ? { skills: payload.skills } : {},
|
|
10104
10387
|
...payload?.tools ? { tools: payload.tools } : {},
|
|
10105
10388
|
...payload?.allowedCapabilities ? { allowedCapabilities: payload.allowedCapabilities } : {},
|
|
10106
10389
|
...payload?.assignee ? { assignee: payload.assignee } : {},
|
|
@@ -10119,7 +10402,10 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10119
10402
|
const board = await releaseTaskClaim(ctx.projectRoot, boardId, taskId, {
|
|
10120
10403
|
...payload?.status ? { status: payload.status } : {},
|
|
10121
10404
|
...payload?.reason ? { reason: payload.reason } : {},
|
|
10122
|
-
...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 } : {}
|
|
10123
10409
|
});
|
|
10124
10410
|
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
10125
10411
|
return true;
|
|
@@ -10175,14 +10461,66 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10175
10461
|
...payload?.role ? { role: payload.role } : {},
|
|
10176
10462
|
...payload?.provider ? { provider: payload.provider } : {},
|
|
10177
10463
|
...payload?.model ? { model: payload.model } : {},
|
|
10464
|
+
...payload?.modelRouting ? { modelRouting: payload.modelRouting } : {},
|
|
10178
10465
|
...payload?.fallbackProfile ? { fallbackProfile: payload.fallbackProfile } : {},
|
|
10179
10466
|
...payload?.fallbackModels ? { fallbackModels: payload.fallbackModels } : {},
|
|
10467
|
+
...payload?.skills ? { skills: payload.skills } : {},
|
|
10180
10468
|
...payload?.tools ? { tools: payload.tools } : {},
|
|
10181
|
-
...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 } : {}
|
|
10182
10474
|
});
|
|
10183
10475
|
board ? ok(ws, type, findTask(board.tasks, taskId)) : fail(ws, type, "Board or task not found");
|
|
10184
10476
|
return true;
|
|
10185
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
|
+
}
|
|
10186
10524
|
case "kanban.task.dispatch":
|
|
10187
10525
|
fail(
|
|
10188
10526
|
ws,
|
|
@@ -10206,7 +10544,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
10206
10544
|
return true;
|
|
10207
10545
|
}
|
|
10208
10546
|
const board = await removeTask(ctx.projectRoot, boardId, taskId);
|
|
10209
|
-
|
|
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 });
|
|
10210
10553
|
return true;
|
|
10211
10554
|
}
|
|
10212
10555
|
case "kanban.task.get": {
|
|
@@ -10518,6 +10861,7 @@ async function handleProviderRoute(ws, msg, routes) {
|
|
|
10518
10861
|
baseUrl,
|
|
10519
10862
|
apiKey
|
|
10520
10863
|
});
|
|
10864
|
+
await routes.adoptDefaultProviderIfUnset(id);
|
|
10521
10865
|
return true;
|
|
10522
10866
|
}
|
|
10523
10867
|
case "provider.remove": {
|
|
@@ -10756,7 +11100,12 @@ function createMessageDispatcher(opts) {
|
|
|
10756
11100
|
if (await handleSpecsRoute(ws, msg, routes.specsRoutes)) return;
|
|
10757
11101
|
if (await handleSddBoardRoute(ws, msg, routes.sddBoardRoutes)) return;
|
|
10758
11102
|
if (await handleSddWizardRoute(ws, msg, routes.sddWizardRoutes)) return;
|
|
10759
|
-
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;
|
|
10760
11109
|
if (msg.type.startsWith("worktree.") && await deps2.worktreeHandler.handleMessage(
|
|
10761
11110
|
msg
|
|
10762
11111
|
))
|
|
@@ -10805,17 +11154,14 @@ function createMessageDispatcher(opts) {
|
|
|
10805
11154
|
let input = content;
|
|
10806
11155
|
const imageBlocks = parseIncomingImages(userPayload.images, userPayload.imageBase64);
|
|
10807
11156
|
if (imageBlocks.length > 0) {
|
|
10808
|
-
const routed = await routeImagesForModel(
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
model: deps2.agent.ctx.model
|
|
10817
|
-
}
|
|
10818
|
-
);
|
|
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
|
+
});
|
|
10819
11165
|
input = routed.blocks;
|
|
10820
11166
|
}
|
|
10821
11167
|
const maxIt = typeof deps2.context.meta["maxIterations"] === "number" ? deps2.context.meta["maxIterations"] : void 0;
|
|
@@ -10911,7 +11257,9 @@ function createMessageDispatcher(opts) {
|
|
|
10911
11257
|
const currentCfg = deps2.configStore.get().tools ?? {};
|
|
10912
11258
|
const currentDisabled = currentCfg.disabledTools ?? [];
|
|
10913
11259
|
if (ok2 && !currentDisabled.includes(name2)) {
|
|
10914
|
-
deps2.configStore.update({
|
|
11260
|
+
deps2.configStore.update({
|
|
11261
|
+
tools: { ...currentCfg, disabledTools: [...currentDisabled, name2] }
|
|
11262
|
+
});
|
|
10915
11263
|
}
|
|
10916
11264
|
send(ws, { type: "tool.disabled", payload: { name: name2, ok: ok2 } });
|
|
10917
11265
|
break;
|
|
@@ -10927,19 +11275,31 @@ function createMessageDispatcher(opts) {
|
|
|
10927
11275
|
const currentCfg = deps2.configStore.get().tools ?? {};
|
|
10928
11276
|
const currentDisabled = currentCfg.disabledTools ?? [];
|
|
10929
11277
|
deps2.configStore.update({
|
|
10930
|
-
tools: {
|
|
11278
|
+
tools: {
|
|
11279
|
+
...currentCfg,
|
|
11280
|
+
disabledTools: currentDisabled.filter((n) => n !== name2)
|
|
11281
|
+
}
|
|
10931
11282
|
});
|
|
10932
11283
|
}
|
|
10933
11284
|
send(ws, { type: "tool.enabled", payload: { name: name2, ok: ok2 } });
|
|
10934
11285
|
break;
|
|
10935
11286
|
}
|
|
10936
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.
|
|
10937
11290
|
case "memory.list":
|
|
10938
11291
|
return handleMemoryList(ws, deps2.memoryStore);
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
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);
|
|
10943
11303
|
// ── MCP tripwires — handleMcpRoute claims these upstream. ──
|
|
10944
11304
|
case "mcp.list":
|
|
10945
11305
|
throw new Error("handleMcpRoute did not claim mcp.list \u2014 check chain order");
|
|
@@ -11274,6 +11634,7 @@ var PREF_KEYS = [
|
|
|
11274
11634
|
// these were browser-editable there but rejected as unknown keys here).
|
|
11275
11635
|
"refinerProvider",
|
|
11276
11636
|
"refinerModel",
|
|
11637
|
+
"refinerFallbackProfile",
|
|
11277
11638
|
"thinkingWord",
|
|
11278
11639
|
"statuslineMode",
|
|
11279
11640
|
"animationStyle",
|
|
@@ -11356,6 +11717,8 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
11356
11717
|
setAutonomy("refinerProvider", payload["refinerProvider"]);
|
|
11357
11718
|
if (typeof payload["refinerModel"] === "string")
|
|
11358
11719
|
setAutonomy("refinerModel", payload["refinerModel"]);
|
|
11720
|
+
if (typeof payload["refinerFallbackProfile"] === "string")
|
|
11721
|
+
setAutonomy("refinerFallbackProfile", payload["refinerFallbackProfile"]);
|
|
11359
11722
|
if (typeof payload["thinkingWord"] === "string")
|
|
11360
11723
|
setAutonomy("thinkingWord", payload["thinkingWord"]);
|
|
11361
11724
|
if (typeof payload["statuslineMode"] === "string")
|
|
@@ -11561,6 +11924,23 @@ function projectSavedProviders(providers) {
|
|
|
11561
11924
|
});
|
|
11562
11925
|
}
|
|
11563
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
|
+
}
|
|
11564
11944
|
function createProviderHandlers(deps2) {
|
|
11565
11945
|
const { globalConfigPath, vault, broadcast: broadcast2, clients } = deps2;
|
|
11566
11946
|
let configWriteLock = deps2.getConfigWriteLock();
|
|
@@ -11847,7 +12227,9 @@ import path20 from "node:path";
|
|
|
11847
12227
|
import {
|
|
11848
12228
|
enhanceUserPrompt,
|
|
11849
12229
|
gatedEnhancerReasoning,
|
|
12230
|
+
nextEnhanceTimeout,
|
|
11850
12231
|
recentTextTurns,
|
|
12232
|
+
resolveEnhanceFallbackRef,
|
|
11851
12233
|
resolveProviderModelList
|
|
11852
12234
|
} from "@wrongstack/core";
|
|
11853
12235
|
import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
|
|
@@ -12012,6 +12394,8 @@ function createModeHandlers(ctx) {
|
|
|
12012
12394
|
const paths = resolveWstackPaths2({ projectRoot: ctx.projectRoot, globalRoot: ctx.globalRoot });
|
|
12013
12395
|
const freshBuilder = new DefaultSystemPromptBuilder2({
|
|
12014
12396
|
memoryStore: ctx.memoryStore,
|
|
12397
|
+
// Single injection channel: Super Memory turn middleware, not a static section.
|
|
12398
|
+
injectMemory: false,
|
|
12015
12399
|
skillLoader: ctx.skillLoader,
|
|
12016
12400
|
modeStore: ctx.modeStore,
|
|
12017
12401
|
modeId: id,
|
|
@@ -12254,7 +12638,9 @@ function createSessionHandlers(ctx) {
|
|
|
12254
12638
|
},
|
|
12255
12639
|
listContextModes: async (ws, msg) => {
|
|
12256
12640
|
if (!ensureCurrentSession(ws, msg, "context.modes.list")) return;
|
|
12257
|
-
const active = String(
|
|
12641
|
+
const active = String(
|
|
12642
|
+
ctx.context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
12643
|
+
);
|
|
12258
12644
|
const allModes = ctx.customModeStore.list().map((m) => ({
|
|
12259
12645
|
id: m.id,
|
|
12260
12646
|
name: m.name,
|
|
@@ -12265,7 +12651,10 @@ function createSessionHandlers(ctx) {
|
|
|
12265
12651
|
eliseThreshold: m.eliseThreshold,
|
|
12266
12652
|
custom: m.custom === true
|
|
12267
12653
|
}));
|
|
12268
|
-
send(ws, {
|
|
12654
|
+
send(ws, {
|
|
12655
|
+
type: "context.modes.list",
|
|
12656
|
+
payload: sessionPayload2({ activeId: active, modes: allModes })
|
|
12657
|
+
});
|
|
12269
12658
|
},
|
|
12270
12659
|
switchContextMode: async (ws, msg) => {
|
|
12271
12660
|
if (!ensureCurrentSession(ws, msg, "context.mode.switch")) return;
|
|
@@ -12347,7 +12736,10 @@ function createSessionHandlers(ctx) {
|
|
|
12347
12736
|
const { id } = parsed.value;
|
|
12348
12737
|
if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
|
|
12349
12738
|
ctx.context.meta["contextWindowMode"] = DEFAULT_CONTEXT_WINDOW_MODE_ID;
|
|
12350
|
-
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
|
|
12739
|
+
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy3(
|
|
12740
|
+
{},
|
|
12741
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
12742
|
+
);
|
|
12351
12743
|
}
|
|
12352
12744
|
const result = ctx.customModeStore.remove(id);
|
|
12353
12745
|
sendResult(ws, result.ok, result.error ?? `Mode "${id}" deleted`);
|
|
@@ -12360,15 +12752,7 @@ function createSessionHandlers(ctx) {
|
|
|
12360
12752
|
send(ws, {
|
|
12361
12753
|
type: "sessions.list",
|
|
12362
12754
|
payload: {
|
|
12363
|
-
sessions: list
|
|
12364
|
-
id: s.id,
|
|
12365
|
-
title: s.title,
|
|
12366
|
-
startedAt: s.startedAt,
|
|
12367
|
-
model: s.model,
|
|
12368
|
-
provider: s.provider,
|
|
12369
|
-
tokenTotal: s.tokenTotal,
|
|
12370
|
-
isCurrent: s.id === currentId
|
|
12371
|
-
}))
|
|
12755
|
+
sessions: toSessionHistoryEntries(list, currentId)
|
|
12372
12756
|
}
|
|
12373
12757
|
});
|
|
12374
12758
|
} catch (err) {
|
|
@@ -12382,8 +12766,17 @@ function createSessionHandlers(ctx) {
|
|
|
12382
12766
|
sendResult(ws, false, "Cannot delete the active session");
|
|
12383
12767
|
return;
|
|
12384
12768
|
}
|
|
12385
|
-
|
|
12769
|
+
const store = ctx.getSessionStore();
|
|
12770
|
+
await store.delete(id);
|
|
12386
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
|
+
}
|
|
12387
12780
|
} catch (err) {
|
|
12388
12781
|
sendResult(ws, false, errMessage(err));
|
|
12389
12782
|
}
|
|
@@ -12399,23 +12792,17 @@ function createSessionHandlers(ctx) {
|
|
|
12399
12792
|
try {
|
|
12400
12793
|
await ctx.getSessionStore().rename(id, name2);
|
|
12401
12794
|
sendResult(ws, true, name2 ? `Renamed session to "${name2}"` : `Cleared session name`);
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
provider: s.provider,
|
|
12414
|
-
tokenTotal: s.tokenTotal,
|
|
12415
|
-
isCurrent: s.id === currentId
|
|
12416
|
-
}))
|
|
12417
|
-
}
|
|
12418
|
-
});
|
|
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
|
+
}
|
|
12419
12806
|
} catch (err) {
|
|
12420
12807
|
sendResult(ws, false, errMessage(err));
|
|
12421
12808
|
}
|
|
@@ -12515,6 +12902,25 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12515
12902
|
clients: state.getClients(),
|
|
12516
12903
|
modelsRegistry: deps2.modelsRegistry
|
|
12517
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
|
+
}
|
|
12518
12924
|
const providerRoutes = {
|
|
12519
12925
|
providerHandlers,
|
|
12520
12926
|
listProviders: async (ws) => {
|
|
@@ -12551,11 +12957,20 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12551
12957
|
const provider = await resolveProviderCatalogForModels(deps2.modelsRegistry, providerId, cfg);
|
|
12552
12958
|
const siblingId = SIBLING_CATALOG[providerId];
|
|
12553
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
|
+
}
|
|
12554
12969
|
const models = await enrichProviderModelDescriptors(
|
|
12555
12970
|
deps2.modelsRegistry,
|
|
12556
12971
|
providerId,
|
|
12557
12972
|
cfg,
|
|
12558
|
-
|
|
12973
|
+
resolved
|
|
12559
12974
|
);
|
|
12560
12975
|
send(ws, {
|
|
12561
12976
|
type: "provider.models",
|
|
@@ -12573,21 +12988,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12573
12988
|
}
|
|
12574
12989
|
const { provider: newProvider, model: newModel } = parsed.value;
|
|
12575
12990
|
try {
|
|
12576
|
-
|
|
12577
|
-
state.setConfig(patchConfig(cur, { provider: newProvider, model: newModel }));
|
|
12578
|
-
deps2.configStore.update({ provider: newProvider, model: newModel });
|
|
12579
|
-
deps2.context.model = newModel;
|
|
12580
|
-
const newCfg = state.getConfig();
|
|
12581
|
-
const providerCfg = newCfg.providers?.[newProvider] ?? {
|
|
12582
|
-
type: newProvider
|
|
12583
|
-
};
|
|
12584
|
-
const newProv = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
|
|
12585
|
-
deps2.context.provider = newProv;
|
|
12586
|
-
await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg);
|
|
12587
|
-
await cb.updateGlobalConfig((config) => {
|
|
12588
|
-
config.provider = newProvider;
|
|
12589
|
-
config.model = newModel;
|
|
12590
|
-
}, "model.switch");
|
|
12991
|
+
await applyModelSwitchCore(newProvider, newModel);
|
|
12591
12992
|
send(ws, {
|
|
12592
12993
|
type: "key.operation_result",
|
|
12593
12994
|
payload: { success: true, message: `Switched to ${newProvider} / ${newModel}` }
|
|
@@ -12600,45 +13001,100 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12600
13001
|
message: `Switch failed: ${errMessage(err)}`
|
|
12601
13002
|
}
|
|
12602
13003
|
});
|
|
12603
|
-
return;
|
|
12604
13004
|
}
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
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
|
+
}
|
|
12609
13027
|
},
|
|
12610
13028
|
refineModel: async (ws, msg) => {
|
|
12611
|
-
const
|
|
13029
|
+
const payload = msg.payload;
|
|
13030
|
+
const { text } = payload;
|
|
12612
13031
|
if (!text?.trim()) {
|
|
12613
13032
|
send(ws, {
|
|
12614
13033
|
type: "model.refine_result",
|
|
12615
|
-
payload: { refined: "", english: "", error: "Empty text" }
|
|
13034
|
+
payload: { refined: "", english: "", error: "Empty text", errorKind: "provider_error" }
|
|
12616
13035
|
});
|
|
12617
13036
|
return;
|
|
12618
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;
|
|
12619
13071
|
try {
|
|
12620
13072
|
const history = recentTextTurns(deps2.context.messages);
|
|
12621
|
-
const cfg = state.getConfig();
|
|
12622
13073
|
const resolved = await resolveProviderModelMetadata(
|
|
12623
13074
|
deps2.modelsRegistry,
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
cfg.providers?.[
|
|
13075
|
+
providerId,
|
|
13076
|
+
model,
|
|
13077
|
+
cfg.providers?.[providerId]
|
|
12627
13078
|
).catch(() => void 0);
|
|
12628
13079
|
const reasoning = gatedEnhancerReasoning(resolved?.capabilities?.reasoningConfig);
|
|
13080
|
+
let failureKind;
|
|
12629
13081
|
const result = await enhanceUserPrompt({
|
|
12630
|
-
provider
|
|
12631
|
-
model
|
|
13082
|
+
provider,
|
|
13083
|
+
model,
|
|
12632
13084
|
text,
|
|
12633
13085
|
history,
|
|
12634
|
-
timeoutMs
|
|
13086
|
+
timeoutMs,
|
|
12635
13087
|
...reasoning ? { reasoning } : {},
|
|
12636
|
-
onError: (reason) => {
|
|
13088
|
+
onError: (reason, kind) => {
|
|
13089
|
+
failureKind = kind;
|
|
12637
13090
|
console.warn(
|
|
12638
13091
|
JSON.stringify({
|
|
12639
13092
|
level: "warn",
|
|
12640
13093
|
event: "model.refine_failed",
|
|
12641
13094
|
reason,
|
|
13095
|
+
kind,
|
|
13096
|
+
provider: providerId,
|
|
13097
|
+
model,
|
|
12642
13098
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
12643
13099
|
})
|
|
12644
13100
|
);
|
|
@@ -12647,12 +13103,23 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12647
13103
|
if (result) {
|
|
12648
13104
|
send(ws, {
|
|
12649
13105
|
type: "model.refine_result",
|
|
12650
|
-
payload: {
|
|
13106
|
+
payload: {
|
|
13107
|
+
refined: result.refined,
|
|
13108
|
+
english: result.english,
|
|
13109
|
+
refinedWith: { provider: providerId, model }
|
|
13110
|
+
}
|
|
12651
13111
|
});
|
|
12652
13112
|
} else {
|
|
12653
13113
|
send(ws, {
|
|
12654
13114
|
type: "model.refine_result",
|
|
12655
|
-
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
|
+
}
|
|
12656
13123
|
});
|
|
12657
13124
|
}
|
|
12658
13125
|
} catch (err) {
|
|
@@ -12666,7 +13133,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
12666
13133
|
);
|
|
12667
13134
|
send(ws, {
|
|
12668
13135
|
type: "model.refine_result",
|
|
12669
|
-
payload: {
|
|
13136
|
+
payload: {
|
|
13137
|
+
refined: text,
|
|
13138
|
+
english: text,
|
|
13139
|
+
error: errMessage(err),
|
|
13140
|
+
errorKind: "provider_error",
|
|
13141
|
+
...fallbackRef ? { fallbackRef } : {}
|
|
13142
|
+
}
|
|
12670
13143
|
});
|
|
12671
13144
|
}
|
|
12672
13145
|
}
|
|
@@ -13028,8 +13501,16 @@ async function startWebUI(opts = {}) {
|
|
|
13028
13501
|
console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
|
|
13029
13502
|
if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
|
|
13030
13503
|
const firstKey = expectDefined3(Object.keys(config.providers)[0]);
|
|
13031
|
-
|
|
13032
|
-
|
|
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
|
+
);
|
|
13033
13514
|
}
|
|
13034
13515
|
const needsProvider = !config.provider || !config.model;
|
|
13035
13516
|
const preContext = await createPreContextServices({
|
|
@@ -13288,7 +13769,11 @@ async function startWebUI(opts = {}) {
|
|
|
13288
13769
|
};
|
|
13289
13770
|
const cb = {
|
|
13290
13771
|
sessionStartPayload,
|
|
13291
|
-
onSessionSwapped: (sessionId) =>
|
|
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
|
+
},
|
|
13292
13777
|
updateAutoCompactionMaxContext,
|
|
13293
13778
|
updateGlobalConfig: updateGlobalConfig2,
|
|
13294
13779
|
persistPrefsToConfig: persistPrefsToConfig2,
|