@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/index.js
CHANGED
|
@@ -28,7 +28,14 @@ function isRecord(value) {
|
|
|
28
28
|
var AUTONOMY_VALUES = /* @__PURE__ */ new Set(["off", "suggest", "auto", "eternal", "eternal-parallel"]);
|
|
29
29
|
var CONTEXT_STRATEGY_VALUES = /* @__PURE__ */ new Set(["hybrid", "intelligent", "selective"]);
|
|
30
30
|
var CONTEXT_MODE_VALUES = /* @__PURE__ */ new Set(["balanced", "frugal", "deep"]);
|
|
31
|
-
var TOKEN_SAVING_TIER_VALUES = /* @__PURE__ */ new Set([
|
|
31
|
+
var TOKEN_SAVING_TIER_VALUES = /* @__PURE__ */ new Set([
|
|
32
|
+
"auto",
|
|
33
|
+
"off",
|
|
34
|
+
"minimal",
|
|
35
|
+
"light",
|
|
36
|
+
"medium",
|
|
37
|
+
"aggressive"
|
|
38
|
+
]);
|
|
32
39
|
var ENHANCE_LANGUAGE_VALUES = /* @__PURE__ */ new Set(["original", "english"]);
|
|
33
40
|
var LOG_LEVEL_VALUES = /* @__PURE__ */ new Set(["debug", "info", "warn", "error"]);
|
|
34
41
|
var AUDIT_LEVEL_VALUES = /* @__PURE__ */ new Set(["minimal", "standard", "full"]);
|
|
@@ -285,12 +292,8 @@ function validatePreferenceValue(key, value) {
|
|
|
285
292
|
if (!Array.isArray(value)) return `prefs.update payload.${key} must be an array`;
|
|
286
293
|
for (let i = 0; i < value.length; i++) {
|
|
287
294
|
const item = value[i];
|
|
288
|
-
if (!isRecord(item))
|
|
289
|
-
|
|
290
|
-
const error2 = arrayValidator(
|
|
291
|
-
item,
|
|
292
|
-
`prefs.update payload.${key}[${i}]`
|
|
293
|
-
);
|
|
295
|
+
if (!isRecord(item)) return `prefs.update payload.${key}[${i}] must be an object`;
|
|
296
|
+
const error2 = arrayValidator(item, `prefs.update payload.${key}[${i}]`);
|
|
294
297
|
if (error2) return error2;
|
|
295
298
|
}
|
|
296
299
|
return null;
|
|
@@ -1099,11 +1102,11 @@ import { randomBytes } from "node:crypto";
|
|
|
1099
1102
|
import { scrubErrorDetail } from "@wrongstack/core/security";
|
|
1100
1103
|
import { WebSocket } from "ws";
|
|
1101
1104
|
var WEBUI_WS_MAX_BUFFERED_BYTES = 32 * 1024 * 1024;
|
|
1102
|
-
function sendSerialized(ws, data) {
|
|
1105
|
+
function sendSerialized(ws, data, frameBytes) {
|
|
1103
1106
|
if (ws.readyState !== WebSocket.OPEN) return false;
|
|
1104
1107
|
const buffered = Number.isFinite(ws.bufferedAmount) ? ws.bufferedAmount : 0;
|
|
1105
|
-
const
|
|
1106
|
-
if (buffered +
|
|
1108
|
+
const bytes = frameBytes ?? Buffer.byteLength(data, "utf8");
|
|
1109
|
+
if (buffered + bytes > WEBUI_WS_MAX_BUFFERED_BYTES) {
|
|
1107
1110
|
try {
|
|
1108
1111
|
ws.terminate();
|
|
1109
1112
|
} catch {
|
|
@@ -1126,8 +1129,9 @@ function send(ws, msg) {
|
|
|
1126
1129
|
}
|
|
1127
1130
|
function broadcast(clients, msg) {
|
|
1128
1131
|
const data = JSON.stringify(msg);
|
|
1132
|
+
const frameBytes = Buffer.byteLength(data, "utf8");
|
|
1129
1133
|
for (const [ws] of clients) {
|
|
1130
|
-
sendSerialized(ws, data);
|
|
1134
|
+
sendSerialized(ws, data, frameBytes);
|
|
1131
1135
|
}
|
|
1132
1136
|
}
|
|
1133
1137
|
function sendResult2(ws, success, message) {
|
|
@@ -2202,6 +2206,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2202
2206
|
this.broadcast(sessionId, this.stateMessage(sessionId));
|
|
2203
2207
|
}
|
|
2204
2208
|
}, 2e3);
|
|
2209
|
+
this.broadcastInterval.unref?.();
|
|
2205
2210
|
}
|
|
2206
2211
|
stopBroadcast() {
|
|
2207
2212
|
if (this.broadcastInterval) {
|
|
@@ -7217,13 +7222,18 @@ var GoalWebSocketHandler = class {
|
|
|
7217
7222
|
const { execFile: execFile2 } = await import("node:child_process");
|
|
7218
7223
|
const result = await new Promise((resolve17) => {
|
|
7219
7224
|
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
7220
|
-
execFile2(
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7225
|
+
execFile2(
|
|
7226
|
+
npxCommand,
|
|
7227
|
+
["tsc", "--noEmit"],
|
|
7228
|
+
{ cwd, timeout: 6e4 },
|
|
7229
|
+
(err, stdout, stderr) => {
|
|
7230
|
+
if (err && err.code === "ENOENT") {
|
|
7231
|
+
resolve17("[verify] tsc not found \u2014 skipping");
|
|
7232
|
+
return;
|
|
7233
|
+
}
|
|
7234
|
+
resolve17(stdout + stderr);
|
|
7224
7235
|
}
|
|
7225
|
-
|
|
7226
|
-
});
|
|
7236
|
+
);
|
|
7227
7237
|
});
|
|
7228
7238
|
if (result.includes("[verify]") || result.trim().length === 0) {
|
|
7229
7239
|
return { ok: true };
|
|
@@ -7525,6 +7535,7 @@ ${result_.finalText.slice(0, 2e3)}`
|
|
|
7525
7535
|
if (progress) this.broadcast({ type: "goal.progress", payload: progress });
|
|
7526
7536
|
this.broadcastState();
|
|
7527
7537
|
}, 2e3);
|
|
7538
|
+
this.broadcastInterval.unref?.();
|
|
7528
7539
|
}
|
|
7529
7540
|
stopBroadcast() {
|
|
7530
7541
|
if (this.broadcastInterval) {
|
|
@@ -7649,8 +7660,9 @@ ${result_.finalText.slice(0, 2e3)}`
|
|
|
7649
7660
|
}
|
|
7650
7661
|
broadcast(msg) {
|
|
7651
7662
|
const data = JSON.stringify(msg);
|
|
7663
|
+
const frameBytes = Buffer.byteLength(data, "utf8");
|
|
7652
7664
|
for (const client of this.clients) {
|
|
7653
|
-
sendSerialized(client.ws, data);
|
|
7665
|
+
sendSerialized(client.ws, data, frameBytes);
|
|
7654
7666
|
}
|
|
7655
7667
|
}
|
|
7656
7668
|
send(client, msg) {
|
|
@@ -9561,7 +9573,19 @@ function isTrustedLoopbackOrigin(origin, hostHeader) {
|
|
|
9561
9573
|
if (!host) return false;
|
|
9562
9574
|
const hostUrl = new URL(`${url.protocol}//${host}`);
|
|
9563
9575
|
if (!isLoopbackHostname(hostUrl.hostname)) return false;
|
|
9564
|
-
return effectivePort(url) === effectivePort(hostUrl);
|
|
9576
|
+
return normalizeHostname(url.hostname) === normalizeHostname(hostUrl.hostname) && effectivePort(url) === effectivePort(hostUrl);
|
|
9577
|
+
} catch {
|
|
9578
|
+
return false;
|
|
9579
|
+
}
|
|
9580
|
+
}
|
|
9581
|
+
function originMatchesHost(origin, hostHeader) {
|
|
9582
|
+
try {
|
|
9583
|
+
const originUrl = new URL(origin);
|
|
9584
|
+
if (originUrl.protocol !== "http:" && originUrl.protocol !== "https:") return false;
|
|
9585
|
+
const host = (hostHeader ?? "").trim();
|
|
9586
|
+
if (!host) return false;
|
|
9587
|
+
const requestUrl = new URL(`${originUrl.protocol}//${host}`);
|
|
9588
|
+
return normalizeHostname(originUrl.hostname) === normalizeHostname(requestUrl.hostname) && effectivePort(originUrl) === effectivePort(requestUrl);
|
|
9565
9589
|
} catch {
|
|
9566
9590
|
return false;
|
|
9567
9591
|
}
|
|
@@ -9666,13 +9690,15 @@ function verifyClient(input) {
|
|
|
9666
9690
|
try {
|
|
9667
9691
|
const { hostname: originHostname } = new URL(origin);
|
|
9668
9692
|
if (isLoopbackHostname(originHostname)) {
|
|
9669
|
-
if (requireToken || !isLoopbackBind(wsHost))
|
|
9693
|
+
if (requireToken || !isLoopbackBind(wsHost)) {
|
|
9694
|
+
return cookieTokenOk && (originMatchesHost(origin, hostHeader) || Boolean(allowCrossPortLoopbackCookie));
|
|
9695
|
+
}
|
|
9670
9696
|
if (!isTrustedLoopbackOrigin(origin, hostHeader)) {
|
|
9671
9697
|
return Boolean(allowCrossPortLoopbackCookie) && cookieTokenOk;
|
|
9672
9698
|
}
|
|
9673
9699
|
return true;
|
|
9674
9700
|
}
|
|
9675
|
-
return cookieTokenOk || Boolean(allowBrowserUrlToken) && urlTokenOk && allowedHostname(originHostname, allowedHostnames);
|
|
9701
|
+
return cookieTokenOk && originMatchesHost(origin, hostHeader) || Boolean(allowBrowserUrlToken) && urlTokenOk && allowedHostname(originHostname, allowedHostnames);
|
|
9676
9702
|
} catch {
|
|
9677
9703
|
return false;
|
|
9678
9704
|
}
|
|
@@ -10637,7 +10663,8 @@ async function handleIntrospectionRoute(ctx, ws, message) {
|
|
|
10637
10663
|
switch (message.type) {
|
|
10638
10664
|
case "diag.get": {
|
|
10639
10665
|
if (!sessionAllowed(ctx, ws, message)) return true;
|
|
10640
|
-
const
|
|
10666
|
+
const registry = ctx.agent.tools;
|
|
10667
|
+
const tools = registry.listForProvider?.() ?? registry.list();
|
|
10641
10668
|
ctx.send(ws, {
|
|
10642
10669
|
type: "diag.get",
|
|
10643
10670
|
payload: {
|
|
@@ -10720,6 +10747,7 @@ async function handleIntrospectionRoute(ctx, ws, message) {
|
|
|
10720
10747
|
description: tool.description ?? "",
|
|
10721
10748
|
params: schema.properties ? Object.keys(schema.properties) : [],
|
|
10722
10749
|
disabled: registry.isDisabled?.(tool.name) ?? false,
|
|
10750
|
+
direct: registry.isExposedToProvider?.(tool.name) ?? true,
|
|
10723
10751
|
mutating: !!tool.mutating,
|
|
10724
10752
|
permission: tool.permission ?? "auto"
|
|
10725
10753
|
};
|
|
@@ -11029,7 +11057,6 @@ async function handleKanbanHostRoute(ws, msg, handlers) {
|
|
|
11029
11057
|
import { deserializeTaskGraph, serializeTaskGraph } from "@wrongstack/core/tasking";
|
|
11030
11058
|
import {
|
|
11031
11059
|
addCheckToTask,
|
|
11032
|
-
addColumn,
|
|
11033
11060
|
addGoalMetricToTask,
|
|
11034
11061
|
addNoteToTask,
|
|
11035
11062
|
addTask,
|
|
@@ -11044,7 +11071,10 @@ import {
|
|
|
11044
11071
|
getKanbanOrchestrationSnapshot,
|
|
11045
11072
|
getKanbanQueueHealth,
|
|
11046
11073
|
getTaskChain,
|
|
11074
|
+
hasKanbanQueueAnomalies,
|
|
11075
|
+
kanbanQueueAnomalyCount,
|
|
11047
11076
|
listBoards as listBoards2,
|
|
11077
|
+
listBoardHistory,
|
|
11048
11078
|
listReadyTasks,
|
|
11049
11079
|
mergeTasks,
|
|
11050
11080
|
moveTask,
|
|
@@ -11053,7 +11083,6 @@ import {
|
|
|
11053
11083
|
recoverStaleTaskAssignments,
|
|
11054
11084
|
releaseTaskClaim,
|
|
11055
11085
|
removeBoard,
|
|
11056
|
-
removeColumn,
|
|
11057
11086
|
setTaskChain,
|
|
11058
11087
|
splitTask,
|
|
11059
11088
|
syncBoardFromTaskGraph,
|
|
@@ -11065,14 +11094,16 @@ import {
|
|
|
11065
11094
|
updateTask as updateTask2
|
|
11066
11095
|
} from "@wrongstack/kanban";
|
|
11067
11096
|
|
|
11068
|
-
// src/server/kanban-
|
|
11097
|
+
// src/server/kanban-contract-routes.ts
|
|
11069
11098
|
import {
|
|
11070
|
-
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11099
|
+
addContractEdge,
|
|
11100
|
+
configureContractGraph,
|
|
11101
|
+
evaluateTaskContractGraph,
|
|
11102
|
+
getContractGraph,
|
|
11103
|
+
removeContractEdge,
|
|
11104
|
+
removeContractNode,
|
|
11105
|
+
upsertContractNode
|
|
11074
11106
|
} from "@wrongstack/kanban";
|
|
11075
|
-
import { recordKanbanVerificationEvidence as recordKanbanVerificationEvidence2 } from "@wrongstack/tools";
|
|
11076
11107
|
|
|
11077
11108
|
// src/server/kanban-route-helpers.ts
|
|
11078
11109
|
import { touchKanbanPresence } from "@wrongstack/kanban";
|
|
@@ -11124,7 +11155,150 @@ function findTask2(tasks, taskId) {
|
|
|
11124
11155
|
return tasks.find((task) => task.id === taskId || task.id.startsWith(taskId));
|
|
11125
11156
|
}
|
|
11126
11157
|
|
|
11158
|
+
// src/server/kanban-contract-routes.ts
|
|
11159
|
+
async function handleKanbanContractRoute(ws, type, payload, ctx) {
|
|
11160
|
+
switch (type) {
|
|
11161
|
+
case "kanban.contract.get":
|
|
11162
|
+
await handleGet(ws, type, payload, ctx);
|
|
11163
|
+
return true;
|
|
11164
|
+
case "kanban.contract.configure":
|
|
11165
|
+
await handleConfigure(ws, type, payload, ctx);
|
|
11166
|
+
return true;
|
|
11167
|
+
case "kanban.contract.node.upsert":
|
|
11168
|
+
await handleNodeUpsert(ws, type, payload, ctx);
|
|
11169
|
+
return true;
|
|
11170
|
+
case "kanban.contract.node.remove":
|
|
11171
|
+
await handleNodeRemove(ws, type, payload, ctx);
|
|
11172
|
+
return true;
|
|
11173
|
+
case "kanban.contract.edge.add":
|
|
11174
|
+
await handleEdgeAdd(ws, type, payload, ctx);
|
|
11175
|
+
return true;
|
|
11176
|
+
case "kanban.contract.edge.remove":
|
|
11177
|
+
await handleEdgeRemove(ws, type, payload, ctx);
|
|
11178
|
+
return true;
|
|
11179
|
+
default:
|
|
11180
|
+
return false;
|
|
11181
|
+
}
|
|
11182
|
+
}
|
|
11183
|
+
var str = (payload, key) => typeof payload?.[key] === "string" ? payload[key] : void 0;
|
|
11184
|
+
async function publishBoard(ctx, board) {
|
|
11185
|
+
await publishKanbanBoard((message) => ctx.broadcast?.(message), board);
|
|
11186
|
+
}
|
|
11187
|
+
async function handleGet(ws, type, payload, ctx) {
|
|
11188
|
+
const boardId = str(payload, "boardId");
|
|
11189
|
+
if (!boardId) return fail(ws, type, "boardId required");
|
|
11190
|
+
const found = await getContractGraph(ctx.projectRoot, boardId);
|
|
11191
|
+
if (!found) return fail(ws, type, "Board not found");
|
|
11192
|
+
const taskId = str(payload, "taskId");
|
|
11193
|
+
const evaluated = taskId ? await evaluateTaskContractGraph(ctx.projectRoot, boardId, taskId) : null;
|
|
11194
|
+
if (taskId && !evaluated) return fail(ws, type, "Task not found on this board");
|
|
11195
|
+
ok(ws, type, {
|
|
11196
|
+
boardId,
|
|
11197
|
+
graph: found.graph,
|
|
11198
|
+
...evaluated ? { evaluation: evaluated.evaluation } : {}
|
|
11199
|
+
});
|
|
11200
|
+
}
|
|
11201
|
+
async function handleConfigure(ws, type, payload, ctx) {
|
|
11202
|
+
const boardId = str(payload, "boardId");
|
|
11203
|
+
if (!boardId) return fail(ws, type, "boardId required");
|
|
11204
|
+
const enforcement = str(payload, "enforcement") ?? "advisory";
|
|
11205
|
+
const board = await configureContractGraph(ctx.projectRoot, boardId, enforcement);
|
|
11206
|
+
if (!board) return fail(ws, type, "Board not found");
|
|
11207
|
+
await publishBoard(ctx, board);
|
|
11208
|
+
ok(ws, type, { boardId, graph: board.contractGraph ?? null });
|
|
11209
|
+
}
|
|
11210
|
+
async function handleNodeUpsert(ws, type, payload, ctx) {
|
|
11211
|
+
const boardId = str(payload, "boardId");
|
|
11212
|
+
const taskId = str(payload, "taskId");
|
|
11213
|
+
const kind = str(payload, "kind");
|
|
11214
|
+
const title = str(payload, "title");
|
|
11215
|
+
if (!boardId || !taskId || !kind || !title) {
|
|
11216
|
+
return fail(ws, type, "boardId, taskId, kind, and title required");
|
|
11217
|
+
}
|
|
11218
|
+
const state = str(payload, "state");
|
|
11219
|
+
const waiverActor = str(payload, "waiverActor");
|
|
11220
|
+
const waiverReason = str(payload, "waiverReason");
|
|
11221
|
+
if (state === "waived" && (!waiverActor?.trim() || !waiverReason?.trim())) {
|
|
11222
|
+
return fail(ws, type, "A waived contract node requires waiverActor and waiverReason");
|
|
11223
|
+
}
|
|
11224
|
+
try {
|
|
11225
|
+
const result = await upsertContractNode(ctx.projectRoot, boardId, {
|
|
11226
|
+
taskId,
|
|
11227
|
+
kind,
|
|
11228
|
+
title,
|
|
11229
|
+
...str(payload, "nodeId") !== void 0 ? { id: str(payload, "nodeId") } : {},
|
|
11230
|
+
...str(payload, "description") !== void 0 ? { description: str(payload, "description") } : {},
|
|
11231
|
+
...state !== void 0 ? { state } : {},
|
|
11232
|
+
...str(payload, "enforcement") !== void 0 ? { enforcement: str(payload, "enforcement") } : {},
|
|
11233
|
+
...str(payload, "checkId") !== void 0 ? { checkId: str(payload, "checkId") } : {},
|
|
11234
|
+
...str(payload, "metricId") !== void 0 ? { metricId: str(payload, "metricId") } : {},
|
|
11235
|
+
...state === "waived" ? {
|
|
11236
|
+
waiver: {
|
|
11237
|
+
actor: waiverActor,
|
|
11238
|
+
reason: waiverReason,
|
|
11239
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11240
|
+
}
|
|
11241
|
+
} : {},
|
|
11242
|
+
...str(payload, "createdBy") !== void 0 ? { createdBy: str(payload, "createdBy") } : { createdBy: "webui" }
|
|
11243
|
+
});
|
|
11244
|
+
if (!result) return fail(ws, type, "Board or task not found");
|
|
11245
|
+
await publishBoard(ctx, result.board);
|
|
11246
|
+
ok(ws, type, { boardId, node: result.node, graph: result.board.contractGraph ?? null });
|
|
11247
|
+
} catch (err) {
|
|
11248
|
+
fail(ws, type, err instanceof Error ? err.message : String(err));
|
|
11249
|
+
}
|
|
11250
|
+
}
|
|
11251
|
+
async function handleNodeRemove(ws, type, payload, ctx) {
|
|
11252
|
+
const boardId = str(payload, "boardId");
|
|
11253
|
+
const nodeId = str(payload, "nodeId");
|
|
11254
|
+
if (!boardId || !nodeId) return fail(ws, type, "boardId and nodeId required");
|
|
11255
|
+
const board = await removeContractNode(ctx.projectRoot, boardId, nodeId);
|
|
11256
|
+
if (!board) return fail(ws, type, "Contract node not found");
|
|
11257
|
+
await publishBoard(ctx, board);
|
|
11258
|
+
ok(ws, type, { boardId, graph: board.contractGraph ?? null });
|
|
11259
|
+
}
|
|
11260
|
+
async function handleEdgeAdd(ws, type, payload, ctx) {
|
|
11261
|
+
const boardId = str(payload, "boardId");
|
|
11262
|
+
const from = str(payload, "from");
|
|
11263
|
+
const to = str(payload, "to");
|
|
11264
|
+
const edgeType = str(payload, "edgeType");
|
|
11265
|
+
if (!boardId || !from || !to || !edgeType) {
|
|
11266
|
+
return fail(ws, type, "boardId, from, to, and edgeType required");
|
|
11267
|
+
}
|
|
11268
|
+
try {
|
|
11269
|
+
const result = await addContractEdge(ctx.projectRoot, boardId, {
|
|
11270
|
+
from,
|
|
11271
|
+
to,
|
|
11272
|
+
type: edgeType,
|
|
11273
|
+
...str(payload, "enforcement") !== void 0 ? { enforcement: str(payload, "enforcement") } : {},
|
|
11274
|
+
...str(payload, "rationale") !== void 0 ? { rationale: str(payload, "rationale") } : {},
|
|
11275
|
+
createdBy: str(payload, "createdBy") ?? "webui"
|
|
11276
|
+
});
|
|
11277
|
+
if (!result) return fail(ws, type, "Board not found");
|
|
11278
|
+
await publishBoard(ctx, result.board);
|
|
11279
|
+
ok(ws, type, { boardId, edge: result.edge, graph: result.board.contractGraph ?? null });
|
|
11280
|
+
} catch (err) {
|
|
11281
|
+
fail(ws, type, err instanceof Error ? err.message : String(err));
|
|
11282
|
+
}
|
|
11283
|
+
}
|
|
11284
|
+
async function handleEdgeRemove(ws, type, payload, ctx) {
|
|
11285
|
+
const boardId = str(payload, "boardId");
|
|
11286
|
+
const edgeId = str(payload, "edgeId");
|
|
11287
|
+
if (!boardId || !edgeId) return fail(ws, type, "boardId and edgeId required");
|
|
11288
|
+
const board = await removeContractEdge(ctx.projectRoot, boardId, edgeId);
|
|
11289
|
+
if (!board) return fail(ws, type, "Contract edge not found");
|
|
11290
|
+
await publishBoard(ctx, board);
|
|
11291
|
+
ok(ws, type, { boardId, graph: board.contractGraph ?? null });
|
|
11292
|
+
}
|
|
11293
|
+
|
|
11127
11294
|
// src/server/kanban-decomposition-routes.ts
|
|
11295
|
+
import {
|
|
11296
|
+
listBoards,
|
|
11297
|
+
resolveDecompositionProposal,
|
|
11298
|
+
updateTask,
|
|
11299
|
+
verifyTaskCompletion
|
|
11300
|
+
} from "@wrongstack/kanban";
|
|
11301
|
+
import { recordKanbanVerificationEvidence as recordKanbanVerificationEvidence2 } from "@wrongstack/tools";
|
|
11128
11302
|
async function handleKanbanDecompositionRoute(ws, type, payload, ctx) {
|
|
11129
11303
|
switch (type) {
|
|
11130
11304
|
case "kanban.decomposition.approve":
|
|
@@ -11373,8 +11547,12 @@ async function handleTaskActivityAdd(ws, type, payload, ctx) {
|
|
|
11373
11547
|
// src/server/kanban-route-protocol.ts
|
|
11374
11548
|
var KANBAN_CLIENT_MESSAGE_TYPES = [
|
|
11375
11549
|
"kanban.capabilities",
|
|
11376
|
-
"kanban.
|
|
11377
|
-
"kanban.
|
|
11550
|
+
"kanban.contract.configure",
|
|
11551
|
+
"kanban.contract.edge.add",
|
|
11552
|
+
"kanban.contract.edge.remove",
|
|
11553
|
+
"kanban.contract.get",
|
|
11554
|
+
"kanban.contract.node.remove",
|
|
11555
|
+
"kanban.contract.node.upsert",
|
|
11378
11556
|
"kanban.create",
|
|
11379
11557
|
"kanban.decomposition.approve",
|
|
11380
11558
|
"kanban.decomposition.reject",
|
|
@@ -11425,6 +11603,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11425
11603
|
const type = msg.type;
|
|
11426
11604
|
try {
|
|
11427
11605
|
if (await handleKanbanDecompositionRoute(ws, type, payload, ctx)) return true;
|
|
11606
|
+
if (await handleKanbanContractRoute(ws, type, payload, ctx)) return true;
|
|
11428
11607
|
if (await handleKanbanTaskRoute(ws, type, payload, ctx)) return true;
|
|
11429
11608
|
switch (type) {
|
|
11430
11609
|
case "kanban.list": {
|
|
@@ -11463,7 +11642,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11463
11642
|
fail(ws, type, "boardId required");
|
|
11464
11643
|
return true;
|
|
11465
11644
|
}
|
|
11466
|
-
ok(
|
|
11645
|
+
ok(
|
|
11646
|
+
ws,
|
|
11647
|
+
type,
|
|
11648
|
+
await getKanbanQueueHealth(ctx.projectRoot, {
|
|
11649
|
+
boardId: hBoardId,
|
|
11650
|
+
includeClassifications: false
|
|
11651
|
+
})
|
|
11652
|
+
);
|
|
11467
11653
|
return true;
|
|
11468
11654
|
}
|
|
11469
11655
|
case "kanban.supervisor.status":
|
|
@@ -11493,16 +11679,16 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11493
11679
|
reason: "On-demand standalone Kanban supervisor audit."
|
|
11494
11680
|
}) : null;
|
|
11495
11681
|
if (recovered) health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
11496
|
-
const anomalyCount = health
|
|
11682
|
+
const anomalyCount = kanbanQueueAnomalyCount(health);
|
|
11497
11683
|
ok(ws, type, {
|
|
11498
11684
|
boardId,
|
|
11499
|
-
status: board.supervisor?.enabled === false ? "disabled" :
|
|
11685
|
+
status: board.supervisor?.enabled === false ? "disabled" : hasKanbanQueueAnomalies(health) ? "attention" : "healthy",
|
|
11500
11686
|
mode: board.supervisor?.mode ?? "deterministic",
|
|
11501
11687
|
lastAuditAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11502
11688
|
reconciledTaskIds: reconciled?.tasks.map((task) => task.id) ?? [],
|
|
11503
11689
|
staleRecoveredTaskIds: recovered?.tasks.map((task) => task.id) ?? [],
|
|
11504
11690
|
anomalyCount,
|
|
11505
|
-
summary: `${health.counts.running} running \xB7 ${health.counts.
|
|
11691
|
+
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`
|
|
11506
11692
|
});
|
|
11507
11693
|
return true;
|
|
11508
11694
|
}
|
|
@@ -11519,7 +11705,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11519
11705
|
title,
|
|
11520
11706
|
...payload?.description ? { description: payload.description } : {},
|
|
11521
11707
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
11522
|
-
...payload?.columns ? { columns: payload.columns } : {},
|
|
11523
11708
|
...has(payload, "lifecycle") ? { lifecycle: payload?.lifecycle } : {},
|
|
11524
11709
|
...has(payload, "boundary") ? { boundary: payload?.boundary } : {}
|
|
11525
11710
|
})
|
|
@@ -11536,7 +11721,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11536
11721
|
...payload?.title ? { title: payload.title } : {},
|
|
11537
11722
|
...payload?.description ? { description: payload.description } : {},
|
|
11538
11723
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
11539
|
-
...payload?.columns ? { columns: payload.columns } : {},
|
|
11540
11724
|
...has(payload, "lifecycle") ? {
|
|
11541
11725
|
lifecycle: payload?.lifecycle ?? null
|
|
11542
11726
|
} : {},
|
|
@@ -11583,6 +11767,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11583
11767
|
});
|
|
11584
11768
|
return true;
|
|
11585
11769
|
}
|
|
11770
|
+
case "kanban.board.history": {
|
|
11771
|
+
const boardId = payload?.boardId;
|
|
11772
|
+
const history = await listBoardHistory(ctx.projectRoot, boardId);
|
|
11773
|
+
ok(ws, type, history);
|
|
11774
|
+
return true;
|
|
11775
|
+
}
|
|
11586
11776
|
case "kanban.generate": {
|
|
11587
11777
|
const description = payload?.description;
|
|
11588
11778
|
if (!description) {
|
|
@@ -12044,8 +12234,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
12044
12234
|
taskId,
|
|
12045
12235
|
{
|
|
12046
12236
|
description,
|
|
12237
|
+
// The old cast listed `manual | auto | agent | test | review` —
|
|
12238
|
+
// three of which have no verifier plugin, while the six that do
|
|
12239
|
+
// (command, file_exists, file_matches, git_diff, metric, test)
|
|
12240
|
+
// were unreachable. `notes` carries the executable body every
|
|
12241
|
+
// deterministic plugin reads.
|
|
12047
12242
|
type: payload?.checkType ?? "manual",
|
|
12048
|
-
status: payload?.status ?? "pending"
|
|
12243
|
+
status: payload?.status ?? "pending",
|
|
12244
|
+
...typeof payload?.notes === "string" ? { notes: payload.notes } : {}
|
|
12049
12245
|
},
|
|
12050
12246
|
activityContext(
|
|
12051
12247
|
ctx,
|
|
@@ -12110,30 +12306,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
12110
12306
|
case "kanban.capabilities":
|
|
12111
12307
|
ok(ws, type, { dispatchSupported: Boolean(ctx.dispatchTask) });
|
|
12112
12308
|
return true;
|
|
12113
|
-
case "kanban.column.add": {
|
|
12114
|
-
const boardId = payload?.boardId;
|
|
12115
|
-
const title = payload?.title;
|
|
12116
|
-
if (!boardId || !title) {
|
|
12117
|
-
fail(ws, type, "boardId and title required");
|
|
12118
|
-
return true;
|
|
12119
|
-
}
|
|
12120
|
-
const result = await addColumn(ctx.projectRoot, boardId, { title });
|
|
12121
|
-
result ? ok(ws, type, result.board.columns) : fail(ws, type, `Board not found: ${boardId}`);
|
|
12122
|
-
return true;
|
|
12123
|
-
}
|
|
12124
|
-
case "kanban.column.remove": {
|
|
12125
|
-
const boardId = payload?.boardId;
|
|
12126
|
-
const columnId = payload?.columnId;
|
|
12127
|
-
if (!boardId || !columnId) {
|
|
12128
|
-
fail(ws, type, "boardId and columnId required");
|
|
12129
|
-
return true;
|
|
12130
|
-
}
|
|
12131
|
-
const board = await removeColumn(ctx.projectRoot, boardId, columnId, {
|
|
12132
|
-
moveTasksToColumnId: payload?.moveTasksToColumnId
|
|
12133
|
-
});
|
|
12134
|
-
board ? ok(ws, type, { removed: true, boardId: board.id, columnId, board }) : fail(ws, type, `Column not found: ${columnId}`);
|
|
12135
|
-
return true;
|
|
12136
|
-
}
|
|
12137
12309
|
default:
|
|
12138
12310
|
fail(ws, type, `Unknown kanban message type: ${type}`);
|
|
12139
12311
|
return true;
|
|
@@ -13403,6 +13575,34 @@ async function handleSageRecover(ws, msg, memoryStore) {
|
|
|
13403
13575
|
send(ws, { type: "memory.sage.recover", payload: { error: errMessage(err) } });
|
|
13404
13576
|
}
|
|
13405
13577
|
}
|
|
13578
|
+
async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
13579
|
+
const Sage = getSageSurface(memoryStore);
|
|
13580
|
+
if (!Sage) {
|
|
13581
|
+
send(ws, {
|
|
13582
|
+
type: "memory.sage.listCandidates",
|
|
13583
|
+
payload: { error: requiresSage("memory.sage.listCandidates") }
|
|
13584
|
+
});
|
|
13585
|
+
return;
|
|
13586
|
+
}
|
|
13587
|
+
try {
|
|
13588
|
+
const payload = msg.payload ?? {};
|
|
13589
|
+
const includeResolved = payload["includeResolved"] === true;
|
|
13590
|
+
if (typeof Sage.listCandidates !== "function") {
|
|
13591
|
+
send(ws, {
|
|
13592
|
+
type: "memory.sage.listCandidates",
|
|
13593
|
+
payload: { error: "listCandidates is not available on this SAGE surface" }
|
|
13594
|
+
});
|
|
13595
|
+
return;
|
|
13596
|
+
}
|
|
13597
|
+
const candidates = await Sage.listCandidates(includeResolved);
|
|
13598
|
+
send(ws, { type: "memory.sage.listCandidates", payload: { candidates } });
|
|
13599
|
+
} catch (err) {
|
|
13600
|
+
send(ws, {
|
|
13601
|
+
type: "memory.sage.listCandidates",
|
|
13602
|
+
payload: { error: errMessage(err) }
|
|
13603
|
+
});
|
|
13604
|
+
}
|
|
13605
|
+
}
|
|
13406
13606
|
async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
13407
13607
|
const Sage = getSageSurface(memoryStore);
|
|
13408
13608
|
if (!Sage) {
|
|
@@ -13581,6 +13781,9 @@ async function handleMemoryRoute(ctx, ws, message) {
|
|
|
13581
13781
|
case "memory.sage.recover":
|
|
13582
13782
|
await handleSageRecover(ws, message, store);
|
|
13583
13783
|
return true;
|
|
13784
|
+
case "memory.sage.listCandidates":
|
|
13785
|
+
await handleSageListCandidates(ws, message, store);
|
|
13786
|
+
return true;
|
|
13584
13787
|
case "memory.sage.candidateResolve":
|
|
13585
13788
|
await handleSageCandidateResolve(ws, message, store);
|
|
13586
13789
|
return true;
|
|
@@ -15147,6 +15350,7 @@ import {
|
|
|
15147
15350
|
finalizeTaskCompletion,
|
|
15148
15351
|
getBoard as getBoard3,
|
|
15149
15352
|
getKanbanQueueHealth as getKanbanQueueHealth2,
|
|
15353
|
+
kanbanQueueAnomalyCount as kanbanQueueAnomalyCount2,
|
|
15150
15354
|
listBoards as listBoards4,
|
|
15151
15355
|
reconcileKanbanBoard as reconcileKanbanBoard2,
|
|
15152
15356
|
recoverStaleTaskAssignments as recoverStaleTaskAssignments2,
|
|
@@ -15229,7 +15433,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
15229
15433
|
}) : null;
|
|
15230
15434
|
if (recovered)
|
|
15231
15435
|
health = await getKanbanQueueHealth2(resolveProjectRoot(deps2), { boardId: board.id });
|
|
15232
|
-
const anomalyCount =
|
|
15436
|
+
const anomalyCount = kanbanQueueAnomalyCount2(health);
|
|
15233
15437
|
const snapshot = {
|
|
15234
15438
|
boardId: board.id,
|
|
15235
15439
|
status: anomalyCount > 0 ? "attention" : "healthy",
|
|
@@ -15428,13 +15632,10 @@ function dispatchRoute(routing) {
|
|
|
15428
15632
|
...routing.fallbackModels?.length ? { fallbackModels: routing.fallbackModels } : {}
|
|
15429
15633
|
};
|
|
15430
15634
|
}
|
|
15431
|
-
function countAnomalies(health) {
|
|
15432
|
-
return health.staleAssignments.count + health.heartbeatDue.count + health.counts.failed + health.counts.blocked;
|
|
15433
|
-
}
|
|
15434
15635
|
function healthSummary(health) {
|
|
15435
15636
|
return [
|
|
15436
15637
|
`${health.counts.running} running`,
|
|
15437
|
-
`${health.counts.
|
|
15638
|
+
`${health.counts.startable} ready`,
|
|
15438
15639
|
`${health.counts.review} review`,
|
|
15439
15640
|
`${health.counts.blocked} blocked`,
|
|
15440
15641
|
`${health.counts.failed} failed`,
|
|
@@ -16196,7 +16397,7 @@ import { makeProviderFromConfig } from "@wrongstack/providers";
|
|
|
16196
16397
|
import * as fs14 from "node:fs/promises";
|
|
16197
16398
|
import * as path19 from "node:path";
|
|
16198
16399
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
16199
|
-
import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
16400
|
+
import { activateProjectStateGuard, resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
16200
16401
|
function createProjectHandlers(ctx) {
|
|
16201
16402
|
const sendTo = (ws, message) => {
|
|
16202
16403
|
if (ctx.sendMessage) ctx.sendMessage(ws, message);
|
|
@@ -16361,6 +16562,7 @@ function createProjectHandlers(ctx) {
|
|
|
16361
16562
|
try {
|
|
16362
16563
|
await ctx.onSessionSwapped?.(next.id, identityTarget);
|
|
16363
16564
|
await ctx.onBeforeSessionTodosReplaced?.(next.id, paths.projectSessions);
|
|
16565
|
+
await activateProjectStateGuard(resolved);
|
|
16364
16566
|
} catch (err) {
|
|
16365
16567
|
try {
|
|
16366
16568
|
await ctx.onBeforeSessionTodosReplaced?.(previous.id, previousPaths.projectSessions);
|
|
@@ -17378,6 +17580,7 @@ var CLIENT_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
17378
17580
|
"memory.sage.get",
|
|
17379
17581
|
"memory.sage.graph",
|
|
17380
17582
|
"memory.sage.list",
|
|
17583
|
+
"memory.sage.listCandidates",
|
|
17381
17584
|
"memory.sage.listPage",
|
|
17382
17585
|
"memory.sage.recover",
|
|
17383
17586
|
"memory.sage.remember",
|
|
@@ -17657,6 +17860,7 @@ var SERVER_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
17657
17860
|
"memory.sage.get",
|
|
17658
17861
|
"memory.sage.graph",
|
|
17659
17862
|
"memory.sage.list",
|
|
17863
|
+
"memory.sage.listCandidates",
|
|
17660
17864
|
"memory.sage.listPage",
|
|
17661
17865
|
"memory.sage.recover",
|
|
17662
17866
|
"memory.sage.remember",
|
|
@@ -18508,6 +18712,7 @@ function createSessionHandlers(ctx) {
|
|
|
18508
18712
|
ctx.abortActiveRun?.(current2.id);
|
|
18509
18713
|
} catch {
|
|
18510
18714
|
}
|
|
18715
|
+
await ctx.context.flushConversationJournal?.().catch(() => void 0);
|
|
18511
18716
|
await finalizeSession(current2);
|
|
18512
18717
|
}
|
|
18513
18718
|
ctx.setSession(next);
|
|
@@ -19206,6 +19411,7 @@ import {
|
|
|
19206
19411
|
clearProjectAgentConsolidated,
|
|
19207
19412
|
clearProjectSkillAugmentation,
|
|
19208
19413
|
createProjectAgent,
|
|
19414
|
+
DEFAULT_EAGER_SKILL_LIMIT,
|
|
19209
19415
|
detectLearnedConflicts,
|
|
19210
19416
|
evaluateAutoOptimize,
|
|
19211
19417
|
FLEET_ROSTER,
|
|
@@ -19223,12 +19429,15 @@ import {
|
|
|
19223
19429
|
loadProjectSkillAugmentation,
|
|
19224
19430
|
loadSkillAffinity,
|
|
19225
19431
|
optimizeProjectAgentLearning,
|
|
19432
|
+
rankRoleSkills,
|
|
19433
|
+
readQuarantinedDirectives,
|
|
19226
19434
|
readRawLearnedEntries,
|
|
19227
19435
|
resetProjectAgentIdentity,
|
|
19228
19436
|
resolveAutoOptimizePolicy,
|
|
19229
19437
|
resolveRoleSkillCandidates,
|
|
19230
19438
|
saveProjectAgentConsolidated,
|
|
19231
19439
|
saveProjectSkillAugmentation,
|
|
19440
|
+
scoreSkillAffinity,
|
|
19232
19441
|
setSkillPinned,
|
|
19233
19442
|
slugifyProjectAgentRole,
|
|
19234
19443
|
updateProjectAgentConfig,
|
|
@@ -19535,9 +19744,7 @@ ${String(p.content ?? "")}`;
|
|
|
19535
19744
|
// eligible right now, and why not when it does not. Surfacing the reason
|
|
19536
19745
|
// is what keeps "nothing happened" from looking like a broken feature.
|
|
19537
19746
|
case "agent-roster.auto-optimize-status": {
|
|
19538
|
-
const policy = resolveAutoOptimizePolicy(
|
|
19539
|
-
this.getAutoOptimizeSettings?.() ?? void 0
|
|
19540
|
-
);
|
|
19747
|
+
const policy = resolveAutoOptimizePolicy(this.getAutoOptimizeSettings?.() ?? void 0);
|
|
19541
19748
|
const roles = role ? [role] : listProjectAgentRoles(projectRoot);
|
|
19542
19749
|
return {
|
|
19543
19750
|
type,
|
|
@@ -19560,18 +19767,30 @@ ${String(p.content ?? "")}`;
|
|
|
19560
19767
|
const candidates = resolveRoleSkillCandidates(role, projectRoot);
|
|
19561
19768
|
const developed = listProjectSkillAugmentations(role, projectRoot);
|
|
19562
19769
|
const affinity = loadSkillAffinity(role, projectRoot);
|
|
19770
|
+
const eager = new Set(rankRoleSkills(role, candidates, projectRoot));
|
|
19563
19771
|
return {
|
|
19564
19772
|
type,
|
|
19565
19773
|
payload: {
|
|
19566
19774
|
role,
|
|
19775
|
+
eagerLimit: DEFAULT_EAGER_SKILL_LIMIT,
|
|
19567
19776
|
skills: candidates.map((skill) => ({
|
|
19568
19777
|
skill,
|
|
19569
19778
|
developed: developed.includes(skill),
|
|
19570
|
-
affinity: affinity.entries[skill] ?? null
|
|
19779
|
+
affinity: affinity.entries[skill] ?? null,
|
|
19780
|
+
score: scoreSkillAffinity(affinity.entries[skill]) + (developed.includes(skill) ? 1 : 0),
|
|
19781
|
+
eager: eager.has(skill)
|
|
19571
19782
|
}))
|
|
19572
19783
|
}
|
|
19573
19784
|
};
|
|
19574
19785
|
}
|
|
19786
|
+
// ── Directives the loop stopped believing ─────────────────────────────
|
|
19787
|
+
case "agent-roster.quarantine": {
|
|
19788
|
+
if (!role) return { type, payload: { error: "role required" } };
|
|
19789
|
+
return {
|
|
19790
|
+
type,
|
|
19791
|
+
payload: { role, retired: readQuarantinedDirectives(role, projectRoot) }
|
|
19792
|
+
};
|
|
19793
|
+
}
|
|
19575
19794
|
case "agent-roster.read-skill": {
|
|
19576
19795
|
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
19577
19796
|
if (!role || !skill) return { type, payload: { error: "role and skill required" } };
|
|
@@ -21977,6 +22196,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
21977
22196
|
const logWatcherMetricsEnabled = shouldLogWatcherStats();
|
|
21978
22197
|
const logWatcherMetrics = () => logFileWatcherMetrics(watcherMetrics);
|
|
21979
22198
|
const metricsInterval = logWatcherMetricsEnabled ? setInterval(logWatcherMetrics, 6e4) : void 0;
|
|
22199
|
+
metricsInterval?.unref?.();
|
|
21980
22200
|
const broadcastStatus = (_projectHash, statusData, actualDelayMs) => {
|
|
21981
22201
|
broadcast2(clients, { type: "client.status_update", payload: statusData });
|
|
21982
22202
|
if (watcherMetrics) {
|
|
@@ -23196,14 +23416,7 @@ import {
|
|
|
23196
23416
|
toErrorMessage as toErrorMessage10
|
|
23197
23417
|
} from "@wrongstack/core/utils";
|
|
23198
23418
|
import { makeLightSubagentFactory } from "@wrongstack/runtime";
|
|
23199
|
-
import {
|
|
23200
|
-
createSageContextMonitorMiddleware,
|
|
23201
|
-
createSageToolCallMiddleware,
|
|
23202
|
-
createSageTurnMiddleware,
|
|
23203
|
-
getSageRetrieval,
|
|
23204
|
-
getSageService,
|
|
23205
|
-
InjectionTracker
|
|
23206
|
-
} from "@wrongstack/sage";
|
|
23419
|
+
import { getSageService, setupSage } from "@wrongstack/sage";
|
|
23207
23420
|
|
|
23208
23421
|
// src/server/discover-mailbox-bridge.ts
|
|
23209
23422
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -24006,6 +24219,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
24006
24219
|
this.broadcast(this.stateMessage());
|
|
24007
24220
|
if (this.broadcastInterval) return;
|
|
24008
24221
|
this.broadcastInterval = setInterval(() => this.broadcast(this.stateMessage()), 2e3);
|
|
24222
|
+
this.broadcastInterval.unref?.();
|
|
24009
24223
|
}
|
|
24010
24224
|
stopBroadcast() {
|
|
24011
24225
|
this.broadcast(this.stateMessage());
|
|
@@ -24057,62 +24271,15 @@ async function createAgentServices(input) {
|
|
|
24057
24271
|
const collabPause = collabPauseMiddleware(collabBus, { logger });
|
|
24058
24272
|
pipelines.toolCall.prepend(collabPause);
|
|
24059
24273
|
installDesignStudioMiddleware({ pipelines, ctx: context });
|
|
24060
|
-
const
|
|
24061
|
-
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
|
|
24067
|
-
|
|
24068
|
-
|
|
24069
|
-
maxCharsPerTool: config.Sage?.inject?.maxCharsPerTool,
|
|
24070
|
-
taskAware: config.Sage?.inject?.taskAware,
|
|
24071
|
-
minScore: config.Sage?.inject?.minScore,
|
|
24072
|
-
minImportance: config.Sage?.inject?.minImportance,
|
|
24073
|
-
// Forward the explicit relation floor so an operator-configured
|
|
24074
|
-
// `Sage.inject.relationFloor` is honored in WebUI sessions. Without
|
|
24075
|
-
// this we silently fall back to MIN_RELATION_STRENGTH (0.85), which
|
|
24076
|
-
// is the CLI default but masks operator overrides.
|
|
24077
|
-
relationFloor: config.Sage?.inject?.relationFloor,
|
|
24078
|
-
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
24079
|
-
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
24080
|
-
triggers: config.Sage?.inject?.triggers,
|
|
24081
|
-
// Resolve the live session so retrieval and cooldown are session-scoped
|
|
24082
|
-
// (matching the CLI wiring in wiring/sage.ts). Without this, the
|
|
24083
|
-
// middleware falls back to ctx.session.id for cooldown but passes
|
|
24084
|
-
// undefined to retrieval, causing owned session-scoped memories to be
|
|
24085
|
-
// silently excluded from tool-call injection.
|
|
24086
|
-
getSessionId: getSageSessionId,
|
|
24087
|
-
tracker: sageInjectionTracker,
|
|
24088
|
-
events
|
|
24089
|
-
})
|
|
24090
|
-
);
|
|
24091
|
-
}
|
|
24092
|
-
if (config.Sage?.inject?.turnContext === true) {
|
|
24093
|
-
pipelines.request.use(
|
|
24094
|
-
createSageTurnMiddleware({
|
|
24095
|
-
memory: memoryRetrieval,
|
|
24096
|
-
maxMemories: config.Sage?.inject?.maxTurnMemories,
|
|
24097
|
-
maxChars: config.Sage?.inject?.maxCharsPerTurn,
|
|
24098
|
-
minScore: config.Sage?.inject?.minScore,
|
|
24099
|
-
// CLI parity: honor `Sage.retrieval.metadataWeight` so the same config
|
|
24100
|
-
// value drives both runtimes instead of silently falling back to the
|
|
24101
|
-
// 0.3 default. The undefined case keeps the middleware's own default.
|
|
24102
|
-
metadataWeight: config.Sage?.retrieval?.metadataWeight,
|
|
24103
|
-
getSessionId: getSageSessionId,
|
|
24104
|
-
tracker: sageInjectionTracker
|
|
24105
|
-
})
|
|
24106
|
-
);
|
|
24107
|
-
}
|
|
24108
|
-
pipelines.request.use(
|
|
24109
|
-
createSageContextMonitorMiddleware({
|
|
24110
|
-
tracker: sageInjectionTracker,
|
|
24111
|
-
events,
|
|
24112
|
-
getSessionId: getSageSessionId
|
|
24113
|
-
})
|
|
24114
|
-
);
|
|
24115
|
-
}
|
|
24274
|
+
const runSageSessionHygiene = setupSage({
|
|
24275
|
+
config,
|
|
24276
|
+
pipelines,
|
|
24277
|
+
memoryStore,
|
|
24278
|
+
logger,
|
|
24279
|
+
events,
|
|
24280
|
+
getSessionId: () => input.sessionGetter().id,
|
|
24281
|
+
projectRoot
|
|
24282
|
+
});
|
|
24116
24283
|
const codebaseIndexing = setupWebUICodebaseIndexing({
|
|
24117
24284
|
config,
|
|
24118
24285
|
context,
|
|
@@ -24224,7 +24391,12 @@ async function createAgentServices(input) {
|
|
|
24224
24391
|
confirmAwaiter: void 0,
|
|
24225
24392
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
24226
24393
|
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
24227
|
-
tracer: void 0
|
|
24394
|
+
tracer: void 0,
|
|
24395
|
+
// Off unless the operator opts in. The WebUI drives the same agent as the
|
|
24396
|
+
// CLI, so it must resolve this identically — a surface-dependent gate
|
|
24397
|
+
// would mean the same repo governs under `wstack` and not in the browser.
|
|
24398
|
+
// See packages/cli/src/wiring/pipeline.ts.
|
|
24399
|
+
requireKanbanGovernance: config.tools?.kanbanGovernance ?? DEFAULT_TOOLS_CONFIG.kanbanGovernance
|
|
24228
24400
|
});
|
|
24229
24401
|
input.installToolBoundary?.(pipelines);
|
|
24230
24402
|
const webuiLogger = container.resolve(TOKENS2.Logger);
|
|
@@ -24244,6 +24416,7 @@ async function createAgentServices(input) {
|
|
|
24244
24416
|
providers: providerRegistry,
|
|
24245
24417
|
events,
|
|
24246
24418
|
pipelines,
|
|
24419
|
+
refreshSystemPrompt: true,
|
|
24247
24420
|
context,
|
|
24248
24421
|
maxIterations: config.tools?.maxIterations ?? DEFAULT_TOOLS_CONFIG.maxIterations,
|
|
24249
24422
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
@@ -24511,6 +24684,7 @@ async function createAgentServices(input) {
|
|
|
24511
24684
|
terminalHandler,
|
|
24512
24685
|
collabHandler,
|
|
24513
24686
|
disposeRealtimeHandlers,
|
|
24687
|
+
runSageSessionHygiene,
|
|
24514
24688
|
updateAutoCompactionMaxContext
|
|
24515
24689
|
};
|
|
24516
24690
|
}
|
|
@@ -24990,15 +25164,9 @@ import {
|
|
|
24990
25164
|
makeMailInboxTool,
|
|
24991
25165
|
makeMailSendTool
|
|
24992
25166
|
} from "@wrongstack/core/coordination";
|
|
24993
|
-
import {
|
|
24994
|
-
DefaultPromptLoader,
|
|
24995
|
-
DefaultSkillLoader
|
|
24996
|
-
} from "@wrongstack/core/execution";
|
|
24997
|
-
import {
|
|
24998
|
-
EventBus,
|
|
24999
|
-
TOKENS as TOKENS3
|
|
25000
|
-
} from "@wrongstack/core/kernel";
|
|
25167
|
+
import { DefaultPromptLoader, DefaultSkillLoader } from "@wrongstack/core/execution";
|
|
25001
25168
|
import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
|
|
25169
|
+
import { EventBus, TOKENS as TOKENS3 } from "@wrongstack/core/kernel";
|
|
25002
25170
|
import { DefaultModelsRegistry, DefaultModeStore } from "@wrongstack/core/models";
|
|
25003
25171
|
import { ProviderRegistry, ToolRegistry } from "@wrongstack/core/registry";
|
|
25004
25172
|
import { SkillInstaller } from "@wrongstack/core/skills";
|
|
@@ -25656,6 +25824,7 @@ async function createPreContextServices(input) {
|
|
|
25656
25824
|
modeId,
|
|
25657
25825
|
modePrompt,
|
|
25658
25826
|
modelCapabilities: () => modelCapabilitiesRef.current,
|
|
25827
|
+
tokenSavingMode: config.features.tokenSavingMode,
|
|
25659
25828
|
instructionPaths: {
|
|
25660
25829
|
globalDir: wpaths.globalInstructions,
|
|
25661
25830
|
projectDir: wpaths.inProjectInstructions,
|
|
@@ -25676,7 +25845,8 @@ async function createPreContextServices(input) {
|
|
|
25676
25845
|
const systemPrompt = await systemPromptBuilder.build({
|
|
25677
25846
|
cwd: projectRoot,
|
|
25678
25847
|
projectRoot,
|
|
25679
|
-
tools: toolRegistry.
|
|
25848
|
+
tools: toolRegistry.listForProvider(),
|
|
25849
|
+
catalogTools: toolRegistry.list(),
|
|
25680
25850
|
provider: config.provider,
|
|
25681
25851
|
model: config.model,
|
|
25682
25852
|
onlineAgents
|
|
@@ -25694,6 +25864,7 @@ async function createPreContextServices(input) {
|
|
|
25694
25864
|
projectRoot,
|
|
25695
25865
|
model: config.model
|
|
25696
25866
|
});
|
|
25867
|
+
context.meta["promptOnlineAgents"] = onlineAgents;
|
|
25697
25868
|
const initialContextPolicy = resolveContextWindowPolicy3(config.context);
|
|
25698
25869
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
25699
25870
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
@@ -25766,6 +25937,7 @@ function createModeHandlers(context) {
|
|
|
25766
25937
|
modeId: id,
|
|
25767
25938
|
modePrompt,
|
|
25768
25939
|
modelCapabilities: context.modelCapabilities,
|
|
25940
|
+
tokenSavingMode: config.features?.tokenSavingMode,
|
|
25769
25941
|
instructionPaths: {
|
|
25770
25942
|
globalDir: paths.globalInstructions,
|
|
25771
25943
|
projectDir: paths.inProjectInstructions,
|
|
@@ -25775,7 +25947,8 @@ function createModeHandlers(context) {
|
|
|
25775
25947
|
context.context.systemPrompt = await builder.build({
|
|
25776
25948
|
cwd: context.projectRoot,
|
|
25777
25949
|
projectRoot: context.projectRoot,
|
|
25778
|
-
tools: context.toolRegistry.
|
|
25950
|
+
tools: context.toolRegistry.listForProvider(),
|
|
25951
|
+
catalogTools: context.toolRegistry.list(),
|
|
25779
25952
|
provider: config.provider,
|
|
25780
25953
|
model: config.model
|
|
25781
25954
|
});
|
|
@@ -27050,15 +27223,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
27050
27223
|
eternalSubscription = null;
|
|
27051
27224
|
}
|
|
27052
27225
|
codebaseIndexing.dispose();
|
|
27053
|
-
|
|
27054
|
-
const candidate = memoryStore;
|
|
27055
|
-
await candidate.hygiene?.({
|
|
27056
|
-
retentionDays: config.Sage?.hygiene?.retentionDays,
|
|
27057
|
-
archiveLowConfidenceAfterDays: config.Sage?.hygiene?.archiveLowConfidenceAfterDays
|
|
27058
|
-
}).catch(
|
|
27059
|
-
(err) => logger.warn(`sage session hygiene failed: ${toErrorMessage14(err)}`)
|
|
27060
|
-
);
|
|
27061
|
-
}
|
|
27226
|
+
await agentServices.runSageSessionHygiene().catch((err) => logger.warn(`sage session hygiene failed: ${toErrorMessage14(err)}`));
|
|
27062
27227
|
await memoryStore.dispose().catch(
|
|
27063
27228
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage14(err)}`)
|
|
27064
27229
|
);
|