@wrongstack/webui-server 0.302.2 → 0.305.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +661 -340
- package/dist/protocol/client-integrations.d.ts +1 -1
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/server-integrations.d.ts +1 -1
- package/dist/server/agent-roster-handlers.d.ts +7 -6
- package/dist/server/backend-services.d.ts +5 -0
- package/dist/server/codemap-cache.d.ts +13 -1
- package/dist/server/entry.js +637 -336
- package/dist/server/handlers/worklist-handlers.d.ts +23 -4
- package/dist/server/handlers.js +126 -23
- package/dist/server/kanban-contract-routes.d.ts +8 -0
- package/dist/server/kanban-route-protocol.d.ts +1 -1
- package/dist/server/kanban-routes.d.ts +2 -2
- package/dist/server/memory-handlers.d.ts +6 -0
- package/dist/server/mode-handlers.d.ts +2 -2
- package/dist/server/pre-context-services.d.ts +1 -1
- package/dist/server/server-runtime.d.ts +14 -2
- package/dist/server/setup-events-status-watcher.d.ts +1 -1
- package/dist/server/ws-utils.d.ts +1 -1
- package/package.json +11 -11
package/dist/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) {
|
|
@@ -1464,7 +1468,13 @@ function indexDbVersion(projectRoot, indexDir) {
|
|
|
1464
1468
|
try {
|
|
1465
1469
|
const dir = resolveIndexDir(projectRoot, indexDir);
|
|
1466
1470
|
const st = fs.statSync(path.join(dir, DB_FILE));
|
|
1467
|
-
|
|
1471
|
+
let wal = "";
|
|
1472
|
+
try {
|
|
1473
|
+
const walSt = fs.statSync(path.join(dir, `${DB_FILE}-wal`));
|
|
1474
|
+
wal = `:${walSt.mtimeMs}:${walSt.size}`;
|
|
1475
|
+
} catch {
|
|
1476
|
+
}
|
|
1477
|
+
return `${st.mtimeMs}:${st.size}${wal}`;
|
|
1468
1478
|
} catch {
|
|
1469
1479
|
return "missing";
|
|
1470
1480
|
}
|
|
@@ -2196,6 +2206,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2196
2206
|
this.broadcast(sessionId, this.stateMessage(sessionId));
|
|
2197
2207
|
}
|
|
2198
2208
|
}, 2e3);
|
|
2209
|
+
this.broadcastInterval.unref?.();
|
|
2199
2210
|
}
|
|
2200
2211
|
stopBroadcast() {
|
|
2201
2212
|
if (this.broadcastInterval) {
|
|
@@ -7211,13 +7222,18 @@ var GoalWebSocketHandler = class {
|
|
|
7211
7222
|
const { execFile: execFile2 } = await import("node:child_process");
|
|
7212
7223
|
const result = await new Promise((resolve17) => {
|
|
7213
7224
|
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
7214
|
-
execFile2(
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
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);
|
|
7218
7235
|
}
|
|
7219
|
-
|
|
7220
|
-
});
|
|
7236
|
+
);
|
|
7221
7237
|
});
|
|
7222
7238
|
if (result.includes("[verify]") || result.trim().length === 0) {
|
|
7223
7239
|
return { ok: true };
|
|
@@ -7519,6 +7535,7 @@ ${result_.finalText.slice(0, 2e3)}`
|
|
|
7519
7535
|
if (progress) this.broadcast({ type: "goal.progress", payload: progress });
|
|
7520
7536
|
this.broadcastState();
|
|
7521
7537
|
}, 2e3);
|
|
7538
|
+
this.broadcastInterval.unref?.();
|
|
7522
7539
|
}
|
|
7523
7540
|
stopBroadcast() {
|
|
7524
7541
|
if (this.broadcastInterval) {
|
|
@@ -7643,8 +7660,9 @@ ${result_.finalText.slice(0, 2e3)}`
|
|
|
7643
7660
|
}
|
|
7644
7661
|
broadcast(msg) {
|
|
7645
7662
|
const data = JSON.stringify(msg);
|
|
7663
|
+
const frameBytes = Buffer.byteLength(data, "utf8");
|
|
7646
7664
|
for (const client of this.clients) {
|
|
7647
|
-
sendSerialized(client.ws, data);
|
|
7665
|
+
sendSerialized(client.ws, data, frameBytes);
|
|
7648
7666
|
}
|
|
7649
7667
|
}
|
|
7650
7668
|
send(client, msg) {
|
|
@@ -7689,12 +7707,34 @@ function handleTodosGet(ctx, ws) {
|
|
|
7689
7707
|
payload: sessionPayload(ctx, { todos: [...ctx.context.todos] })
|
|
7690
7708
|
});
|
|
7691
7709
|
}
|
|
7692
|
-
function
|
|
7693
|
-
ctx.
|
|
7694
|
-
|
|
7695
|
-
|
|
7710
|
+
async function commitTodos(ctx, todos) {
|
|
7711
|
+
if (ctx.mutateTodos) {
|
|
7712
|
+
const result = await ctx.mutateTodos(todos);
|
|
7713
|
+
return { todos: result.todos, warnings: result.warnings ?? [] };
|
|
7714
|
+
}
|
|
7715
|
+
ctx.replaceTodos?.(todos);
|
|
7716
|
+
return { todos: [...todos], warnings: [] };
|
|
7717
|
+
}
|
|
7718
|
+
function managedProjectionMessage() {
|
|
7719
|
+
return "Kanban-bound todos are task projections. Change or remove the task from Kanban.";
|
|
7720
|
+
}
|
|
7721
|
+
async function handleTodosClear(ctx, ws) {
|
|
7722
|
+
if (ctx.context.todos.some((todo) => todo.kanbanBoardId && todo.kanbanTaskId)) {
|
|
7723
|
+
sendResult3(ctx, ws, false, managedProjectionMessage());
|
|
7724
|
+
return;
|
|
7725
|
+
}
|
|
7726
|
+
try {
|
|
7727
|
+
const result = await commitTodos(ctx, []);
|
|
7728
|
+
sendResult3(ctx, ws, true, "Todos cleared");
|
|
7729
|
+
ctx.broadcast({
|
|
7730
|
+
type: "todos.updated",
|
|
7731
|
+
payload: sessionPayload(ctx, { todos: result.todos })
|
|
7732
|
+
});
|
|
7733
|
+
} catch (error2) {
|
|
7734
|
+
sendResult3(ctx, ws, false, error2 instanceof Error ? error2.message : String(error2));
|
|
7735
|
+
}
|
|
7696
7736
|
}
|
|
7697
|
-
function handleTodosRemove(ctx, ws, payload) {
|
|
7737
|
+
async function handleTodosRemove(ctx, ws, payload) {
|
|
7698
7738
|
if (!payload) {
|
|
7699
7739
|
sendResult3(ctx, ws, false, "Missing id or index");
|
|
7700
7740
|
return;
|
|
@@ -7711,12 +7751,27 @@ function handleTodosRemove(ctx, ws, payload) {
|
|
|
7711
7751
|
sendResult3(ctx, ws, false, "Todo not found");
|
|
7712
7752
|
return;
|
|
7713
7753
|
}
|
|
7754
|
+
if (removed.kanbanBoardId && removed.kanbanTaskId) {
|
|
7755
|
+
sendResult3(ctx, ws, false, managedProjectionMessage());
|
|
7756
|
+
return;
|
|
7757
|
+
}
|
|
7714
7758
|
const next = [...todos.slice(0, targetIndex), ...todos.slice(targetIndex + 1)];
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7759
|
+
try {
|
|
7760
|
+
const result = await commitTodos(ctx, next);
|
|
7761
|
+
sendResult3(ctx, ws, true, `Removed: ${removed.content}`);
|
|
7762
|
+
ctx.broadcast({
|
|
7763
|
+
type: "todos.updated",
|
|
7764
|
+
payload: sessionPayload(ctx, { todos: result.todos })
|
|
7765
|
+
});
|
|
7766
|
+
} catch (error2) {
|
|
7767
|
+
sendResult3(ctx, ws, false, error2 instanceof Error ? error2.message : String(error2));
|
|
7768
|
+
}
|
|
7718
7769
|
}
|
|
7719
|
-
function handleTodoUpdate(ctx, ws, payload) {
|
|
7770
|
+
async function handleTodoUpdate(ctx, ws, payload) {
|
|
7771
|
+
if (!payload || typeof payload.id !== "string" || payload.status !== void 0 && payload.status !== "pending" && payload.status !== "in_progress" && payload.status !== "completed" || payload.activeForm !== void 0 && typeof payload.activeForm !== "string") {
|
|
7772
|
+
sendResult3(ctx, ws, false, "Invalid todo update payload");
|
|
7773
|
+
return;
|
|
7774
|
+
}
|
|
7720
7775
|
const index = ctx.context.todos.findIndex((todo) => todo.id === payload.id);
|
|
7721
7776
|
const existing = ctx.context.todos[index];
|
|
7722
7777
|
if (index === -1 || !existing) {
|
|
@@ -7729,9 +7784,25 @@ function handleTodoUpdate(ctx, ws, payload) {
|
|
|
7729
7784
|
status: payload.status ?? existing.status,
|
|
7730
7785
|
activeForm: payload.activeForm !== void 0 ? payload.activeForm : existing.activeForm
|
|
7731
7786
|
};
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7787
|
+
try {
|
|
7788
|
+
const result = await commitTodos(ctx, next);
|
|
7789
|
+
const projected = result.todos.find((todo) => todo.id === existing.id);
|
|
7790
|
+
const requestedStatus = payload.status ?? existing.status;
|
|
7791
|
+
const projectionRejected = Boolean(existing.kanbanBoardId && existing.kanbanTaskId) && projected?.status !== requestedStatus;
|
|
7792
|
+
const warning = result.warnings[0];
|
|
7793
|
+
sendResult3(
|
|
7794
|
+
ctx,
|
|
7795
|
+
ws,
|
|
7796
|
+
!projectionRejected,
|
|
7797
|
+
projectionRejected ? warning ?? `Kanban kept "${existing.content}" at ${projected?.status ?? "its current state"}.` : warning ? `Todo "${existing.content}" updated. ${warning}` : `Todo "${existing.content}" updated`
|
|
7798
|
+
);
|
|
7799
|
+
ctx.broadcast({
|
|
7800
|
+
type: "todos.updated",
|
|
7801
|
+
payload: sessionPayload(ctx, { todos: result.todos })
|
|
7802
|
+
});
|
|
7803
|
+
} catch (error2) {
|
|
7804
|
+
sendResult3(ctx, ws, false, error2 instanceof Error ? error2.message : String(error2));
|
|
7805
|
+
}
|
|
7735
7806
|
}
|
|
7736
7807
|
async function handleTasksGet(ctx, ws) {
|
|
7737
7808
|
const taskPath = taskPathOf(ctx);
|
|
@@ -7759,14 +7830,32 @@ async function handleTaskUpdate(ctx, ws, payload) {
|
|
|
7759
7830
|
return;
|
|
7760
7831
|
}
|
|
7761
7832
|
try {
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7833
|
+
let file;
|
|
7834
|
+
if (ctx.mutateTaskStatus) {
|
|
7835
|
+
const result = await ctx.mutateTaskStatus(payload.id, payload.status);
|
|
7836
|
+
if (!result.ok) {
|
|
7837
|
+
sendResult3(ctx, ws, false, result.message);
|
|
7838
|
+
return;
|
|
7839
|
+
}
|
|
7840
|
+
file = await loadTasks(taskPath);
|
|
7841
|
+
if (!file) throw new Error("Task mutation succeeded but its persisted snapshot is missing.");
|
|
7842
|
+
sendResult3(ctx, ws, true, result.message);
|
|
7843
|
+
} else {
|
|
7844
|
+
let matched = false;
|
|
7845
|
+
file = await mutateTasks(taskPath, currentSessionId(ctx), async (tasks) => {
|
|
7846
|
+
const task = tasks.tasks.find((candidate) => candidate.id === payload.id);
|
|
7847
|
+
if (!task) return tasks;
|
|
7848
|
+
matched = true;
|
|
7849
|
+
task.status = payload.status;
|
|
7850
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7851
|
+
return tasks;
|
|
7852
|
+
});
|
|
7853
|
+
if (!matched) {
|
|
7854
|
+
sendResult3(ctx, ws, false, `Task "${payload.id}" not found.`);
|
|
7855
|
+
return;
|
|
7856
|
+
}
|
|
7857
|
+
sendResult3(ctx, ws, true, `Task status updated to "${payload.status}".`);
|
|
7858
|
+
}
|
|
7770
7859
|
ctx.broadcast({
|
|
7771
7860
|
type: "tasks.updated",
|
|
7772
7861
|
payload: sessionPayload(ctx, { tasks: file.tasks })
|
|
@@ -7813,6 +7902,18 @@ async function handlePlanTemplateUse(ctx, ws, template) {
|
|
|
7813
7902
|
return;
|
|
7814
7903
|
}
|
|
7815
7904
|
try {
|
|
7905
|
+
if (ctx.mutatePlan) {
|
|
7906
|
+
const result = await ctx.mutatePlan({ action: "template_use", template });
|
|
7907
|
+
if (!result.ok) {
|
|
7908
|
+
sendResult3(ctx, ws, false, result.message);
|
|
7909
|
+
return;
|
|
7910
|
+
}
|
|
7911
|
+
const plan2 = await loadPlan(planPath);
|
|
7912
|
+
if (!plan2) throw new Error("Plan mutation succeeded but its persisted snapshot is missing.");
|
|
7913
|
+
sendResult3(ctx, ws, true, result.message);
|
|
7914
|
+
ctx.broadcast({ type: "plan.updated", payload: sessionPayload(ctx, { plan: plan2 }) });
|
|
7915
|
+
return;
|
|
7916
|
+
}
|
|
7816
7917
|
const templateDefinition = getPlanTemplate(template);
|
|
7817
7918
|
if (!templateDefinition) {
|
|
7818
7919
|
sendResult3(ctx, ws, false, `Unknown template "${template}".`);
|
|
@@ -7841,6 +7942,22 @@ async function handlePlanItemUpdate(ctx, ws, payload) {
|
|
|
7841
7942
|
return;
|
|
7842
7943
|
}
|
|
7843
7944
|
try {
|
|
7945
|
+
if (ctx.mutatePlan) {
|
|
7946
|
+
const result = await ctx.mutatePlan({
|
|
7947
|
+
action: "status",
|
|
7948
|
+
target: payload.target,
|
|
7949
|
+
status: payload.status
|
|
7950
|
+
});
|
|
7951
|
+
if (!result.ok) {
|
|
7952
|
+
sendResult3(ctx, ws, false, result.message);
|
|
7953
|
+
return;
|
|
7954
|
+
}
|
|
7955
|
+
const plan2 = await loadPlan(planPath);
|
|
7956
|
+
if (!plan2) throw new Error("Plan mutation succeeded but its persisted snapshot is missing.");
|
|
7957
|
+
sendResult3(ctx, ws, true, result.message);
|
|
7958
|
+
ctx.broadcast({ type: "plan.updated", payload: sessionPayload(ctx, { plan: plan2 }) });
|
|
7959
|
+
return;
|
|
7960
|
+
}
|
|
7844
7961
|
let changed = false;
|
|
7845
7962
|
const plan = await mutatePlan(planPath, currentSessionId(ctx), async (currentPlan) => {
|
|
7846
7963
|
const before = currentPlan.updatedAt;
|
|
@@ -7864,13 +7981,17 @@ async function handleWorklistMessage(ctx, ws, message) {
|
|
|
7864
7981
|
handleTodosGet(ctx, ws);
|
|
7865
7982
|
return;
|
|
7866
7983
|
case "todos.clear":
|
|
7867
|
-
handleTodosClear(ctx, ws);
|
|
7984
|
+
await handleTodosClear(ctx, ws);
|
|
7868
7985
|
return;
|
|
7869
7986
|
case "todos.remove":
|
|
7870
|
-
handleTodosRemove(
|
|
7987
|
+
await handleTodosRemove(
|
|
7988
|
+
ctx,
|
|
7989
|
+
ws,
|
|
7990
|
+
message.payload
|
|
7991
|
+
);
|
|
7871
7992
|
return;
|
|
7872
7993
|
case "todo.update":
|
|
7873
|
-
handleTodoUpdate(
|
|
7994
|
+
await handleTodoUpdate(
|
|
7874
7995
|
ctx,
|
|
7875
7996
|
ws,
|
|
7876
7997
|
message.payload
|
|
@@ -9452,7 +9573,19 @@ function isTrustedLoopbackOrigin(origin, hostHeader) {
|
|
|
9452
9573
|
if (!host) return false;
|
|
9453
9574
|
const hostUrl = new URL(`${url.protocol}//${host}`);
|
|
9454
9575
|
if (!isLoopbackHostname(hostUrl.hostname)) return false;
|
|
9455
|
-
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);
|
|
9456
9589
|
} catch {
|
|
9457
9590
|
return false;
|
|
9458
9591
|
}
|
|
@@ -9557,13 +9690,15 @@ function verifyClient(input) {
|
|
|
9557
9690
|
try {
|
|
9558
9691
|
const { hostname: originHostname } = new URL(origin);
|
|
9559
9692
|
if (isLoopbackHostname(originHostname)) {
|
|
9560
|
-
if (requireToken || !isLoopbackBind(wsHost))
|
|
9693
|
+
if (requireToken || !isLoopbackBind(wsHost)) {
|
|
9694
|
+
return cookieTokenOk && (originMatchesHost(origin, hostHeader) || Boolean(allowCrossPortLoopbackCookie));
|
|
9695
|
+
}
|
|
9561
9696
|
if (!isTrustedLoopbackOrigin(origin, hostHeader)) {
|
|
9562
9697
|
return Boolean(allowCrossPortLoopbackCookie) && cookieTokenOk;
|
|
9563
9698
|
}
|
|
9564
9699
|
return true;
|
|
9565
9700
|
}
|
|
9566
|
-
return cookieTokenOk || Boolean(allowBrowserUrlToken) && urlTokenOk && allowedHostname(originHostname, allowedHostnames);
|
|
9701
|
+
return cookieTokenOk && originMatchesHost(origin, hostHeader) || Boolean(allowBrowserUrlToken) && urlTokenOk && allowedHostname(originHostname, allowedHostnames);
|
|
9567
9702
|
} catch {
|
|
9568
9703
|
return false;
|
|
9569
9704
|
}
|
|
@@ -10528,7 +10663,8 @@ async function handleIntrospectionRoute(ctx, ws, message) {
|
|
|
10528
10663
|
switch (message.type) {
|
|
10529
10664
|
case "diag.get": {
|
|
10530
10665
|
if (!sessionAllowed(ctx, ws, message)) return true;
|
|
10531
|
-
const
|
|
10666
|
+
const registry = ctx.agent.tools;
|
|
10667
|
+
const tools = registry.listForProvider?.() ?? registry.list();
|
|
10532
10668
|
ctx.send(ws, {
|
|
10533
10669
|
type: "diag.get",
|
|
10534
10670
|
payload: {
|
|
@@ -10611,6 +10747,7 @@ async function handleIntrospectionRoute(ctx, ws, message) {
|
|
|
10611
10747
|
description: tool.description ?? "",
|
|
10612
10748
|
params: schema.properties ? Object.keys(schema.properties) : [],
|
|
10613
10749
|
disabled: registry.isDisabled?.(tool.name) ?? false,
|
|
10750
|
+
direct: registry.isExposedToProvider?.(tool.name) ?? true,
|
|
10614
10751
|
mutating: !!tool.mutating,
|
|
10615
10752
|
permission: tool.permission ?? "auto"
|
|
10616
10753
|
};
|
|
@@ -10920,7 +11057,6 @@ async function handleKanbanHostRoute(ws, msg, handlers) {
|
|
|
10920
11057
|
import { deserializeTaskGraph, serializeTaskGraph } from "@wrongstack/core/tasking";
|
|
10921
11058
|
import {
|
|
10922
11059
|
addCheckToTask,
|
|
10923
|
-
addColumn,
|
|
10924
11060
|
addGoalMetricToTask,
|
|
10925
11061
|
addNoteToTask,
|
|
10926
11062
|
addTask,
|
|
@@ -10928,14 +11064,17 @@ import {
|
|
|
10928
11064
|
claimReadyTask,
|
|
10929
11065
|
copyTaskToBoard,
|
|
10930
11066
|
createBoard,
|
|
11067
|
+
createBoardFromText,
|
|
10931
11068
|
duplicateBoard,
|
|
10932
11069
|
exportBoardToTaskGraph,
|
|
10933
|
-
createBoardFromText,
|
|
10934
11070
|
getBoard,
|
|
10935
11071
|
getKanbanOrchestrationSnapshot,
|
|
10936
11072
|
getKanbanQueueHealth,
|
|
10937
11073
|
getTaskChain,
|
|
11074
|
+
hasKanbanQueueAnomalies,
|
|
11075
|
+
kanbanQueueAnomalyCount,
|
|
10938
11076
|
listBoards as listBoards2,
|
|
11077
|
+
listBoardHistory,
|
|
10939
11078
|
listReadyTasks,
|
|
10940
11079
|
mergeTasks,
|
|
10941
11080
|
moveTask,
|
|
@@ -10944,7 +11083,6 @@ import {
|
|
|
10944
11083
|
recoverStaleTaskAssignments,
|
|
10945
11084
|
releaseTaskClaim,
|
|
10946
11085
|
removeBoard,
|
|
10947
|
-
removeColumn,
|
|
10948
11086
|
setTaskChain,
|
|
10949
11087
|
splitTask,
|
|
10950
11088
|
syncBoardFromTaskGraph,
|
|
@@ -10956,14 +11094,16 @@ import {
|
|
|
10956
11094
|
updateTask as updateTask2
|
|
10957
11095
|
} from "@wrongstack/kanban";
|
|
10958
11096
|
|
|
10959
|
-
// src/server/kanban-
|
|
11097
|
+
// src/server/kanban-contract-routes.ts
|
|
10960
11098
|
import {
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
11099
|
+
addContractEdge,
|
|
11100
|
+
configureContractGraph,
|
|
11101
|
+
evaluateTaskContractGraph,
|
|
11102
|
+
getContractGraph,
|
|
11103
|
+
removeContractEdge,
|
|
11104
|
+
removeContractNode,
|
|
11105
|
+
upsertContractNode
|
|
10965
11106
|
} from "@wrongstack/kanban";
|
|
10966
|
-
import { recordKanbanVerificationEvidence as recordKanbanVerificationEvidence2 } from "@wrongstack/tools";
|
|
10967
11107
|
|
|
10968
11108
|
// src/server/kanban-route-helpers.ts
|
|
10969
11109
|
import { touchKanbanPresence } from "@wrongstack/kanban";
|
|
@@ -11015,7 +11155,150 @@ function findTask2(tasks, taskId) {
|
|
|
11015
11155
|
return tasks.find((task) => task.id === taskId || task.id.startsWith(taskId));
|
|
11016
11156
|
}
|
|
11017
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
|
+
|
|
11018
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";
|
|
11019
11302
|
async function handleKanbanDecompositionRoute(ws, type, payload, ctx) {
|
|
11020
11303
|
switch (type) {
|
|
11021
11304
|
case "kanban.decomposition.approve":
|
|
@@ -11118,8 +11401,35 @@ async function handleTaskVerification(ws, type, payload, ctx) {
|
|
|
11118
11401
|
}
|
|
11119
11402
|
}
|
|
11120
11403
|
|
|
11404
|
+
// src/server/kanban-route-pagination.ts
|
|
11405
|
+
function paginateKanbanBoards(boards, input) {
|
|
11406
|
+
const pageSize = Math.min(100, Math.max(1, Math.floor(input.pageSize)));
|
|
11407
|
+
const activeSessionIds = new Set(input.activeSessionIds ?? []);
|
|
11408
|
+
const isActive = (board) => board.presence?.some((entry) => entry.active) === true || board.tags?.some((tag) => tag.startsWith("session:") && activeSessionIds.has(tag.slice(8))) === true;
|
|
11409
|
+
const sorted = [...boards].sort((left, right) => {
|
|
11410
|
+
const activityOrder = Number(isActive(right)) - Number(isActive(left));
|
|
11411
|
+
return activityOrder || right.updatedAt.localeCompare(left.updatedAt);
|
|
11412
|
+
});
|
|
11413
|
+
const activeTotal = sorted.filter(isActive).length;
|
|
11414
|
+
const total = sorted.length;
|
|
11415
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
11416
|
+
const requestedPage = Number.isFinite(input.page) ? Math.floor(input.page) : 1;
|
|
11417
|
+
const page = Math.min(totalPages, Math.max(1, requestedPage));
|
|
11418
|
+
const start = (page - 1) * pageSize;
|
|
11419
|
+
return {
|
|
11420
|
+
items: sorted.slice(start, start + pageSize),
|
|
11421
|
+
total,
|
|
11422
|
+
page,
|
|
11423
|
+
pageSize,
|
|
11424
|
+
totalPages,
|
|
11425
|
+
activeTotal,
|
|
11426
|
+
orphanedTotal: total - activeTotal
|
|
11427
|
+
};
|
|
11428
|
+
}
|
|
11429
|
+
|
|
11121
11430
|
// src/server/kanban-task-routes.ts
|
|
11122
11431
|
import {
|
|
11432
|
+
getKanbanWorkbench,
|
|
11123
11433
|
getTask,
|
|
11124
11434
|
listTaskActivity,
|
|
11125
11435
|
recordTaskActivity,
|
|
@@ -11127,6 +11437,16 @@ import {
|
|
|
11127
11437
|
} from "@wrongstack/kanban";
|
|
11128
11438
|
async function handleKanbanTaskRoute(ws, type, payload, ctx) {
|
|
11129
11439
|
switch (type) {
|
|
11440
|
+
case "kanban.workbench":
|
|
11441
|
+
ok(
|
|
11442
|
+
ws,
|
|
11443
|
+
type,
|
|
11444
|
+
await getKanbanWorkbench(ctx.projectRoot, {
|
|
11445
|
+
...typeof payload?.limitPerLane === "number" ? { limitPerLane: payload.limitPerLane } : {},
|
|
11446
|
+
...typeof payload?.alertLimit === "number" ? { alertLimit: payload.alertLimit } : {}
|
|
11447
|
+
})
|
|
11448
|
+
);
|
|
11449
|
+
return true;
|
|
11130
11450
|
case "kanban.task.remove":
|
|
11131
11451
|
await handleTaskRemove(ws, type, payload, ctx);
|
|
11132
11452
|
return true;
|
|
@@ -11219,45 +11539,20 @@ async function handleTaskActivityAdd(ws, type, payload, ctx) {
|
|
|
11219
11539
|
outcome,
|
|
11220
11540
|
...typeof payload?.details === "string" && payload.details.trim() ? { details: payload.details.trim() } : {}
|
|
11221
11541
|
},
|
|
11222
|
-
activityContext(
|
|
11223
|
-
ctx,
|
|
11224
|
-
payload?.actor ?? ctx.context?.agentId ?? "webui"
|
|
11225
|
-
)
|
|
11542
|
+
activityContext(ctx, payload?.actor ?? ctx.context?.agentId ?? "webui")
|
|
11226
11543
|
);
|
|
11227
11544
|
board ? ok(ws, type, board) : fail(ws, type, "Board or task not found");
|
|
11228
11545
|
}
|
|
11229
11546
|
|
|
11230
|
-
// src/server/kanban-route-pagination.ts
|
|
11231
|
-
function paginateKanbanBoards(boards, input) {
|
|
11232
|
-
const pageSize = Math.min(100, Math.max(1, Math.floor(input.pageSize)));
|
|
11233
|
-
const activeSessionIds = new Set(input.activeSessionIds ?? []);
|
|
11234
|
-
const isActive = (board) => board.presence?.some((entry) => entry.active) === true || board.tags?.some((tag) => tag.startsWith("session:") && activeSessionIds.has(tag.slice(8))) === true;
|
|
11235
|
-
const sorted = [...boards].sort((left, right) => {
|
|
11236
|
-
const activityOrder = Number(isActive(right)) - Number(isActive(left));
|
|
11237
|
-
return activityOrder || right.updatedAt.localeCompare(left.updatedAt);
|
|
11238
|
-
});
|
|
11239
|
-
const activeTotal = sorted.filter(isActive).length;
|
|
11240
|
-
const total = sorted.length;
|
|
11241
|
-
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
11242
|
-
const requestedPage = Number.isFinite(input.page) ? Math.floor(input.page) : 1;
|
|
11243
|
-
const page = Math.min(totalPages, Math.max(1, requestedPage));
|
|
11244
|
-
const start = (page - 1) * pageSize;
|
|
11245
|
-
return {
|
|
11246
|
-
items: sorted.slice(start, start + pageSize),
|
|
11247
|
-
total,
|
|
11248
|
-
page,
|
|
11249
|
-
pageSize,
|
|
11250
|
-
totalPages,
|
|
11251
|
-
activeTotal,
|
|
11252
|
-
orphanedTotal: total - activeTotal
|
|
11253
|
-
};
|
|
11254
|
-
}
|
|
11255
|
-
|
|
11256
11547
|
// src/server/kanban-route-protocol.ts
|
|
11257
11548
|
var KANBAN_CLIENT_MESSAGE_TYPES = [
|
|
11258
11549
|
"kanban.capabilities",
|
|
11259
|
-
"kanban.
|
|
11260
|
-
"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",
|
|
11261
11556
|
"kanban.create",
|
|
11262
11557
|
"kanban.decomposition.approve",
|
|
11263
11558
|
"kanban.decomposition.reject",
|
|
@@ -11297,7 +11592,8 @@ var KANBAN_CLIENT_MESSAGE_TYPES = [
|
|
|
11297
11592
|
"kanban.task.verify",
|
|
11298
11593
|
"kanban.taskgraph.export",
|
|
11299
11594
|
"kanban.taskgraph.sync",
|
|
11300
|
-
"kanban.update"
|
|
11595
|
+
"kanban.update",
|
|
11596
|
+
"kanban.workbench"
|
|
11301
11597
|
];
|
|
11302
11598
|
|
|
11303
11599
|
// src/server/kanban-routes.ts
|
|
@@ -11307,6 +11603,7 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11307
11603
|
const type = msg.type;
|
|
11308
11604
|
try {
|
|
11309
11605
|
if (await handleKanbanDecompositionRoute(ws, type, payload, ctx)) return true;
|
|
11606
|
+
if (await handleKanbanContractRoute(ws, type, payload, ctx)) return true;
|
|
11310
11607
|
if (await handleKanbanTaskRoute(ws, type, payload, ctx)) return true;
|
|
11311
11608
|
switch (type) {
|
|
11312
11609
|
case "kanban.list": {
|
|
@@ -11345,7 +11642,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11345
11642
|
fail(ws, type, "boardId required");
|
|
11346
11643
|
return true;
|
|
11347
11644
|
}
|
|
11348
|
-
ok(
|
|
11645
|
+
ok(
|
|
11646
|
+
ws,
|
|
11647
|
+
type,
|
|
11648
|
+
await getKanbanQueueHealth(ctx.projectRoot, {
|
|
11649
|
+
boardId: hBoardId,
|
|
11650
|
+
includeClassifications: false
|
|
11651
|
+
})
|
|
11652
|
+
);
|
|
11349
11653
|
return true;
|
|
11350
11654
|
}
|
|
11351
11655
|
case "kanban.supervisor.status":
|
|
@@ -11375,16 +11679,16 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11375
11679
|
reason: "On-demand standalone Kanban supervisor audit."
|
|
11376
11680
|
}) : null;
|
|
11377
11681
|
if (recovered) health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
11378
|
-
const anomalyCount = health
|
|
11682
|
+
const anomalyCount = kanbanQueueAnomalyCount(health);
|
|
11379
11683
|
ok(ws, type, {
|
|
11380
11684
|
boardId,
|
|
11381
|
-
status: board.supervisor?.enabled === false ? "disabled" :
|
|
11685
|
+
status: board.supervisor?.enabled === false ? "disabled" : hasKanbanQueueAnomalies(health) ? "attention" : "healthy",
|
|
11382
11686
|
mode: board.supervisor?.mode ?? "deterministic",
|
|
11383
11687
|
lastAuditAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11384
11688
|
reconciledTaskIds: reconciled?.tasks.map((task) => task.id) ?? [],
|
|
11385
11689
|
staleRecoveredTaskIds: recovered?.tasks.map((task) => task.id) ?? [],
|
|
11386
11690
|
anomalyCount,
|
|
11387
|
-
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`
|
|
11388
11692
|
});
|
|
11389
11693
|
return true;
|
|
11390
11694
|
}
|
|
@@ -11401,7 +11705,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11401
11705
|
title,
|
|
11402
11706
|
...payload?.description ? { description: payload.description } : {},
|
|
11403
11707
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
11404
|
-
...payload?.columns ? { columns: payload.columns } : {},
|
|
11405
11708
|
...has(payload, "lifecycle") ? { lifecycle: payload?.lifecycle } : {},
|
|
11406
11709
|
...has(payload, "boundary") ? { boundary: payload?.boundary } : {}
|
|
11407
11710
|
})
|
|
@@ -11418,7 +11721,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11418
11721
|
...payload?.title ? { title: payload.title } : {},
|
|
11419
11722
|
...payload?.description ? { description: payload.description } : {},
|
|
11420
11723
|
...payload?.tags ? { tags: payload.tags } : {},
|
|
11421
|
-
...payload?.columns ? { columns: payload.columns } : {},
|
|
11422
11724
|
...has(payload, "lifecycle") ? {
|
|
11423
11725
|
lifecycle: payload?.lifecycle ?? null
|
|
11424
11726
|
} : {},
|
|
@@ -11465,6 +11767,12 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11465
11767
|
});
|
|
11466
11768
|
return true;
|
|
11467
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
|
+
}
|
|
11468
11776
|
case "kanban.generate": {
|
|
11469
11777
|
const description = payload?.description;
|
|
11470
11778
|
if (!description) {
|
|
@@ -11926,8 +12234,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11926
12234
|
taskId,
|
|
11927
12235
|
{
|
|
11928
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.
|
|
11929
12242
|
type: payload?.checkType ?? "manual",
|
|
11930
|
-
status: payload?.status ?? "pending"
|
|
12243
|
+
status: payload?.status ?? "pending",
|
|
12244
|
+
...typeof payload?.notes === "string" ? { notes: payload.notes } : {}
|
|
11931
12245
|
},
|
|
11932
12246
|
activityContext(
|
|
11933
12247
|
ctx,
|
|
@@ -11992,30 +12306,6 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11992
12306
|
case "kanban.capabilities":
|
|
11993
12307
|
ok(ws, type, { dispatchSupported: Boolean(ctx.dispatchTask) });
|
|
11994
12308
|
return true;
|
|
11995
|
-
case "kanban.column.add": {
|
|
11996
|
-
const boardId = payload?.boardId;
|
|
11997
|
-
const title = payload?.title;
|
|
11998
|
-
if (!boardId || !title) {
|
|
11999
|
-
fail(ws, type, "boardId and title required");
|
|
12000
|
-
return true;
|
|
12001
|
-
}
|
|
12002
|
-
const result = await addColumn(ctx.projectRoot, boardId, { title });
|
|
12003
|
-
result ? ok(ws, type, result.board.columns) : fail(ws, type, `Board not found: ${boardId}`);
|
|
12004
|
-
return true;
|
|
12005
|
-
}
|
|
12006
|
-
case "kanban.column.remove": {
|
|
12007
|
-
const boardId = payload?.boardId;
|
|
12008
|
-
const columnId = payload?.columnId;
|
|
12009
|
-
if (!boardId || !columnId) {
|
|
12010
|
-
fail(ws, type, "boardId and columnId required");
|
|
12011
|
-
return true;
|
|
12012
|
-
}
|
|
12013
|
-
const board = await removeColumn(ctx.projectRoot, boardId, columnId, {
|
|
12014
|
-
moveTasksToColumnId: payload?.moveTasksToColumnId
|
|
12015
|
-
});
|
|
12016
|
-
board ? ok(ws, type, { removed: true, boardId: board.id, columnId, board }) : fail(ws, type, `Column not found: ${columnId}`);
|
|
12017
|
-
return true;
|
|
12018
|
-
}
|
|
12019
12309
|
default:
|
|
12020
12310
|
fail(ws, type, `Unknown kanban message type: ${type}`);
|
|
12021
12311
|
return true;
|
|
@@ -12078,7 +12368,9 @@ function subscribeKanbanDaemonEvents(projectRoot, broadcastMessage) {
|
|
|
12078
12368
|
projectRoot,
|
|
12079
12369
|
async (event) => {
|
|
12080
12370
|
const family = event.event?.split(".")[0];
|
|
12081
|
-
if (family !== "board" && family !== "task" && family !== "column")
|
|
12371
|
+
if (family !== "board" && family !== "task" && family !== "column" && family !== "contract") {
|
|
12372
|
+
return;
|
|
12373
|
+
}
|
|
12082
12374
|
const evData = event.data;
|
|
12083
12375
|
const boardId = evData?.boardId;
|
|
12084
12376
|
if (!boardId) return;
|
|
@@ -13283,6 +13575,34 @@ async function handleSageRecover(ws, msg, memoryStore) {
|
|
|
13283
13575
|
send(ws, { type: "memory.sage.recover", payload: { error: errMessage(err) } });
|
|
13284
13576
|
}
|
|
13285
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
|
+
}
|
|
13286
13606
|
async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
13287
13607
|
const Sage = getSageSurface(memoryStore);
|
|
13288
13608
|
if (!Sage) {
|
|
@@ -13395,14 +13715,17 @@ async function handleSageForFile(ws, msg, memoryStore) {
|
|
|
13395
13715
|
send(ws, { type: "memory.sage.forFile", payload: { error: "filePath is required" } });
|
|
13396
13716
|
return;
|
|
13397
13717
|
}
|
|
13718
|
+
const includeSuperseded = typeof payload["showSuperseded"] === "boolean" ? payload["showSuperseded"] : typeof payload["includeSuperseded"] === "boolean" ? payload["includeSuperseded"] : void 0;
|
|
13719
|
+
const includeDeleted = payload["showDeleted"] === true || payload["includeDeleted"] === true;
|
|
13398
13720
|
try {
|
|
13399
13721
|
const response = await Sage.findMemoriesForFile(filePath, {
|
|
13400
13722
|
...typeof payload["lineStart"] === "number" ? { lineStart: payload["lineStart"] } : {},
|
|
13401
13723
|
...typeof payload["lineEnd"] === "number" ? { lineEnd: payload["lineEnd"] } : {},
|
|
13402
13724
|
...typeof payload["limit"] === "number" ? { limit: payload["limit"] } : {},
|
|
13403
|
-
...
|
|
13725
|
+
...includeSuperseded !== void 0 ? { includeSuperseded } : {},
|
|
13726
|
+
...includeDeleted ? { includeDeleted: true } : {}
|
|
13404
13727
|
});
|
|
13405
|
-
send(ws, { type: "memory.sage.forFile", payload: response });
|
|
13728
|
+
send(ws, { type: "memory.sage.forFile", payload: { response } });
|
|
13406
13729
|
} catch (err) {
|
|
13407
13730
|
send(ws, { type: "memory.sage.forFile", payload: { error: errMessage(err) } });
|
|
13408
13731
|
}
|
|
@@ -13458,6 +13781,9 @@ async function handleMemoryRoute(ctx, ws, message) {
|
|
|
13458
13781
|
case "memory.sage.recover":
|
|
13459
13782
|
await handleSageRecover(ws, message, store);
|
|
13460
13783
|
return true;
|
|
13784
|
+
case "memory.sage.listCandidates":
|
|
13785
|
+
await handleSageListCandidates(ws, message, store);
|
|
13786
|
+
return true;
|
|
13461
13787
|
case "memory.sage.candidateResolve":
|
|
13462
13788
|
await handleSageCandidateResolve(ws, message, store);
|
|
13463
13789
|
return true;
|
|
@@ -15024,6 +15350,7 @@ import {
|
|
|
15024
15350
|
finalizeTaskCompletion,
|
|
15025
15351
|
getBoard as getBoard3,
|
|
15026
15352
|
getKanbanQueueHealth as getKanbanQueueHealth2,
|
|
15353
|
+
kanbanQueueAnomalyCount as kanbanQueueAnomalyCount2,
|
|
15027
15354
|
listBoards as listBoards4,
|
|
15028
15355
|
reconcileKanbanBoard as reconcileKanbanBoard2,
|
|
15029
15356
|
recoverStaleTaskAssignments as recoverStaleTaskAssignments2,
|
|
@@ -15104,8 +15431,9 @@ function createKanbanSupervisor(deps2) {
|
|
|
15104
15431
|
mode: config.recoveryMode ?? "auto",
|
|
15105
15432
|
reason: "Kanban supervisor found an expired worker lease."
|
|
15106
15433
|
}) : null;
|
|
15107
|
-
if (recovered)
|
|
15108
|
-
|
|
15434
|
+
if (recovered)
|
|
15435
|
+
health = await getKanbanQueueHealth2(resolveProjectRoot(deps2), { boardId: board.id });
|
|
15436
|
+
const anomalyCount = kanbanQueueAnomalyCount2(health);
|
|
15109
15437
|
const snapshot = {
|
|
15110
15438
|
boardId: board.id,
|
|
15111
15439
|
status: anomalyCount > 0 ? "attention" : "healthy",
|
|
@@ -15199,7 +15527,9 @@ function createKanbanSupervisor(deps2) {
|
|
|
15199
15527
|
} else {
|
|
15200
15528
|
const summaries = await listBoards4(resolveProjectRoot(deps2));
|
|
15201
15529
|
pruneAbsentBoards(new Set(summaries.map((summary) => summary.id)));
|
|
15202
|
-
boards = (await Promise.all(
|
|
15530
|
+
boards = (await Promise.all(
|
|
15531
|
+
summaries.map((summary) => getBoard3(resolveProjectRoot(deps2), summary.id))
|
|
15532
|
+
)).filter((board) => Boolean(board));
|
|
15203
15533
|
}
|
|
15204
15534
|
const results = [];
|
|
15205
15535
|
for (const board of boards) results.push(await auditBoard(board));
|
|
@@ -15302,13 +15632,10 @@ function dispatchRoute(routing) {
|
|
|
15302
15632
|
...routing.fallbackModels?.length ? { fallbackModels: routing.fallbackModels } : {}
|
|
15303
15633
|
};
|
|
15304
15634
|
}
|
|
15305
|
-
function countAnomalies(health) {
|
|
15306
|
-
return health.staleAssignments.count + health.heartbeatDue.count + health.counts.failed + health.counts.blocked;
|
|
15307
|
-
}
|
|
15308
15635
|
function healthSummary(health) {
|
|
15309
15636
|
return [
|
|
15310
15637
|
`${health.counts.running} running`,
|
|
15311
|
-
`${health.counts.
|
|
15638
|
+
`${health.counts.startable} ready`,
|
|
15312
15639
|
`${health.counts.review} review`,
|
|
15313
15640
|
`${health.counts.blocked} blocked`,
|
|
15314
15641
|
`${health.counts.failed} failed`,
|
|
@@ -16070,7 +16397,7 @@ import { makeProviderFromConfig } from "@wrongstack/providers";
|
|
|
16070
16397
|
import * as fs14 from "node:fs/promises";
|
|
16071
16398
|
import * as path19 from "node:path";
|
|
16072
16399
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
16073
|
-
import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
16400
|
+
import { activateProjectStateGuard, resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
16074
16401
|
function createProjectHandlers(ctx) {
|
|
16075
16402
|
const sendTo = (ws, message) => {
|
|
16076
16403
|
if (ctx.sendMessage) ctx.sendMessage(ws, message);
|
|
@@ -16235,6 +16562,7 @@ function createProjectHandlers(ctx) {
|
|
|
16235
16562
|
try {
|
|
16236
16563
|
await ctx.onSessionSwapped?.(next.id, identityTarget);
|
|
16237
16564
|
await ctx.onBeforeSessionTodosReplaced?.(next.id, paths.projectSessions);
|
|
16565
|
+
await activateProjectStateGuard(resolved);
|
|
16238
16566
|
} catch (err) {
|
|
16239
16567
|
try {
|
|
16240
16568
|
await ctx.onBeforeSessionTodosReplaced?.(previous.id, previousPaths.projectSessions);
|
|
@@ -17252,6 +17580,7 @@ var CLIENT_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
17252
17580
|
"memory.sage.get",
|
|
17253
17581
|
"memory.sage.graph",
|
|
17254
17582
|
"memory.sage.list",
|
|
17583
|
+
"memory.sage.listCandidates",
|
|
17255
17584
|
"memory.sage.listPage",
|
|
17256
17585
|
"memory.sage.recover",
|
|
17257
17586
|
"memory.sage.remember",
|
|
@@ -17531,6 +17860,7 @@ var SERVER_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
17531
17860
|
"memory.sage.get",
|
|
17532
17861
|
"memory.sage.graph",
|
|
17533
17862
|
"memory.sage.list",
|
|
17863
|
+
"memory.sage.listCandidates",
|
|
17534
17864
|
"memory.sage.listPage",
|
|
17535
17865
|
"memory.sage.recover",
|
|
17536
17866
|
"memory.sage.remember",
|
|
@@ -18382,6 +18712,7 @@ function createSessionHandlers(ctx) {
|
|
|
18382
18712
|
ctx.abortActiveRun?.(current2.id);
|
|
18383
18713
|
} catch {
|
|
18384
18714
|
}
|
|
18715
|
+
await ctx.context.flushConversationJournal?.().catch(() => void 0);
|
|
18385
18716
|
await finalizeSession(current2);
|
|
18386
18717
|
}
|
|
18387
18718
|
ctx.setSession(next);
|
|
@@ -19071,89 +19402,60 @@ function createEmbeddedProjectRoutes(ctx) {
|
|
|
19071
19402
|
|
|
19072
19403
|
// src/server/embedded-message-router.ts
|
|
19073
19404
|
import { makeProviderFromConfig as makeProviderFromConfig2 } from "@wrongstack/providers";
|
|
19405
|
+
import { planTool, taskTool, todoTool } from "@wrongstack/tools";
|
|
19074
19406
|
|
|
19075
19407
|
// src/server/agent-roster-handlers.ts
|
|
19076
19408
|
import {
|
|
19077
19409
|
applyProjectAgentConfig,
|
|
19078
|
-
buildConsolidationInstruction,
|
|
19079
19410
|
captureLearnedFromAgentOutputDetailed,
|
|
19080
19411
|
clearProjectAgentConsolidated,
|
|
19412
|
+
clearProjectSkillAugmentation,
|
|
19081
19413
|
createProjectAgent,
|
|
19414
|
+
DEFAULT_EAGER_SKILL_LIMIT,
|
|
19082
19415
|
detectLearnedConflicts,
|
|
19416
|
+
evaluateAutoOptimize,
|
|
19083
19417
|
FLEET_ROSTER,
|
|
19084
19418
|
getProjectAgentLearnStats,
|
|
19085
19419
|
isConsolidated,
|
|
19086
19420
|
listProjectAgentLearnedEntries,
|
|
19087
19421
|
listProjectAgentRoles,
|
|
19422
|
+
listProjectSkillAugmentations,
|
|
19088
19423
|
loadConsolidationMetadata,
|
|
19089
19424
|
loadProjectAgentConfig,
|
|
19090
19425
|
loadProjectAgentConsolidated,
|
|
19091
19426
|
loadProjectAgentIdentity,
|
|
19092
19427
|
loadProjectAgentLearned,
|
|
19093
19428
|
loadProjectAgentProfile,
|
|
19429
|
+
loadProjectSkillAugmentation,
|
|
19430
|
+
loadSkillAffinity,
|
|
19431
|
+
optimizeProjectAgentLearning,
|
|
19432
|
+
rankRoleSkills,
|
|
19433
|
+
readQuarantinedDirectives,
|
|
19434
|
+
readRawLearnedEntries,
|
|
19094
19435
|
resetProjectAgentIdentity,
|
|
19436
|
+
resolveAutoOptimizePolicy,
|
|
19437
|
+
resolveRoleSkillCandidates,
|
|
19095
19438
|
saveProjectAgentConsolidated,
|
|
19439
|
+
saveProjectSkillAugmentation,
|
|
19440
|
+
scoreSkillAffinity,
|
|
19441
|
+
setSkillPinned,
|
|
19096
19442
|
slugifyProjectAgentRole,
|
|
19097
19443
|
updateProjectAgentConfig,
|
|
19098
19444
|
updateProjectAgentIdentity,
|
|
19099
19445
|
updateProjectAgentLearned,
|
|
19100
19446
|
updateProjectAgentLearningPolicy
|
|
19101
19447
|
} from "@wrongstack/core/coordination";
|
|
19102
|
-
import { isTextBlock } from "@wrongstack/core/types";
|
|
19103
|
-
var CONSOLIDATION_MAX_TOKENS = 8e3;
|
|
19104
|
-
var CONSOLIDATION_TIMEOUT_MS = 12e4;
|
|
19105
19448
|
var AgentRosterWSHandler = class {
|
|
19106
19449
|
getProjectRoot;
|
|
19107
19450
|
getLlm;
|
|
19108
19451
|
broadcast;
|
|
19452
|
+
getAutoOptimizeSettings;
|
|
19109
19453
|
constructor(opts) {
|
|
19110
19454
|
this.getProjectRoot = typeof opts.projectRoot === "function" ? opts.projectRoot : () => opts.projectRoot;
|
|
19111
19455
|
this.getLlm = opts.getLlm ?? (() => void 0);
|
|
19112
19456
|
this.broadcast = opts.broadcast ?? (() => {
|
|
19113
19457
|
});
|
|
19114
|
-
|
|
19115
|
-
/**
|
|
19116
|
-
* Run the consolidation LLM synthesis headlessly and return the cleaned
|
|
19117
|
-
* document text. Returns undefined when no LLM is available so the caller
|
|
19118
|
-
* can fall back to the instruction-only path.
|
|
19119
|
-
*/
|
|
19120
|
-
async synthesizeConsolidation(instruction) {
|
|
19121
|
-
const llm = this.getLlm();
|
|
19122
|
-
if (!llm) return void 0;
|
|
19123
|
-
const req = {
|
|
19124
|
-
model: llm.model,
|
|
19125
|
-
system: [
|
|
19126
|
-
{
|
|
19127
|
-
type: "text",
|
|
19128
|
-
text: "You are a precise technical editor. You consolidate an AI agent role's captured learning entries into a single, durable, role-scoped instruction document. Output ONLY the consolidated markdown document \u2014 no preamble, no code fences, no commentary about the consolidation process."
|
|
19129
|
-
}
|
|
19130
|
-
],
|
|
19131
|
-
messages: [{ role: "user", content: instruction }],
|
|
19132
|
-
maxTokens: CONSOLIDATION_MAX_TOKENS
|
|
19133
|
-
};
|
|
19134
|
-
const timer = new AbortController();
|
|
19135
|
-
let timedOut = false;
|
|
19136
|
-
const to = setTimeout(() => {
|
|
19137
|
-
timedOut = true;
|
|
19138
|
-
timer.abort(new Error("consolidation timeout"));
|
|
19139
|
-
}, CONSOLIDATION_TIMEOUT_MS);
|
|
19140
|
-
to.unref();
|
|
19141
|
-
try {
|
|
19142
|
-
const res = await llm.provider.complete(req, { signal: timer.signal });
|
|
19143
|
-
const text2 = res.content.filter(isTextBlock).map((block) => block.text).join("\n").trim();
|
|
19144
|
-
const wholeDocFence = /^```(?:markdown|md)?[^\n]*\n([\s\S]*?)\n?```\s*$/i;
|
|
19145
|
-
const wrapped = wholeDocFence.exec(text2);
|
|
19146
|
-
const inner = wrapped?.[1];
|
|
19147
|
-
const unfenced = inner !== void 0 ? inner.trim() : text2;
|
|
19148
|
-
return { content: unfenced, model: llm.model };
|
|
19149
|
-
} catch (err) {
|
|
19150
|
-
if (timedOut) {
|
|
19151
|
-
throw new Error(`consolidation timed out after ${CONSOLIDATION_TIMEOUT_MS}ms`);
|
|
19152
|
-
}
|
|
19153
|
-
throw err;
|
|
19154
|
-
} finally {
|
|
19155
|
-
clearTimeout(to);
|
|
19156
|
-
}
|
|
19458
|
+
this.getAutoOptimizeSettings = opts.getAutoOptimizeSettings;
|
|
19157
19459
|
}
|
|
19158
19460
|
/** Handle an incoming client message. Returns a response payload. */
|
|
19159
19461
|
async handleMessage(_ws, type, payload) {
|
|
@@ -19358,70 +19660,47 @@ ${String(p.content ?? "")}`;
|
|
|
19358
19660
|
const conflicts = detectLearnedConflicts(projectRoot);
|
|
19359
19661
|
return { type, payload: { conflicts } };
|
|
19360
19662
|
}
|
|
19361
|
-
// ──
|
|
19362
|
-
//
|
|
19363
|
-
//
|
|
19364
|
-
//
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
case "agent-roster.consolidate": {
|
|
19663
|
+
// ── Optimize: distil captures into skill addenda + a consolidated doc,
|
|
19664
|
+
// then archive and reset the raw buffer. Shared implementation with the
|
|
19665
|
+
// CLI (`optimizeProjectAgentLearning`) so both surfaces persist the same
|
|
19666
|
+
// artifacts instead of the CLI producing markdown nobody saved.
|
|
19667
|
+
case "agent-roster.consolidate":
|
|
19668
|
+
case "agent-roster.optimize": {
|
|
19368
19669
|
if (!role) return { type, payload: { error: "role required" } };
|
|
19369
|
-
const
|
|
19370
|
-
|
|
19670
|
+
const hasExistingConsolidation = isConsolidated(role, projectRoot);
|
|
19671
|
+
const pending = readRawLearnedEntries(role, projectRoot);
|
|
19672
|
+
if (pending.length === 0) {
|
|
19371
19673
|
return {
|
|
19372
19674
|
type: "agent-roster.consolidate",
|
|
19373
19675
|
payload: {
|
|
19374
19676
|
role,
|
|
19375
19677
|
consolidated: false,
|
|
19376
19678
|
rawEntryCount: 0,
|
|
19679
|
+
skills: [],
|
|
19377
19680
|
hasExistingConsolidation,
|
|
19378
19681
|
currentStats: getProjectAgentLearnStats(role, projectRoot)
|
|
19379
19682
|
}
|
|
19380
19683
|
};
|
|
19381
19684
|
}
|
|
19382
|
-
|
|
19383
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
|
|
19393
|
-
|
|
19394
|
-
|
|
19395
|
-
|
|
19396
|
-
|
|
19397
|
-
}
|
|
19398
|
-
if (synth && synth.content.length > 0) {
|
|
19399
|
-
let stats;
|
|
19400
|
-
let metadata;
|
|
19401
|
-
try {
|
|
19402
|
-
saveProjectAgentConsolidated(role, synth.content, projectRoot, {
|
|
19403
|
-
trigger: "manual",
|
|
19404
|
-
model: synth.model
|
|
19405
|
-
});
|
|
19406
|
-
stats = getProjectAgentLearnStats(role, projectRoot);
|
|
19407
|
-
metadata = loadConsolidationMetadata(role, projectRoot);
|
|
19408
|
-
} catch (err) {
|
|
19409
|
-
return {
|
|
19410
|
-
type: "agent-roster.consolidate",
|
|
19411
|
-
payload: {
|
|
19412
|
-
role,
|
|
19413
|
-
consolidated: false,
|
|
19414
|
-
rawEntryCount: rawEntries.length,
|
|
19415
|
-
hasExistingConsolidation,
|
|
19416
|
-
currentStats: getProjectAgentLearnStats(role, projectRoot),
|
|
19417
|
-
error: err instanceof Error ? err.message : "failed to persist consolidation"
|
|
19418
|
-
}
|
|
19419
|
-
};
|
|
19420
|
-
}
|
|
19685
|
+
const llm = this.getLlm();
|
|
19686
|
+
const result = await optimizeProjectAgentLearning(role, projectRoot, {
|
|
19687
|
+
...llm ? { llm } : {},
|
|
19688
|
+
trigger: "manual"
|
|
19689
|
+
});
|
|
19690
|
+
const currentStats = getProjectAgentLearnStats(role, projectRoot);
|
|
19691
|
+
const metadata = loadConsolidationMetadata(role, projectRoot);
|
|
19692
|
+
const basePayload = {
|
|
19693
|
+
role,
|
|
19694
|
+
rawEntryCount: result.rawEntryCount,
|
|
19695
|
+
skills: result.skills,
|
|
19696
|
+
hasExistingConsolidation,
|
|
19697
|
+
currentStats
|
|
19698
|
+
};
|
|
19699
|
+
if (result.status === "optimized") {
|
|
19421
19700
|
try {
|
|
19422
19701
|
this.broadcast({
|
|
19423
19702
|
type: "agent-roster.updated",
|
|
19424
|
-
payload: { role, reason: "consolidated", currentStats
|
|
19703
|
+
payload: { role, reason: "consolidated", currentStats, metadata }
|
|
19425
19704
|
});
|
|
19426
19705
|
} catch (e) {
|
|
19427
19706
|
console.warn(
|
|
@@ -19437,44 +19716,111 @@ ${String(p.content ?? "")}`;
|
|
|
19437
19716
|
return {
|
|
19438
19717
|
type: "agent-roster.consolidate",
|
|
19439
19718
|
payload: {
|
|
19440
|
-
|
|
19719
|
+
...basePayload,
|
|
19441
19720
|
consolidated: true,
|
|
19442
|
-
|
|
19443
|
-
|
|
19444
|
-
|
|
19445
|
-
currentStats: stats,
|
|
19721
|
+
content: result.content,
|
|
19722
|
+
model: result.model,
|
|
19723
|
+
pruned: result.pruned,
|
|
19446
19724
|
metadata
|
|
19447
19725
|
}
|
|
19448
19726
|
};
|
|
19449
19727
|
}
|
|
19450
|
-
if (synth) {
|
|
19451
|
-
return {
|
|
19452
|
-
type: "agent-roster.consolidate",
|
|
19453
|
-
payload: {
|
|
19454
|
-
role,
|
|
19455
|
-
consolidated: false,
|
|
19456
|
-
emptySynthesis: true,
|
|
19457
|
-
model: synth.model,
|
|
19458
|
-
rawEntryCount: rawEntries.length,
|
|
19459
|
-
hasExistingConsolidation,
|
|
19460
|
-
currentStats: getProjectAgentLearnStats(role, projectRoot)
|
|
19461
|
-
}
|
|
19462
|
-
};
|
|
19463
|
-
}
|
|
19464
19728
|
return {
|
|
19465
19729
|
type: "agent-roster.consolidate",
|
|
19466
19730
|
payload: {
|
|
19467
|
-
|
|
19731
|
+
...basePayload,
|
|
19468
19732
|
consolidated: false,
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19733
|
+
...result.status === "empty-synthesis" ? { emptySynthesis: true, model: result.model } : {},
|
|
19734
|
+
...result.status === "failed" ? { error: result.error } : {},
|
|
19735
|
+
...result.status === "no-llm" ? {
|
|
19736
|
+
instruction: result.instruction,
|
|
19737
|
+
leaderInstruction: `Optimize what the "${role}" agent has learned. Read its raw learned entries, synthesize them into a single narrowly-scoped document preserving every fact, and save the result. The instruction text contains the full details and raw entries.`
|
|
19738
|
+
} : {}
|
|
19739
|
+
}
|
|
19740
|
+
};
|
|
19741
|
+
}
|
|
19742
|
+
// ── Automatic-optimization status ─────────────────────────────────
|
|
19743
|
+
// Read-only: says whether the background scheduler considers each role
|
|
19744
|
+
// eligible right now, and why not when it does not. Surfacing the reason
|
|
19745
|
+
// is what keeps "nothing happened" from looking like a broken feature.
|
|
19746
|
+
case "agent-roster.auto-optimize-status": {
|
|
19747
|
+
const policy = resolveAutoOptimizePolicy(this.getAutoOptimizeSettings?.() ?? void 0);
|
|
19748
|
+
const roles = role ? [role] : listProjectAgentRoles(projectRoot);
|
|
19749
|
+
return {
|
|
19750
|
+
type,
|
|
19751
|
+
payload: {
|
|
19752
|
+
policy,
|
|
19753
|
+
roles: roles.map((current2) => {
|
|
19754
|
+
try {
|
|
19755
|
+
const decision = evaluateAutoOptimize(current2, projectRoot, policy);
|
|
19756
|
+
return { role: current2, ...decision };
|
|
19757
|
+
} catch {
|
|
19758
|
+
return { role: current2, eligible: false, reason: "disabled" };
|
|
19759
|
+
}
|
|
19760
|
+
})
|
|
19761
|
+
}
|
|
19762
|
+
};
|
|
19763
|
+
}
|
|
19764
|
+
// ── Skill layer: what this project has developed for each role skill ──
|
|
19765
|
+
case "agent-roster.skills": {
|
|
19766
|
+
if (!role) return { type, payload: { error: "role required" } };
|
|
19767
|
+
const candidates = resolveRoleSkillCandidates(role, projectRoot);
|
|
19768
|
+
const developed = listProjectSkillAugmentations(role, projectRoot);
|
|
19769
|
+
const affinity = loadSkillAffinity(role, projectRoot);
|
|
19770
|
+
const eager = new Set(rankRoleSkills(role, candidates, projectRoot));
|
|
19771
|
+
return {
|
|
19772
|
+
type,
|
|
19773
|
+
payload: {
|
|
19774
|
+
role,
|
|
19775
|
+
eagerLimit: DEFAULT_EAGER_SKILL_LIMIT,
|
|
19776
|
+
skills: candidates.map((skill) => ({
|
|
19777
|
+
skill,
|
|
19778
|
+
developed: developed.includes(skill),
|
|
19779
|
+
affinity: affinity.entries[skill] ?? null,
|
|
19780
|
+
score: scoreSkillAffinity(affinity.entries[skill]) + (developed.includes(skill) ? 1 : 0),
|
|
19781
|
+
eager: eager.has(skill)
|
|
19782
|
+
}))
|
|
19475
19783
|
}
|
|
19476
19784
|
};
|
|
19477
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
|
+
}
|
|
19794
|
+
case "agent-roster.read-skill": {
|
|
19795
|
+
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
19796
|
+
if (!role || !skill) return { type, payload: { error: "role and skill required" } };
|
|
19797
|
+
return {
|
|
19798
|
+
type,
|
|
19799
|
+
payload: { role, skill, content: loadProjectSkillAugmentation(role, skill, projectRoot) }
|
|
19800
|
+
};
|
|
19801
|
+
}
|
|
19802
|
+
case "agent-roster.save-skill": {
|
|
19803
|
+
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
19804
|
+
if (!role || !skill || typeof p.content !== "string") {
|
|
19805
|
+
return { type, payload: { error: "role, skill and content required" } };
|
|
19806
|
+
}
|
|
19807
|
+
const savedPath = saveProjectSkillAugmentation(role, skill, p.content, projectRoot);
|
|
19808
|
+
return { type, payload: { role, skill, path: savedPath, success: true } };
|
|
19809
|
+
}
|
|
19810
|
+
case "agent-roster.clear-skill": {
|
|
19811
|
+
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
19812
|
+
if (!role) return { type, payload: { error: "role required" } };
|
|
19813
|
+
clearProjectSkillAugmentation(role, skill || void 0, projectRoot);
|
|
19814
|
+
return { type, payload: { role, skill: skill || null, success: true } };
|
|
19815
|
+
}
|
|
19816
|
+
case "agent-roster.pin-skill": {
|
|
19817
|
+
const skill = typeof p.skill === "string" ? p.skill : "";
|
|
19818
|
+
if (!role || !skill || typeof p.pinned !== "boolean") {
|
|
19819
|
+
return { type, payload: { error: "role, skill and boolean pinned required" } };
|
|
19820
|
+
}
|
|
19821
|
+
const affinity = setSkillPinned(role, skill, p.pinned, projectRoot);
|
|
19822
|
+
return { type, payload: { role, skill, pinned: p.pinned, affinity, success: true } };
|
|
19823
|
+
}
|
|
19478
19824
|
// ── Save consolidated document ────────────────────────────────────
|
|
19479
19825
|
case "agent-roster.save-consolidated": {
|
|
19480
19826
|
if (!role || typeof p.content !== "string") {
|
|
@@ -20447,7 +20793,20 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
20447
20793
|
},
|
|
20448
20794
|
send: send2,
|
|
20449
20795
|
broadcast: deps2.providerCtx.broadcast,
|
|
20450
|
-
replaceTodos: (todos) => opts.agent.ctx.state.replaceTodos(todos)
|
|
20796
|
+
replaceTodos: (todos) => opts.agent.ctx.state.replaceTodos(todos),
|
|
20797
|
+
mutateTodos: async (todos) => {
|
|
20798
|
+
const result = await todoTool.execute({ todos }, opts.agent.ctx, {
|
|
20799
|
+
signal: AbortSignal.timeout(3e4)
|
|
20800
|
+
});
|
|
20801
|
+
return {
|
|
20802
|
+
todos: [...opts.agent.ctx.todos],
|
|
20803
|
+
...result.kanban_warnings ? { warnings: result.kanban_warnings } : {}
|
|
20804
|
+
};
|
|
20805
|
+
},
|
|
20806
|
+
mutateTaskStatus: async (id, status) => taskTool.execute({ action: "status", id, status }, opts.agent.ctx, {
|
|
20807
|
+
signal: AbortSignal.timeout(3e4)
|
|
20808
|
+
}),
|
|
20809
|
+
mutatePlan: async (operation) => planTool.execute(operation, opts.agent.ctx, { signal: AbortSignal.timeout(3e4) })
|
|
20451
20810
|
})
|
|
20452
20811
|
});
|
|
20453
20812
|
const processRoutes = {
|
|
@@ -20531,6 +20890,7 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
20531
20890
|
agentRoster: {
|
|
20532
20891
|
rosterHandler: new AgentRosterWSHandler({
|
|
20533
20892
|
projectRoot,
|
|
20893
|
+
getAutoOptimizeSettings: () => deps2.agentConfigCtx.getConfig?.()?.fleet?.learning?.autoOptimize,
|
|
20534
20894
|
getLlm: () => {
|
|
20535
20895
|
const ctx = opts.agent.ctx;
|
|
20536
20896
|
return ctx.provider && ctx.model ? { provider: ctx.provider, model: ctx.model } : void 0;
|
|
@@ -21836,6 +22196,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
21836
22196
|
const logWatcherMetricsEnabled = shouldLogWatcherStats();
|
|
21837
22197
|
const logWatcherMetrics = () => logFileWatcherMetrics(watcherMetrics);
|
|
21838
22198
|
const metricsInterval = logWatcherMetricsEnabled ? setInterval(logWatcherMetrics, 6e4) : void 0;
|
|
22199
|
+
metricsInterval?.unref?.();
|
|
21839
22200
|
const broadcastStatus = (_projectHash, statusData, actualDelayMs) => {
|
|
21840
22201
|
broadcast2(clients, { type: "client.status_update", payload: statusData });
|
|
21841
22202
|
if (watcherMetrics) {
|
|
@@ -23055,14 +23416,7 @@ import {
|
|
|
23055
23416
|
toErrorMessage as toErrorMessage10
|
|
23056
23417
|
} from "@wrongstack/core/utils";
|
|
23057
23418
|
import { makeLightSubagentFactory } from "@wrongstack/runtime";
|
|
23058
|
-
import {
|
|
23059
|
-
createSageContextMonitorMiddleware,
|
|
23060
|
-
createSageToolCallMiddleware,
|
|
23061
|
-
createSageTurnMiddleware,
|
|
23062
|
-
getSageRetrieval,
|
|
23063
|
-
getSageService,
|
|
23064
|
-
InjectionTracker
|
|
23065
|
-
} from "@wrongstack/sage";
|
|
23419
|
+
import { getSageService, setupSage } from "@wrongstack/sage";
|
|
23066
23420
|
|
|
23067
23421
|
// src/server/discover-mailbox-bridge.ts
|
|
23068
23422
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -23865,6 +24219,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
23865
24219
|
this.broadcast(this.stateMessage());
|
|
23866
24220
|
if (this.broadcastInterval) return;
|
|
23867
24221
|
this.broadcastInterval = setInterval(() => this.broadcast(this.stateMessage()), 2e3);
|
|
24222
|
+
this.broadcastInterval.unref?.();
|
|
23868
24223
|
}
|
|
23869
24224
|
stopBroadcast() {
|
|
23870
24225
|
this.broadcast(this.stateMessage());
|
|
@@ -23916,62 +24271,15 @@ async function createAgentServices(input) {
|
|
|
23916
24271
|
const collabPause = collabPauseMiddleware(collabBus, { logger });
|
|
23917
24272
|
pipelines.toolCall.prepend(collabPause);
|
|
23918
24273
|
installDesignStudioMiddleware({ pipelines, ctx: context });
|
|
23919
|
-
const
|
|
23920
|
-
|
|
23921
|
-
|
|
23922
|
-
|
|
23923
|
-
|
|
23924
|
-
|
|
23925
|
-
|
|
23926
|
-
|
|
23927
|
-
|
|
23928
|
-
maxCharsPerTool: config.Sage?.inject?.maxCharsPerTool,
|
|
23929
|
-
taskAware: config.Sage?.inject?.taskAware,
|
|
23930
|
-
minScore: config.Sage?.inject?.minScore,
|
|
23931
|
-
minImportance: config.Sage?.inject?.minImportance,
|
|
23932
|
-
// Forward the explicit relation floor so an operator-configured
|
|
23933
|
-
// `Sage.inject.relationFloor` is honored in WebUI sessions. Without
|
|
23934
|
-
// this we silently fall back to MIN_RELATION_STRENGTH (0.85), which
|
|
23935
|
-
// is the CLI default but masks operator overrides.
|
|
23936
|
-
relationFloor: config.Sage?.inject?.relationFloor,
|
|
23937
|
-
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
23938
|
-
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
23939
|
-
triggers: config.Sage?.inject?.triggers,
|
|
23940
|
-
// Resolve the live session so retrieval and cooldown are session-scoped
|
|
23941
|
-
// (matching the CLI wiring in wiring/sage.ts). Without this, the
|
|
23942
|
-
// middleware falls back to ctx.session.id for cooldown but passes
|
|
23943
|
-
// undefined to retrieval, causing owned session-scoped memories to be
|
|
23944
|
-
// silently excluded from tool-call injection.
|
|
23945
|
-
getSessionId: getSageSessionId,
|
|
23946
|
-
tracker: sageInjectionTracker,
|
|
23947
|
-
events
|
|
23948
|
-
})
|
|
23949
|
-
);
|
|
23950
|
-
}
|
|
23951
|
-
if (config.Sage?.inject?.turnContext === true) {
|
|
23952
|
-
pipelines.request.use(
|
|
23953
|
-
createSageTurnMiddleware({
|
|
23954
|
-
memory: memoryRetrieval,
|
|
23955
|
-
maxMemories: config.Sage?.inject?.maxTurnMemories,
|
|
23956
|
-
maxChars: config.Sage?.inject?.maxCharsPerTurn,
|
|
23957
|
-
minScore: config.Sage?.inject?.minScore,
|
|
23958
|
-
// CLI parity: honor `Sage.retrieval.metadataWeight` so the same config
|
|
23959
|
-
// value drives both runtimes instead of silently falling back to the
|
|
23960
|
-
// 0.3 default. The undefined case keeps the middleware's own default.
|
|
23961
|
-
metadataWeight: config.Sage?.retrieval?.metadataWeight,
|
|
23962
|
-
getSessionId: getSageSessionId,
|
|
23963
|
-
tracker: sageInjectionTracker
|
|
23964
|
-
})
|
|
23965
|
-
);
|
|
23966
|
-
}
|
|
23967
|
-
pipelines.request.use(
|
|
23968
|
-
createSageContextMonitorMiddleware({
|
|
23969
|
-
tracker: sageInjectionTracker,
|
|
23970
|
-
events,
|
|
23971
|
-
getSessionId: getSageSessionId
|
|
23972
|
-
})
|
|
23973
|
-
);
|
|
23974
|
-
}
|
|
24274
|
+
const runSageSessionHygiene = setupSage({
|
|
24275
|
+
config,
|
|
24276
|
+
pipelines,
|
|
24277
|
+
memoryStore,
|
|
24278
|
+
logger,
|
|
24279
|
+
events,
|
|
24280
|
+
getSessionId: () => input.sessionGetter().id,
|
|
24281
|
+
projectRoot
|
|
24282
|
+
});
|
|
23975
24283
|
const codebaseIndexing = setupWebUICodebaseIndexing({
|
|
23976
24284
|
config,
|
|
23977
24285
|
context,
|
|
@@ -24083,7 +24391,12 @@ async function createAgentServices(input) {
|
|
|
24083
24391
|
confirmAwaiter: void 0,
|
|
24084
24392
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
24085
24393
|
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
24086
|
-
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
|
|
24087
24400
|
});
|
|
24088
24401
|
input.installToolBoundary?.(pipelines);
|
|
24089
24402
|
const webuiLogger = container.resolve(TOKENS2.Logger);
|
|
@@ -24103,6 +24416,7 @@ async function createAgentServices(input) {
|
|
|
24103
24416
|
providers: providerRegistry,
|
|
24104
24417
|
events,
|
|
24105
24418
|
pipelines,
|
|
24419
|
+
refreshSystemPrompt: true,
|
|
24106
24420
|
context,
|
|
24107
24421
|
maxIterations: config.tools?.maxIterations ?? DEFAULT_TOOLS_CONFIG.maxIterations,
|
|
24108
24422
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
@@ -24370,6 +24684,7 @@ async function createAgentServices(input) {
|
|
|
24370
24684
|
terminalHandler,
|
|
24371
24685
|
collabHandler,
|
|
24372
24686
|
disposeRealtimeHandlers,
|
|
24687
|
+
runSageSessionHygiene,
|
|
24373
24688
|
updateAutoCompactionMaxContext
|
|
24374
24689
|
};
|
|
24375
24690
|
}
|
|
@@ -24526,6 +24841,7 @@ async function setupWebUiGovernance(input, dependencies = DEFAULT_DEPENDENCIES)
|
|
|
24526
24841
|
|
|
24527
24842
|
// src/server/message-dispatcher.ts
|
|
24528
24843
|
import path28 from "node:path";
|
|
24844
|
+
import { planTool as planTool2, taskTool as taskTool2, todoTool as todoTool2 } from "@wrongstack/tools";
|
|
24529
24845
|
function createMessageDispatcher(opts) {
|
|
24530
24846
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
24531
24847
|
function makeWorklistContext() {
|
|
@@ -24537,7 +24853,20 @@ function createMessageDispatcher(opts) {
|
|
|
24537
24853
|
},
|
|
24538
24854
|
send: (w, m) => send(w, m),
|
|
24539
24855
|
broadcast: (m) => broadcast(state.getClients(), m),
|
|
24540
|
-
replaceTodos: (todos) => deps2.context.state.replaceTodos(todos)
|
|
24856
|
+
replaceTodos: (todos) => deps2.context.state.replaceTodos(todos),
|
|
24857
|
+
mutateTodos: async (todos) => {
|
|
24858
|
+
const result = await todoTool2.execute({ todos }, deps2.context, {
|
|
24859
|
+
signal: AbortSignal.timeout(3e4)
|
|
24860
|
+
});
|
|
24861
|
+
return {
|
|
24862
|
+
todos: [...deps2.context.todos],
|
|
24863
|
+
...result.kanban_warnings ? { warnings: result.kanban_warnings } : {}
|
|
24864
|
+
};
|
|
24865
|
+
},
|
|
24866
|
+
mutateTaskStatus: async (id, status) => taskTool2.execute({ action: "status", id, status }, deps2.context, {
|
|
24867
|
+
signal: AbortSignal.timeout(3e4)
|
|
24868
|
+
}),
|
|
24869
|
+
mutatePlan: async (operation) => planTool2.execute(operation, deps2.context, { signal: AbortSignal.timeout(3e4) })
|
|
24541
24870
|
};
|
|
24542
24871
|
}
|
|
24543
24872
|
function makeSkillsContext() {
|
|
@@ -24749,6 +25078,7 @@ function createMessageDispatcher(opts) {
|
|
|
24749
25078
|
agentRoster: {
|
|
24750
25079
|
rosterHandler: new AgentRosterWSHandler({
|
|
24751
25080
|
projectRoot: state.getProjectRoot,
|
|
25081
|
+
getAutoOptimizeSettings: () => state.getConfig().fleet?.learning?.autoOptimize,
|
|
24752
25082
|
getLlm: () => {
|
|
24753
25083
|
const ctx = deps2.agent.ctx;
|
|
24754
25084
|
return ctx.provider && ctx.model ? { provider: ctx.provider, model: ctx.model } : void 0;
|
|
@@ -24834,15 +25164,9 @@ import {
|
|
|
24834
25164
|
makeMailInboxTool,
|
|
24835
25165
|
makeMailSendTool
|
|
24836
25166
|
} from "@wrongstack/core/coordination";
|
|
24837
|
-
import {
|
|
24838
|
-
DefaultPromptLoader,
|
|
24839
|
-
DefaultSkillLoader
|
|
24840
|
-
} from "@wrongstack/core/execution";
|
|
24841
|
-
import {
|
|
24842
|
-
EventBus,
|
|
24843
|
-
TOKENS as TOKENS3
|
|
24844
|
-
} from "@wrongstack/core/kernel";
|
|
25167
|
+
import { DefaultPromptLoader, DefaultSkillLoader } from "@wrongstack/core/execution";
|
|
24845
25168
|
import { DefaultTokenCounter } from "@wrongstack/core/infrastructure";
|
|
25169
|
+
import { EventBus, TOKENS as TOKENS3 } from "@wrongstack/core/kernel";
|
|
24846
25170
|
import { DefaultModelsRegistry, DefaultModeStore } from "@wrongstack/core/models";
|
|
24847
25171
|
import { ProviderRegistry, ToolRegistry } from "@wrongstack/core/registry";
|
|
24848
25172
|
import { SkillInstaller } from "@wrongstack/core/skills";
|
|
@@ -25500,6 +25824,7 @@ async function createPreContextServices(input) {
|
|
|
25500
25824
|
modeId,
|
|
25501
25825
|
modePrompt,
|
|
25502
25826
|
modelCapabilities: () => modelCapabilitiesRef.current,
|
|
25827
|
+
tokenSavingMode: config.features.tokenSavingMode,
|
|
25503
25828
|
instructionPaths: {
|
|
25504
25829
|
globalDir: wpaths.globalInstructions,
|
|
25505
25830
|
projectDir: wpaths.inProjectInstructions,
|
|
@@ -25520,7 +25845,8 @@ async function createPreContextServices(input) {
|
|
|
25520
25845
|
const systemPrompt = await systemPromptBuilder.build({
|
|
25521
25846
|
cwd: projectRoot,
|
|
25522
25847
|
projectRoot,
|
|
25523
|
-
tools: toolRegistry.
|
|
25848
|
+
tools: toolRegistry.listForProvider(),
|
|
25849
|
+
catalogTools: toolRegistry.list(),
|
|
25524
25850
|
provider: config.provider,
|
|
25525
25851
|
model: config.model,
|
|
25526
25852
|
onlineAgents
|
|
@@ -25538,6 +25864,7 @@ async function createPreContextServices(input) {
|
|
|
25538
25864
|
projectRoot,
|
|
25539
25865
|
model: config.model
|
|
25540
25866
|
});
|
|
25867
|
+
context.meta["promptOnlineAgents"] = onlineAgents;
|
|
25541
25868
|
const initialContextPolicy = resolveContextWindowPolicy3(config.context);
|
|
25542
25869
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
25543
25870
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
@@ -25610,6 +25937,7 @@ function createModeHandlers(context) {
|
|
|
25610
25937
|
modeId: id,
|
|
25611
25938
|
modePrompt,
|
|
25612
25939
|
modelCapabilities: context.modelCapabilities,
|
|
25940
|
+
tokenSavingMode: config.features?.tokenSavingMode,
|
|
25613
25941
|
instructionPaths: {
|
|
25614
25942
|
globalDir: paths.globalInstructions,
|
|
25615
25943
|
projectDir: paths.inProjectInstructions,
|
|
@@ -25619,7 +25947,8 @@ function createModeHandlers(context) {
|
|
|
25619
25947
|
context.context.systemPrompt = await builder.build({
|
|
25620
25948
|
cwd: context.projectRoot,
|
|
25621
25949
|
projectRoot: context.projectRoot,
|
|
25622
|
-
tools: context.toolRegistry.
|
|
25950
|
+
tools: context.toolRegistry.listForProvider(),
|
|
25951
|
+
catalogTools: context.toolRegistry.list(),
|
|
25623
25952
|
provider: config.provider,
|
|
25624
25953
|
model: config.model
|
|
25625
25954
|
});
|
|
@@ -25959,11 +26288,11 @@ function buildRoutes(state, deps2, cb) {
|
|
|
25959
26288
|
}
|
|
25960
26289
|
|
|
25961
26290
|
// src/server/server-runtime.ts
|
|
25962
|
-
import * as path33 from "node:path";
|
|
25963
26291
|
import { createRequire as createRequire4 } from "node:module";
|
|
26292
|
+
import * as path33 from "node:path";
|
|
25964
26293
|
import { fileURLToPath } from "node:url";
|
|
25965
|
-
import { WebSocketServer } from "ws";
|
|
25966
26294
|
import { toErrorMessage as toErrorMessage13 } from "@wrongstack/core/utils";
|
|
26295
|
+
import { WebSocketServer } from "ws";
|
|
25967
26296
|
async function resolvePorts(opts) {
|
|
25968
26297
|
const surface = opts.surface ?? "webui";
|
|
25969
26298
|
const surfaceDefaults = surface === "simpleui" ? { http: 3466 } : { http: 3456 };
|
|
@@ -26894,15 +27223,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26894
27223
|
eternalSubscription = null;
|
|
26895
27224
|
}
|
|
26896
27225
|
codebaseIndexing.dispose();
|
|
26897
|
-
|
|
26898
|
-
const candidate = memoryStore;
|
|
26899
|
-
await candidate.hygiene?.({
|
|
26900
|
-
retentionDays: config.Sage?.hygiene?.retentionDays,
|
|
26901
|
-
archiveLowConfidenceAfterDays: config.Sage?.hygiene?.archiveLowConfidenceAfterDays
|
|
26902
|
-
}).catch(
|
|
26903
|
-
(err) => logger.warn(`sage session hygiene failed: ${toErrorMessage14(err)}`)
|
|
26904
|
-
);
|
|
26905
|
-
}
|
|
27226
|
+
await agentServices.runSageSessionHygiene().catch((err) => logger.warn(`sage session hygiene failed: ${toErrorMessage14(err)}`));
|
|
26906
27227
|
await memoryStore.dispose().catch(
|
|
26907
27228
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage14(err)}`)
|
|
26908
27229
|
);
|