@wrongstack/webui-server 0.303.0 → 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 +322 -157
- 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/backend-services.d.ts +5 -0
- package/dist/server/entry.js +316 -155
- package/dist/server/kanban-contract-routes.d.ts +8 -0
- package/dist/server/kanban-route-protocol.d.ts +1 -1
- 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/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) {
|
|
@@ -2159,6 +2163,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2159
2163
|
this.broadcast(sessionId, this.stateMessage(sessionId));
|
|
2160
2164
|
}
|
|
2161
2165
|
}, 2e3);
|
|
2166
|
+
this.broadcastInterval.unref?.();
|
|
2162
2167
|
}
|
|
2163
2168
|
stopBroadcast() {
|
|
2164
2169
|
if (this.broadcastInterval) {
|
|
@@ -7131,13 +7136,18 @@ var GoalWebSocketHandler = class {
|
|
|
7131
7136
|
const { execFile: execFile2 } = await import("node:child_process");
|
|
7132
7137
|
const result = await new Promise((resolve16) => {
|
|
7133
7138
|
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
7134
|
-
execFile2(
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
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);
|
|
7138
7149
|
}
|
|
7139
|
-
|
|
7140
|
-
});
|
|
7150
|
+
);
|
|
7141
7151
|
});
|
|
7142
7152
|
if (result.includes("[verify]") || result.trim().length === 0) {
|
|
7143
7153
|
return { ok: true };
|
|
@@ -7439,6 +7449,7 @@ ${result_.finalText.slice(0, 2e3)}`
|
|
|
7439
7449
|
if (progress) this.broadcast({ type: "goal.progress", payload: progress });
|
|
7440
7450
|
this.broadcastState();
|
|
7441
7451
|
}, 2e3);
|
|
7452
|
+
this.broadcastInterval.unref?.();
|
|
7442
7453
|
}
|
|
7443
7454
|
stopBroadcast() {
|
|
7444
7455
|
if (this.broadcastInterval) {
|
|
@@ -7563,8 +7574,9 @@ ${result_.finalText.slice(0, 2e3)}`
|
|
|
7563
7574
|
}
|
|
7564
7575
|
broadcast(msg) {
|
|
7565
7576
|
const data = JSON.stringify(msg);
|
|
7577
|
+
const frameBytes = Buffer.byteLength(data, "utf8");
|
|
7566
7578
|
for (const client of this.clients) {
|
|
7567
|
-
sendSerialized(client.ws, data);
|
|
7579
|
+
sendSerialized(client.ws, data, frameBytes);
|
|
7568
7580
|
}
|
|
7569
7581
|
}
|
|
7570
7582
|
send(client, msg) {
|
|
@@ -9467,7 +9479,19 @@ function isTrustedLoopbackOrigin(origin, hostHeader) {
|
|
|
9467
9479
|
if (!host) return false;
|
|
9468
9480
|
const hostUrl = new URL(`${url.protocol}//${host}`);
|
|
9469
9481
|
if (!isLoopbackHostname(hostUrl.hostname)) return false;
|
|
9470
|
-
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);
|
|
9471
9495
|
} catch {
|
|
9472
9496
|
return false;
|
|
9473
9497
|
}
|
|
@@ -9572,13 +9596,15 @@ function verifyClient(input) {
|
|
|
9572
9596
|
try {
|
|
9573
9597
|
const { hostname: originHostname } = new URL(origin);
|
|
9574
9598
|
if (isLoopbackHostname(originHostname)) {
|
|
9575
|
-
if (requireToken || !isLoopbackBind(wsHost))
|
|
9599
|
+
if (requireToken || !isLoopbackBind(wsHost)) {
|
|
9600
|
+
return cookieTokenOk && (originMatchesHost(origin, hostHeader) || Boolean(allowCrossPortLoopbackCookie));
|
|
9601
|
+
}
|
|
9576
9602
|
if (!isTrustedLoopbackOrigin(origin, hostHeader)) {
|
|
9577
9603
|
return Boolean(allowCrossPortLoopbackCookie) && cookieTokenOk;
|
|
9578
9604
|
}
|
|
9579
9605
|
return true;
|
|
9580
9606
|
}
|
|
9581
|
-
return cookieTokenOk || Boolean(allowBrowserUrlToken) && urlTokenOk && allowedHostname(originHostname, allowedHostnames);
|
|
9607
|
+
return cookieTokenOk && originMatchesHost(origin, hostHeader) || Boolean(allowBrowserUrlToken) && urlTokenOk && allowedHostname(originHostname, allowedHostnames);
|
|
9582
9608
|
} catch {
|
|
9583
9609
|
return false;
|
|
9584
9610
|
}
|
|
@@ -10468,7 +10494,8 @@ async function handleIntrospectionRoute(ctx, ws, message) {
|
|
|
10468
10494
|
switch (message.type) {
|
|
10469
10495
|
case "diag.get": {
|
|
10470
10496
|
if (!sessionAllowed(ctx, ws, message)) return true;
|
|
10471
|
-
const
|
|
10497
|
+
const registry = ctx.agent.tools;
|
|
10498
|
+
const tools = registry.listForProvider?.() ?? registry.list();
|
|
10472
10499
|
ctx.send(ws, {
|
|
10473
10500
|
type: "diag.get",
|
|
10474
10501
|
payload: {
|
|
@@ -10551,6 +10578,7 @@ async function handleIntrospectionRoute(ctx, ws, message) {
|
|
|
10551
10578
|
description: tool.description ?? "",
|
|
10552
10579
|
params: schema.properties ? Object.keys(schema.properties) : [],
|
|
10553
10580
|
disabled: registry.isDisabled?.(tool.name) ?? false,
|
|
10581
|
+
direct: registry.isExposedToProvider?.(tool.name) ?? true,
|
|
10554
10582
|
mutating: !!tool.mutating,
|
|
10555
10583
|
permission: tool.permission ?? "auto"
|
|
10556
10584
|
};
|
|
@@ -10846,7 +10874,6 @@ async function handleKanbanHostRoute(ws, msg, handlers) {
|
|
|
10846
10874
|
import { deserializeTaskGraph, serializeTaskGraph } from "@wrongstack/core/tasking";
|
|
10847
10875
|
import {
|
|
10848
10876
|
addCheckToTask,
|
|
10849
|
-
addColumn,
|
|
10850
10877
|
addGoalMetricToTask,
|
|
10851
10878
|
addNoteToTask,
|
|
10852
10879
|
addTask,
|
|
@@ -10861,7 +10888,10 @@ import {
|
|
|
10861
10888
|
getKanbanOrchestrationSnapshot,
|
|
10862
10889
|
getKanbanQueueHealth,
|
|
10863
10890
|
getTaskChain,
|
|
10891
|
+
hasKanbanQueueAnomalies,
|
|
10892
|
+
kanbanQueueAnomalyCount,
|
|
10864
10893
|
listBoards as listBoards2,
|
|
10894
|
+
listBoardHistory,
|
|
10865
10895
|
listReadyTasks,
|
|
10866
10896
|
mergeTasks,
|
|
10867
10897
|
moveTask,
|
|
@@ -10870,7 +10900,6 @@ import {
|
|
|
10870
10900
|
recoverStaleTaskAssignments,
|
|
10871
10901
|
releaseTaskClaim,
|
|
10872
10902
|
removeBoard,
|
|
10873
|
-
removeColumn,
|
|
10874
10903
|
setTaskChain,
|
|
10875
10904
|
splitTask,
|
|
10876
10905
|
syncBoardFromTaskGraph,
|
|
@@ -10882,14 +10911,16 @@ import {
|
|
|
10882
10911
|
updateTask as updateTask2
|
|
10883
10912
|
} from "@wrongstack/kanban";
|
|
10884
10913
|
|
|
10885
|
-
// src/server/kanban-
|
|
10914
|
+
// src/server/kanban-contract-routes.ts
|
|
10886
10915
|
import {
|
|
10887
|
-
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
|
|
10916
|
+
addContractEdge,
|
|
10917
|
+
configureContractGraph,
|
|
10918
|
+
evaluateTaskContractGraph,
|
|
10919
|
+
getContractGraph,
|
|
10920
|
+
removeContractEdge,
|
|
10921
|
+
removeContractNode,
|
|
10922
|
+
upsertContractNode
|
|
10891
10923
|
} from "@wrongstack/kanban";
|
|
10892
|
-
import { recordKanbanVerificationEvidence as recordKanbanVerificationEvidence2 } from "@wrongstack/tools";
|
|
10893
10924
|
|
|
10894
10925
|
// src/server/kanban-route-helpers.ts
|
|
10895
10926
|
import { touchKanbanPresence } from "@wrongstack/kanban";
|
|
@@ -10941,7 +10972,150 @@ function findTask2(tasks, taskId) {
|
|
|
10941
10972
|
return tasks.find((task) => task.id === taskId || task.id.startsWith(taskId));
|
|
10942
10973
|
}
|
|
10943
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
|
+
|
|
10944
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";
|
|
10945
11119
|
async function handleKanbanDecompositionRoute(ws, type, payload, ctx) {
|
|
10946
11120
|
switch (type) {
|
|
10947
11121
|
case "kanban.decomposition.approve":
|
|
@@ -11194,6 +11368,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11194
11368
|
const type = msg.type;
|
|
11195
11369
|
try {
|
|
11196
11370
|
if (await handleKanbanDecompositionRoute(ws, type, payload, ctx)) return true;
|
|
11371
|
+
if (await handleKanbanContractRoute(ws, type, payload, ctx)) return true;
|
|
11197
11372
|
if (await handleKanbanTaskRoute(ws, type, payload, ctx)) return true;
|
|
11198
11373
|
switch (type) {
|
|
11199
11374
|
case "kanban.list": {
|
|
@@ -11232,7 +11407,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11232
11407
|
fail(ws, type, "boardId required");
|
|
11233
11408
|
return true;
|
|
11234
11409
|
}
|
|
11235
|
-
ok(
|
|
11410
|
+
ok(
|
|
11411
|
+
ws,
|
|
11412
|
+
type,
|
|
11413
|
+
await getKanbanQueueHealth(ctx.projectRoot, {
|
|
11414
|
+
boardId: hBoardId,
|
|
11415
|
+
includeClassifications: false
|
|
11416
|
+
})
|
|
11417
|
+
);
|
|
11236
11418
|
return true;
|
|
11237
11419
|
}
|
|
11238
11420
|
case "kanban.supervisor.status":
|
|
@@ -11262,16 +11444,16 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11262
11444
|
reason: "On-demand standalone Kanban supervisor audit."
|
|
11263
11445
|
}) : null;
|
|
11264
11446
|
if (recovered) health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
11265
|
-
const anomalyCount = health
|
|
11447
|
+
const anomalyCount = kanbanQueueAnomalyCount(health);
|
|
11266
11448
|
ok(ws, type, {
|
|
11267
11449
|
boardId,
|
|
11268
|
-
status: board.supervisor?.enabled === false ? "disabled" :
|
|
11450
|
+
status: board.supervisor?.enabled === false ? "disabled" : hasKanbanQueueAnomalies(health) ? "attention" : "healthy",
|
|
11269
11451
|
mode: board.supervisor?.mode ?? "deterministic",
|
|
11270
11452
|
lastAuditAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11271
11453
|
reconciledTaskIds: reconciled?.tasks.map((task) => task.id) ?? [],
|
|
11272
11454
|
staleRecoveredTaskIds: recovered?.tasks.map((task) => task.id) ?? [],
|
|
11273
11455
|
anomalyCount,
|
|
11274
|
-
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`
|
|
11275
11457
|
});
|
|
11276
11458
|
return true;
|
|
11277
11459
|
}
|
|
@@ -11288,7 +11470,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11288
11470
|
title,
|
|
11289
11471
|
...payload?.description ? { description: payload.description } : {},
|
|
11290
11472
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
11291
|
-
...payload?.columns ? { columns: payload.columns } : {},
|
|
11292
11473
|
...has(payload, "lifecycle") ? { lifecycle: payload?.lifecycle } : {},
|
|
11293
11474
|
...has(payload, "boundary") ? { boundary: payload?.boundary } : {}
|
|
11294
11475
|
})
|
|
@@ -11305,7 +11486,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11305
11486
|
...payload?.title ? { title: payload.title } : {},
|
|
11306
11487
|
...payload?.description ? { description: payload.description } : {},
|
|
11307
11488
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
11308
|
-
...payload?.columns ? { columns: payload.columns } : {},
|
|
11309
11489
|
...has(payload, "lifecycle") ? {
|
|
11310
11490
|
lifecycle: payload?.lifecycle ?? null
|
|
11311
11491
|
} : {},
|
|
@@ -11352,6 +11532,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11352
11532
|
});
|
|
11353
11533
|
return true;
|
|
11354
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
|
+
}
|
|
11355
11541
|
case "kanban.generate": {
|
|
11356
11542
|
const description = payload?.description;
|
|
11357
11543
|
if (!description) {
|
|
@@ -11813,8 +11999,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11813
11999
|
taskId,
|
|
11814
12000
|
{
|
|
11815
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.
|
|
11816
12007
|
type: payload?.checkType ?? "manual",
|
|
11817
|
-
status: payload?.status ?? "pending"
|
|
12008
|
+
status: payload?.status ?? "pending",
|
|
12009
|
+
...typeof payload?.notes === "string" ? { notes: payload.notes } : {}
|
|
11818
12010
|
},
|
|
11819
12011
|
activityContext(
|
|
11820
12012
|
ctx,
|
|
@@ -11879,30 +12071,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11879
12071
|
case "kanban.capabilities":
|
|
11880
12072
|
ok(ws, type, { dispatchSupported: Boolean(ctx.dispatchTask) });
|
|
11881
12073
|
return true;
|
|
11882
|
-
case "kanban.column.add": {
|
|
11883
|
-
const boardId = payload?.boardId;
|
|
11884
|
-
const title = payload?.title;
|
|
11885
|
-
if (!boardId || !title) {
|
|
11886
|
-
fail(ws, type, "boardId and title required");
|
|
11887
|
-
return true;
|
|
11888
|
-
}
|
|
11889
|
-
const result = await addColumn(ctx.projectRoot, boardId, { title });
|
|
11890
|
-
result ? ok(ws, type, result.board.columns) : fail(ws, type, `Board not found: ${boardId}`);
|
|
11891
|
-
return true;
|
|
11892
|
-
}
|
|
11893
|
-
case "kanban.column.remove": {
|
|
11894
|
-
const boardId = payload?.boardId;
|
|
11895
|
-
const columnId = payload?.columnId;
|
|
11896
|
-
if (!boardId || !columnId) {
|
|
11897
|
-
fail(ws, type, "boardId and columnId required");
|
|
11898
|
-
return true;
|
|
11899
|
-
}
|
|
11900
|
-
const board = await removeColumn(ctx.projectRoot, boardId, columnId, {
|
|
11901
|
-
moveTasksToColumnId: payload?.moveTasksToColumnId
|
|
11902
|
-
});
|
|
11903
|
-
board ? ok(ws, type, { removed: true, boardId: board.id, columnId, board }) : fail(ws, type, `Column not found: ${columnId}`);
|
|
11904
|
-
return true;
|
|
11905
|
-
}
|
|
11906
12074
|
default:
|
|
11907
12075
|
fail(ws, type, `Unknown kanban message type: ${type}`);
|
|
11908
12076
|
return true;
|
|
@@ -13167,6 +13335,34 @@ async function handleSageRecover(ws, msg, memoryStore) {
|
|
|
13167
13335
|
send(ws, { type: "memory.sage.recover", payload: { error: errMessage(err) } });
|
|
13168
13336
|
}
|
|
13169
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
|
+
}
|
|
13170
13366
|
async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
13171
13367
|
const Sage = getSageSurface(memoryStore);
|
|
13172
13368
|
if (!Sage) {
|
|
@@ -13345,6 +13541,9 @@ async function handleMemoryRoute(ctx, ws, message) {
|
|
|
13345
13541
|
case "memory.sage.recover":
|
|
13346
13542
|
await handleSageRecover(ws, message, store);
|
|
13347
13543
|
return true;
|
|
13544
|
+
case "memory.sage.listCandidates":
|
|
13545
|
+
await handleSageListCandidates(ws, message, store);
|
|
13546
|
+
return true;
|
|
13348
13547
|
case "memory.sage.candidateResolve":
|
|
13349
13548
|
await handleSageCandidateResolve(ws, message, store);
|
|
13350
13549
|
return true;
|
|
@@ -13947,6 +14146,7 @@ import {
|
|
|
13947
14146
|
finalizeTaskCompletion,
|
|
13948
14147
|
getBoard as getBoard2,
|
|
13949
14148
|
getKanbanQueueHealth as getKanbanQueueHealth2,
|
|
14149
|
+
kanbanQueueAnomalyCount as kanbanQueueAnomalyCount2,
|
|
13950
14150
|
listBoards as listBoards3,
|
|
13951
14151
|
reconcileKanbanBoard as reconcileKanbanBoard2,
|
|
13952
14152
|
recoverStaleTaskAssignments as recoverStaleTaskAssignments2,
|
|
@@ -14029,7 +14229,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
14029
14229
|
}) : null;
|
|
14030
14230
|
if (recovered)
|
|
14031
14231
|
health = await getKanbanQueueHealth2(resolveProjectRoot(deps2), { boardId: board.id });
|
|
14032
|
-
const anomalyCount =
|
|
14232
|
+
const anomalyCount = kanbanQueueAnomalyCount2(health);
|
|
14033
14233
|
const snapshot = {
|
|
14034
14234
|
boardId: board.id,
|
|
14035
14235
|
status: anomalyCount > 0 ? "attention" : "healthy",
|
|
@@ -14228,13 +14428,10 @@ function dispatchRoute(routing) {
|
|
|
14228
14428
|
...routing.fallbackModels?.length ? { fallbackModels: routing.fallbackModels } : {}
|
|
14229
14429
|
};
|
|
14230
14430
|
}
|
|
14231
|
-
function countAnomalies(health) {
|
|
14232
|
-
return health.staleAssignments.count + health.heartbeatDue.count + health.counts.failed + health.counts.blocked;
|
|
14233
|
-
}
|
|
14234
14431
|
function healthSummary(health) {
|
|
14235
14432
|
return [
|
|
14236
14433
|
`${health.counts.running} running`,
|
|
14237
|
-
`${health.counts.
|
|
14434
|
+
`${health.counts.startable} ready`,
|
|
14238
14435
|
`${health.counts.review} review`,
|
|
14239
14436
|
`${health.counts.blocked} blocked`,
|
|
14240
14437
|
`${health.counts.failed} failed`,
|
|
@@ -14988,7 +15185,7 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
14988
15185
|
import * as fs14 from "node:fs/promises";
|
|
14989
15186
|
import * as path16 from "node:path";
|
|
14990
15187
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
14991
|
-
import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
15188
|
+
import { activateProjectStateGuard, resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
14992
15189
|
function createProjectHandlers(ctx) {
|
|
14993
15190
|
const sendTo = (ws, message) => {
|
|
14994
15191
|
if (ctx.sendMessage) ctx.sendMessage(ws, message);
|
|
@@ -15153,6 +15350,7 @@ function createProjectHandlers(ctx) {
|
|
|
15153
15350
|
try {
|
|
15154
15351
|
await ctx.onSessionSwapped?.(next.id, identityTarget);
|
|
15155
15352
|
await ctx.onBeforeSessionTodosReplaced?.(next.id, paths.projectSessions);
|
|
15353
|
+
await activateProjectStateGuard(resolved);
|
|
15156
15354
|
} catch (err) {
|
|
15157
15355
|
try {
|
|
15158
15356
|
await ctx.onBeforeSessionTodosReplaced?.(previous.id, previousPaths.projectSessions);
|
|
@@ -16107,6 +16305,7 @@ var CLIENT_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
16107
16305
|
"memory.sage.get",
|
|
16108
16306
|
"memory.sage.graph",
|
|
16109
16307
|
"memory.sage.list",
|
|
16308
|
+
"memory.sage.listCandidates",
|
|
16110
16309
|
"memory.sage.listPage",
|
|
16111
16310
|
"memory.sage.recover",
|
|
16112
16311
|
"memory.sage.remember",
|
|
@@ -16386,6 +16585,7 @@ var SERVER_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
16386
16585
|
"memory.sage.get",
|
|
16387
16586
|
"memory.sage.graph",
|
|
16388
16587
|
"memory.sage.list",
|
|
16588
|
+
"memory.sage.listCandidates",
|
|
16389
16589
|
"memory.sage.listPage",
|
|
16390
16590
|
"memory.sage.recover",
|
|
16391
16591
|
"memory.sage.remember",
|
|
@@ -17046,6 +17246,7 @@ function createSessionHandlers(ctx) {
|
|
|
17046
17246
|
ctx.abortActiveRun?.(current2.id);
|
|
17047
17247
|
} catch {
|
|
17048
17248
|
}
|
|
17249
|
+
await ctx.context.flushConversationJournal?.().catch(() => void 0);
|
|
17049
17250
|
await finalizeSession(current2);
|
|
17050
17251
|
}
|
|
17051
17252
|
ctx.setSession(next);
|
|
@@ -17580,6 +17781,7 @@ import {
|
|
|
17580
17781
|
clearProjectAgentConsolidated,
|
|
17581
17782
|
clearProjectSkillAugmentation,
|
|
17582
17783
|
createProjectAgent,
|
|
17784
|
+
DEFAULT_EAGER_SKILL_LIMIT,
|
|
17583
17785
|
detectLearnedConflicts,
|
|
17584
17786
|
evaluateAutoOptimize,
|
|
17585
17787
|
FLEET_ROSTER,
|
|
@@ -17597,12 +17799,15 @@ import {
|
|
|
17597
17799
|
loadProjectSkillAugmentation,
|
|
17598
17800
|
loadSkillAffinity,
|
|
17599
17801
|
optimizeProjectAgentLearning,
|
|
17802
|
+
rankRoleSkills,
|
|
17803
|
+
readQuarantinedDirectives,
|
|
17600
17804
|
readRawLearnedEntries,
|
|
17601
17805
|
resetProjectAgentIdentity,
|
|
17602
17806
|
resolveAutoOptimizePolicy,
|
|
17603
17807
|
resolveRoleSkillCandidates,
|
|
17604
17808
|
saveProjectAgentConsolidated,
|
|
17605
17809
|
saveProjectSkillAugmentation,
|
|
17810
|
+
scoreSkillAffinity,
|
|
17606
17811
|
setSkillPinned,
|
|
17607
17812
|
slugifyProjectAgentRole,
|
|
17608
17813
|
updateProjectAgentConfig,
|
|
@@ -17909,9 +18114,7 @@ ${String(p.content ?? "")}`;
|
|
|
17909
18114
|
// eligible right now, and why not when it does not. Surfacing the reason
|
|
17910
18115
|
// is what keeps "nothing happened" from looking like a broken feature.
|
|
17911
18116
|
case "agent-roster.auto-optimize-status": {
|
|
17912
|
-
const policy = resolveAutoOptimizePolicy(
|
|
17913
|
-
this.getAutoOptimizeSettings?.() ?? void 0
|
|
17914
|
-
);
|
|
18117
|
+
const policy = resolveAutoOptimizePolicy(this.getAutoOptimizeSettings?.() ?? void 0);
|
|
17915
18118
|
const roles = role ? [role] : listProjectAgentRoles(projectRoot);
|
|
17916
18119
|
return {
|
|
17917
18120
|
type,
|
|
@@ -17934,18 +18137,30 @@ ${String(p.content ?? "")}`;
|
|
|
17934
18137
|
const candidates = resolveRoleSkillCandidates(role, projectRoot);
|
|
17935
18138
|
const developed = listProjectSkillAugmentations(role, projectRoot);
|
|
17936
18139
|
const affinity = loadSkillAffinity(role, projectRoot);
|
|
18140
|
+
const eager = new Set(rankRoleSkills(role, candidates, projectRoot));
|
|
17937
18141
|
return {
|
|
17938
18142
|
type,
|
|
17939
18143
|
payload: {
|
|
17940
18144
|
role,
|
|
18145
|
+
eagerLimit: DEFAULT_EAGER_SKILL_LIMIT,
|
|
17941
18146
|
skills: candidates.map((skill) => ({
|
|
17942
18147
|
skill,
|
|
17943
18148
|
developed: developed.includes(skill),
|
|
17944
|
-
affinity: affinity.entries[skill] ?? null
|
|
18149
|
+
affinity: affinity.entries[skill] ?? null,
|
|
18150
|
+
score: scoreSkillAffinity(affinity.entries[skill]) + (developed.includes(skill) ? 1 : 0),
|
|
18151
|
+
eager: eager.has(skill)
|
|
17945
18152
|
}))
|
|
17946
18153
|
}
|
|
17947
18154
|
};
|
|
17948
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
|
+
}
|
|
17949
18164
|
case "agent-roster.read-skill": {
|
|
17950
18165
|
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
17951
18166
|
if (!role || !skill) return { type, payload: { error: "role and skill required" } };
|
|
@@ -19864,6 +20079,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
19864
20079
|
const logWatcherMetricsEnabled = shouldLogWatcherStats();
|
|
19865
20080
|
const logWatcherMetrics = () => logFileWatcherMetrics(watcherMetrics);
|
|
19866
20081
|
const metricsInterval = logWatcherMetricsEnabled ? setInterval(logWatcherMetrics, 6e4) : void 0;
|
|
20082
|
+
metricsInterval?.unref?.();
|
|
19867
20083
|
const broadcastStatus = (_projectHash, statusData, actualDelayMs) => {
|
|
19868
20084
|
broadcast2(clients, { type: "client.status_update", payload: statusData });
|
|
19869
20085
|
if (watcherMetrics) {
|
|
@@ -21083,14 +21299,7 @@ import {
|
|
|
21083
21299
|
toErrorMessage as toErrorMessage9
|
|
21084
21300
|
} from "@wrongstack/core/utils";
|
|
21085
21301
|
import { makeLightSubagentFactory } from "@wrongstack/runtime";
|
|
21086
|
-
import {
|
|
21087
|
-
createSageContextMonitorMiddleware,
|
|
21088
|
-
createSageToolCallMiddleware,
|
|
21089
|
-
createSageTurnMiddleware,
|
|
21090
|
-
getSageRetrieval,
|
|
21091
|
-
getSageService,
|
|
21092
|
-
InjectionTracker
|
|
21093
|
-
} from "@wrongstack/sage";
|
|
21302
|
+
import { getSageService, setupSage } from "@wrongstack/sage";
|
|
21094
21303
|
|
|
21095
21304
|
// src/server/discover-mailbox-bridge.ts
|
|
21096
21305
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -21893,6 +22102,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
21893
22102
|
this.broadcast(this.stateMessage());
|
|
21894
22103
|
if (this.broadcastInterval) return;
|
|
21895
22104
|
this.broadcastInterval = setInterval(() => this.broadcast(this.stateMessage()), 2e3);
|
|
22105
|
+
this.broadcastInterval.unref?.();
|
|
21896
22106
|
}
|
|
21897
22107
|
stopBroadcast() {
|
|
21898
22108
|
this.broadcast(this.stateMessage());
|
|
@@ -21944,62 +22154,15 @@ async function createAgentServices(input) {
|
|
|
21944
22154
|
const collabPause = collabPauseMiddleware(collabBus, { logger });
|
|
21945
22155
|
pipelines.toolCall.prepend(collabPause);
|
|
21946
22156
|
installDesignStudioMiddleware({ pipelines, ctx: context });
|
|
21947
|
-
const
|
|
21948
|
-
|
|
21949
|
-
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
maxCharsPerTool: config.Sage?.inject?.maxCharsPerTool,
|
|
21957
|
-
taskAware: config.Sage?.inject?.taskAware,
|
|
21958
|
-
minScore: config.Sage?.inject?.minScore,
|
|
21959
|
-
minImportance: config.Sage?.inject?.minImportance,
|
|
21960
|
-
// Forward the explicit relation floor so an operator-configured
|
|
21961
|
-
// `Sage.inject.relationFloor` is honored in WebUI sessions. Without
|
|
21962
|
-
// this we silently fall back to MIN_RELATION_STRENGTH (0.85), which
|
|
21963
|
-
// is the CLI default but masks operator overrides.
|
|
21964
|
-
relationFloor: config.Sage?.inject?.relationFloor,
|
|
21965
|
-
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
21966
|
-
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
21967
|
-
triggers: config.Sage?.inject?.triggers,
|
|
21968
|
-
// Resolve the live session so retrieval and cooldown are session-scoped
|
|
21969
|
-
// (matching the CLI wiring in wiring/sage.ts). Without this, the
|
|
21970
|
-
// middleware falls back to ctx.session.id for cooldown but passes
|
|
21971
|
-
// undefined to retrieval, causing owned session-scoped memories to be
|
|
21972
|
-
// silently excluded from tool-call injection.
|
|
21973
|
-
getSessionId: getSageSessionId,
|
|
21974
|
-
tracker: sageInjectionTracker,
|
|
21975
|
-
events
|
|
21976
|
-
})
|
|
21977
|
-
);
|
|
21978
|
-
}
|
|
21979
|
-
if (config.Sage?.inject?.turnContext === true) {
|
|
21980
|
-
pipelines.request.use(
|
|
21981
|
-
createSageTurnMiddleware({
|
|
21982
|
-
memory: memoryRetrieval,
|
|
21983
|
-
maxMemories: config.Sage?.inject?.maxTurnMemories,
|
|
21984
|
-
maxChars: config.Sage?.inject?.maxCharsPerTurn,
|
|
21985
|
-
minScore: config.Sage?.inject?.minScore,
|
|
21986
|
-
// CLI parity: honor `Sage.retrieval.metadataWeight` so the same config
|
|
21987
|
-
// value drives both runtimes instead of silently falling back to the
|
|
21988
|
-
// 0.3 default. The undefined case keeps the middleware's own default.
|
|
21989
|
-
metadataWeight: config.Sage?.retrieval?.metadataWeight,
|
|
21990
|
-
getSessionId: getSageSessionId,
|
|
21991
|
-
tracker: sageInjectionTracker
|
|
21992
|
-
})
|
|
21993
|
-
);
|
|
21994
|
-
}
|
|
21995
|
-
pipelines.request.use(
|
|
21996
|
-
createSageContextMonitorMiddleware({
|
|
21997
|
-
tracker: sageInjectionTracker,
|
|
21998
|
-
events,
|
|
21999
|
-
getSessionId: getSageSessionId
|
|
22000
|
-
})
|
|
22001
|
-
);
|
|
22002
|
-
}
|
|
22157
|
+
const runSageSessionHygiene = setupSage({
|
|
22158
|
+
config,
|
|
22159
|
+
pipelines,
|
|
22160
|
+
memoryStore,
|
|
22161
|
+
logger,
|
|
22162
|
+
events,
|
|
22163
|
+
getSessionId: () => input.sessionGetter().id,
|
|
22164
|
+
projectRoot
|
|
22165
|
+
});
|
|
22003
22166
|
const codebaseIndexing = setupWebUICodebaseIndexing({
|
|
22004
22167
|
config,
|
|
22005
22168
|
context,
|
|
@@ -22111,7 +22274,12 @@ async function createAgentServices(input) {
|
|
|
22111
22274
|
confirmAwaiter: void 0,
|
|
22112
22275
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
22113
22276
|
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
22114
|
-
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
|
|
22115
22283
|
});
|
|
22116
22284
|
input.installToolBoundary?.(pipelines);
|
|
22117
22285
|
const webuiLogger = container.resolve(TOKENS.Logger);
|
|
@@ -22131,6 +22299,7 @@ async function createAgentServices(input) {
|
|
|
22131
22299
|
providers: providerRegistry,
|
|
22132
22300
|
events,
|
|
22133
22301
|
pipelines,
|
|
22302
|
+
refreshSystemPrompt: true,
|
|
22134
22303
|
context,
|
|
22135
22304
|
maxIterations: config.tools?.maxIterations ?? DEFAULT_TOOLS_CONFIG.maxIterations,
|
|
22136
22305
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
@@ -22398,6 +22567,7 @@ async function createAgentServices(input) {
|
|
|
22398
22567
|
terminalHandler,
|
|
22399
22568
|
collabHandler,
|
|
22400
22569
|
disposeRealtimeHandlers,
|
|
22570
|
+
runSageSessionHygiene,
|
|
22401
22571
|
updateAutoCompactionMaxContext
|
|
22402
22572
|
};
|
|
22403
22573
|
}
|
|
@@ -22877,15 +23047,9 @@ import {
|
|
|
22877
23047
|
makeMailInboxTool,
|
|
22878
23048
|
makeMailSendTool
|
|
22879
23049
|
} from "@wrongstack/core/coordination";
|
|
22880
|
-
import {
|
|
22881
|
-
DefaultPromptLoader,
|
|
22882
|
-
DefaultSkillLoader
|
|
22883
|
-
} from "@wrongstack/core/execution";
|
|
22884
|
-
import {
|
|
22885
|
-
EventBus,
|
|
22886
|
-
TOKENS as TOKENS2
|
|
22887
|
-
} from "@wrongstack/core/kernel";
|
|
23050
|
+
import { DefaultPromptLoader, DefaultSkillLoader } from "@wrongstack/core/execution";
|
|
22888
23051
|
import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
|
|
23052
|
+
import { EventBus, TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
22889
23053
|
import { DefaultModelsRegistry, DefaultModeStore } from "@wrongstack/core/models";
|
|
22890
23054
|
import { ProviderRegistry, ToolRegistry } from "@wrongstack/core/registry";
|
|
22891
23055
|
import { SkillInstaller } from "@wrongstack/core/skills";
|
|
@@ -23543,6 +23707,7 @@ async function createPreContextServices(input) {
|
|
|
23543
23707
|
modeId,
|
|
23544
23708
|
modePrompt,
|
|
23545
23709
|
modelCapabilities: () => modelCapabilitiesRef.current,
|
|
23710
|
+
tokenSavingMode: config.features.tokenSavingMode,
|
|
23546
23711
|
instructionPaths: {
|
|
23547
23712
|
globalDir: wpaths.globalInstructions,
|
|
23548
23713
|
projectDir: wpaths.inProjectInstructions,
|
|
@@ -23563,7 +23728,8 @@ async function createPreContextServices(input) {
|
|
|
23563
23728
|
const systemPrompt = await systemPromptBuilder.build({
|
|
23564
23729
|
cwd: projectRoot,
|
|
23565
23730
|
projectRoot,
|
|
23566
|
-
tools: toolRegistry.
|
|
23731
|
+
tools: toolRegistry.listForProvider(),
|
|
23732
|
+
catalogTools: toolRegistry.list(),
|
|
23567
23733
|
provider: config.provider,
|
|
23568
23734
|
model: config.model,
|
|
23569
23735
|
onlineAgents
|
|
@@ -23581,6 +23747,7 @@ async function createPreContextServices(input) {
|
|
|
23581
23747
|
projectRoot,
|
|
23582
23748
|
model: config.model
|
|
23583
23749
|
});
|
|
23750
|
+
context.meta["promptOnlineAgents"] = onlineAgents;
|
|
23584
23751
|
const initialContextPolicy = resolveContextWindowPolicy3(config.context);
|
|
23585
23752
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
23586
23753
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
@@ -23653,6 +23820,7 @@ function createModeHandlers(context) {
|
|
|
23653
23820
|
modeId: id,
|
|
23654
23821
|
modePrompt,
|
|
23655
23822
|
modelCapabilities: context.modelCapabilities,
|
|
23823
|
+
tokenSavingMode: config.features?.tokenSavingMode,
|
|
23656
23824
|
instructionPaths: {
|
|
23657
23825
|
globalDir: paths.globalInstructions,
|
|
23658
23826
|
projectDir: paths.inProjectInstructions,
|
|
@@ -23662,7 +23830,8 @@ function createModeHandlers(context) {
|
|
|
23662
23830
|
context.context.systemPrompt = await builder.build({
|
|
23663
23831
|
cwd: context.projectRoot,
|
|
23664
23832
|
projectRoot: context.projectRoot,
|
|
23665
|
-
tools: context.toolRegistry.
|
|
23833
|
+
tools: context.toolRegistry.listForProvider(),
|
|
23834
|
+
catalogTools: context.toolRegistry.list(),
|
|
23666
23835
|
provider: config.provider,
|
|
23667
23836
|
model: config.model
|
|
23668
23837
|
});
|
|
@@ -24937,15 +25106,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
24937
25106
|
eternalSubscription = null;
|
|
24938
25107
|
}
|
|
24939
25108
|
codebaseIndexing.dispose();
|
|
24940
|
-
|
|
24941
|
-
const candidate = memoryStore;
|
|
24942
|
-
await candidate.hygiene?.({
|
|
24943
|
-
retentionDays: config.Sage?.hygiene?.retentionDays,
|
|
24944
|
-
archiveLowConfidenceAfterDays: config.Sage?.hygiene?.archiveLowConfidenceAfterDays
|
|
24945
|
-
}).catch(
|
|
24946
|
-
(err) => logger.warn(`sage session hygiene failed: ${toErrorMessage13(err)}`)
|
|
24947
|
-
);
|
|
24948
|
-
}
|
|
25109
|
+
await agentServices.runSageSessionHygiene().catch((err) => logger.warn(`sage session hygiene failed: ${toErrorMessage13(err)}`));
|
|
24949
25110
|
await memoryStore.dispose().catch(
|
|
24950
25111
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage13(err)}`)
|
|
24951
25112
|
);
|