@wrongstack/webui-server 0.302.2 → 0.305.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 +661 -340
- package/dist/protocol/client-integrations.d.ts +1 -1
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/server-integrations.d.ts +1 -1
- package/dist/server/agent-roster-handlers.d.ts +7 -6
- package/dist/server/backend-services.d.ts +5 -0
- package/dist/server/codemap-cache.d.ts +13 -1
- package/dist/server/entry.js +637 -336
- package/dist/server/handlers/worklist-handlers.d.ts +23 -4
- package/dist/server/handlers.js +126 -23
- package/dist/server/kanban-contract-routes.d.ts +8 -0
- package/dist/server/kanban-route-protocol.d.ts +1 -1
- package/dist/server/kanban-routes.d.ts +2 -2
- package/dist/server/memory-handlers.d.ts +6 -0
- package/dist/server/mode-handlers.d.ts +2 -2
- package/dist/server/pre-context-services.d.ts +1 -1
- package/dist/server/server-runtime.d.ts +14 -2
- package/dist/server/setup-events-status-watcher.d.ts +1 -1
- package/dist/server/ws-utils.d.ts +1 -1
- package/package.json +11 -11
package/dist/server/entry.js
CHANGED
|
@@ -32,7 +32,14 @@ function isRecord(value) {
|
|
|
32
32
|
var AUTONOMY_VALUES = /* @__PURE__ */ new Set(["off", "suggest", "auto", "eternal", "eternal-parallel"]);
|
|
33
33
|
var CONTEXT_STRATEGY_VALUES = /* @__PURE__ */ new Set(["hybrid", "intelligent", "selective"]);
|
|
34
34
|
var CONTEXT_MODE_VALUES = /* @__PURE__ */ new Set(["balanced", "frugal", "deep"]);
|
|
35
|
-
var TOKEN_SAVING_TIER_VALUES = /* @__PURE__ */ new Set([
|
|
35
|
+
var TOKEN_SAVING_TIER_VALUES = /* @__PURE__ */ new Set([
|
|
36
|
+
"auto",
|
|
37
|
+
"off",
|
|
38
|
+
"minimal",
|
|
39
|
+
"light",
|
|
40
|
+
"medium",
|
|
41
|
+
"aggressive"
|
|
42
|
+
]);
|
|
36
43
|
var ENHANCE_LANGUAGE_VALUES = /* @__PURE__ */ new Set(["original", "english"]);
|
|
37
44
|
var LOG_LEVEL_VALUES = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
38
45
|
var AUDIT_LEVEL_VALUES = /* @__PURE__ */ new Set(["minimal", "standard", "full"]);
|
|
@@ -289,12 +296,8 @@ function validatePreferenceValue(key, value) {
|
|
|
289
296
|
if (!Array.isArray(value)) return `prefs.update payload.${key} must be an array`;
|
|
290
297
|
for (let i = 0; i < value.length; i++) {
|
|
291
298
|
const item = value[i];
|
|
292
|
-
if (!isRecord(item))
|
|
293
|
-
|
|
294
|
-
const error2 = arrayValidator(
|
|
295
|
-
item,
|
|
296
|
-
`prefs.update payload.${key}[${i}]`
|
|
297
|
-
);
|
|
299
|
+
if (!isRecord(item)) return `prefs.update payload.${key}[${i}] must be an object`;
|
|
300
|
+
const error2 = arrayValidator(item, `prefs.update payload.${key}[${i}]`);
|
|
298
301
|
if (error2) return error2;
|
|
299
302
|
}
|
|
300
303
|
return null;
|
|
@@ -1079,11 +1082,11 @@ import { randomBytes } from "node:crypto";
|
|
|
1079
1082
|
import { scrubErrorDetail } from "@wrongstack/core/security";
|
|
1080
1083
|
import { WebSocket } from "ws";
|
|
1081
1084
|
var WEBUI_WS_MAX_BUFFERED_BYTES = 32 * 1024 * 1024;
|
|
1082
|
-
function sendSerialized(ws, data) {
|
|
1085
|
+
function sendSerialized(ws, data, frameBytes) {
|
|
1083
1086
|
if (ws.readyState !== WebSocket.OPEN) return false;
|
|
1084
1087
|
const buffered = Number.isFinite(ws.bufferedAmount) ? ws.bufferedAmount : 0;
|
|
1085
|
-
const
|
|
1086
|
-
if (buffered +
|
|
1088
|
+
const bytes = frameBytes ?? Buffer.byteLength(data, "utf8");
|
|
1089
|
+
if (buffered + bytes > WEBUI_WS_MAX_BUFFERED_BYTES) {
|
|
1087
1090
|
try {
|
|
1088
1091
|
ws.terminate();
|
|
1089
1092
|
} catch {
|
|
@@ -1106,8 +1109,9 @@ function send(ws, msg) {
|
|
|
1106
1109
|
}
|
|
1107
1110
|
function broadcast(clients, msg) {
|
|
1108
1111
|
const data = JSON.stringify(msg);
|
|
1112
|
+
const frameBytes = Buffer.byteLength(data, "utf8");
|
|
1109
1113
|
for (const [ws] of clients) {
|
|
1110
|
-
sendSerialized(ws, data);
|
|
1114
|
+
sendSerialized(ws, data, frameBytes);
|
|
1111
1115
|
}
|
|
1112
1116
|
}
|
|
1113
1117
|
function sendResult2(ws, success, message) {
|
|
@@ -1421,7 +1425,13 @@ function indexDbVersion(projectRoot, indexDir) {
|
|
|
1421
1425
|
try {
|
|
1422
1426
|
const dir = resolveIndexDir(projectRoot, indexDir);
|
|
1423
1427
|
const st = fs.statSync(path.join(dir, DB_FILE));
|
|
1424
|
-
|
|
1428
|
+
let wal = "";
|
|
1429
|
+
try {
|
|
1430
|
+
const walSt = fs.statSync(path.join(dir, `${DB_FILE}-wal`));
|
|
1431
|
+
wal = `:${walSt.mtimeMs}:${walSt.size}`;
|
|
1432
|
+
} catch {
|
|
1433
|
+
}
|
|
1434
|
+
return `${st.mtimeMs}:${st.size}${wal}`;
|
|
1425
1435
|
} catch {
|
|
1426
1436
|
return "missing";
|
|
1427
1437
|
}
|
|
@@ -2153,6 +2163,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2153
2163
|
this.broadcast(sessionId, this.stateMessage(sessionId));
|
|
2154
2164
|
}
|
|
2155
2165
|
}, 2e3);
|
|
2166
|
+
this.broadcastInterval.unref?.();
|
|
2156
2167
|
}
|
|
2157
2168
|
stopBroadcast() {
|
|
2158
2169
|
if (this.broadcastInterval) {
|
|
@@ -7125,13 +7136,18 @@ var GoalWebSocketHandler = class {
|
|
|
7125
7136
|
const { execFile: execFile2 } = await import("node:child_process");
|
|
7126
7137
|
const result = await new Promise((resolve16) => {
|
|
7127
7138
|
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
7128
|
-
execFile2(
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7139
|
+
execFile2(
|
|
7140
|
+
npxCommand,
|
|
7141
|
+
["tsc", "--noEmit"],
|
|
7142
|
+
{ cwd, timeout: 6e4 },
|
|
7143
|
+
(err, stdout, stderr) => {
|
|
7144
|
+
if (err && err.code === "ENOENT") {
|
|
7145
|
+
resolve16("[verify] tsc not found \u2014 skipping");
|
|
7146
|
+
return;
|
|
7147
|
+
}
|
|
7148
|
+
resolve16(stdout + stderr);
|
|
7132
7149
|
}
|
|
7133
|
-
|
|
7134
|
-
});
|
|
7150
|
+
);
|
|
7135
7151
|
});
|
|
7136
7152
|
if (result.includes("[verify]") || result.trim().length === 0) {
|
|
7137
7153
|
return { ok: true };
|
|
@@ -7433,6 +7449,7 @@ ${result_.finalText.slice(0, 2e3)}`
|
|
|
7433
7449
|
if (progress) this.broadcast({ type: "goal.progress", payload: progress });
|
|
7434
7450
|
this.broadcastState();
|
|
7435
7451
|
}, 2e3);
|
|
7452
|
+
this.broadcastInterval.unref?.();
|
|
7436
7453
|
}
|
|
7437
7454
|
stopBroadcast() {
|
|
7438
7455
|
if (this.broadcastInterval) {
|
|
@@ -7557,8 +7574,9 @@ ${result_.finalText.slice(0, 2e3)}`
|
|
|
7557
7574
|
}
|
|
7558
7575
|
broadcast(msg) {
|
|
7559
7576
|
const data = JSON.stringify(msg);
|
|
7577
|
+
const frameBytes = Buffer.byteLength(data, "utf8");
|
|
7560
7578
|
for (const client of this.clients) {
|
|
7561
|
-
sendSerialized(client.ws, data);
|
|
7579
|
+
sendSerialized(client.ws, data, frameBytes);
|
|
7562
7580
|
}
|
|
7563
7581
|
}
|
|
7564
7582
|
send(client, msg) {
|
|
@@ -7603,12 +7621,34 @@ function handleTodosGet(ctx, ws) {
|
|
|
7603
7621
|
payload: sessionPayload(ctx, { todos: [...ctx.context.todos] })
|
|
7604
7622
|
});
|
|
7605
7623
|
}
|
|
7606
|
-
function
|
|
7607
|
-
ctx.
|
|
7608
|
-
|
|
7609
|
-
|
|
7624
|
+
async function commitTodos(ctx, todos) {
|
|
7625
|
+
if (ctx.mutateTodos) {
|
|
7626
|
+
const result = await ctx.mutateTodos(todos);
|
|
7627
|
+
return { todos: result.todos, warnings: result.warnings ?? [] };
|
|
7628
|
+
}
|
|
7629
|
+
ctx.replaceTodos?.(todos);
|
|
7630
|
+
return { todos: [...todos], warnings: [] };
|
|
7631
|
+
}
|
|
7632
|
+
function managedProjectionMessage() {
|
|
7633
|
+
return "Kanban-bound todos are task projections. Change or remove the task from Kanban.";
|
|
7634
|
+
}
|
|
7635
|
+
async function handleTodosClear(ctx, ws) {
|
|
7636
|
+
if (ctx.context.todos.some((todo) => todo.kanbanBoardId && todo.kanbanTaskId)) {
|
|
7637
|
+
sendResult3(ctx, ws, false, managedProjectionMessage());
|
|
7638
|
+
return;
|
|
7639
|
+
}
|
|
7640
|
+
try {
|
|
7641
|
+
const result = await commitTodos(ctx, []);
|
|
7642
|
+
sendResult3(ctx, ws, true, "Todos cleared");
|
|
7643
|
+
ctx.broadcast({
|
|
7644
|
+
type: "todos.updated",
|
|
7645
|
+
payload: sessionPayload(ctx, { todos: result.todos })
|
|
7646
|
+
});
|
|
7647
|
+
} catch (error2) {
|
|
7648
|
+
sendResult3(ctx, ws, false, error2 instanceof Error ? error2.message : String(error2));
|
|
7649
|
+
}
|
|
7610
7650
|
}
|
|
7611
|
-
function handleTodosRemove(ctx, ws, payload) {
|
|
7651
|
+
async function handleTodosRemove(ctx, ws, payload) {
|
|
7612
7652
|
if (!payload) {
|
|
7613
7653
|
sendResult3(ctx, ws, false, "Missing id or index");
|
|
7614
7654
|
return;
|
|
@@ -7625,12 +7665,27 @@ function handleTodosRemove(ctx, ws, payload) {
|
|
|
7625
7665
|
sendResult3(ctx, ws, false, "Todo not found");
|
|
7626
7666
|
return;
|
|
7627
7667
|
}
|
|
7668
|
+
if (removed.kanbanBoardId && removed.kanbanTaskId) {
|
|
7669
|
+
sendResult3(ctx, ws, false, managedProjectionMessage());
|
|
7670
|
+
return;
|
|
7671
|
+
}
|
|
7628
7672
|
const next = [...todos.slice(0, targetIndex), ...todos.slice(targetIndex + 1)];
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7673
|
+
try {
|
|
7674
|
+
const result = await commitTodos(ctx, next);
|
|
7675
|
+
sendResult3(ctx, ws, true, `Removed: ${removed.content}`);
|
|
7676
|
+
ctx.broadcast({
|
|
7677
|
+
type: "todos.updated",
|
|
7678
|
+
payload: sessionPayload(ctx, { todos: result.todos })
|
|
7679
|
+
});
|
|
7680
|
+
} catch (error2) {
|
|
7681
|
+
sendResult3(ctx, ws, false, error2 instanceof Error ? error2.message : String(error2));
|
|
7682
|
+
}
|
|
7632
7683
|
}
|
|
7633
|
-
function handleTodoUpdate(ctx, ws, payload) {
|
|
7684
|
+
async function handleTodoUpdate(ctx, ws, payload) {
|
|
7685
|
+
if (!payload || typeof payload.id !== "string" || payload.status !== void 0 && payload.status !== "pending" && payload.status !== "in_progress" && payload.status !== "completed" || payload.activeForm !== void 0 && typeof payload.activeForm !== "string") {
|
|
7686
|
+
sendResult3(ctx, ws, false, "Invalid todo update payload");
|
|
7687
|
+
return;
|
|
7688
|
+
}
|
|
7634
7689
|
const index = ctx.context.todos.findIndex((todo) => todo.id === payload.id);
|
|
7635
7690
|
const existing = ctx.context.todos[index];
|
|
7636
7691
|
if (index === -1 || !existing) {
|
|
@@ -7643,9 +7698,25 @@ function handleTodoUpdate(ctx, ws, payload) {
|
|
|
7643
7698
|
status: payload.status ?? existing.status,
|
|
7644
7699
|
activeForm: payload.activeForm !== void 0 ? payload.activeForm : existing.activeForm
|
|
7645
7700
|
};
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7701
|
+
try {
|
|
7702
|
+
const result = await commitTodos(ctx, next);
|
|
7703
|
+
const projected = result.todos.find((todo) => todo.id === existing.id);
|
|
7704
|
+
const requestedStatus = payload.status ?? existing.status;
|
|
7705
|
+
const projectionRejected = Boolean(existing.kanbanBoardId && existing.kanbanTaskId) && projected?.status !== requestedStatus;
|
|
7706
|
+
const warning = result.warnings[0];
|
|
7707
|
+
sendResult3(
|
|
7708
|
+
ctx,
|
|
7709
|
+
ws,
|
|
7710
|
+
!projectionRejected,
|
|
7711
|
+
projectionRejected ? warning ?? `Kanban kept "${existing.content}" at ${projected?.status ?? "its current state"}.` : warning ? `Todo "${existing.content}" updated. ${warning}` : `Todo "${existing.content}" updated`
|
|
7712
|
+
);
|
|
7713
|
+
ctx.broadcast({
|
|
7714
|
+
type: "todos.updated",
|
|
7715
|
+
payload: sessionPayload(ctx, { todos: result.todos })
|
|
7716
|
+
});
|
|
7717
|
+
} catch (error2) {
|
|
7718
|
+
sendResult3(ctx, ws, false, error2 instanceof Error ? error2.message : String(error2));
|
|
7719
|
+
}
|
|
7649
7720
|
}
|
|
7650
7721
|
async function handleTasksGet(ctx, ws) {
|
|
7651
7722
|
const taskPath = taskPathOf(ctx);
|
|
@@ -7673,14 +7744,32 @@ async function handleTaskUpdate(ctx, ws, payload) {
|
|
|
7673
7744
|
return;
|
|
7674
7745
|
}
|
|
7675
7746
|
try {
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7747
|
+
let file;
|
|
7748
|
+
if (ctx.mutateTaskStatus) {
|
|
7749
|
+
const result = await ctx.mutateTaskStatus(payload.id, payload.status);
|
|
7750
|
+
if (!result.ok) {
|
|
7751
|
+
sendResult3(ctx, ws, false, result.message);
|
|
7752
|
+
return;
|
|
7753
|
+
}
|
|
7754
|
+
file = await loadTasks(taskPath);
|
|
7755
|
+
if (!file) throw new Error("Task mutation succeeded but its persisted snapshot is missing.");
|
|
7756
|
+
sendResult3(ctx, ws, true, result.message);
|
|
7757
|
+
} else {
|
|
7758
|
+
let matched = false;
|
|
7759
|
+
file = await mutateTasks(taskPath, currentSessionId(ctx), async (tasks) => {
|
|
7760
|
+
const task = tasks.tasks.find((candidate) => candidate.id === payload.id);
|
|
7761
|
+
if (!task) return tasks;
|
|
7762
|
+
matched = true;
|
|
7763
|
+
task.status = payload.status;
|
|
7764
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7765
|
+
return tasks;
|
|
7766
|
+
});
|
|
7767
|
+
if (!matched) {
|
|
7768
|
+
sendResult3(ctx, ws, false, `Task "${payload.id}" not found.`);
|
|
7769
|
+
return;
|
|
7770
|
+
}
|
|
7771
|
+
sendResult3(ctx, ws, true, `Task status updated to "${payload.status}".`);
|
|
7772
|
+
}
|
|
7684
7773
|
ctx.broadcast({
|
|
7685
7774
|
type: "tasks.updated",
|
|
7686
7775
|
payload: sessionPayload(ctx, { tasks: file.tasks })
|
|
@@ -7727,6 +7816,18 @@ async function handlePlanTemplateUse(ctx, ws, template) {
|
|
|
7727
7816
|
return;
|
|
7728
7817
|
}
|
|
7729
7818
|
try {
|
|
7819
|
+
if (ctx.mutatePlan) {
|
|
7820
|
+
const result = await ctx.mutatePlan({ action: "template_use", template });
|
|
7821
|
+
if (!result.ok) {
|
|
7822
|
+
sendResult3(ctx, ws, false, result.message);
|
|
7823
|
+
return;
|
|
7824
|
+
}
|
|
7825
|
+
const plan2 = await loadPlan(planPath);
|
|
7826
|
+
if (!plan2) throw new Error("Plan mutation succeeded but its persisted snapshot is missing.");
|
|
7827
|
+
sendResult3(ctx, ws, true, result.message);
|
|
7828
|
+
ctx.broadcast({ type: "plan.updated", payload: sessionPayload(ctx, { plan: plan2 }) });
|
|
7829
|
+
return;
|
|
7830
|
+
}
|
|
7730
7831
|
const templateDefinition = getPlanTemplate(template);
|
|
7731
7832
|
if (!templateDefinition) {
|
|
7732
7833
|
sendResult3(ctx, ws, false, `Unknown template "${template}".`);
|
|
@@ -7755,6 +7856,22 @@ async function handlePlanItemUpdate(ctx, ws, payload) {
|
|
|
7755
7856
|
return;
|
|
7756
7857
|
}
|
|
7757
7858
|
try {
|
|
7859
|
+
if (ctx.mutatePlan) {
|
|
7860
|
+
const result = await ctx.mutatePlan({
|
|
7861
|
+
action: "status",
|
|
7862
|
+
target: payload.target,
|
|
7863
|
+
status: payload.status
|
|
7864
|
+
});
|
|
7865
|
+
if (!result.ok) {
|
|
7866
|
+
sendResult3(ctx, ws, false, result.message);
|
|
7867
|
+
return;
|
|
7868
|
+
}
|
|
7869
|
+
const plan2 = await loadPlan(planPath);
|
|
7870
|
+
if (!plan2) throw new Error("Plan mutation succeeded but its persisted snapshot is missing.");
|
|
7871
|
+
sendResult3(ctx, ws, true, result.message);
|
|
7872
|
+
ctx.broadcast({ type: "plan.updated", payload: sessionPayload(ctx, { plan: plan2 }) });
|
|
7873
|
+
return;
|
|
7874
|
+
}
|
|
7758
7875
|
let changed = false;
|
|
7759
7876
|
const plan = await mutatePlan(planPath, currentSessionId(ctx), async (currentPlan) => {
|
|
7760
7877
|
const before = currentPlan.updatedAt;
|
|
@@ -7778,13 +7895,17 @@ async function handleWorklistMessage(ctx, ws, message) {
|
|
|
7778
7895
|
handleTodosGet(ctx, ws);
|
|
7779
7896
|
return;
|
|
7780
7897
|
case "todos.clear":
|
|
7781
|
-
handleTodosClear(ctx, ws);
|
|
7898
|
+
await handleTodosClear(ctx, ws);
|
|
7782
7899
|
return;
|
|
7783
7900
|
case "todos.remove":
|
|
7784
|
-
handleTodosRemove(
|
|
7901
|
+
await handleTodosRemove(
|
|
7902
|
+
ctx,
|
|
7903
|
+
ws,
|
|
7904
|
+
message.payload
|
|
7905
|
+
);
|
|
7785
7906
|
return;
|
|
7786
7907
|
case "todo.update":
|
|
7787
|
-
handleTodoUpdate(
|
|
7908
|
+
await handleTodoUpdate(
|
|
7788
7909
|
ctx,
|
|
7789
7910
|
ws,
|
|
7790
7911
|
message.payload
|
|
@@ -9358,7 +9479,19 @@ function isTrustedLoopbackOrigin(origin, hostHeader) {
|
|
|
9358
9479
|
if (!host) return false;
|
|
9359
9480
|
const hostUrl = new URL(`${url.protocol}//${host}`);
|
|
9360
9481
|
if (!isLoopbackHostname(hostUrl.hostname)) return false;
|
|
9361
|
-
return effectivePort(url) === effectivePort(hostUrl);
|
|
9482
|
+
return normalizeHostname(url.hostname) === normalizeHostname(hostUrl.hostname) && effectivePort(url) === effectivePort(hostUrl);
|
|
9483
|
+
} catch {
|
|
9484
|
+
return false;
|
|
9485
|
+
}
|
|
9486
|
+
}
|
|
9487
|
+
function originMatchesHost(origin, hostHeader) {
|
|
9488
|
+
try {
|
|
9489
|
+
const originUrl = new URL(origin);
|
|
9490
|
+
if (originUrl.protocol !== "http:" && originUrl.protocol !== "https:") return false;
|
|
9491
|
+
const host = (hostHeader ?? "").trim();
|
|
9492
|
+
if (!host) return false;
|
|
9493
|
+
const requestUrl = new URL(`${originUrl.protocol}//${host}`);
|
|
9494
|
+
return normalizeHostname(originUrl.hostname) === normalizeHostname(requestUrl.hostname) && effectivePort(originUrl) === effectivePort(requestUrl);
|
|
9362
9495
|
} catch {
|
|
9363
9496
|
return false;
|
|
9364
9497
|
}
|
|
@@ -9463,13 +9596,15 @@ function verifyClient(input) {
|
|
|
9463
9596
|
try {
|
|
9464
9597
|
const { hostname: originHostname } = new URL(origin);
|
|
9465
9598
|
if (isLoopbackHostname(originHostname)) {
|
|
9466
|
-
if (requireToken || !isLoopbackBind(wsHost))
|
|
9599
|
+
if (requireToken || !isLoopbackBind(wsHost)) {
|
|
9600
|
+
return cookieTokenOk && (originMatchesHost(origin, hostHeader) || Boolean(allowCrossPortLoopbackCookie));
|
|
9601
|
+
}
|
|
9467
9602
|
if (!isTrustedLoopbackOrigin(origin, hostHeader)) {
|
|
9468
9603
|
return Boolean(allowCrossPortLoopbackCookie) && cookieTokenOk;
|
|
9469
9604
|
}
|
|
9470
9605
|
return true;
|
|
9471
9606
|
}
|
|
9472
|
-
return cookieTokenOk || Boolean(allowBrowserUrlToken) && urlTokenOk && allowedHostname(originHostname, allowedHostnames);
|
|
9607
|
+
return cookieTokenOk && originMatchesHost(origin, hostHeader) || Boolean(allowBrowserUrlToken) && urlTokenOk && allowedHostname(originHostname, allowedHostnames);
|
|
9473
9608
|
} catch {
|
|
9474
9609
|
return false;
|
|
9475
9610
|
}
|
|
@@ -10359,7 +10494,8 @@ async function handleIntrospectionRoute(ctx, ws, message) {
|
|
|
10359
10494
|
switch (message.type) {
|
|
10360
10495
|
case "diag.get": {
|
|
10361
10496
|
if (!sessionAllowed(ctx, ws, message)) return true;
|
|
10362
|
-
const
|
|
10497
|
+
const registry = ctx.agent.tools;
|
|
10498
|
+
const tools = registry.listForProvider?.() ?? registry.list();
|
|
10363
10499
|
ctx.send(ws, {
|
|
10364
10500
|
type: "diag.get",
|
|
10365
10501
|
payload: {
|
|
@@ -10442,6 +10578,7 @@ async function handleIntrospectionRoute(ctx, ws, message) {
|
|
|
10442
10578
|
description: tool.description ?? "",
|
|
10443
10579
|
params: schema.properties ? Object.keys(schema.properties) : [],
|
|
10444
10580
|
disabled: registry.isDisabled?.(tool.name) ?? false,
|
|
10581
|
+
direct: registry.isExposedToProvider?.(tool.name) ?? true,
|
|
10445
10582
|
mutating: !!tool.mutating,
|
|
10446
10583
|
permission: tool.permission ?? "auto"
|
|
10447
10584
|
};
|
|
@@ -10737,7 +10874,6 @@ async function handleKanbanHostRoute(ws, msg, handlers) {
|
|
|
10737
10874
|
import { deserializeTaskGraph, serializeTaskGraph } from "@wrongstack/core/tasking";
|
|
10738
10875
|
import {
|
|
10739
10876
|
addCheckToTask,
|
|
10740
|
-
addColumn,
|
|
10741
10877
|
addGoalMetricToTask,
|
|
10742
10878
|
addNoteToTask,
|
|
10743
10879
|
addTask,
|
|
@@ -10745,14 +10881,17 @@ import {
|
|
|
10745
10881
|
claimReadyTask,
|
|
10746
10882
|
copyTaskToBoard,
|
|
10747
10883
|
createBoard,
|
|
10884
|
+
createBoardFromText,
|
|
10748
10885
|
duplicateBoard,
|
|
10749
10886
|
exportBoardToTaskGraph,
|
|
10750
|
-
createBoardFromText,
|
|
10751
10887
|
getBoard,
|
|
10752
10888
|
getKanbanOrchestrationSnapshot,
|
|
10753
10889
|
getKanbanQueueHealth,
|
|
10754
10890
|
getTaskChain,
|
|
10891
|
+
hasKanbanQueueAnomalies,
|
|
10892
|
+
kanbanQueueAnomalyCount,
|
|
10755
10893
|
listBoards as listBoards2,
|
|
10894
|
+
listBoardHistory,
|
|
10756
10895
|
listReadyTasks,
|
|
10757
10896
|
mergeTasks,
|
|
10758
10897
|
moveTask,
|
|
@@ -10761,7 +10900,6 @@ import {
|
|
|
10761
10900
|
recoverStaleTaskAssignments,
|
|
10762
10901
|
releaseTaskClaim,
|
|
10763
10902
|
removeBoard,
|
|
10764
|
-
removeColumn,
|
|
10765
10903
|
setTaskChain,
|
|
10766
10904
|
splitTask,
|
|
10767
10905
|
syncBoardFromTaskGraph,
|
|
@@ -10773,14 +10911,16 @@ import {
|
|
|
10773
10911
|
updateTask as updateTask2
|
|
10774
10912
|
} from "@wrongstack/kanban";
|
|
10775
10913
|
|
|
10776
|
-
// src/server/kanban-
|
|
10914
|
+
// src/server/kanban-contract-routes.ts
|
|
10777
10915
|
import {
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10916
|
+
addContractEdge,
|
|
10917
|
+
configureContractGraph,
|
|
10918
|
+
evaluateTaskContractGraph,
|
|
10919
|
+
getContractGraph,
|
|
10920
|
+
removeContractEdge,
|
|
10921
|
+
removeContractNode,
|
|
10922
|
+
upsertContractNode
|
|
10782
10923
|
} from "@wrongstack/kanban";
|
|
10783
|
-
import { recordKanbanVerificationEvidence as recordKanbanVerificationEvidence2 } from "@wrongstack/tools";
|
|
10784
10924
|
|
|
10785
10925
|
// src/server/kanban-route-helpers.ts
|
|
10786
10926
|
import { touchKanbanPresence } from "@wrongstack/kanban";
|
|
@@ -10832,7 +10972,150 @@ function findTask2(tasks, taskId) {
|
|
|
10832
10972
|
return tasks.find((task) => task.id === taskId || task.id.startsWith(taskId));
|
|
10833
10973
|
}
|
|
10834
10974
|
|
|
10975
|
+
// src/server/kanban-contract-routes.ts
|
|
10976
|
+
async function handleKanbanContractRoute(ws, type, payload, ctx) {
|
|
10977
|
+
switch (type) {
|
|
10978
|
+
case "kanban.contract.get":
|
|
10979
|
+
await handleGet(ws, type, payload, ctx);
|
|
10980
|
+
return true;
|
|
10981
|
+
case "kanban.contract.configure":
|
|
10982
|
+
await handleConfigure(ws, type, payload, ctx);
|
|
10983
|
+
return true;
|
|
10984
|
+
case "kanban.contract.node.upsert":
|
|
10985
|
+
await handleNodeUpsert(ws, type, payload, ctx);
|
|
10986
|
+
return true;
|
|
10987
|
+
case "kanban.contract.node.remove":
|
|
10988
|
+
await handleNodeRemove(ws, type, payload, ctx);
|
|
10989
|
+
return true;
|
|
10990
|
+
case "kanban.contract.edge.add":
|
|
10991
|
+
await handleEdgeAdd(ws, type, payload, ctx);
|
|
10992
|
+
return true;
|
|
10993
|
+
case "kanban.contract.edge.remove":
|
|
10994
|
+
await handleEdgeRemove(ws, type, payload, ctx);
|
|
10995
|
+
return true;
|
|
10996
|
+
default:
|
|
10997
|
+
return false;
|
|
10998
|
+
}
|
|
10999
|
+
}
|
|
11000
|
+
var str = (payload, key) => typeof payload?.[key] === "string" ? payload[key] : void 0;
|
|
11001
|
+
async function publishBoard(ctx, board) {
|
|
11002
|
+
await publishKanbanBoard((message) => ctx.broadcast?.(message), board);
|
|
11003
|
+
}
|
|
11004
|
+
async function handleGet(ws, type, payload, ctx) {
|
|
11005
|
+
const boardId = str(payload, "boardId");
|
|
11006
|
+
if (!boardId) return fail(ws, type, "boardId required");
|
|
11007
|
+
const found = await getContractGraph(ctx.projectRoot, boardId);
|
|
11008
|
+
if (!found) return fail(ws, type, "Board not found");
|
|
11009
|
+
const taskId = str(payload, "taskId");
|
|
11010
|
+
const evaluated = taskId ? await evaluateTaskContractGraph(ctx.projectRoot, boardId, taskId) : null;
|
|
11011
|
+
if (taskId && !evaluated) return fail(ws, type, "Task not found on this board");
|
|
11012
|
+
ok(ws, type, {
|
|
11013
|
+
boardId,
|
|
11014
|
+
graph: found.graph,
|
|
11015
|
+
...evaluated ? { evaluation: evaluated.evaluation } : {}
|
|
11016
|
+
});
|
|
11017
|
+
}
|
|
11018
|
+
async function handleConfigure(ws, type, payload, ctx) {
|
|
11019
|
+
const boardId = str(payload, "boardId");
|
|
11020
|
+
if (!boardId) return fail(ws, type, "boardId required");
|
|
11021
|
+
const enforcement = str(payload, "enforcement") ?? "advisory";
|
|
11022
|
+
const board = await configureContractGraph(ctx.projectRoot, boardId, enforcement);
|
|
11023
|
+
if (!board) return fail(ws, type, "Board not found");
|
|
11024
|
+
await publishBoard(ctx, board);
|
|
11025
|
+
ok(ws, type, { boardId, graph: board.contractGraph ?? null });
|
|
11026
|
+
}
|
|
11027
|
+
async function handleNodeUpsert(ws, type, payload, ctx) {
|
|
11028
|
+
const boardId = str(payload, "boardId");
|
|
11029
|
+
const taskId = str(payload, "taskId");
|
|
11030
|
+
const kind = str(payload, "kind");
|
|
11031
|
+
const title = str(payload, "title");
|
|
11032
|
+
if (!boardId || !taskId || !kind || !title) {
|
|
11033
|
+
return fail(ws, type, "boardId, taskId, kind, and title required");
|
|
11034
|
+
}
|
|
11035
|
+
const state = str(payload, "state");
|
|
11036
|
+
const waiverActor = str(payload, "waiverActor");
|
|
11037
|
+
const waiverReason = str(payload, "waiverReason");
|
|
11038
|
+
if (state === "waived" && (!waiverActor?.trim() || !waiverReason?.trim())) {
|
|
11039
|
+
return fail(ws, type, "A waived contract node requires waiverActor and waiverReason");
|
|
11040
|
+
}
|
|
11041
|
+
try {
|
|
11042
|
+
const result = await upsertContractNode(ctx.projectRoot, boardId, {
|
|
11043
|
+
taskId,
|
|
11044
|
+
kind,
|
|
11045
|
+
title,
|
|
11046
|
+
...str(payload, "nodeId") !== void 0 ? { id: str(payload, "nodeId") } : {},
|
|
11047
|
+
...str(payload, "description") !== void 0 ? { description: str(payload, "description") } : {},
|
|
11048
|
+
...state !== void 0 ? { state } : {},
|
|
11049
|
+
...str(payload, "enforcement") !== void 0 ? { enforcement: str(payload, "enforcement") } : {},
|
|
11050
|
+
...str(payload, "checkId") !== void 0 ? { checkId: str(payload, "checkId") } : {},
|
|
11051
|
+
...str(payload, "metricId") !== void 0 ? { metricId: str(payload, "metricId") } : {},
|
|
11052
|
+
...state === "waived" ? {
|
|
11053
|
+
waiver: {
|
|
11054
|
+
actor: waiverActor,
|
|
11055
|
+
reason: waiverReason,
|
|
11056
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11057
|
+
}
|
|
11058
|
+
} : {},
|
|
11059
|
+
...str(payload, "createdBy") !== void 0 ? { createdBy: str(payload, "createdBy") } : { createdBy: "webui" }
|
|
11060
|
+
});
|
|
11061
|
+
if (!result) return fail(ws, type, "Board or task not found");
|
|
11062
|
+
await publishBoard(ctx, result.board);
|
|
11063
|
+
ok(ws, type, { boardId, node: result.node, graph: result.board.contractGraph ?? null });
|
|
11064
|
+
} catch (err) {
|
|
11065
|
+
fail(ws, type, err instanceof Error ? err.message : String(err));
|
|
11066
|
+
}
|
|
11067
|
+
}
|
|
11068
|
+
async function handleNodeRemove(ws, type, payload, ctx) {
|
|
11069
|
+
const boardId = str(payload, "boardId");
|
|
11070
|
+
const nodeId = str(payload, "nodeId");
|
|
11071
|
+
if (!boardId || !nodeId) return fail(ws, type, "boardId and nodeId required");
|
|
11072
|
+
const board = await removeContractNode(ctx.projectRoot, boardId, nodeId);
|
|
11073
|
+
if (!board) return fail(ws, type, "Contract node not found");
|
|
11074
|
+
await publishBoard(ctx, board);
|
|
11075
|
+
ok(ws, type, { boardId, graph: board.contractGraph ?? null });
|
|
11076
|
+
}
|
|
11077
|
+
async function handleEdgeAdd(ws, type, payload, ctx) {
|
|
11078
|
+
const boardId = str(payload, "boardId");
|
|
11079
|
+
const from = str(payload, "from");
|
|
11080
|
+
const to = str(payload, "to");
|
|
11081
|
+
const edgeType = str(payload, "edgeType");
|
|
11082
|
+
if (!boardId || !from || !to || !edgeType) {
|
|
11083
|
+
return fail(ws, type, "boardId, from, to, and edgeType required");
|
|
11084
|
+
}
|
|
11085
|
+
try {
|
|
11086
|
+
const result = await addContractEdge(ctx.projectRoot, boardId, {
|
|
11087
|
+
from,
|
|
11088
|
+
to,
|
|
11089
|
+
type: edgeType,
|
|
11090
|
+
...str(payload, "enforcement") !== void 0 ? { enforcement: str(payload, "enforcement") } : {},
|
|
11091
|
+
...str(payload, "rationale") !== void 0 ? { rationale: str(payload, "rationale") } : {},
|
|
11092
|
+
createdBy: str(payload, "createdBy") ?? "webui"
|
|
11093
|
+
});
|
|
11094
|
+
if (!result) return fail(ws, type, "Board not found");
|
|
11095
|
+
await publishBoard(ctx, result.board);
|
|
11096
|
+
ok(ws, type, { boardId, edge: result.edge, graph: result.board.contractGraph ?? null });
|
|
11097
|
+
} catch (err) {
|
|
11098
|
+
fail(ws, type, err instanceof Error ? err.message : String(err));
|
|
11099
|
+
}
|
|
11100
|
+
}
|
|
11101
|
+
async function handleEdgeRemove(ws, type, payload, ctx) {
|
|
11102
|
+
const boardId = str(payload, "boardId");
|
|
11103
|
+
const edgeId = str(payload, "edgeId");
|
|
11104
|
+
if (!boardId || !edgeId) return fail(ws, type, "boardId and edgeId required");
|
|
11105
|
+
const board = await removeContractEdge(ctx.projectRoot, boardId, edgeId);
|
|
11106
|
+
if (!board) return fail(ws, type, "Contract edge not found");
|
|
11107
|
+
await publishBoard(ctx, board);
|
|
11108
|
+
ok(ws, type, { boardId, graph: board.contractGraph ?? null });
|
|
11109
|
+
}
|
|
11110
|
+
|
|
10835
11111
|
// src/server/kanban-decomposition-routes.ts
|
|
11112
|
+
import {
|
|
11113
|
+
listBoards,
|
|
11114
|
+
resolveDecompositionProposal,
|
|
11115
|
+
updateTask,
|
|
11116
|
+
verifyTaskCompletion
|
|
11117
|
+
} from "@wrongstack/kanban";
|
|
11118
|
+
import { recordKanbanVerificationEvidence as recordKanbanVerificationEvidence2 } from "@wrongstack/tools";
|
|
10836
11119
|
async function handleKanbanDecompositionRoute(ws, type, payload, ctx) {
|
|
10837
11120
|
switch (type) {
|
|
10838
11121
|
case "kanban.decomposition.approve":
|
|
@@ -10935,8 +11218,35 @@ async function handleTaskVerification(ws, type, payload, ctx) {
|
|
|
10935
11218
|
}
|
|
10936
11219
|
}
|
|
10937
11220
|
|
|
11221
|
+
// src/server/kanban-route-pagination.ts
|
|
11222
|
+
function paginateKanbanBoards(boards, input) {
|
|
11223
|
+
const pageSize = Math.min(100, Math.max(1, Math.floor(input.pageSize)));
|
|
11224
|
+
const activeSessionIds = new Set(input.activeSessionIds ?? []);
|
|
11225
|
+
const isActive = (board) => board.presence?.some((entry) => entry.active) === true || board.tags?.some((tag) => tag.startsWith("session:") && activeSessionIds.has(tag.slice(8))) === true;
|
|
11226
|
+
const sorted = [...boards].sort((left, right) => {
|
|
11227
|
+
const activityOrder = Number(isActive(right)) - Number(isActive(left));
|
|
11228
|
+
return activityOrder || right.updatedAt.localeCompare(left.updatedAt);
|
|
11229
|
+
});
|
|
11230
|
+
const activeTotal = sorted.filter(isActive).length;
|
|
11231
|
+
const total = sorted.length;
|
|
11232
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
11233
|
+
const requestedPage = Number.isFinite(input.page) ? Math.floor(input.page) : 1;
|
|
11234
|
+
const page = Math.min(totalPages, Math.max(1, requestedPage));
|
|
11235
|
+
const start = (page - 1) * pageSize;
|
|
11236
|
+
return {
|
|
11237
|
+
items: sorted.slice(start, start + pageSize),
|
|
11238
|
+
total,
|
|
11239
|
+
page,
|
|
11240
|
+
pageSize,
|
|
11241
|
+
totalPages,
|
|
11242
|
+
activeTotal,
|
|
11243
|
+
orphanedTotal: total - activeTotal
|
|
11244
|
+
};
|
|
11245
|
+
}
|
|
11246
|
+
|
|
10938
11247
|
// src/server/kanban-task-routes.ts
|
|
10939
11248
|
import {
|
|
11249
|
+
getKanbanWorkbench,
|
|
10940
11250
|
getTask,
|
|
10941
11251
|
listTaskActivity,
|
|
10942
11252
|
recordTaskActivity,
|
|
@@ -10944,6 +11254,16 @@ import {
|
|
|
10944
11254
|
} from "@wrongstack/kanban";
|
|
10945
11255
|
async function handleKanbanTaskRoute(ws, type, payload, ctx) {
|
|
10946
11256
|
switch (type) {
|
|
11257
|
+
case "kanban.workbench":
|
|
11258
|
+
ok(
|
|
11259
|
+
ws,
|
|
11260
|
+
type,
|
|
11261
|
+
await getKanbanWorkbench(ctx.projectRoot, {
|
|
11262
|
+
...typeof payload?.limitPerLane === "number" ? { limitPerLane: payload.limitPerLane } : {},
|
|
11263
|
+
...typeof payload?.alertLimit === "number" ? { alertLimit: payload.alertLimit } : {}
|
|
11264
|
+
})
|
|
11265
|
+
);
|
|
11266
|
+
return true;
|
|
10947
11267
|
case "kanban.task.remove":
|
|
10948
11268
|
await handleTaskRemove(ws, type, payload, ctx);
|
|
10949
11269
|
return true;
|
|
@@ -11036,40 +11356,11 @@ async function handleTaskActivityAdd(ws, type, payload, ctx) {
|
|
|
11036
11356
|
outcome,
|
|
11037
11357
|
...typeof payload?.details === "string" && payload.details.trim() ? { details: payload.details.trim() } : {}
|
|
11038
11358
|
},
|
|
11039
|
-
activityContext(
|
|
11040
|
-
ctx,
|
|
11041
|
-
payload?.actor ?? ctx.context?.agentId ?? "webui"
|
|
11042
|
-
)
|
|
11359
|
+
activityContext(ctx, payload?.actor ?? ctx.context?.agentId ?? "webui")
|
|
11043
11360
|
);
|
|
11044
11361
|
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
11045
11362
|
}
|
|
11046
11363
|
|
|
11047
|
-
// src/server/kanban-route-pagination.ts
|
|
11048
|
-
function paginateKanbanBoards(boards, input) {
|
|
11049
|
-
const pageSize = Math.min(100, Math.max(1, Math.floor(input.pageSize)));
|
|
11050
|
-
const activeSessionIds = new Set(input.activeSessionIds ?? []);
|
|
11051
|
-
const isActive = (board) => board.presence?.some((entry) => entry.active) === true || board.tags?.some((tag) => tag.startsWith("session:") && activeSessionIds.has(tag.slice(8))) === true;
|
|
11052
|
-
const sorted = [...boards].sort((left, right) => {
|
|
11053
|
-
const activityOrder = Number(isActive(right)) - Number(isActive(left));
|
|
11054
|
-
return activityOrder || right.updatedAt.localeCompare(left.updatedAt);
|
|
11055
|
-
});
|
|
11056
|
-
const activeTotal = sorted.filter(isActive).length;
|
|
11057
|
-
const total = sorted.length;
|
|
11058
|
-
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
11059
|
-
const requestedPage = Number.isFinite(input.page) ? Math.floor(input.page) : 1;
|
|
11060
|
-
const page = Math.min(totalPages, Math.max(1, requestedPage));
|
|
11061
|
-
const start = (page - 1) * pageSize;
|
|
11062
|
-
return {
|
|
11063
|
-
items: sorted.slice(start, start + pageSize),
|
|
11064
|
-
total,
|
|
11065
|
-
page,
|
|
11066
|
-
pageSize,
|
|
11067
|
-
totalPages,
|
|
11068
|
-
activeTotal,
|
|
11069
|
-
orphanedTotal: total - activeTotal
|
|
11070
|
-
};
|
|
11071
|
-
}
|
|
11072
|
-
|
|
11073
11364
|
// src/server/kanban-routes.ts
|
|
11074
11365
|
async function handleKanbanRoute(ws, msg, ctx) {
|
|
11075
11366
|
if (!msg.type.startsWith("kanban.")) return false;
|
|
@@ -11077,6 +11368,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11077
11368
|
const type = msg.type;
|
|
11078
11369
|
try {
|
|
11079
11370
|
if (await handleKanbanDecompositionRoute(ws, type, payload, ctx)) return true;
|
|
11371
|
+
if (await handleKanbanContractRoute(ws, type, payload, ctx)) return true;
|
|
11080
11372
|
if (await handleKanbanTaskRoute(ws, type, payload, ctx)) return true;
|
|
11081
11373
|
switch (type) {
|
|
11082
11374
|
case "kanban.list": {
|
|
@@ -11115,7 +11407,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11115
11407
|
fail(ws, type, "boardId required");
|
|
11116
11408
|
return true;
|
|
11117
11409
|
}
|
|
11118
|
-
ok(
|
|
11410
|
+
ok(
|
|
11411
|
+
ws,
|
|
11412
|
+
type,
|
|
11413
|
+
await getKanbanQueueHealth(ctx.projectRoot, {
|
|
11414
|
+
boardId: hBoardId,
|
|
11415
|
+
includeClassifications: false
|
|
11416
|
+
})
|
|
11417
|
+
);
|
|
11119
11418
|
return true;
|
|
11120
11419
|
}
|
|
11121
11420
|
case "kanban.supervisor.status":
|
|
@@ -11145,16 +11444,16 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11145
11444
|
reason: "On-demand standalone Kanban supervisor audit."
|
|
11146
11445
|
}) : null;
|
|
11147
11446
|
if (recovered) health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
11148
|
-
const anomalyCount = health
|
|
11447
|
+
const anomalyCount = kanbanQueueAnomalyCount(health);
|
|
11149
11448
|
ok(ws, type, {
|
|
11150
11449
|
boardId,
|
|
11151
|
-
status: board.supervisor?.enabled === false ? "disabled" :
|
|
11450
|
+
status: board.supervisor?.enabled === false ? "disabled" : hasKanbanQueueAnomalies(health) ? "attention" : "healthy",
|
|
11152
11451
|
mode: board.supervisor?.mode ?? "deterministic",
|
|
11153
11452
|
lastAuditAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11154
11453
|
reconciledTaskIds: reconciled?.tasks.map((task) => task.id) ?? [],
|
|
11155
11454
|
staleRecoveredTaskIds: recovered?.tasks.map((task) => task.id) ?? [],
|
|
11156
11455
|
anomalyCount,
|
|
11157
|
-
summary: `${health.counts.running} running \xB7 ${health.counts.
|
|
11456
|
+
summary: `${health.counts.running} running \xB7 ${health.counts.startable} ready \xB7 ${health.counts.review} review \xB7 ${health.counts.blocked} blocked \xB7 ${health.counts.failed} failed`
|
|
11158
11457
|
});
|
|
11159
11458
|
return true;
|
|
11160
11459
|
}
|
|
@@ -11171,7 +11470,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11171
11470
|
title,
|
|
11172
11471
|
...payload?.description ? { description: payload.description } : {},
|
|
11173
11472
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
11174
|
-
...payload?.columns ? { columns: payload.columns } : {},
|
|
11175
11473
|
...has(payload, "lifecycle") ? { lifecycle: payload?.lifecycle } : {},
|
|
11176
11474
|
...has(payload, "boundary") ? { boundary: payload?.boundary } : {}
|
|
11177
11475
|
})
|
|
@@ -11188,7 +11486,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11188
11486
|
...payload?.title ? { title: payload.title } : {},
|
|
11189
11487
|
...payload?.description ? { description: payload.description } : {},
|
|
11190
11488
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
11191
|
-
...payload?.columns ? { columns: payload.columns } : {},
|
|
11192
11489
|
...has(payload, "lifecycle") ? {
|
|
11193
11490
|
lifecycle: payload?.lifecycle ?? null
|
|
11194
11491
|
} : {},
|
|
@@ -11235,6 +11532,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11235
11532
|
});
|
|
11236
11533
|
return true;
|
|
11237
11534
|
}
|
|
11535
|
+
case "kanban.board.history": {
|
|
11536
|
+
const boardId = payload?.boardId;
|
|
11537
|
+
const history = await listBoardHistory(ctx.projectRoot, boardId);
|
|
11538
|
+
ok(ws, type, history);
|
|
11539
|
+
return true;
|
|
11540
|
+
}
|
|
11238
11541
|
case "kanban.generate": {
|
|
11239
11542
|
const description = payload?.description;
|
|
11240
11543
|
if (!description) {
|
|
@@ -11696,8 +11999,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11696
11999
|
taskId,
|
|
11697
12000
|
{
|
|
11698
12001
|
description,
|
|
12002
|
+
// The old cast listed `manual | auto | agent | test | review` —
|
|
12003
|
+
// three of which have no verifier plugin, while the six that do
|
|
12004
|
+
// (command, file_exists, file_matches, git_diff, metric, test)
|
|
12005
|
+
// were unreachable. `notes` carries the executable body every
|
|
12006
|
+
// deterministic plugin reads.
|
|
11699
12007
|
type: payload?.checkType ?? "manual",
|
|
11700
|
-
status: payload?.status ?? "pending"
|
|
12008
|
+
status: payload?.status ?? "pending",
|
|
12009
|
+
...typeof payload?.notes === "string" ? { notes: payload.notes } : {}
|
|
11701
12010
|
},
|
|
11702
12011
|
activityContext(
|
|
11703
12012
|
ctx,
|
|
@@ -11762,30 +12071,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11762
12071
|
case "kanban.capabilities":
|
|
11763
12072
|
ok(ws, type, { dispatchSupported: Boolean(ctx.dispatchTask) });
|
|
11764
12073
|
return true;
|
|
11765
|
-
case "kanban.column.add": {
|
|
11766
|
-
const boardId = payload?.boardId;
|
|
11767
|
-
const title = payload?.title;
|
|
11768
|
-
if (!boardId || !title) {
|
|
11769
|
-
fail(ws, type, "boardId and title required");
|
|
11770
|
-
return true;
|
|
11771
|
-
}
|
|
11772
|
-
const result = await addColumn(ctx.projectRoot, boardId, { title });
|
|
11773
|
-
result ? ok(ws, type, result.board.columns) : fail(ws, type, `Board not found: ${boardId}`);
|
|
11774
|
-
return true;
|
|
11775
|
-
}
|
|
11776
|
-
case "kanban.column.remove": {
|
|
11777
|
-
const boardId = payload?.boardId;
|
|
11778
|
-
const columnId = payload?.columnId;
|
|
11779
|
-
if (!boardId || !columnId) {
|
|
11780
|
-
fail(ws, type, "boardId and columnId required");
|
|
11781
|
-
return true;
|
|
11782
|
-
}
|
|
11783
|
-
const board = await removeColumn(ctx.projectRoot, boardId, columnId, {
|
|
11784
|
-
moveTasksToColumnId: payload?.moveTasksToColumnId
|
|
11785
|
-
});
|
|
11786
|
-
board ? ok(ws, type, { removed: true, boardId: board.id, columnId, board }) : fail(ws, type, `Column not found: ${columnId}`);
|
|
11787
|
-
return true;
|
|
11788
|
-
}
|
|
11789
12074
|
default:
|
|
11790
12075
|
fail(ws, type, `Unknown kanban message type: ${type}`);
|
|
11791
12076
|
return true;
|
|
@@ -11848,7 +12133,9 @@ function subscribeKanbanDaemonEvents(projectRoot, broadcastMessage) {
|
|
|
11848
12133
|
projectRoot,
|
|
11849
12134
|
async (event) => {
|
|
11850
12135
|
const family = event.event?.split(".")[0];
|
|
11851
|
-
if (family !== "board" && family !== "task" && family !== "column")
|
|
12136
|
+
if (family !== "board" && family !== "task" && family !== "column" && family !== "contract") {
|
|
12137
|
+
return;
|
|
12138
|
+
}
|
|
11852
12139
|
const evData = event.data;
|
|
11853
12140
|
const boardId = evData?.boardId;
|
|
11854
12141
|
if (!boardId) return;
|
|
@@ -13048,6 +13335,34 @@ async function handleSageRecover(ws, msg, memoryStore) {
|
|
|
13048
13335
|
send(ws, { type: "memory.sage.recover", payload: { error: errMessage(err) } });
|
|
13049
13336
|
}
|
|
13050
13337
|
}
|
|
13338
|
+
async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
13339
|
+
const Sage = getSageSurface(memoryStore);
|
|
13340
|
+
if (!Sage) {
|
|
13341
|
+
send(ws, {
|
|
13342
|
+
type: "memory.sage.listCandidates",
|
|
13343
|
+
payload: { error: requiresSage("memory.sage.listCandidates") }
|
|
13344
|
+
});
|
|
13345
|
+
return;
|
|
13346
|
+
}
|
|
13347
|
+
try {
|
|
13348
|
+
const payload = msg.payload ?? {};
|
|
13349
|
+
const includeResolved = payload["includeResolved"] === true;
|
|
13350
|
+
if (typeof Sage.listCandidates !== "function") {
|
|
13351
|
+
send(ws, {
|
|
13352
|
+
type: "memory.sage.listCandidates",
|
|
13353
|
+
payload: { error: "listCandidates is not available on this SAGE surface" }
|
|
13354
|
+
});
|
|
13355
|
+
return;
|
|
13356
|
+
}
|
|
13357
|
+
const candidates = await Sage.listCandidates(includeResolved);
|
|
13358
|
+
send(ws, { type: "memory.sage.listCandidates", payload: { candidates } });
|
|
13359
|
+
} catch (err) {
|
|
13360
|
+
send(ws, {
|
|
13361
|
+
type: "memory.sage.listCandidates",
|
|
13362
|
+
payload: { error: errMessage(err) }
|
|
13363
|
+
});
|
|
13364
|
+
}
|
|
13365
|
+
}
|
|
13051
13366
|
async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
13052
13367
|
const Sage = getSageSurface(memoryStore);
|
|
13053
13368
|
if (!Sage) {
|
|
@@ -13160,14 +13475,17 @@ async function handleSageForFile(ws, msg, memoryStore) {
|
|
|
13160
13475
|
send(ws, { type: "memory.sage.forFile", payload: { error: "filePath is required" } });
|
|
13161
13476
|
return;
|
|
13162
13477
|
}
|
|
13478
|
+
const includeSuperseded = typeof payload["showSuperseded"] === "boolean" ? payload["showSuperseded"] : typeof payload["includeSuperseded"] === "boolean" ? payload["includeSuperseded"] : void 0;
|
|
13479
|
+
const includeDeleted = payload["showDeleted"] === true || payload["includeDeleted"] === true;
|
|
13163
13480
|
try {
|
|
13164
13481
|
const response = await Sage.findMemoriesForFile(filePath, {
|
|
13165
13482
|
...typeof payload["lineStart"] === "number" ? { lineStart: payload["lineStart"] } : {},
|
|
13166
13483
|
...typeof payload["lineEnd"] === "number" ? { lineEnd: payload["lineEnd"] } : {},
|
|
13167
13484
|
...typeof payload["limit"] === "number" ? { limit: payload["limit"] } : {},
|
|
13168
|
-
...
|
|
13485
|
+
...includeSuperseded !== void 0 ? { includeSuperseded } : {},
|
|
13486
|
+
...includeDeleted ? { includeDeleted: true } : {}
|
|
13169
13487
|
});
|
|
13170
|
-
send(ws, { type: "memory.sage.forFile", payload: response });
|
|
13488
|
+
send(ws, { type: "memory.sage.forFile", payload: { response } });
|
|
13171
13489
|
} catch (err) {
|
|
13172
13490
|
send(ws, { type: "memory.sage.forFile", payload: { error: errMessage(err) } });
|
|
13173
13491
|
}
|
|
@@ -13223,6 +13541,9 @@ async function handleMemoryRoute(ctx, ws, message) {
|
|
|
13223
13541
|
case "memory.sage.recover":
|
|
13224
13542
|
await handleSageRecover(ws, message, store);
|
|
13225
13543
|
return true;
|
|
13544
|
+
case "memory.sage.listCandidates":
|
|
13545
|
+
await handleSageListCandidates(ws, message, store);
|
|
13546
|
+
return true;
|
|
13226
13547
|
case "memory.sage.candidateResolve":
|
|
13227
13548
|
await handleSageCandidateResolve(ws, message, store);
|
|
13228
13549
|
return true;
|
|
@@ -13825,6 +14146,7 @@ import {
|
|
|
13825
14146
|
finalizeTaskCompletion,
|
|
13826
14147
|
getBoard as getBoard2,
|
|
13827
14148
|
getKanbanQueueHealth as getKanbanQueueHealth2,
|
|
14149
|
+
kanbanQueueAnomalyCount as kanbanQueueAnomalyCount2,
|
|
13828
14150
|
listBoards as listBoards3,
|
|
13829
14151
|
reconcileKanbanBoard as reconcileKanbanBoard2,
|
|
13830
14152
|
recoverStaleTaskAssignments as recoverStaleTaskAssignments2,
|
|
@@ -13905,8 +14227,9 @@ function createKanbanSupervisor(deps2) {
|
|
|
13905
14227
|
mode: config.recoveryMode ?? "auto",
|
|
13906
14228
|
reason: "Kanban supervisor found an expired worker lease."
|
|
13907
14229
|
}) : null;
|
|
13908
|
-
if (recovered)
|
|
13909
|
-
|
|
14230
|
+
if (recovered)
|
|
14231
|
+
health = await getKanbanQueueHealth2(resolveProjectRoot(deps2), { boardId: board.id });
|
|
14232
|
+
const anomalyCount = kanbanQueueAnomalyCount2(health);
|
|
13910
14233
|
const snapshot = {
|
|
13911
14234
|
boardId: board.id,
|
|
13912
14235
|
status: anomalyCount > 0 ? "attention" : "healthy",
|
|
@@ -14000,7 +14323,9 @@ function createKanbanSupervisor(deps2) {
|
|
|
14000
14323
|
} else {
|
|
14001
14324
|
const summaries = await listBoards3(resolveProjectRoot(deps2));
|
|
14002
14325
|
pruneAbsentBoards(new Set(summaries.map((summary) => summary.id)));
|
|
14003
|
-
boards = (await Promise.all(
|
|
14326
|
+
boards = (await Promise.all(
|
|
14327
|
+
summaries.map((summary) => getBoard2(resolveProjectRoot(deps2), summary.id))
|
|
14328
|
+
)).filter((board) => Boolean(board));
|
|
14004
14329
|
}
|
|
14005
14330
|
const results = [];
|
|
14006
14331
|
for (const board of boards) results.push(await auditBoard(board));
|
|
@@ -14103,13 +14428,10 @@ function dispatchRoute(routing) {
|
|
|
14103
14428
|
...routing.fallbackModels?.length ? { fallbackModels: routing.fallbackModels } : {}
|
|
14104
14429
|
};
|
|
14105
14430
|
}
|
|
14106
|
-
function countAnomalies(health) {
|
|
14107
|
-
return health.staleAssignments.count + health.heartbeatDue.count + health.counts.failed + health.counts.blocked;
|
|
14108
|
-
}
|
|
14109
14431
|
function healthSummary(health) {
|
|
14110
14432
|
return [
|
|
14111
14433
|
`${health.counts.running} running`,
|
|
14112
|
-
`${health.counts.
|
|
14434
|
+
`${health.counts.startable} ready`,
|
|
14113
14435
|
`${health.counts.review} review`,
|
|
14114
14436
|
`${health.counts.blocked} blocked`,
|
|
14115
14437
|
`${health.counts.failed} failed`,
|
|
@@ -14863,7 +15185,7 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
14863
15185
|
import * as fs14 from "node:fs/promises";
|
|
14864
15186
|
import * as path16 from "node:path";
|
|
14865
15187
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
14866
|
-
import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
15188
|
+
import { activateProjectStateGuard, resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
14867
15189
|
function createProjectHandlers(ctx) {
|
|
14868
15190
|
const sendTo = (ws, message) => {
|
|
14869
15191
|
if (ctx.sendMessage) ctx.sendMessage(ws, message);
|
|
@@ -15028,6 +15350,7 @@ function createProjectHandlers(ctx) {
|
|
|
15028
15350
|
try {
|
|
15029
15351
|
await ctx.onSessionSwapped?.(next.id, identityTarget);
|
|
15030
15352
|
await ctx.onBeforeSessionTodosReplaced?.(next.id, paths.projectSessions);
|
|
15353
|
+
await activateProjectStateGuard(resolved);
|
|
15031
15354
|
} catch (err) {
|
|
15032
15355
|
try {
|
|
15033
15356
|
await ctx.onBeforeSessionTodosReplaced?.(previous.id, previousPaths.projectSessions);
|
|
@@ -15982,6 +16305,7 @@ var CLIENT_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
15982
16305
|
"memory.sage.get",
|
|
15983
16306
|
"memory.sage.graph",
|
|
15984
16307
|
"memory.sage.list",
|
|
16308
|
+
"memory.sage.listCandidates",
|
|
15985
16309
|
"memory.sage.listPage",
|
|
15986
16310
|
"memory.sage.recover",
|
|
15987
16311
|
"memory.sage.remember",
|
|
@@ -16261,6 +16585,7 @@ var SERVER_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
16261
16585
|
"memory.sage.get",
|
|
16262
16586
|
"memory.sage.graph",
|
|
16263
16587
|
"memory.sage.list",
|
|
16588
|
+
"memory.sage.listCandidates",
|
|
16264
16589
|
"memory.sage.listPage",
|
|
16265
16590
|
"memory.sage.recover",
|
|
16266
16591
|
"memory.sage.remember",
|
|
@@ -16921,6 +17246,7 @@ function createSessionHandlers(ctx) {
|
|
|
16921
17246
|
ctx.abortActiveRun?.(current2.id);
|
|
16922
17247
|
} catch {
|
|
16923
17248
|
}
|
|
17249
|
+
await ctx.context.flushConversationJournal?.().catch(() => void 0);
|
|
16924
17250
|
await finalizeSession(current2);
|
|
16925
17251
|
}
|
|
16926
17252
|
ctx.setSession(next);
|
|
@@ -17451,85 +17777,55 @@ function createSessionHandlers(ctx) {
|
|
|
17451
17777
|
// src/server/agent-roster-handlers.ts
|
|
17452
17778
|
import {
|
|
17453
17779
|
applyProjectAgentConfig,
|
|
17454
|
-
buildConsolidationInstruction,
|
|
17455
17780
|
captureLearnedFromAgentOutputDetailed,
|
|
17456
17781
|
clearProjectAgentConsolidated,
|
|
17782
|
+
clearProjectSkillAugmentation,
|
|
17457
17783
|
createProjectAgent,
|
|
17784
|
+
DEFAULT_EAGER_SKILL_LIMIT,
|
|
17458
17785
|
detectLearnedConflicts,
|
|
17786
|
+
evaluateAutoOptimize,
|
|
17459
17787
|
FLEET_ROSTER,
|
|
17460
17788
|
getProjectAgentLearnStats,
|
|
17461
17789
|
isConsolidated,
|
|
17462
17790
|
listProjectAgentLearnedEntries,
|
|
17463
17791
|
listProjectAgentRoles,
|
|
17792
|
+
listProjectSkillAugmentations,
|
|
17464
17793
|
loadConsolidationMetadata,
|
|
17465
17794
|
loadProjectAgentConfig,
|
|
17466
17795
|
loadProjectAgentConsolidated,
|
|
17467
17796
|
loadProjectAgentIdentity,
|
|
17468
17797
|
loadProjectAgentLearned,
|
|
17469
17798
|
loadProjectAgentProfile,
|
|
17799
|
+
loadProjectSkillAugmentation,
|
|
17800
|
+
loadSkillAffinity,
|
|
17801
|
+
optimizeProjectAgentLearning,
|
|
17802
|
+
rankRoleSkills,
|
|
17803
|
+
readQuarantinedDirectives,
|
|
17804
|
+
readRawLearnedEntries,
|
|
17470
17805
|
resetProjectAgentIdentity,
|
|
17806
|
+
resolveAutoOptimizePolicy,
|
|
17807
|
+
resolveRoleSkillCandidates,
|
|
17471
17808
|
saveProjectAgentConsolidated,
|
|
17809
|
+
saveProjectSkillAugmentation,
|
|
17810
|
+
scoreSkillAffinity,
|
|
17811
|
+
setSkillPinned,
|
|
17472
17812
|
slugifyProjectAgentRole,
|
|
17473
17813
|
updateProjectAgentConfig,
|
|
17474
17814
|
updateProjectAgentIdentity,
|
|
17475
17815
|
updateProjectAgentLearned,
|
|
17476
17816
|
updateProjectAgentLearningPolicy
|
|
17477
17817
|
} from "@wrongstack/core/coordination";
|
|
17478
|
-
import { isTextBlock } from "@wrongstack/core/types";
|
|
17479
|
-
var CONSOLIDATION_MAX_TOKENS = 8e3;
|
|
17480
|
-
var CONSOLIDATION_TIMEOUT_MS = 12e4;
|
|
17481
17818
|
var AgentRosterWSHandler = class {
|
|
17482
17819
|
getProjectRoot;
|
|
17483
17820
|
getLlm;
|
|
17484
17821
|
broadcast;
|
|
17822
|
+
getAutoOptimizeSettings;
|
|
17485
17823
|
constructor(opts) {
|
|
17486
17824
|
this.getProjectRoot = typeof opts.projectRoot === "function" ? opts.projectRoot : () => opts.projectRoot;
|
|
17487
17825
|
this.getLlm = opts.getLlm ?? (() => void 0);
|
|
17488
17826
|
this.broadcast = opts.broadcast ?? (() => {
|
|
17489
17827
|
});
|
|
17490
|
-
|
|
17491
|
-
/**
|
|
17492
|
-
* Run the consolidation LLM synthesis headlessly and return the cleaned
|
|
17493
|
-
* document text. Returns undefined when no LLM is available so the caller
|
|
17494
|
-
* can fall back to the instruction-only path.
|
|
17495
|
-
*/
|
|
17496
|
-
async synthesizeConsolidation(instruction) {
|
|
17497
|
-
const llm = this.getLlm();
|
|
17498
|
-
if (!llm) return void 0;
|
|
17499
|
-
const req = {
|
|
17500
|
-
model: llm.model,
|
|
17501
|
-
system: [
|
|
17502
|
-
{
|
|
17503
|
-
type: "text",
|
|
17504
|
-
text: "You are a precise technical editor. You consolidate an AI agent role's captured learning entries into a single, durable, role-scoped instruction document. Output ONLY the consolidated markdown document \u2014 no preamble, no code fences, no commentary about the consolidation process."
|
|
17505
|
-
}
|
|
17506
|
-
],
|
|
17507
|
-
messages: [{ role: "user", content: instruction }],
|
|
17508
|
-
maxTokens: CONSOLIDATION_MAX_TOKENS
|
|
17509
|
-
};
|
|
17510
|
-
const timer = new AbortController();
|
|
17511
|
-
let timedOut = false;
|
|
17512
|
-
const to = setTimeout(() => {
|
|
17513
|
-
timedOut = true;
|
|
17514
|
-
timer.abort(new Error("consolidation timeout"));
|
|
17515
|
-
}, CONSOLIDATION_TIMEOUT_MS);
|
|
17516
|
-
to.unref();
|
|
17517
|
-
try {
|
|
17518
|
-
const res = await llm.provider.complete(req, { signal: timer.signal });
|
|
17519
|
-
const text = res.content.filter(isTextBlock).map((block) => block.text).join("\n").trim();
|
|
17520
|
-
const wholeDocFence = /^```(?:markdown|md)?[^\n]*\n([\s\S]*?)\n?```\s*$/i;
|
|
17521
|
-
const wrapped = wholeDocFence.exec(text);
|
|
17522
|
-
const inner = wrapped?.[1];
|
|
17523
|
-
const unfenced = inner !== void 0 ? inner.trim() : text;
|
|
17524
|
-
return { content: unfenced, model: llm.model };
|
|
17525
|
-
} catch (err) {
|
|
17526
|
-
if (timedOut) {
|
|
17527
|
-
throw new Error(`consolidation timed out after ${CONSOLIDATION_TIMEOUT_MS}ms`);
|
|
17528
|
-
}
|
|
17529
|
-
throw err;
|
|
17530
|
-
} finally {
|
|
17531
|
-
clearTimeout(to);
|
|
17532
|
-
}
|
|
17828
|
+
this.getAutoOptimizeSettings = opts.getAutoOptimizeSettings;
|
|
17533
17829
|
}
|
|
17534
17830
|
/** Handle an incoming client message. Returns a response payload. */
|
|
17535
17831
|
async handleMessage(_ws, type, payload) {
|
|
@@ -17734,70 +18030,47 @@ ${String(p.content ?? "")}`;
|
|
|
17734
18030
|
const conflicts = detectLearnedConflicts(projectRoot);
|
|
17735
18031
|
return { type, payload: { conflicts } };
|
|
17736
18032
|
}
|
|
17737
|
-
// ──
|
|
17738
|
-
//
|
|
17739
|
-
//
|
|
17740
|
-
//
|
|
17741
|
-
|
|
17742
|
-
|
|
17743
|
-
case "agent-roster.consolidate": {
|
|
18033
|
+
// ── Optimize: distil captures into skill addenda + a consolidated doc,
|
|
18034
|
+
// then archive and reset the raw buffer. Shared implementation with the
|
|
18035
|
+
// CLI (`optimizeProjectAgentLearning`) so both surfaces persist the same
|
|
18036
|
+
// artifacts instead of the CLI producing markdown nobody saved.
|
|
18037
|
+
case "agent-roster.consolidate":
|
|
18038
|
+
case "agent-roster.optimize": {
|
|
17744
18039
|
if (!role) return { type, payload: { error: "role required" } };
|
|
17745
|
-
const
|
|
17746
|
-
|
|
18040
|
+
const hasExistingConsolidation = isConsolidated(role, projectRoot);
|
|
18041
|
+
const pending = readRawLearnedEntries(role, projectRoot);
|
|
18042
|
+
if (pending.length === 0) {
|
|
17747
18043
|
return {
|
|
17748
18044
|
type: "agent-roster.consolidate",
|
|
17749
18045
|
payload: {
|
|
17750
18046
|
role,
|
|
17751
18047
|
consolidated: false,
|
|
17752
18048
|
rawEntryCount: 0,
|
|
18049
|
+
skills: [],
|
|
17753
18050
|
hasExistingConsolidation,
|
|
17754
18051
|
currentStats: getProjectAgentLearnStats(role, projectRoot)
|
|
17755
18052
|
}
|
|
17756
18053
|
};
|
|
17757
18054
|
}
|
|
17758
|
-
|
|
17759
|
-
|
|
17760
|
-
|
|
17761
|
-
|
|
17762
|
-
|
|
17763
|
-
|
|
17764
|
-
|
|
17765
|
-
|
|
17766
|
-
|
|
17767
|
-
|
|
17768
|
-
|
|
17769
|
-
|
|
17770
|
-
|
|
17771
|
-
|
|
17772
|
-
|
|
17773
|
-
}
|
|
17774
|
-
if (synth && synth.content.length > 0) {
|
|
17775
|
-
let stats;
|
|
17776
|
-
let metadata;
|
|
17777
|
-
try {
|
|
17778
|
-
saveProjectAgentConsolidated(role, synth.content, projectRoot, {
|
|
17779
|
-
trigger: "manual",
|
|
17780
|
-
model: synth.model
|
|
17781
|
-
});
|
|
17782
|
-
stats = getProjectAgentLearnStats(role, projectRoot);
|
|
17783
|
-
metadata = loadConsolidationMetadata(role, projectRoot);
|
|
17784
|
-
} catch (err) {
|
|
17785
|
-
return {
|
|
17786
|
-
type: "agent-roster.consolidate",
|
|
17787
|
-
payload: {
|
|
17788
|
-
role,
|
|
17789
|
-
consolidated: false,
|
|
17790
|
-
rawEntryCount: rawEntries.length,
|
|
17791
|
-
hasExistingConsolidation,
|
|
17792
|
-
currentStats: getProjectAgentLearnStats(role, projectRoot),
|
|
17793
|
-
error: err instanceof Error ? err.message : "failed to persist consolidation"
|
|
17794
|
-
}
|
|
17795
|
-
};
|
|
17796
|
-
}
|
|
18055
|
+
const llm = this.getLlm();
|
|
18056
|
+
const result = await optimizeProjectAgentLearning(role, projectRoot, {
|
|
18057
|
+
...llm ? { llm } : {},
|
|
18058
|
+
trigger: "manual"
|
|
18059
|
+
});
|
|
18060
|
+
const currentStats = getProjectAgentLearnStats(role, projectRoot);
|
|
18061
|
+
const metadata = loadConsolidationMetadata(role, projectRoot);
|
|
18062
|
+
const basePayload = {
|
|
18063
|
+
role,
|
|
18064
|
+
rawEntryCount: result.rawEntryCount,
|
|
18065
|
+
skills: result.skills,
|
|
18066
|
+
hasExistingConsolidation,
|
|
18067
|
+
currentStats
|
|
18068
|
+
};
|
|
18069
|
+
if (result.status === "optimized") {
|
|
17797
18070
|
try {
|
|
17798
18071
|
this.broadcast({
|
|
17799
18072
|
type: "agent-roster.updated",
|
|
17800
|
-
payload: { role, reason: "consolidated", currentStats
|
|
18073
|
+
payload: { role, reason: "consolidated", currentStats, metadata }
|
|
17801
18074
|
});
|
|
17802
18075
|
} catch (e) {
|
|
17803
18076
|
console.warn(
|
|
@@ -17813,44 +18086,111 @@ ${String(p.content ?? "")}`;
|
|
|
17813
18086
|
return {
|
|
17814
18087
|
type: "agent-roster.consolidate",
|
|
17815
18088
|
payload: {
|
|
17816
|
-
|
|
18089
|
+
...basePayload,
|
|
17817
18090
|
consolidated: true,
|
|
17818
|
-
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
currentStats: stats,
|
|
18091
|
+
content: result.content,
|
|
18092
|
+
model: result.model,
|
|
18093
|
+
pruned: result.pruned,
|
|
17822
18094
|
metadata
|
|
17823
18095
|
}
|
|
17824
18096
|
};
|
|
17825
18097
|
}
|
|
17826
|
-
if (synth) {
|
|
17827
|
-
return {
|
|
17828
|
-
type: "agent-roster.consolidate",
|
|
17829
|
-
payload: {
|
|
17830
|
-
role,
|
|
17831
|
-
consolidated: false,
|
|
17832
|
-
emptySynthesis: true,
|
|
17833
|
-
model: synth.model,
|
|
17834
|
-
rawEntryCount: rawEntries.length,
|
|
17835
|
-
hasExistingConsolidation,
|
|
17836
|
-
currentStats: getProjectAgentLearnStats(role, projectRoot)
|
|
17837
|
-
}
|
|
17838
|
-
};
|
|
17839
|
-
}
|
|
17840
18098
|
return {
|
|
17841
18099
|
type: "agent-roster.consolidate",
|
|
17842
18100
|
payload: {
|
|
17843
|
-
|
|
18101
|
+
...basePayload,
|
|
17844
18102
|
consolidated: false,
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
18103
|
+
...result.status === "empty-synthesis" ? { emptySynthesis: true, model: result.model } : {},
|
|
18104
|
+
...result.status === "failed" ? { error: result.error } : {},
|
|
18105
|
+
...result.status === "no-llm" ? {
|
|
18106
|
+
instruction: result.instruction,
|
|
18107
|
+
leaderInstruction: `Optimize what the "${role}" agent has learned. Read its raw learned entries, synthesize them into a single narrowly-scoped document preserving every fact, and save the result. The instruction text contains the full details and raw entries.`
|
|
18108
|
+
} : {}
|
|
17851
18109
|
}
|
|
17852
18110
|
};
|
|
17853
18111
|
}
|
|
18112
|
+
// ── Automatic-optimization status ─────────────────────────────────
|
|
18113
|
+
// Read-only: says whether the background scheduler considers each role
|
|
18114
|
+
// eligible right now, and why not when it does not. Surfacing the reason
|
|
18115
|
+
// is what keeps "nothing happened" from looking like a broken feature.
|
|
18116
|
+
case "agent-roster.auto-optimize-status": {
|
|
18117
|
+
const policy = resolveAutoOptimizePolicy(this.getAutoOptimizeSettings?.() ?? void 0);
|
|
18118
|
+
const roles = role ? [role] : listProjectAgentRoles(projectRoot);
|
|
18119
|
+
return {
|
|
18120
|
+
type,
|
|
18121
|
+
payload: {
|
|
18122
|
+
policy,
|
|
18123
|
+
roles: roles.map((current2) => {
|
|
18124
|
+
try {
|
|
18125
|
+
const decision = evaluateAutoOptimize(current2, projectRoot, policy);
|
|
18126
|
+
return { role: current2, ...decision };
|
|
18127
|
+
} catch {
|
|
18128
|
+
return { role: current2, eligible: false, reason: "disabled" };
|
|
18129
|
+
}
|
|
18130
|
+
})
|
|
18131
|
+
}
|
|
18132
|
+
};
|
|
18133
|
+
}
|
|
18134
|
+
// ── Skill layer: what this project has developed for each role skill ──
|
|
18135
|
+
case "agent-roster.skills": {
|
|
18136
|
+
if (!role) return { type, payload: { error: "role required" } };
|
|
18137
|
+
const candidates = resolveRoleSkillCandidates(role, projectRoot);
|
|
18138
|
+
const developed = listProjectSkillAugmentations(role, projectRoot);
|
|
18139
|
+
const affinity = loadSkillAffinity(role, projectRoot);
|
|
18140
|
+
const eager = new Set(rankRoleSkills(role, candidates, projectRoot));
|
|
18141
|
+
return {
|
|
18142
|
+
type,
|
|
18143
|
+
payload: {
|
|
18144
|
+
role,
|
|
18145
|
+
eagerLimit: DEFAULT_EAGER_SKILL_LIMIT,
|
|
18146
|
+
skills: candidates.map((skill) => ({
|
|
18147
|
+
skill,
|
|
18148
|
+
developed: developed.includes(skill),
|
|
18149
|
+
affinity: affinity.entries[skill] ?? null,
|
|
18150
|
+
score: scoreSkillAffinity(affinity.entries[skill]) + (developed.includes(skill) ? 1 : 0),
|
|
18151
|
+
eager: eager.has(skill)
|
|
18152
|
+
}))
|
|
18153
|
+
}
|
|
18154
|
+
};
|
|
18155
|
+
}
|
|
18156
|
+
// ── Directives the loop stopped believing ─────────────────────────────
|
|
18157
|
+
case "agent-roster.quarantine": {
|
|
18158
|
+
if (!role) return { type, payload: { error: "role required" } };
|
|
18159
|
+
return {
|
|
18160
|
+
type,
|
|
18161
|
+
payload: { role, retired: readQuarantinedDirectives(role, projectRoot) }
|
|
18162
|
+
};
|
|
18163
|
+
}
|
|
18164
|
+
case "agent-roster.read-skill": {
|
|
18165
|
+
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
18166
|
+
if (!role || !skill) return { type, payload: { error: "role and skill required" } };
|
|
18167
|
+
return {
|
|
18168
|
+
type,
|
|
18169
|
+
payload: { role, skill, content: loadProjectSkillAugmentation(role, skill, projectRoot) }
|
|
18170
|
+
};
|
|
18171
|
+
}
|
|
18172
|
+
case "agent-roster.save-skill": {
|
|
18173
|
+
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
18174
|
+
if (!role || !skill || typeof p.content !== "string") {
|
|
18175
|
+
return { type, payload: { error: "role, skill and content required" } };
|
|
18176
|
+
}
|
|
18177
|
+
const savedPath = saveProjectSkillAugmentation(role, skill, p.content, projectRoot);
|
|
18178
|
+
return { type, payload: { role, skill, path: savedPath, success: true } };
|
|
18179
|
+
}
|
|
18180
|
+
case "agent-roster.clear-skill": {
|
|
18181
|
+
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
18182
|
+
if (!role) return { type, payload: { error: "role required" } };
|
|
18183
|
+
clearProjectSkillAugmentation(role, skill || void 0, projectRoot);
|
|
18184
|
+
return { type, payload: { role, skill: skill || null, success: true } };
|
|
18185
|
+
}
|
|
18186
|
+
case "agent-roster.pin-skill": {
|
|
18187
|
+
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
18188
|
+
if (!role || !skill || typeof p.pinned !== "boolean") {
|
|
18189
|
+
return { type, payload: { error: "role, skill and boolean pinned required" } };
|
|
18190
|
+
}
|
|
18191
|
+
const affinity = setSkillPinned(role, skill, p.pinned, projectRoot);
|
|
18192
|
+
return { type, payload: { role, skill, pinned: p.pinned, affinity, success: true } };
|
|
18193
|
+
}
|
|
17854
18194
|
// ── Save consolidated document ────────────────────────────────────
|
|
17855
18195
|
case "agent-roster.save-consolidated": {
|
|
17856
18196
|
if (!role || typeof p.content !== "string") {
|
|
@@ -19739,6 +20079,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
19739
20079
|
const logWatcherMetricsEnabled = shouldLogWatcherStats();
|
|
19740
20080
|
const logWatcherMetrics = () => logFileWatcherMetrics(watcherMetrics);
|
|
19741
20081
|
const metricsInterval = logWatcherMetricsEnabled ? setInterval(logWatcherMetrics, 6e4) : void 0;
|
|
20082
|
+
metricsInterval?.unref?.();
|
|
19742
20083
|
const broadcastStatus = (_projectHash, statusData, actualDelayMs) => {
|
|
19743
20084
|
broadcast2(clients, { type: "client.status_update", payload: statusData });
|
|
19744
20085
|
if (watcherMetrics) {
|
|
@@ -20958,14 +21299,7 @@ import {
|
|
|
20958
21299
|
toErrorMessage as toErrorMessage9
|
|
20959
21300
|
} from "@wrongstack/core/utils";
|
|
20960
21301
|
import { makeLightSubagentFactory } from "@wrongstack/runtime";
|
|
20961
|
-
import {
|
|
20962
|
-
createSageContextMonitorMiddleware,
|
|
20963
|
-
createSageToolCallMiddleware,
|
|
20964
|
-
createSageTurnMiddleware,
|
|
20965
|
-
getSageRetrieval,
|
|
20966
|
-
getSageService,
|
|
20967
|
-
InjectionTracker
|
|
20968
|
-
} from "@wrongstack/sage";
|
|
21302
|
+
import { getSageService, setupSage } from "@wrongstack/sage";
|
|
20969
21303
|
|
|
20970
21304
|
// src/server/discover-mailbox-bridge.ts
|
|
20971
21305
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -21768,6 +22102,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
21768
22102
|
this.broadcast(this.stateMessage());
|
|
21769
22103
|
if (this.broadcastInterval) return;
|
|
21770
22104
|
this.broadcastInterval = setInterval(() => this.broadcast(this.stateMessage()), 2e3);
|
|
22105
|
+
this.broadcastInterval.unref?.();
|
|
21771
22106
|
}
|
|
21772
22107
|
stopBroadcast() {
|
|
21773
22108
|
this.broadcast(this.stateMessage());
|
|
@@ -21819,62 +22154,15 @@ async function createAgentServices(input) {
|
|
|
21819
22154
|
const collabPause = collabPauseMiddleware(collabBus, { logger });
|
|
21820
22155
|
pipelines.toolCall.prepend(collabPause);
|
|
21821
22156
|
installDesignStudioMiddleware({ pipelines, ctx: context });
|
|
21822
|
-
const
|
|
21823
|
-
|
|
21824
|
-
|
|
21825
|
-
|
|
21826
|
-
|
|
21827
|
-
|
|
21828
|
-
|
|
21829
|
-
|
|
21830
|
-
|
|
21831
|
-
maxCharsPerTool: config.Sage?.inject?.maxCharsPerTool,
|
|
21832
|
-
taskAware: config.Sage?.inject?.taskAware,
|
|
21833
|
-
minScore: config.Sage?.inject?.minScore,
|
|
21834
|
-
minImportance: config.Sage?.inject?.minImportance,
|
|
21835
|
-
// Forward the explicit relation floor so an operator-configured
|
|
21836
|
-
// `Sage.inject.relationFloor` is honored in WebUI sessions. Without
|
|
21837
|
-
// this we silently fall back to MIN_RELATION_STRENGTH (0.85), which
|
|
21838
|
-
// is the CLI default but masks operator overrides.
|
|
21839
|
-
relationFloor: config.Sage?.inject?.relationFloor,
|
|
21840
|
-
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
21841
|
-
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
21842
|
-
triggers: config.Sage?.inject?.triggers,
|
|
21843
|
-
// Resolve the live session so retrieval and cooldown are session-scoped
|
|
21844
|
-
// (matching the CLI wiring in wiring/sage.ts). Without this, the
|
|
21845
|
-
// middleware falls back to ctx.session.id for cooldown but passes
|
|
21846
|
-
// undefined to retrieval, causing owned session-scoped memories to be
|
|
21847
|
-
// silently excluded from tool-call injection.
|
|
21848
|
-
getSessionId: getSageSessionId,
|
|
21849
|
-
tracker: sageInjectionTracker,
|
|
21850
|
-
events
|
|
21851
|
-
})
|
|
21852
|
-
);
|
|
21853
|
-
}
|
|
21854
|
-
if (config.Sage?.inject?.turnContext === true) {
|
|
21855
|
-
pipelines.request.use(
|
|
21856
|
-
createSageTurnMiddleware({
|
|
21857
|
-
memory: memoryRetrieval,
|
|
21858
|
-
maxMemories: config.Sage?.inject?.maxTurnMemories,
|
|
21859
|
-
maxChars: config.Sage?.inject?.maxCharsPerTurn,
|
|
21860
|
-
minScore: config.Sage?.inject?.minScore,
|
|
21861
|
-
// CLI parity: honor `Sage.retrieval.metadataWeight` so the same config
|
|
21862
|
-
// value drives both runtimes instead of silently falling back to the
|
|
21863
|
-
// 0.3 default. The undefined case keeps the middleware's own default.
|
|
21864
|
-
metadataWeight: config.Sage?.retrieval?.metadataWeight,
|
|
21865
|
-
getSessionId: getSageSessionId,
|
|
21866
|
-
tracker: sageInjectionTracker
|
|
21867
|
-
})
|
|
21868
|
-
);
|
|
21869
|
-
}
|
|
21870
|
-
pipelines.request.use(
|
|
21871
|
-
createSageContextMonitorMiddleware({
|
|
21872
|
-
tracker: sageInjectionTracker,
|
|
21873
|
-
events,
|
|
21874
|
-
getSessionId: getSageSessionId
|
|
21875
|
-
})
|
|
21876
|
-
);
|
|
21877
|
-
}
|
|
22157
|
+
const runSageSessionHygiene = setupSage({
|
|
22158
|
+
config,
|
|
22159
|
+
pipelines,
|
|
22160
|
+
memoryStore,
|
|
22161
|
+
logger,
|
|
22162
|
+
events,
|
|
22163
|
+
getSessionId: () => input.sessionGetter().id,
|
|
22164
|
+
projectRoot
|
|
22165
|
+
});
|
|
21878
22166
|
const codebaseIndexing = setupWebUICodebaseIndexing({
|
|
21879
22167
|
config,
|
|
21880
22168
|
context,
|
|
@@ -21986,7 +22274,12 @@ async function createAgentServices(input) {
|
|
|
21986
22274
|
confirmAwaiter: void 0,
|
|
21987
22275
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
21988
22276
|
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
21989
|
-
tracer: void 0
|
|
22277
|
+
tracer: void 0,
|
|
22278
|
+
// Off unless the operator opts in. The WebUI drives the same agent as the
|
|
22279
|
+
// CLI, so it must resolve this identically — a surface-dependent gate
|
|
22280
|
+
// would mean the same repo governs under `wstack` and not in the browser.
|
|
22281
|
+
// See packages/cli/src/wiring/pipeline.ts.
|
|
22282
|
+
requireKanbanGovernance: config.tools?.kanbanGovernance ?? DEFAULT_TOOLS_CONFIG.kanbanGovernance
|
|
21990
22283
|
});
|
|
21991
22284
|
input.installToolBoundary?.(pipelines);
|
|
21992
22285
|
const webuiLogger = container.resolve(TOKENS.Logger);
|
|
@@ -22006,6 +22299,7 @@ async function createAgentServices(input) {
|
|
|
22006
22299
|
providers: providerRegistry,
|
|
22007
22300
|
events,
|
|
22008
22301
|
pipelines,
|
|
22302
|
+
refreshSystemPrompt: true,
|
|
22009
22303
|
context,
|
|
22010
22304
|
maxIterations: config.tools?.maxIterations ?? DEFAULT_TOOLS_CONFIG.maxIterations,
|
|
22011
22305
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
@@ -22273,6 +22567,7 @@ async function createAgentServices(input) {
|
|
|
22273
22567
|
terminalHandler,
|
|
22274
22568
|
collabHandler,
|
|
22275
22569
|
disposeRealtimeHandlers,
|
|
22570
|
+
runSageSessionHygiene,
|
|
22276
22571
|
updateAutoCompactionMaxContext
|
|
22277
22572
|
};
|
|
22278
22573
|
}
|
|
@@ -22429,6 +22724,7 @@ async function setupWebUiGovernance(input, dependencies = DEFAULT_DEPENDENCIES)
|
|
|
22429
22724
|
|
|
22430
22725
|
// src/server/message-dispatcher.ts
|
|
22431
22726
|
import path23 from "node:path";
|
|
22727
|
+
import { planTool, taskTool, todoTool } from "@wrongstack/tools";
|
|
22432
22728
|
function createMessageDispatcher(opts) {
|
|
22433
22729
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
22434
22730
|
function makeWorklistContext() {
|
|
@@ -22440,7 +22736,20 @@ function createMessageDispatcher(opts) {
|
|
|
22440
22736
|
},
|
|
22441
22737
|
send: (w, m) => send(w, m),
|
|
22442
22738
|
broadcast: (m) => broadcast(state.getClients(), m),
|
|
22443
|
-
replaceTodos: (todos) => deps2.context.state.replaceTodos(todos)
|
|
22739
|
+
replaceTodos: (todos) => deps2.context.state.replaceTodos(todos),
|
|
22740
|
+
mutateTodos: async (todos) => {
|
|
22741
|
+
const result = await todoTool.execute({ todos }, deps2.context, {
|
|
22742
|
+
signal: AbortSignal.timeout(3e4)
|
|
22743
|
+
});
|
|
22744
|
+
return {
|
|
22745
|
+
todos: [...deps2.context.todos],
|
|
22746
|
+
...result.kanban_warnings ? { warnings: result.kanban_warnings } : {}
|
|
22747
|
+
};
|
|
22748
|
+
},
|
|
22749
|
+
mutateTaskStatus: async (id, status) => taskTool.execute({ action: "status", id, status }, deps2.context, {
|
|
22750
|
+
signal: AbortSignal.timeout(3e4)
|
|
22751
|
+
}),
|
|
22752
|
+
mutatePlan: async (operation) => planTool.execute(operation, deps2.context, { signal: AbortSignal.timeout(3e4) })
|
|
22444
22753
|
};
|
|
22445
22754
|
}
|
|
22446
22755
|
function makeSkillsContext() {
|
|
@@ -22652,6 +22961,7 @@ function createMessageDispatcher(opts) {
|
|
|
22652
22961
|
agentRoster: {
|
|
22653
22962
|
rosterHandler: new AgentRosterWSHandler({
|
|
22654
22963
|
projectRoot: state.getProjectRoot,
|
|
22964
|
+
getAutoOptimizeSettings: () => state.getConfig().fleet?.learning?.autoOptimize,
|
|
22655
22965
|
getLlm: () => {
|
|
22656
22966
|
const ctx = deps2.agent.ctx;
|
|
22657
22967
|
return ctx.provider && ctx.model ? { provider: ctx.provider, model: ctx.model } : void 0;
|
|
@@ -22737,15 +23047,9 @@ import {
|
|
|
22737
23047
|
makeMailInboxTool,
|
|
22738
23048
|
makeMailSendTool
|
|
22739
23049
|
} from "@wrongstack/core/coordination";
|
|
22740
|
-
import {
|
|
22741
|
-
DefaultPromptLoader,
|
|
22742
|
-
DefaultSkillLoader
|
|
22743
|
-
} from "@wrongstack/core/execution";
|
|
22744
|
-
import {
|
|
22745
|
-
EventBus,
|
|
22746
|
-
TOKENS as TOKENS2
|
|
22747
|
-
} from "@wrongstack/core/kernel";
|
|
23050
|
+
import { DefaultPromptLoader, DefaultSkillLoader } from "@wrongstack/core/execution";
|
|
22748
23051
|
import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
|
|
23052
|
+
import { EventBus, TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
22749
23053
|
import { DefaultModelsRegistry, DefaultModeStore } from "@wrongstack/core/models";
|
|
22750
23054
|
import { ProviderRegistry, ToolRegistry } from "@wrongstack/core/registry";
|
|
22751
23055
|
import { SkillInstaller } from "@wrongstack/core/skills";
|
|
@@ -23403,6 +23707,7 @@ async function createPreContextServices(input) {
|
|
|
23403
23707
|
modeId,
|
|
23404
23708
|
modePrompt,
|
|
23405
23709
|
modelCapabilities: () => modelCapabilitiesRef.current,
|
|
23710
|
+
tokenSavingMode: config.features.tokenSavingMode,
|
|
23406
23711
|
instructionPaths: {
|
|
23407
23712
|
globalDir: wpaths.globalInstructions,
|
|
23408
23713
|
projectDir: wpaths.inProjectInstructions,
|
|
@@ -23423,7 +23728,8 @@ async function createPreContextServices(input) {
|
|
|
23423
23728
|
const systemPrompt = await systemPromptBuilder.build({
|
|
23424
23729
|
cwd: projectRoot,
|
|
23425
23730
|
projectRoot,
|
|
23426
|
-
tools: toolRegistry.
|
|
23731
|
+
tools: toolRegistry.listForProvider(),
|
|
23732
|
+
catalogTools: toolRegistry.list(),
|
|
23427
23733
|
provider: config.provider,
|
|
23428
23734
|
model: config.model,
|
|
23429
23735
|
onlineAgents
|
|
@@ -23441,6 +23747,7 @@ async function createPreContextServices(input) {
|
|
|
23441
23747
|
projectRoot,
|
|
23442
23748
|
model: config.model
|
|
23443
23749
|
});
|
|
23750
|
+
context.meta["promptOnlineAgents"] = onlineAgents;
|
|
23444
23751
|
const initialContextPolicy = resolveContextWindowPolicy3(config.context);
|
|
23445
23752
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
23446
23753
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
@@ -23513,6 +23820,7 @@ function createModeHandlers(context) {
|
|
|
23513
23820
|
modeId: id,
|
|
23514
23821
|
modePrompt,
|
|
23515
23822
|
modelCapabilities: context.modelCapabilities,
|
|
23823
|
+
tokenSavingMode: config.features?.tokenSavingMode,
|
|
23516
23824
|
instructionPaths: {
|
|
23517
23825
|
globalDir: paths.globalInstructions,
|
|
23518
23826
|
projectDir: paths.inProjectInstructions,
|
|
@@ -23522,7 +23830,8 @@ function createModeHandlers(context) {
|
|
|
23522
23830
|
context.context.systemPrompt = await builder.build({
|
|
23523
23831
|
cwd: context.projectRoot,
|
|
23524
23832
|
projectRoot: context.projectRoot,
|
|
23525
|
-
tools: context.toolRegistry.
|
|
23833
|
+
tools: context.toolRegistry.listForProvider(),
|
|
23834
|
+
catalogTools: context.toolRegistry.list(),
|
|
23526
23835
|
provider: config.provider,
|
|
23527
23836
|
model: config.model
|
|
23528
23837
|
});
|
|
@@ -23862,11 +24171,11 @@ function buildRoutes(state, deps2, cb) {
|
|
|
23862
24171
|
}
|
|
23863
24172
|
|
|
23864
24173
|
// src/server/server-runtime.ts
|
|
23865
|
-
import * as path28 from "node:path";
|
|
23866
24174
|
import { createRequire as createRequire4 } from "node:module";
|
|
24175
|
+
import * as path28 from "node:path";
|
|
23867
24176
|
import { fileURLToPath } from "node:url";
|
|
23868
|
-
import { WebSocketServer } from "ws";
|
|
23869
24177
|
import { toErrorMessage as toErrorMessage12 } from "@wrongstack/core/utils";
|
|
24178
|
+
import { WebSocketServer } from "ws";
|
|
23870
24179
|
async function resolvePorts(opts) {
|
|
23871
24180
|
const surface = opts.surface ?? "webui";
|
|
23872
24181
|
const surfaceDefaults = surface === "simpleui" ? { http: 3466 } : { http: 3456 };
|
|
@@ -24797,15 +25106,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
24797
25106
|
eternalSubscription = null;
|
|
24798
25107
|
}
|
|
24799
25108
|
codebaseIndexing.dispose();
|
|
24800
|
-
|
|
24801
|
-
const candidate = memoryStore;
|
|
24802
|
-
await candidate.hygiene?.({
|
|
24803
|
-
retentionDays: config.Sage?.hygiene?.retentionDays,
|
|
24804
|
-
archiveLowConfidenceAfterDays: config.Sage?.hygiene?.archiveLowConfidenceAfterDays
|
|
24805
|
-
}).catch(
|
|
24806
|
-
(err) => logger.warn(`sage session hygiene failed: ${toErrorMessage13(err)}`)
|
|
24807
|
-
);
|
|
24808
|
-
}
|
|
25109
|
+
await agentServices.runSageSessionHygiene().catch((err) => logger.warn(`sage session hygiene failed: ${toErrorMessage13(err)}`));
|
|
24809
25110
|
await memoryStore.dispose().catch(
|
|
24810
25111
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage13(err)}`)
|
|
24811
25112
|
);
|