@wrongstack/webui-server 0.301.0 → 0.302.2
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 +436 -187
- package/dist/server/connections-health-route.d.ts +2 -2
- package/dist/server/conversation-operations.d.ts +9 -4
- package/dist/server/embedded-host-adapters.d.ts +7 -5
- package/dist/server/embedded-lifecycle.d.ts +17 -2
- package/dist/server/entry.js +615 -143
- package/dist/server/index.d.ts +1 -1
- package/dist/server/instance-registry.d.ts +51 -2
- package/dist/server/kanban-routes.d.ts +8 -0
- package/dist/server/kanban-supervisor.d.ts +1 -1
- package/dist/server/lifecycle.d.ts +7 -0
- package/dist/server/message-dispatcher.d.ts +10 -0
- package/dist/server/server-runtime.d.ts +8 -1
- package/dist/server/session-handlers.d.ts +3 -2
- package/dist/server/standalone-session-identity.d.ts +10 -2
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -4409,7 +4409,8 @@ function createConversationOperations(ctx) {
|
|
|
4409
4409
|
userMessage: async (ws, msg) => {
|
|
4410
4410
|
if (!ensureCurrentSession(ws, msg, "user_message")) return;
|
|
4411
4411
|
const payload = msg.payload ?? {};
|
|
4412
|
-
const
|
|
4412
|
+
const originSessionId = ctx.getSessionId();
|
|
4413
|
+
const controller = ctx.runControl.begin(ws, originSessionId);
|
|
4413
4414
|
if (!controller) {
|
|
4414
4415
|
ctx.send(ws, {
|
|
4415
4416
|
type: "error",
|
|
@@ -4420,7 +4421,6 @@ function createConversationOperations(ctx) {
|
|
|
4420
4421
|
});
|
|
4421
4422
|
return;
|
|
4422
4423
|
}
|
|
4423
|
-
const originSessionId = ctx.getSessionId();
|
|
4424
4424
|
try {
|
|
4425
4425
|
const agent = ctx.getAgent();
|
|
4426
4426
|
if (payload.freshContext === true) await startFreshTopicContext(agent.ctx);
|
|
@@ -4479,12 +4479,13 @@ function createConversationOperations(ctx) {
|
|
|
4479
4479
|
});
|
|
4480
4480
|
}
|
|
4481
4481
|
} finally {
|
|
4482
|
-
ctx.runControl.end(ws, controller);
|
|
4482
|
+
ctx.runControl.end(ws, originSessionId, controller);
|
|
4483
4483
|
}
|
|
4484
4484
|
},
|
|
4485
4485
|
abort: (ws, msg) => {
|
|
4486
4486
|
if (!ensureCurrentSession(ws, msg, "abort")) return;
|
|
4487
|
-
ctx.
|
|
4487
|
+
const sessionId = requestedSessionId(msg) ?? ctx.getSessionId();
|
|
4488
|
+
ctx.runControl.abort(ws, sessionId);
|
|
4488
4489
|
ctx.notifyAbort(ws, {
|
|
4489
4490
|
type: "error",
|
|
4490
4491
|
payload: sessionPayload2({ phase: "abort", message: "User aborted" })
|
|
@@ -5536,6 +5537,7 @@ function createConnectionLifecycle(options) {
|
|
|
5536
5537
|
}
|
|
5537
5538
|
|
|
5538
5539
|
// src/server/connections-health-route.ts
|
|
5540
|
+
import * as net from "node:net";
|
|
5539
5541
|
import {
|
|
5540
5542
|
ChronicleProjectServerClient,
|
|
5541
5543
|
createChronicleProjectAccess as createChronicleProjectAccess2,
|
|
@@ -5545,13 +5547,22 @@ import {
|
|
|
5545
5547
|
isMailboxProjectServerAvailable,
|
|
5546
5548
|
MailboxProjectServerConnection
|
|
5547
5549
|
} from "@wrongstack/core/coordination";
|
|
5550
|
+
import { SessionCatalogProjectClient } from "@wrongstack/core/session-catalog";
|
|
5548
5551
|
import { resolveWstackPaths as resolveWstackPaths2 } from "@wrongstack/core/utils";
|
|
5549
5552
|
import {
|
|
5550
5553
|
closeKanbanServerConnections,
|
|
5551
5554
|
getKanbanServerConnection,
|
|
5552
5555
|
isKanbanServerAvailable
|
|
5553
5556
|
} from "@wrongstack/kanban";
|
|
5554
|
-
import
|
|
5557
|
+
import { readGovernanceDaemonOperatorStatus } from "@wrongstack/runtime/governance-bootstrap";
|
|
5558
|
+
import { isSageProjectServerAvailable, SageProjectServerConnection } from "@wrongstack/sage";
|
|
5559
|
+
import {
|
|
5560
|
+
checkCodebaseIndexServerHealth,
|
|
5561
|
+
ensureCodebaseIndexServer as ensureCodebaseIndexServer2,
|
|
5562
|
+
getIndexState,
|
|
5563
|
+
resolveProjectIndexDaemonAvailability,
|
|
5564
|
+
shutdownCodebaseIndexServer
|
|
5565
|
+
} from "@wrongstack/tools";
|
|
5555
5566
|
|
|
5556
5567
|
// src/server/privileged-actions.ts
|
|
5557
5568
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -5585,15 +5596,6 @@ async function authorizeWebUIAction(boundary, action, logger) {
|
|
|
5585
5596
|
}
|
|
5586
5597
|
|
|
5587
5598
|
// src/server/connections-health-route.ts
|
|
5588
|
-
import { readGovernanceDaemonOperatorStatus } from "@wrongstack/runtime/governance-bootstrap";
|
|
5589
|
-
import { isSageProjectServerAvailable, SageProjectServerConnection } from "@wrongstack/sage";
|
|
5590
|
-
import {
|
|
5591
|
-
checkCodebaseIndexServerHealth,
|
|
5592
|
-
ensureCodebaseIndexServer as ensureCodebaseIndexServer2,
|
|
5593
|
-
getIndexState,
|
|
5594
|
-
resolveProjectIndexDaemonAvailability,
|
|
5595
|
-
shutdownCodebaseIndexServer
|
|
5596
|
-
} from "@wrongstack/tools";
|
|
5597
5599
|
async function handleConnectionsHealthRoute(context, ws, message) {
|
|
5598
5600
|
if (message.type !== "connections.health") return false;
|
|
5599
5601
|
try {
|
|
@@ -5614,6 +5616,7 @@ async function handleConnectionsHealthRoute(context, ws, message) {
|
|
|
5614
5616
|
async function collectConnectionsHealth(options) {
|
|
5615
5617
|
const services = await Promise.all([
|
|
5616
5618
|
Promise.resolve(webuiHealth(options.backend)),
|
|
5619
|
+
sessionCatalogHealth(options.projectRoot),
|
|
5617
5620
|
chronicleHealth(options.projectRoot),
|
|
5618
5621
|
codebaseIndexHealth(options.projectRoot, options.indexDir),
|
|
5619
5622
|
sageHealth(options.projectRoot),
|
|
@@ -5631,6 +5634,50 @@ async function collectConnectionsHealth(options) {
|
|
|
5631
5634
|
services
|
|
5632
5635
|
};
|
|
5633
5636
|
}
|
|
5637
|
+
async function sessionCatalogHealth(projectRoot) {
|
|
5638
|
+
const startedAt = Date.now();
|
|
5639
|
+
try {
|
|
5640
|
+
const paths = resolveWstackPaths2({ projectRoot });
|
|
5641
|
+
const client = new SessionCatalogProjectClient({
|
|
5642
|
+
projectDir: paths.projectDir,
|
|
5643
|
+
projectRoot
|
|
5644
|
+
});
|
|
5645
|
+
try {
|
|
5646
|
+
const health = await client.ping();
|
|
5647
|
+
return {
|
|
5648
|
+
id: "session-catalog",
|
|
5649
|
+
label: "Session Catalog",
|
|
5650
|
+
status: health.damagedRows > 0 ? "degraded" : "healthy",
|
|
5651
|
+
required: true,
|
|
5652
|
+
mode: "project-daemon",
|
|
5653
|
+
detail: health.damagedRows > 0 ? `${health.damagedRows} damaged catalog row(s); rebuild is required.` : `${health.catalogRows} catalog session(s), ${health.liveLeases} live lease(s), ${health.reservations} reservation(s).`,
|
|
5654
|
+
ownerPid: health.pid,
|
|
5655
|
+
endpoint: health.endpoint,
|
|
5656
|
+
storage: health.databasePath,
|
|
5657
|
+
uptimeMs: health.uptimeMs,
|
|
5658
|
+
latencyMs: Date.now() - startedAt,
|
|
5659
|
+
clients: health.clients,
|
|
5660
|
+
activeRequests: health.activeRequests,
|
|
5661
|
+
queuedWork: health.reservations + health.maintenanceLeases,
|
|
5662
|
+
control: "none"
|
|
5663
|
+
};
|
|
5664
|
+
} finally {
|
|
5665
|
+
await client.close().catch(() => void 0);
|
|
5666
|
+
}
|
|
5667
|
+
} catch (error2) {
|
|
5668
|
+
return {
|
|
5669
|
+
id: "session-catalog",
|
|
5670
|
+
label: "Session Catalog",
|
|
5671
|
+
status: "error",
|
|
5672
|
+
required: true,
|
|
5673
|
+
mode: "project-daemon",
|
|
5674
|
+
detail: "Project-scoped session ownership and catalog are unavailable.",
|
|
5675
|
+
latencyMs: Date.now() - startedAt,
|
|
5676
|
+
lastError: error2 instanceof Error ? error2.message : String(error2),
|
|
5677
|
+
control: "none"
|
|
5678
|
+
};
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5634
5681
|
function webuiHealth(backend) {
|
|
5635
5682
|
return {
|
|
5636
5683
|
id: "webui",
|
|
@@ -6229,7 +6276,11 @@ async function restartSageServer(projectRoot) {
|
|
|
6229
6276
|
});
|
|
6230
6277
|
const verifyConn = new SageProjectServerConnection(projectRoot);
|
|
6231
6278
|
try {
|
|
6232
|
-
await verifyConn.call(
|
|
6279
|
+
await verifyConn.call(
|
|
6280
|
+
"ping",
|
|
6281
|
+
{},
|
|
6282
|
+
{ timeoutMs: 1e4, meta: { clientId: `sage-restart-${process.pid}` } }
|
|
6283
|
+
);
|
|
6233
6284
|
return {
|
|
6234
6285
|
serviceId: "sage",
|
|
6235
6286
|
action: "restart",
|
|
@@ -6463,28 +6514,28 @@ async function restartMailboxServer(projectRoot) {
|
|
|
6463
6514
|
var RESTART_POLL_INTERVAL_MS = 250;
|
|
6464
6515
|
var RESTART_DEADLINE_MS = 3e3;
|
|
6465
6516
|
function isEndpointAlive(endpoint) {
|
|
6466
|
-
return new Promise((
|
|
6517
|
+
return new Promise((resolve17) => {
|
|
6467
6518
|
const sock = net.createConnection(endpoint);
|
|
6468
6519
|
const timer = setTimeout(() => {
|
|
6469
6520
|
sock.destroy();
|
|
6470
|
-
|
|
6521
|
+
resolve17(false);
|
|
6471
6522
|
}, 500);
|
|
6472
6523
|
timer.unref?.();
|
|
6473
6524
|
sock.once("connect", () => {
|
|
6474
6525
|
clearTimeout(timer);
|
|
6475
6526
|
sock.destroy();
|
|
6476
|
-
|
|
6527
|
+
resolve17(true);
|
|
6477
6528
|
});
|
|
6478
6529
|
sock.once("error", () => {
|
|
6479
6530
|
clearTimeout(timer);
|
|
6480
6531
|
sock.destroy();
|
|
6481
|
-
|
|
6532
|
+
resolve17(false);
|
|
6482
6533
|
});
|
|
6483
6534
|
});
|
|
6484
6535
|
}
|
|
6485
6536
|
async function waitForShutdown(probe) {
|
|
6486
6537
|
if (!probe) {
|
|
6487
|
-
await new Promise((
|
|
6538
|
+
await new Promise((resolve17) => setTimeout(resolve17, RESTART_POLL_INTERVAL_MS));
|
|
6488
6539
|
return;
|
|
6489
6540
|
}
|
|
6490
6541
|
const deadline = Date.now() + RESTART_DEADLINE_MS;
|
|
@@ -6495,7 +6546,7 @@ async function waitForShutdown(probe) {
|
|
|
6495
6546
|
} catch {
|
|
6496
6547
|
return;
|
|
6497
6548
|
}
|
|
6498
|
-
await new Promise((
|
|
6549
|
+
await new Promise((resolve17) => setTimeout(resolve17, RESTART_POLL_INTERVAL_MS));
|
|
6499
6550
|
}
|
|
6500
6551
|
}
|
|
6501
6552
|
function failureService(id, label, required, mode, error2, latencyMs) {
|
|
@@ -6658,9 +6709,9 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
6658
6709
|
const cwd = projectRoot || void 0;
|
|
6659
6710
|
try {
|
|
6660
6711
|
const { execFile: ef } = await import("node:child_process");
|
|
6661
|
-
const git = (args) => new Promise((
|
|
6712
|
+
const git = (args) => new Promise((resolve17) => {
|
|
6662
6713
|
ef("git", args, { cwd, timeout: 3e3 }, (err, stdout) => {
|
|
6663
|
-
|
|
6714
|
+
resolve17(err ? "" : stdout.trim());
|
|
6664
6715
|
});
|
|
6665
6716
|
});
|
|
6666
6717
|
const [branchRaw, diffRaw, statusRaw, upstreamRaw] = await Promise.all([
|
|
@@ -6686,12 +6737,12 @@ async function handleGitInfo(ws, projectRoot) {
|
|
|
6686
6737
|
function makeGit(cwd) {
|
|
6687
6738
|
return async (args) => {
|
|
6688
6739
|
const { execFile: ef } = await import("node:child_process");
|
|
6689
|
-
return new Promise((
|
|
6740
|
+
return new Promise((resolve17) => {
|
|
6690
6741
|
ef(
|
|
6691
6742
|
"git",
|
|
6692
6743
|
args,
|
|
6693
6744
|
{ cwd, timeout: 5e3, maxBuffer: 1024 * 1024 * 16 },
|
|
6694
|
-
(err, stdout) =>
|
|
6745
|
+
(err, stdout) => resolve17(err ? "" : stdout)
|
|
6695
6746
|
);
|
|
6696
6747
|
});
|
|
6697
6748
|
};
|
|
@@ -6856,7 +6907,7 @@ import { execFile } from "node:child_process";
|
|
|
6856
6907
|
var GIT_TIMEOUT_MS = 1e4;
|
|
6857
6908
|
var GIT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
6858
6909
|
function gitStdout(cwd, args) {
|
|
6859
|
-
return new Promise((
|
|
6910
|
+
return new Promise((resolve17) => {
|
|
6860
6911
|
execFile(
|
|
6861
6912
|
"git",
|
|
6862
6913
|
[...args],
|
|
@@ -6867,7 +6918,7 @@ function gitStdout(cwd, args) {
|
|
|
6867
6918
|
timeout: GIT_TIMEOUT_MS,
|
|
6868
6919
|
maxBuffer: GIT_MAX_OUTPUT_BYTES
|
|
6869
6920
|
},
|
|
6870
|
-
(error2, stdout) =>
|
|
6921
|
+
(error2, stdout) => resolve17(error2 ? null : stdout)
|
|
6871
6922
|
);
|
|
6872
6923
|
});
|
|
6873
6924
|
}
|
|
@@ -7158,14 +7209,14 @@ var GoalWebSocketHandler = class {
|
|
|
7158
7209
|
const cwd = env?.cwd ?? this.projectRoot;
|
|
7159
7210
|
try {
|
|
7160
7211
|
const { execFile: execFile2 } = await import("node:child_process");
|
|
7161
|
-
const result = await new Promise((
|
|
7212
|
+
const result = await new Promise((resolve17) => {
|
|
7162
7213
|
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
7163
7214
|
execFile2(npxCommand, ["tsc", "--noEmit"], { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
|
|
7164
7215
|
if (err && err.code === "ENOENT") {
|
|
7165
|
-
|
|
7216
|
+
resolve17("[verify] tsc not found \u2014 skipping");
|
|
7166
7217
|
return;
|
|
7167
7218
|
}
|
|
7168
|
-
|
|
7219
|
+
resolve17(stdout + stderr);
|
|
7169
7220
|
});
|
|
7170
7221
|
});
|
|
7171
7222
|
if (result.includes("[verify]") || result.trim().length === 0) {
|
|
@@ -7903,7 +7954,7 @@ function pushEvent(event) {
|
|
|
7903
7954
|
}
|
|
7904
7955
|
}
|
|
7905
7956
|
function parseBody(req) {
|
|
7906
|
-
return new Promise((
|
|
7957
|
+
return new Promise((resolve17, reject) => {
|
|
7907
7958
|
let body = "";
|
|
7908
7959
|
let bodyBytes = 0;
|
|
7909
7960
|
let tooLarge = false;
|
|
@@ -7923,7 +7974,7 @@ function parseBody(req) {
|
|
|
7923
7974
|
return;
|
|
7924
7975
|
}
|
|
7925
7976
|
try {
|
|
7926
|
-
|
|
7977
|
+
resolve17(JSON.parse(body));
|
|
7927
7978
|
} catch {
|
|
7928
7979
|
reject(new Error("Invalid JSON"));
|
|
7929
7980
|
}
|
|
@@ -8018,7 +8069,7 @@ import * as path10 from "node:path";
|
|
|
8018
8069
|
import { runDeadCodeScan } from "@wrongstack/tools/codebase-index";
|
|
8019
8070
|
var MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
8020
8071
|
function readJsonBody(req) {
|
|
8021
|
-
return new Promise((
|
|
8072
|
+
return new Promise((resolve17, reject) => {
|
|
8022
8073
|
const chunks = [];
|
|
8023
8074
|
let total = 0;
|
|
8024
8075
|
req.on("data", (chunk) => {
|
|
@@ -8030,7 +8081,7 @@ function readJsonBody(req) {
|
|
|
8030
8081
|
}
|
|
8031
8082
|
chunks.push(chunk);
|
|
8032
8083
|
});
|
|
8033
|
-
req.on("end", () =>
|
|
8084
|
+
req.on("end", () => resolve17(Buffer.concat(chunks).toString("utf8")));
|
|
8034
8085
|
req.on("error", (err) => reject(err));
|
|
8035
8086
|
});
|
|
8036
8087
|
}
|
|
@@ -8248,8 +8299,8 @@ async function handleApiSessions(res, globalRoot) {
|
|
|
8248
8299
|
return;
|
|
8249
8300
|
}
|
|
8250
8301
|
try {
|
|
8251
|
-
const {
|
|
8252
|
-
const registry =
|
|
8302
|
+
const { getSessionRegistry: getSessionRegistry3 } = await import("@wrongstack/core/storage");
|
|
8303
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
8253
8304
|
const sessions = await registry.list();
|
|
8254
8305
|
const result = sessions.map((s) => ({
|
|
8255
8306
|
sessionId: s.sessionId,
|
|
@@ -8286,8 +8337,8 @@ async function handleApiSessionAgents(res, globalRoot, sessionId) {
|
|
|
8286
8337
|
return;
|
|
8287
8338
|
}
|
|
8288
8339
|
try {
|
|
8289
|
-
const {
|
|
8290
|
-
const registry =
|
|
8340
|
+
const { getSessionRegistry: getSessionRegistry3 } = await import("@wrongstack/core/storage");
|
|
8341
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
8291
8342
|
const entry = await registry.get(sessionId);
|
|
8292
8343
|
if (!entry) {
|
|
8293
8344
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
@@ -8295,20 +8346,22 @@ async function handleApiSessionAgents(res, globalRoot, sessionId) {
|
|
|
8295
8346
|
return;
|
|
8296
8347
|
}
|
|
8297
8348
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
8298
|
-
res.end(
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8349
|
+
res.end(
|
|
8350
|
+
JSON.stringify({
|
|
8351
|
+
sessionId: entry.sessionId,
|
|
8352
|
+
projectName: entry.projectName,
|
|
8353
|
+
status: entry.status,
|
|
8354
|
+
agents: entry.agents.map((a) => ({
|
|
8355
|
+
id: a.id,
|
|
8356
|
+
name: a.name,
|
|
8357
|
+
status: a.status,
|
|
8358
|
+
currentTool: a.currentTool,
|
|
8359
|
+
iterations: a.iterations,
|
|
8360
|
+
toolCalls: a.toolCalls,
|
|
8361
|
+
lastActivityAt: a.lastActivityAt
|
|
8362
|
+
}))
|
|
8363
|
+
})
|
|
8364
|
+
);
|
|
8312
8365
|
} catch (err) {
|
|
8313
8366
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
8314
8367
|
res.end(JSON.stringify({ error: sanitizeApiError(err) }));
|
|
@@ -8426,9 +8479,9 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
8426
8479
|
return;
|
|
8427
8480
|
}
|
|
8428
8481
|
try {
|
|
8429
|
-
const {
|
|
8482
|
+
const { getSessionRegistry: getSessionRegistry3, DefaultSessionStore: DefaultSessionStore4, DefaultSessionReader: DefaultSessionReader2 } = await import("@wrongstack/core/storage");
|
|
8430
8483
|
const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
|
|
8431
|
-
const registry =
|
|
8484
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
8432
8485
|
const entry = await registry.get(sessionId);
|
|
8433
8486
|
if (!entry) {
|
|
8434
8487
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
@@ -8436,7 +8489,10 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
8436
8489
|
return;
|
|
8437
8490
|
}
|
|
8438
8491
|
const paths = resolveWstackPaths7({ projectRoot: entry.projectRoot, globalRoot });
|
|
8439
|
-
const store = new DefaultSessionStore4({
|
|
8492
|
+
const store = new DefaultSessionStore4({
|
|
8493
|
+
dir: paths.projectSessions,
|
|
8494
|
+
projectRoot: entry.projectRoot
|
|
8495
|
+
});
|
|
8440
8496
|
const reader = new DefaultSessionReader2({ store });
|
|
8441
8497
|
const RING = Math.max(limit * 4, 2e3);
|
|
8442
8498
|
const ring = [];
|
|
@@ -8476,7 +8532,7 @@ async function handleApiSessionEvents(res, globalRoot, sessionId, limit) {
|
|
|
8476
8532
|
}
|
|
8477
8533
|
}
|
|
8478
8534
|
function readJsonBody2(req) {
|
|
8479
|
-
return new Promise((
|
|
8535
|
+
return new Promise((resolve17, reject) => {
|
|
8480
8536
|
const contentType = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
|
|
8481
8537
|
if (contentType !== "application/json") {
|
|
8482
8538
|
reject(new Error(`Unsupported Content-Type: ${contentType || "(absent)"}`));
|
|
@@ -8492,7 +8548,7 @@ function readJsonBody2(req) {
|
|
|
8492
8548
|
});
|
|
8493
8549
|
req.on("end", () => {
|
|
8494
8550
|
try {
|
|
8495
|
-
|
|
8551
|
+
resolve17(data ? JSON.parse(data) : {});
|
|
8496
8552
|
} catch (err) {
|
|
8497
8553
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
8498
8554
|
}
|
|
@@ -8528,10 +8584,10 @@ async function handleApiSessionMessage(res, req, globalRoot, sessionId) {
|
|
|
8528
8584
|
const priority = ["low", "normal", "high"].includes(rawPriority) ? rawPriority : "high";
|
|
8529
8585
|
const subject = typeof body["subject"] === "string" && body["subject"].trim() ? body["subject"].trim() : "Message from Fleet HQ";
|
|
8530
8586
|
try {
|
|
8531
|
-
const {
|
|
8587
|
+
const { getSessionRegistry: getSessionRegistry3 } = await import("@wrongstack/core/storage");
|
|
8532
8588
|
const { getSharedProjectMailbox: getSharedProjectMailbox6, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
|
|
8533
8589
|
const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
|
|
8534
|
-
const registry =
|
|
8590
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
8535
8591
|
const entry = await registry.get(sessionId);
|
|
8536
8592
|
if (!entry) {
|
|
8537
8593
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
@@ -8556,10 +8612,10 @@ async function handleApiSessionMailbox(res, globalRoot, sessionId) {
|
|
|
8556
8612
|
return;
|
|
8557
8613
|
}
|
|
8558
8614
|
try {
|
|
8559
|
-
const {
|
|
8615
|
+
const { getSessionRegistry: getSessionRegistry3 } = await import("@wrongstack/core/storage");
|
|
8560
8616
|
const { getSharedProjectMailbox: getSharedProjectMailbox6, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
|
|
8561
8617
|
const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
|
|
8562
|
-
const registry =
|
|
8618
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
8563
8619
|
const entry = await registry.get(sessionId);
|
|
8564
8620
|
if (!entry) {
|
|
8565
8621
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
@@ -8616,10 +8672,10 @@ async function handleApiSessionInterrupt(res, req, globalRoot, sessionId) {
|
|
|
8616
8672
|
const reason = typeof body["reason"] === "string" && body["reason"].trim() ? body["reason"].trim() : "Operator requested stop from Fleet HQ";
|
|
8617
8673
|
const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
|
|
8618
8674
|
try {
|
|
8619
|
-
const {
|
|
8675
|
+
const { getSessionRegistry: getSessionRegistry3 } = await import("@wrongstack/core/storage");
|
|
8620
8676
|
const { getSharedProjectMailbox: getSharedProjectMailbox6, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
|
|
8621
8677
|
const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
|
|
8622
|
-
const registry =
|
|
8678
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
8623
8679
|
const entry = await registry.get(sessionId);
|
|
8624
8680
|
if (!entry) {
|
|
8625
8681
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
@@ -8665,10 +8721,10 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
8665
8721
|
}
|
|
8666
8722
|
const from = typeof body["from"] === "string" && body["from"].trim() ? body["from"].trim() : "human@webui";
|
|
8667
8723
|
try {
|
|
8668
|
-
const {
|
|
8724
|
+
const { getSessionRegistry: getSessionRegistry3 } = await import("@wrongstack/core/storage");
|
|
8669
8725
|
const { getSharedProjectMailbox: getSharedProjectMailbox6, mailboxSessionTag: mailboxSessionTag2 } = await import("@wrongstack/core/coordination");
|
|
8670
8726
|
const { resolveWstackPaths: resolveWstackPaths7 } = await import("@wrongstack/core/utils");
|
|
8671
|
-
const registry =
|
|
8727
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
8672
8728
|
const all = await registry.list();
|
|
8673
8729
|
const mySlug = all.find((s) => s.pid === process.pid)?.projectSlug;
|
|
8674
8730
|
const targets = all.filter((s) => s.status !== "stale").filter((s) => mySlug ? s.projectSlug === mySlug : true);
|
|
@@ -8768,14 +8824,14 @@ async function readJsonBody3(res, req) {
|
|
|
8768
8824
|
});
|
|
8769
8825
|
return null;
|
|
8770
8826
|
}
|
|
8771
|
-
return new Promise((
|
|
8827
|
+
return new Promise((resolve17) => {
|
|
8772
8828
|
let data = "";
|
|
8773
8829
|
let failed = false;
|
|
8774
8830
|
const fail2 = (message) => {
|
|
8775
8831
|
if (failed) return;
|
|
8776
8832
|
failed = true;
|
|
8777
8833
|
sendJson2(res, 400, { error: { code: "INVALID_BODY", message } });
|
|
8778
|
-
|
|
8834
|
+
resolve17(null);
|
|
8779
8835
|
};
|
|
8780
8836
|
req.on("data", (chunk) => {
|
|
8781
8837
|
if (failed) return;
|
|
@@ -8788,7 +8844,7 @@ async function readJsonBody3(res, req) {
|
|
|
8788
8844
|
req.on("end", () => {
|
|
8789
8845
|
if (failed) return;
|
|
8790
8846
|
try {
|
|
8791
|
-
|
|
8847
|
+
resolve17(data.trim().length === 0 ? {} : JSON.parse(data));
|
|
8792
8848
|
} catch {
|
|
8793
8849
|
fail2("Request body is not valid JSON");
|
|
8794
8850
|
}
|
|
@@ -9627,7 +9683,7 @@ function strictDecodeParam(segment, res) {
|
|
|
9627
9683
|
function createHttpServer(opts) {
|
|
9628
9684
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
9629
9685
|
const distDir = path13.resolve(opts.distDir);
|
|
9630
|
-
const requireAccessToken =
|
|
9686
|
+
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
9631
9687
|
const secureCookies = opts.secureCookies ?? (opts.publicWsUrl?.trim().toLowerCase().startsWith("wss:") ?? false);
|
|
9632
9688
|
const trustedHostnames = (() => {
|
|
9633
9689
|
const names = [...opts.allowedHostnames ?? []];
|
|
@@ -10173,10 +10229,78 @@ function createHttpServer(opts) {
|
|
|
10173
10229
|
}
|
|
10174
10230
|
|
|
10175
10231
|
// src/server/instance-registry.ts
|
|
10232
|
+
import * as fs11 from "node:fs/promises";
|
|
10176
10233
|
import * as os from "node:os";
|
|
10177
10234
|
import * as path14 from "node:path";
|
|
10178
|
-
import * as fs11 from "node:fs/promises";
|
|
10179
10235
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
10236
|
+
function normalizeRoot(root) {
|
|
10237
|
+
const resolved = path14.resolve(root);
|
|
10238
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
10239
|
+
}
|
|
10240
|
+
function isLiveSessionStatus(status) {
|
|
10241
|
+
return status === "active" || status === "idle";
|
|
10242
|
+
}
|
|
10243
|
+
function instanceRole(instance) {
|
|
10244
|
+
return instance.role ?? "standalone";
|
|
10245
|
+
}
|
|
10246
|
+
function resolveAttachability(input) {
|
|
10247
|
+
const { session, instance } = input;
|
|
10248
|
+
if (!isLiveSessionStatus(session.status)) {
|
|
10249
|
+
return { attachable: false, degradedReason: "session-not-live" };
|
|
10250
|
+
}
|
|
10251
|
+
if (!instance) {
|
|
10252
|
+
return { attachable: false, degradedReason: "live-session-no-webui-endpoint" };
|
|
10253
|
+
}
|
|
10254
|
+
if (instance.pid !== session.pid) {
|
|
10255
|
+
return { attachable: false, degradedReason: "endpoint-owner-mismatch" };
|
|
10256
|
+
}
|
|
10257
|
+
if (!instance.sessionId) {
|
|
10258
|
+
return { attachable: false, instance, degradedReason: "endpoint-missing-session-id" };
|
|
10259
|
+
}
|
|
10260
|
+
if (instance.sessionId !== session.sessionId) {
|
|
10261
|
+
return { attachable: false, instance, degradedReason: "endpoint-session-mismatch" };
|
|
10262
|
+
}
|
|
10263
|
+
if (instanceRole(instance) !== "session-child") {
|
|
10264
|
+
return { attachable: false, instance, degradedReason: "endpoint-not-session-child" };
|
|
10265
|
+
}
|
|
10266
|
+
if (instance.attachable === false) {
|
|
10267
|
+
return { attachable: false, instance, degradedReason: "endpoint-not-attachable" };
|
|
10268
|
+
}
|
|
10269
|
+
return {
|
|
10270
|
+
attachable: true,
|
|
10271
|
+
endpoint: {
|
|
10272
|
+
host: instance.host,
|
|
10273
|
+
httpPort: instance.httpPort,
|
|
10274
|
+
url: instance.url,
|
|
10275
|
+
...instance.authToken ? { authToken: instance.authToken } : {}
|
|
10276
|
+
}
|
|
10277
|
+
};
|
|
10278
|
+
}
|
|
10279
|
+
function joinSessionRegistryWithWebUIInstances(input) {
|
|
10280
|
+
const targetRoot = input.projectRoot ? normalizeRoot(input.projectRoot) : void 0;
|
|
10281
|
+
const sessions = input.sessions.filter((session) => {
|
|
10282
|
+
if (input.projectSlug && session.projectSlug !== input.projectSlug) return false;
|
|
10283
|
+
if (targetRoot && normalizeRoot(session.projectRoot) !== targetRoot) return false;
|
|
10284
|
+
return true;
|
|
10285
|
+
});
|
|
10286
|
+
return sessions.map((session) => {
|
|
10287
|
+
const instance = input.instances.find((candidate) => candidate.sessionId === session.sessionId) ?? input.instances.find(
|
|
10288
|
+
(candidate) => candidate.pid === session.pid && normalizeRoot(candidate.projectRoot) === normalizeRoot(session.projectRoot)
|
|
10289
|
+
);
|
|
10290
|
+
const resolved = resolveAttachability({ session, instance });
|
|
10291
|
+
return {
|
|
10292
|
+
sessionId: session.sessionId,
|
|
10293
|
+
projectRoot: session.projectRoot,
|
|
10294
|
+
workingDir: session.workingDir,
|
|
10295
|
+
sessionPid: session.pid,
|
|
10296
|
+
status: session.status,
|
|
10297
|
+
...instance ? { instance } : {},
|
|
10298
|
+
...resolved.endpoint ? { endpoint: resolved.endpoint } : {},
|
|
10299
|
+
attachable: resolved.attachable,
|
|
10300
|
+
...resolved.degradedReason ? { degradedReason: resolved.degradedReason } : {}
|
|
10301
|
+
};
|
|
10302
|
+
});
|
|
10303
|
+
}
|
|
10180
10304
|
function defaultBaseDir() {
|
|
10181
10305
|
return path14.join(os.homedir(), ".wrongstack");
|
|
10182
10306
|
}
|
|
@@ -11236,6 +11360,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11236
11360
|
fail(ws, type, `Board not found: ${boardId}`);
|
|
11237
11361
|
return true;
|
|
11238
11362
|
}
|
|
11363
|
+
if (ctx.supervisor) {
|
|
11364
|
+
const snapshots = await ctx.supervisor.auditNow(boardId);
|
|
11365
|
+
const snapshot = snapshots[0];
|
|
11366
|
+
if (snapshot) {
|
|
11367
|
+
ok(ws, type, snapshot);
|
|
11368
|
+
return true;
|
|
11369
|
+
}
|
|
11370
|
+
}
|
|
11239
11371
|
const reconciled = await reconcileKanbanBoard(ctx.projectRoot, boardId);
|
|
11240
11372
|
let health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
11241
11373
|
const recovered = health.staleAssignments.count ? await recoverStaleTaskAssignments(ctx.projectRoot, boardId, {
|
|
@@ -12000,6 +12132,15 @@ function createShutdown(res) {
|
|
|
12000
12132
|
} catch {
|
|
12001
12133
|
}
|
|
12002
12134
|
}
|
|
12135
|
+
if (res.onPreShutdown) {
|
|
12136
|
+
try {
|
|
12137
|
+
await res.onPreShutdown();
|
|
12138
|
+
} catch (e) {
|
|
12139
|
+
log(
|
|
12140
|
+
`[WebUI] Error during pre-shutdown cleanup: ${e instanceof Error ? e.message : String(e)}`
|
|
12141
|
+
);
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
12003
12144
|
for (const server of res.servers) server?.close();
|
|
12004
12145
|
if (res.onShutdown) {
|
|
12005
12146
|
try {
|
|
@@ -13723,16 +13864,16 @@ function getSurfaceDefaultPorts(surface) {
|
|
|
13723
13864
|
return { http: SURFACE_DEFAULT_PORTS[surface].http };
|
|
13724
13865
|
}
|
|
13725
13866
|
function isPortFree(host, port) {
|
|
13726
|
-
return new Promise((
|
|
13867
|
+
return new Promise((resolve17) => {
|
|
13727
13868
|
const srv = net2.createServer();
|
|
13728
|
-
srv.once("error", () =>
|
|
13869
|
+
srv.once("error", () => resolve17(false));
|
|
13729
13870
|
srv.once("listening", () => {
|
|
13730
|
-
srv.close(() =>
|
|
13871
|
+
srv.close(() => resolve17(true));
|
|
13731
13872
|
});
|
|
13732
13873
|
try {
|
|
13733
13874
|
srv.listen(port, host);
|
|
13734
13875
|
} catch {
|
|
13735
|
-
|
|
13876
|
+
resolve17(false);
|
|
13736
13877
|
}
|
|
13737
13878
|
});
|
|
13738
13879
|
}
|
|
@@ -13888,7 +14029,7 @@ async function startStaticServe(opts, deps2 = {}) {
|
|
|
13888
14029
|
return { server, port: opts.httpPort };
|
|
13889
14030
|
}
|
|
13890
14031
|
function runPnpmBuild(cwd, workspace, timeoutMs) {
|
|
13891
|
-
return new Promise((
|
|
14032
|
+
return new Promise((resolve17, reject) => {
|
|
13892
14033
|
const child = spawn2("pnpm", ["--filter", workspace, "build"], {
|
|
13893
14034
|
cwd,
|
|
13894
14035
|
shell: process.platform === "win32",
|
|
@@ -13906,7 +14047,7 @@ function runPnpmBuild(cwd, workspace, timeoutMs) {
|
|
|
13906
14047
|
});
|
|
13907
14048
|
child.once("close", (code) => {
|
|
13908
14049
|
clearTimeout(timer);
|
|
13909
|
-
if (code === 0)
|
|
14050
|
+
if (code === 0) resolve17();
|
|
13910
14051
|
else reject(new Error(`pnpm build exited with code ${String(code)}`));
|
|
13911
14052
|
});
|
|
13912
14053
|
});
|
|
@@ -13970,27 +14111,41 @@ function formatExternalAccessUrls(opts) {
|
|
|
13970
14111
|
}
|
|
13971
14112
|
|
|
13972
14113
|
// src/server/embedded-lifecycle.ts
|
|
13973
|
-
function registerWebuiInstance(p, deps2 = {}) {
|
|
14114
|
+
async function registerWebuiInstance(p, deps2 = {}) {
|
|
13974
14115
|
const register = deps2.registerFn ?? registerInstance;
|
|
13975
|
-
|
|
13976
|
-
|
|
13977
|
-
|
|
13978
|
-
|
|
13979
|
-
|
|
13980
|
-
|
|
13981
|
-
projectRoot: p.projectRoot,
|
|
13982
|
-
projectName: path16.basename(p.projectRoot) || p.projectRoot,
|
|
13983
|
-
startedAt: p.startedAt,
|
|
13984
|
-
url: buildWebUIAccessUrl({
|
|
14116
|
+
try {
|
|
14117
|
+
await register(
|
|
14118
|
+
{
|
|
14119
|
+
pid: p.pid,
|
|
14120
|
+
surface: p.surface,
|
|
14121
|
+
httpPort: p.httpPort,
|
|
13985
14122
|
host: p.host,
|
|
13986
|
-
|
|
13987
|
-
|
|
13988
|
-
|
|
13989
|
-
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
14123
|
+
projectRoot: p.projectRoot,
|
|
14124
|
+
projectName: path16.basename(p.projectRoot) || p.projectRoot,
|
|
14125
|
+
startedAt: p.startedAt,
|
|
14126
|
+
url: buildWebUIAccessUrl({
|
|
14127
|
+
host: p.host,
|
|
14128
|
+
port: p.httpPort,
|
|
14129
|
+
publicUrl: p.publicUrl
|
|
14130
|
+
}),
|
|
14131
|
+
...p.authToken ? { authToken: p.authToken } : {},
|
|
14132
|
+
...p.role ? { role: p.role } : {},
|
|
14133
|
+
...p.sessionId ? { sessionId: p.sessionId } : {},
|
|
14134
|
+
...p.parentPid !== void 0 ? { parentPid: p.parentPid } : {},
|
|
14135
|
+
...p.parentShellId ? { parentShellId: p.parentShellId } : {},
|
|
14136
|
+
...p.runtimeId ? { runtimeId: p.runtimeId } : {},
|
|
14137
|
+
...p.attachable !== void 0 ? { attachable: p.attachable } : {},
|
|
14138
|
+
...p.authToken ? { auth: { scheme: "registry-token", tokenPresent: true } } : {},
|
|
14139
|
+
...p.lastReadyAt ? { lastReadyAt: p.lastReadyAt } : {},
|
|
14140
|
+
...p.protocolVersion !== void 0 ? { protocolVersion: p.protocolVersion } : {},
|
|
14141
|
+
...p.capabilities ? { capabilities: p.capabilities } : {}
|
|
14142
|
+
},
|
|
14143
|
+
p.registryBaseDir
|
|
14144
|
+
);
|
|
14145
|
+
return true;
|
|
14146
|
+
} catch {
|
|
14147
|
+
return false;
|
|
14148
|
+
}
|
|
13994
14149
|
}
|
|
13995
14150
|
function announceWebuiReady(p) {
|
|
13996
14151
|
const log = p.log ?? ((m) => console.log(m));
|
|
@@ -14028,10 +14183,10 @@ async function runBounded(work, timeoutMs, label, debug) {
|
|
|
14028
14183
|
Promise.resolve().then(() => work()).catch((err) => {
|
|
14029
14184
|
debug(`[webui-server] ${label} failed: ${err}`);
|
|
14030
14185
|
}),
|
|
14031
|
-
new Promise((
|
|
14186
|
+
new Promise((resolve17) => {
|
|
14032
14187
|
timer = setTimeout(() => {
|
|
14033
14188
|
debug(`[webui-server] ${label} timed out after ${timeoutMs}ms`);
|
|
14034
|
-
|
|
14189
|
+
resolve17();
|
|
14035
14190
|
}, timeoutMs);
|
|
14036
14191
|
timer.unref?.();
|
|
14037
14192
|
})
|
|
@@ -14064,8 +14219,8 @@ function createWebuiShutdown(res) {
|
|
|
14064
14219
|
const unregistered = unregister(res.pid, res.registryBaseDir).catch(
|
|
14065
14220
|
(err) => debug(`[webui-server] unregister failed: ${err}`)
|
|
14066
14221
|
);
|
|
14067
|
-
await new Promise((
|
|
14068
|
-
res.wss.close(() =>
|
|
14222
|
+
await new Promise((resolve17) => {
|
|
14223
|
+
res.wss.close(() => resolve17());
|
|
14069
14224
|
});
|
|
14070
14225
|
await unregistered;
|
|
14071
14226
|
log("[WebUI] Server stopped");
|
|
@@ -14874,6 +15029,10 @@ import {
|
|
|
14874
15029
|
recoverStaleTaskAssignments as recoverStaleTaskAssignments2,
|
|
14875
15030
|
resolveGateEnforcement
|
|
14876
15031
|
} from "@wrongstack/kanban";
|
|
15032
|
+
function resolveProjectRoot(deps2) {
|
|
15033
|
+
const root = deps2.projectRoot;
|
|
15034
|
+
return typeof root === "function" ? root() : root;
|
|
15035
|
+
}
|
|
14877
15036
|
var DEFAULT_INTERVAL_MS = 1e4;
|
|
14878
15037
|
var MIN_INTERVAL_MS = 2e3;
|
|
14879
15038
|
var DEFAULT_AGENT_COOLDOWN_MS = 5 * 6e4;
|
|
@@ -14938,14 +15097,14 @@ function createKanbanSupervisor(deps2) {
|
|
|
14938
15097
|
publish(snapshot2);
|
|
14939
15098
|
return snapshot2;
|
|
14940
15099
|
}
|
|
14941
|
-
const reconciled = await reconcileKanbanBoard2(deps2
|
|
15100
|
+
const reconciled = await reconcileKanbanBoard2(resolveProjectRoot(deps2), board.id);
|
|
14942
15101
|
const gateSwept = await sweepGateParkedTasks(deps2, reconciled?.board ?? board);
|
|
14943
|
-
let health = await getKanbanQueueHealth2(deps2
|
|
14944
|
-
const recovered = health.staleAssignments.count ? await recoverStaleTaskAssignments2(deps2
|
|
15102
|
+
let health = await getKanbanQueueHealth2(resolveProjectRoot(deps2), { boardId: board.id });
|
|
15103
|
+
const recovered = health.staleAssignments.count ? await recoverStaleTaskAssignments2(resolveProjectRoot(deps2), board.id, {
|
|
14945
15104
|
mode: config.recoveryMode ?? "auto",
|
|
14946
15105
|
reason: "Kanban supervisor found an expired worker lease."
|
|
14947
15106
|
}) : null;
|
|
14948
|
-
if (recovered) health = await getKanbanQueueHealth2(deps2
|
|
15107
|
+
if (recovered) health = await getKanbanQueueHealth2(resolveProjectRoot(deps2), { boardId: board.id });
|
|
14949
15108
|
const anomalyCount = countAnomalies(health);
|
|
14950
15109
|
const snapshot = {
|
|
14951
15110
|
boardId: board.id,
|
|
@@ -14964,7 +15123,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
14964
15123
|
await publishKanbanBoard(
|
|
14965
15124
|
deps2.broadcast,
|
|
14966
15125
|
changedBoard,
|
|
14967
|
-
() => listBoards4(deps2
|
|
15126
|
+
() => listBoards4(resolveProjectRoot(deps2))
|
|
14968
15127
|
);
|
|
14969
15128
|
}
|
|
14970
15129
|
if (config.mode === "agentic" && anomalyCount > 0) {
|
|
@@ -15002,11 +15161,11 @@ function createKanbanSupervisor(deps2) {
|
|
|
15002
15161
|
// tool-runtime boundary gate (`evaluateToolKanbanBoundary`) can resolve
|
|
15003
15162
|
// the live board policy instead of failing open. Whole-board agentic
|
|
15004
15163
|
// runs have no taskId, so only boardId is propagated.
|
|
15005
|
-
context: { kanban: { boardId: board.id, projectRoot: deps2
|
|
15164
|
+
context: { kanban: { boardId: board.id, projectRoot: resolveProjectRoot(deps2) } },
|
|
15006
15165
|
onDone: async (result) => {
|
|
15007
15166
|
clearTimeout(watchdog);
|
|
15008
15167
|
agentRunning.delete(board.id);
|
|
15009
|
-
if (await getBoard3(deps2
|
|
15168
|
+
if (await getBoard3(resolveProjectRoot(deps2), board.id) === null) return;
|
|
15010
15169
|
const current3 = snapshots.get(board.id) ?? snapshot;
|
|
15011
15170
|
publish({
|
|
15012
15171
|
...current3,
|
|
@@ -15030,7 +15189,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
15030
15189
|
const auditNow = async (boardId) => {
|
|
15031
15190
|
let boards;
|
|
15032
15191
|
if (boardId) {
|
|
15033
|
-
const board = await getBoard3(deps2
|
|
15192
|
+
const board = await getBoard3(resolveProjectRoot(deps2), boardId);
|
|
15034
15193
|
if (board === null) {
|
|
15035
15194
|
forgetBoard(boardId);
|
|
15036
15195
|
boards = [];
|
|
@@ -15038,9 +15197,9 @@ function createKanbanSupervisor(deps2) {
|
|
|
15038
15197
|
boards = [board];
|
|
15039
15198
|
}
|
|
15040
15199
|
} else {
|
|
15041
|
-
const summaries = await listBoards4(deps2
|
|
15200
|
+
const summaries = await listBoards4(resolveProjectRoot(deps2));
|
|
15042
15201
|
pruneAbsentBoards(new Set(summaries.map((summary) => summary.id)));
|
|
15043
|
-
boards = (await Promise.all(summaries.map((summary) => getBoard3(deps2
|
|
15202
|
+
boards = (await Promise.all(summaries.map((summary) => getBoard3(resolveProjectRoot(deps2), summary.id)))).filter((board) => Boolean(board));
|
|
15044
15203
|
}
|
|
15045
15204
|
const results = [];
|
|
15046
15205
|
for (const board of boards) results.push(await auditBoard(board));
|
|
@@ -15075,11 +15234,11 @@ function createKanbanSupervisor(deps2) {
|
|
|
15075
15234
|
if (disposed) return;
|
|
15076
15235
|
try {
|
|
15077
15236
|
const now = Date.now();
|
|
15078
|
-
const summaries = await listBoards4(deps2
|
|
15237
|
+
const summaries = await listBoards4(resolveProjectRoot(deps2));
|
|
15079
15238
|
pruneAbsentBoards(new Set(summaries.map((summary) => summary.id)));
|
|
15080
15239
|
for (const summary of summaries) {
|
|
15081
15240
|
if ((nextDue.get(summary.id) ?? 0) > now) continue;
|
|
15082
|
-
const board = await getBoard3(deps2
|
|
15241
|
+
const board = await getBoard3(resolveProjectRoot(deps2), summary.id);
|
|
15083
15242
|
if (board) await auditBoard(board);
|
|
15084
15243
|
}
|
|
15085
15244
|
} catch (error2) {
|
|
@@ -15122,7 +15281,7 @@ async function sweepGateParkedTasks(deps2, board) {
|
|
|
15122
15281
|
let lastBoard;
|
|
15123
15282
|
for (const task of parked) {
|
|
15124
15283
|
try {
|
|
15125
|
-
const finalized = await finalizeTaskCompletion(deps2
|
|
15284
|
+
const finalized = await finalizeTaskCompletion(resolveProjectRoot(deps2), board.id, task.id, {
|
|
15126
15285
|
eventContext: { actor: "kanban-supervisor" }
|
|
15127
15286
|
});
|
|
15128
15287
|
if (finalized) lastBoard = finalized.board;
|
|
@@ -17912,6 +18071,8 @@ function labelForEvent(e) {
|
|
|
17912
18071
|
const count = e.messagesOmitted ?? e.messages.length;
|
|
17913
18072
|
return `Messages replaced (${e.messagesOmitted ? "~" : ""}${count} msgs)`;
|
|
17914
18073
|
}
|
|
18074
|
+
case "messages_dropped":
|
|
18075
|
+
return `Oldest ${e.count} message${e.count === 1 ? "" : "s"} evicted`;
|
|
17915
18076
|
case "message_truncated":
|
|
17916
18077
|
return `Message truncated: ${e.before} \u2192 ${e.after}`;
|
|
17917
18078
|
case "file_event":
|
|
@@ -17999,6 +18160,8 @@ function detailForEvent(e) {
|
|
|
17999
18160
|
return `at index ${e.index}`;
|
|
18000
18161
|
case "messages_replaced":
|
|
18001
18162
|
return `${e.messagesOmitted ?? e.messages.length} total`;
|
|
18163
|
+
case "messages_dropped":
|
|
18164
|
+
return `dropped ${e.count} from the front`;
|
|
18002
18165
|
case "message_truncated":
|
|
18003
18166
|
return `truncated to ${e.after} tokens`;
|
|
18004
18167
|
case "mode_changed":
|
|
@@ -18216,7 +18379,7 @@ function createSessionHandlers(ctx) {
|
|
|
18216
18379
|
const current2 = ctx.getSession();
|
|
18217
18380
|
if (current2 !== next) {
|
|
18218
18381
|
try {
|
|
18219
|
-
ctx.abortActiveRun?.();
|
|
18382
|
+
ctx.abortActiveRun?.(current2.id);
|
|
18220
18383
|
} catch {
|
|
18221
18384
|
}
|
|
18222
18385
|
await finalizeSession(current2);
|
|
@@ -18803,16 +18966,17 @@ function createEmbeddedConversationRoutes(ctx) {
|
|
|
18803
18966
|
getAgent: () => ctx.agent,
|
|
18804
18967
|
getSessionId: () => ctx.agent.ctx.session?.id ?? "",
|
|
18805
18968
|
runControl: {
|
|
18806
|
-
begin: (
|
|
18807
|
-
if (ctx.abortControllers.has(
|
|
18969
|
+
begin: (_ws, sessionId) => {
|
|
18970
|
+
if (ctx.abortControllers.has(sessionId)) return void 0;
|
|
18808
18971
|
const controller = new AbortController();
|
|
18809
|
-
ctx.abortControllers.set(
|
|
18972
|
+
ctx.abortControllers.set(sessionId, controller);
|
|
18810
18973
|
return controller;
|
|
18811
18974
|
},
|
|
18812
|
-
end: (
|
|
18813
|
-
if (ctx.abortControllers.get(
|
|
18975
|
+
end: (_ws, sessionId, controller) => {
|
|
18976
|
+
if (ctx.abortControllers.get(sessionId) === controller)
|
|
18977
|
+
ctx.abortControllers.delete(sessionId);
|
|
18814
18978
|
},
|
|
18815
|
-
abort: (
|
|
18979
|
+
abort: (_ws, sessionId) => ctx.abortControllers.get(sessionId)?.abort()
|
|
18816
18980
|
},
|
|
18817
18981
|
pendingConfirms: ctx.pendingConfirms,
|
|
18818
18982
|
send: ctx.send,
|
|
@@ -20234,9 +20398,13 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
20234
20398
|
// stream from the previous session would otherwise keep running in the
|
|
20235
20399
|
// background after session.new/resume. The run's own end() cleanup
|
|
20236
20400
|
// removes controllers from the map when it unwinds.
|
|
20237
|
-
abortActiveRun: () => {
|
|
20238
|
-
|
|
20239
|
-
|
|
20401
|
+
abortActiveRun: (sessionId) => {
|
|
20402
|
+
if (sessionId) {
|
|
20403
|
+
deps2.conversationCtx.abortControllers.get(sessionId)?.abort();
|
|
20404
|
+
} else {
|
|
20405
|
+
for (const controller of [...deps2.conversationCtx.abortControllers.values()]) {
|
|
20406
|
+
controller.abort();
|
|
20407
|
+
}
|
|
20240
20408
|
}
|
|
20241
20409
|
},
|
|
20242
20410
|
isRunActive: () => deps2.conversationCtx.abortControllers.size > 0
|
|
@@ -20458,8 +20626,8 @@ function createConfigWriteLock() {
|
|
|
20458
20626
|
acquire() {
|
|
20459
20627
|
const prev = lock;
|
|
20460
20628
|
let release = () => void 0;
|
|
20461
|
-
lock = new Promise((
|
|
20462
|
-
release =
|
|
20629
|
+
lock = new Promise((resolve17) => {
|
|
20630
|
+
release = resolve17;
|
|
20463
20631
|
});
|
|
20464
20632
|
return { prev, release };
|
|
20465
20633
|
}
|
|
@@ -21414,7 +21582,6 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
21414
21582
|
}
|
|
21415
21583
|
|
|
21416
21584
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
21417
|
-
import { watch as fsWatch } from "node:fs";
|
|
21418
21585
|
import * as path25 from "node:path";
|
|
21419
21586
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
21420
21587
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
@@ -21423,8 +21590,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
21423
21590
|
const disposers = [];
|
|
21424
21591
|
const broadcastSessions = async () => {
|
|
21425
21592
|
try {
|
|
21426
|
-
const {
|
|
21427
|
-
const registry =
|
|
21593
|
+
const { getSessionRegistry: getSessionRegistry3 } = await import("@wrongstack/core/storage");
|
|
21594
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
21428
21595
|
const sessions = await registry.list();
|
|
21429
21596
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
21430
21597
|
const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
|
|
@@ -21471,7 +21638,7 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
21471
21638
|
}
|
|
21472
21639
|
};
|
|
21473
21640
|
onFleetBroadcaster?.(broadcastSessions);
|
|
21474
|
-
let
|
|
21641
|
+
let subscriptionLive = false;
|
|
21475
21642
|
let statusTimer;
|
|
21476
21643
|
const scheduleStatusPoll = () => {
|
|
21477
21644
|
if (isDisposed()) return;
|
|
@@ -21480,35 +21647,31 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
21480
21647
|
void broadcastSessions();
|
|
21481
21648
|
scheduleStatusPoll();
|
|
21482
21649
|
},
|
|
21483
|
-
|
|
21650
|
+
subscriptionLive ? 3e4 : 5e3
|
|
21484
21651
|
);
|
|
21485
21652
|
if (statusTimer.unref) statusTimer.unref();
|
|
21486
21653
|
};
|
|
21487
21654
|
disposers.push(() => {
|
|
21488
21655
|
if (statusTimer) clearTimeout(statusTimer);
|
|
21489
21656
|
});
|
|
21490
|
-
let
|
|
21491
|
-
|
|
21492
|
-
|
|
21493
|
-
|
|
21494
|
-
|
|
21495
|
-
|
|
21496
|
-
|
|
21497
|
-
|
|
21498
|
-
|
|
21499
|
-
|
|
21500
|
-
|
|
21501
|
-
|
|
21502
|
-
|
|
21503
|
-
|
|
21504
|
-
|
|
21505
|
-
|
|
21506
|
-
|
|
21507
|
-
|
|
21508
|
-
regWatcher.close();
|
|
21509
|
-
});
|
|
21510
|
-
} catch {
|
|
21511
|
-
}
|
|
21657
|
+
let eventDebounce;
|
|
21658
|
+
let unsubscribe;
|
|
21659
|
+
void import("@wrongstack/core/storage").then(async ({ getSessionRegistry: getSessionRegistry3 }) => {
|
|
21660
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
21661
|
+
const projectSlug2 = wpaths?.projectSlug;
|
|
21662
|
+
if (!projectSlug2 || isDisposed()) return;
|
|
21663
|
+
unsubscribe = await registry.subscribeProject(projectSlug2, context.projectRoot, () => {
|
|
21664
|
+
if (eventDebounce) clearTimeout(eventDebounce);
|
|
21665
|
+
eventDebounce = setTimeout(() => void broadcastSessions(), 25);
|
|
21666
|
+
});
|
|
21667
|
+
subscriptionLive = true;
|
|
21668
|
+
}).catch(() => {
|
|
21669
|
+
subscriptionLive = false;
|
|
21670
|
+
});
|
|
21671
|
+
disposers.push(() => {
|
|
21672
|
+
if (eventDebounce) clearTimeout(eventDebounce);
|
|
21673
|
+
void unsubscribe?.();
|
|
21674
|
+
});
|
|
21512
21675
|
scheduleStatusPoll();
|
|
21513
21676
|
void broadcastSessions();
|
|
21514
21677
|
return () => {
|
|
@@ -21621,7 +21784,7 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
21621
21784
|
}
|
|
21622
21785
|
|
|
21623
21786
|
// src/server/setup-events-status-watcher.ts
|
|
21624
|
-
import { watch as
|
|
21787
|
+
import { watch as fsWatch } from "node:fs";
|
|
21625
21788
|
import * as fs20 from "node:fs/promises";
|
|
21626
21789
|
import * as path27 from "node:path";
|
|
21627
21790
|
|
|
@@ -21711,7 +21874,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
21711
21874
|
try {
|
|
21712
21875
|
await fs20.mkdir(projectsDir, { recursive: true });
|
|
21713
21876
|
if (isDisposed()) return;
|
|
21714
|
-
watcher =
|
|
21877
|
+
watcher = fsWatch(
|
|
21715
21878
|
projectsDir,
|
|
21716
21879
|
{ persistent: true, recursive: true },
|
|
21717
21880
|
async (eventType, filename) => {
|
|
@@ -22873,7 +23036,6 @@ import {
|
|
|
22873
23036
|
mailboxSessionTag,
|
|
22874
23037
|
ObservableBrainArbiter as ObservableBrainArbiterCtor
|
|
22875
23038
|
} from "@wrongstack/core/coordination";
|
|
22876
|
-
import { DEFAULT_TOOLS_CONFIG } from "@wrongstack/core/types";
|
|
22877
23039
|
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
22878
23040
|
import {
|
|
22879
23041
|
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
@@ -22885,6 +23047,7 @@ import {
|
|
|
22885
23047
|
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
22886
23048
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
22887
23049
|
import {
|
|
23050
|
+
DEFAULT_TOOLS_CONFIG,
|
|
22888
23051
|
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
22889
23052
|
} from "@wrongstack/core/types";
|
|
22890
23053
|
import {
|
|
@@ -23045,7 +23208,7 @@ function findWorkspaceCliEntry(projectRoot) {
|
|
|
23045
23208
|
return null;
|
|
23046
23209
|
}
|
|
23047
23210
|
function sleep(ms) {
|
|
23048
|
-
return new Promise((
|
|
23211
|
+
return new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
23049
23212
|
}
|
|
23050
23213
|
|
|
23051
23214
|
// src/server/terminal-ws-handler.ts
|
|
@@ -23288,7 +23451,7 @@ function clampDim(value, fallback) {
|
|
|
23288
23451
|
}
|
|
23289
23452
|
|
|
23290
23453
|
// src/server/worktree-ws-handler.ts
|
|
23291
|
-
import { join as join14, resolve as
|
|
23454
|
+
import { join as join14, resolve as resolve14, sep as sep5 } from "node:path";
|
|
23292
23455
|
import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
|
|
23293
23456
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core/worktree";
|
|
23294
23457
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
@@ -23373,11 +23536,11 @@ var WorktreeWebSocketHandler = class {
|
|
|
23373
23536
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
23374
23537
|
/** Absolute managed-worktrees root for this project. */
|
|
23375
23538
|
worktreesRoot() {
|
|
23376
|
-
return
|
|
23539
|
+
return resolve14(join14(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
23377
23540
|
}
|
|
23378
23541
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
23379
23542
|
underRoot(dir) {
|
|
23380
|
-
const abs =
|
|
23543
|
+
const abs = resolve14(dir);
|
|
23381
23544
|
const root = this.worktreesRoot();
|
|
23382
23545
|
return abs !== root && abs.startsWith(root + sep5);
|
|
23383
23546
|
}
|
|
@@ -23601,7 +23764,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
23601
23764
|
}
|
|
23602
23765
|
const base = baseBranch && MANAGED_BRANCH_RE.test(baseBranch) ? baseBranch : void 0;
|
|
23603
23766
|
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
23604
|
-
const summary = await wt.diffSummary(
|
|
23767
|
+
const summary = await wt.diffSummary(resolve14(dir), base);
|
|
23605
23768
|
this.broadcast({ type: "worktree.diff_result", payload: { dir, summary } });
|
|
23606
23769
|
}
|
|
23607
23770
|
// ── internals ───────────────────────────────────────────────────────────
|
|
@@ -23766,6 +23929,11 @@ async function createAgentServices(input) {
|
|
|
23766
23929
|
taskAware: config.Sage?.inject?.taskAware,
|
|
23767
23930
|
minScore: config.Sage?.inject?.minScore,
|
|
23768
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,
|
|
23769
23937
|
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
23770
23938
|
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
23771
23939
|
triggers: config.Sage?.inject?.triggers,
|
|
@@ -23787,6 +23955,10 @@ async function createAgentServices(input) {
|
|
|
23787
23955
|
maxMemories: config.Sage?.inject?.maxTurnMemories,
|
|
23788
23956
|
maxChars: config.Sage?.inject?.maxCharsPerTurn,
|
|
23789
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,
|
|
23790
23962
|
getSessionId: getSageSessionId,
|
|
23791
23963
|
tracker: sageInjectionTracker
|
|
23792
23964
|
})
|
|
@@ -23811,25 +23983,29 @@ async function createAgentServices(input) {
|
|
|
23811
23983
|
strategy: config.context?.strategy,
|
|
23812
23984
|
preserveK: config.context?.preserveK ?? 10,
|
|
23813
23985
|
eliseThreshold: config.context?.eliseThreshold ?? 2e3,
|
|
23986
|
+
// Match the CLI/TUI runtime: keep corrections, errors and decisions
|
|
23987
|
+
// verbatim while collapsing routine assistant chatter/tool protocol.
|
|
23988
|
+
// Without this WebUI's hybrid strategy builds an ever-growing lossless
|
|
23989
|
+
// digest and eventually relies on blunt emergency head/tail trimming.
|
|
23990
|
+
smart: true,
|
|
23814
23991
|
summarizerModel: config.context?.summarizerModel,
|
|
23815
23992
|
llmSelector: config.context?.llmSelector
|
|
23816
23993
|
});
|
|
23817
23994
|
const initialContextPolicy = resolveContextWindowPolicy2(config.context);
|
|
23818
23995
|
let autoCompactor;
|
|
23819
23996
|
if (config.context?.autoCompact !== false) {
|
|
23820
|
-
let effectiveMaxContext =
|
|
23821
|
-
|
|
23822
|
-
|
|
23823
|
-
|
|
23824
|
-
|
|
23825
|
-
|
|
23826
|
-
|
|
23827
|
-
|
|
23828
|
-
|
|
23829
|
-
|
|
23830
|
-
} catch {
|
|
23831
|
-
}
|
|
23997
|
+
let effectiveMaxContext = 0;
|
|
23998
|
+
try {
|
|
23999
|
+
const m = await resolveProviderModelMetadata(
|
|
24000
|
+
modelsRegistry,
|
|
24001
|
+
config.provider,
|
|
24002
|
+
context.model,
|
|
24003
|
+
config.providers?.[config.provider]
|
|
24004
|
+
);
|
|
24005
|
+
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
24006
|
+
} catch {
|
|
23832
24007
|
}
|
|
24008
|
+
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
23833
24009
|
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
23834
24010
|
autoCompactor = new AutoCompactionMiddlewareCtor(
|
|
23835
24011
|
compactor,
|
|
@@ -24451,16 +24627,24 @@ function createMessageDispatcher(opts) {
|
|
|
24451
24627
|
getAgent: () => deps2.agent,
|
|
24452
24628
|
getSessionId: () => state.getSession().id,
|
|
24453
24629
|
runControl: {
|
|
24454
|
-
begin: () => {
|
|
24630
|
+
begin: (_ws, sessionId) => {
|
|
24455
24631
|
if (runLock.get()) return void 0;
|
|
24456
24632
|
const controller = new AbortController();
|
|
24457
24633
|
runLock.set(controller);
|
|
24634
|
+
runLock.setSession(sessionId);
|
|
24458
24635
|
return controller;
|
|
24459
24636
|
},
|
|
24460
|
-
end: (_ws, controller) => {
|
|
24461
|
-
if (runLock.get() === controller)
|
|
24637
|
+
end: (_ws, _sessionId, controller) => {
|
|
24638
|
+
if (runLock.get() === controller) {
|
|
24639
|
+
runLock.set(null);
|
|
24640
|
+
runLock.setSession(null);
|
|
24641
|
+
}
|
|
24462
24642
|
},
|
|
24463
|
-
abort: () =>
|
|
24643
|
+
abort: (_ws, sessionId) => {
|
|
24644
|
+
if (runLock.getSession() === sessionId || !runLock.getSession()) {
|
|
24645
|
+
runLock.get()?.abort();
|
|
24646
|
+
}
|
|
24647
|
+
}
|
|
24464
24648
|
},
|
|
24465
24649
|
pendingConfirms,
|
|
24466
24650
|
send,
|
|
@@ -24482,10 +24666,20 @@ function createMessageDispatcher(opts) {
|
|
|
24482
24666
|
const goalSnapshotRoutes = {
|
|
24483
24667
|
getSnapshot: () => handleGoalGet(state.getProjectRoot(), (message) => broadcast(state.getClients(), message))
|
|
24484
24668
|
};
|
|
24669
|
+
const kanbanSupervisor = createKanbanSupervisor({
|
|
24670
|
+
projectRoot: () => state.getProjectRoot(),
|
|
24671
|
+
broadcast: (message) => broadcast(state.getClients(), message),
|
|
24672
|
+
log: (message) => deps2.logger.warn?.(`[KanbanSupervisor] ${message}`)
|
|
24673
|
+
});
|
|
24674
|
+
if (opts.onDispose) {
|
|
24675
|
+
const dispose = () => kanbanSupervisor.dispose();
|
|
24676
|
+
opts.onDispose(dispose);
|
|
24677
|
+
}
|
|
24485
24678
|
const kanbanContext = () => ({
|
|
24486
24679
|
projectRoot: state.getProjectRoot(),
|
|
24487
24680
|
context: deps2.context,
|
|
24488
|
-
broadcast: (message) => broadcast(state.getClients(), message)
|
|
24681
|
+
broadcast: (message) => broadcast(state.getClients(), message),
|
|
24682
|
+
supervisor: kanbanSupervisor
|
|
24489
24683
|
});
|
|
24490
24684
|
const kanbanHostRoutes = {
|
|
24491
24685
|
meta: async (ws) => {
|
|
@@ -25001,6 +25195,14 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
25001
25195
|
transition = transition.then(async () => {
|
|
25002
25196
|
if (stopped) return;
|
|
25003
25197
|
if (pendingClaim?.sessionId === sessionId) {
|
|
25198
|
+
await pendingClaim.claim.activate({
|
|
25199
|
+
sessionId,
|
|
25200
|
+
...target,
|
|
25201
|
+
clientType: "webui",
|
|
25202
|
+
pid: process.pid,
|
|
25203
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25204
|
+
agents: statusTracker.getAgents()
|
|
25205
|
+
});
|
|
25004
25206
|
pendingClaim = void 0;
|
|
25005
25207
|
} else {
|
|
25006
25208
|
await register(sessionId, true, target);
|
|
@@ -25024,14 +25226,35 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
25024
25226
|
}
|
|
25025
25227
|
if (sessionId === activeSessionId) return async () => {
|
|
25026
25228
|
};
|
|
25027
|
-
const previousSessionId = activeSessionId;
|
|
25028
|
-
const previousTarget = activeTarget;
|
|
25029
25229
|
const token = Symbol(sessionId);
|
|
25030
|
-
|
|
25031
|
-
|
|
25230
|
+
if ("reserveResume" in registry && typeof registry.reserveResume === "function") {
|
|
25231
|
+
const reservation = await registry.reserveResume({
|
|
25232
|
+
sessionId,
|
|
25233
|
+
projectSlug: target.projectSlug,
|
|
25234
|
+
projectRoot: target.projectRoot
|
|
25235
|
+
});
|
|
25236
|
+
pendingClaim = { sessionId, token, claim: reservation, target };
|
|
25237
|
+
} else {
|
|
25238
|
+
await register(sessionId, true, target);
|
|
25239
|
+
pendingClaim = {
|
|
25240
|
+
sessionId,
|
|
25241
|
+
token,
|
|
25242
|
+
target,
|
|
25243
|
+
claim: {
|
|
25244
|
+
reservation: {
|
|
25245
|
+
reservationId: "legacy",
|
|
25246
|
+
targetSessionId: sessionId,
|
|
25247
|
+
requesterInstanceId: "legacy",
|
|
25248
|
+
expiresAt: Number.MAX_SAFE_INTEGER
|
|
25249
|
+
},
|
|
25250
|
+
activate: async () => void 0,
|
|
25251
|
+
cancel: async () => register(activeSessionId, true, activeTarget)
|
|
25252
|
+
}
|
|
25253
|
+
};
|
|
25254
|
+
}
|
|
25032
25255
|
return async () => {
|
|
25033
25256
|
if (pendingClaim?.token !== token) return;
|
|
25034
|
-
await
|
|
25257
|
+
await pendingClaim.claim.cancel();
|
|
25035
25258
|
pendingClaim = void 0;
|
|
25036
25259
|
};
|
|
25037
25260
|
};
|
|
@@ -25958,10 +26181,11 @@ function startHttpServer(opts) {
|
|
|
25958
26181
|
return httpServer;
|
|
25959
26182
|
}
|
|
25960
26183
|
function registerShutdown(deps2) {
|
|
25961
|
-
registerShutdownHandlers({
|
|
26184
|
+
return registerShutdownHandlers({
|
|
25962
26185
|
flushSession: deps2.flushSession,
|
|
25963
26186
|
clients: deps2.clients,
|
|
25964
26187
|
servers: deps2.servers,
|
|
26188
|
+
onPreShutdown: deps2.onPreShutdown,
|
|
25965
26189
|
onShutdown: deps2.onShutdown
|
|
25966
26190
|
});
|
|
25967
26191
|
}
|
|
@@ -26231,7 +26455,7 @@ async function startWebUI(opts = {}) {
|
|
|
26231
26455
|
if (events.listenerCount("tool.confirm_needed") === 0) {
|
|
26232
26456
|
throw new Error("No permission confirmation surface is connected");
|
|
26233
26457
|
}
|
|
26234
|
-
const decision = await new Promise((
|
|
26458
|
+
const decision = await new Promise((resolve17) => {
|
|
26235
26459
|
events.emit("tool.confirm_needed", {
|
|
26236
26460
|
sessionId: context.session.id,
|
|
26237
26461
|
tool: confirmTool,
|
|
@@ -26241,7 +26465,7 @@ async function startWebUI(opts = {}) {
|
|
|
26241
26465
|
decisionSource: pending.decisionSource,
|
|
26242
26466
|
riskTier: pending.riskTier,
|
|
26243
26467
|
boundaryReason: pending.boundaryReason,
|
|
26244
|
-
resolve:
|
|
26468
|
+
resolve: resolve17
|
|
26245
26469
|
});
|
|
26246
26470
|
});
|
|
26247
26471
|
const rule = { tool: "language_package", pattern: pending.suggestedPattern };
|
|
@@ -26276,10 +26500,15 @@ async function startWebUI(opts = {}) {
|
|
|
26276
26500
|
);
|
|
26277
26501
|
}
|
|
26278
26502
|
let _runLock = null;
|
|
26503
|
+
let _runLockSession = null;
|
|
26279
26504
|
const runLockControl = {
|
|
26280
26505
|
get: () => _runLock,
|
|
26281
26506
|
set: (ctrl) => {
|
|
26282
26507
|
_runLock = ctrl;
|
|
26508
|
+
},
|
|
26509
|
+
getSession: () => _runLockSession,
|
|
26510
|
+
setSession: (id) => {
|
|
26511
|
+
_runLockSession = id;
|
|
26283
26512
|
}
|
|
26284
26513
|
};
|
|
26285
26514
|
const pendingConfirms = /* @__PURE__ */ new Map();
|
|
@@ -26404,6 +26633,7 @@ async function startWebUI(opts = {}) {
|
|
|
26404
26633
|
if (ctrl) {
|
|
26405
26634
|
ctrl.abort();
|
|
26406
26635
|
runLockControl.set(null);
|
|
26636
|
+
runLockControl.setSession(null);
|
|
26407
26637
|
}
|
|
26408
26638
|
},
|
|
26409
26639
|
isRunActive: () => runLockControl.get() !== null,
|
|
@@ -26553,6 +26783,7 @@ async function startWebUI(opts = {}) {
|
|
|
26553
26783
|
})
|
|
26554
26784
|
});
|
|
26555
26785
|
const routes = buildRoutes(state, deps2, cb);
|
|
26786
|
+
let kanbanSupervisorDispose = null;
|
|
26556
26787
|
const handleMessage = createMessageDispatcher({
|
|
26557
26788
|
state,
|
|
26558
26789
|
deps: deps2,
|
|
@@ -26560,7 +26791,10 @@ async function startWebUI(opts = {}) {
|
|
|
26560
26791
|
promptsCtx,
|
|
26561
26792
|
codebaseIndexing,
|
|
26562
26793
|
runLock: runLockControl,
|
|
26563
|
-
pendingConfirms
|
|
26794
|
+
pendingConfirms,
|
|
26795
|
+
onDispose: (dispose) => {
|
|
26796
|
+
kanbanSupervisorDispose = dispose;
|
|
26797
|
+
}
|
|
26564
26798
|
});
|
|
26565
26799
|
const mailbox = getSharedProjectMailbox5(
|
|
26566
26800
|
resolveProjectDir4(context.projectRoot, wstackGlobalRoot4()),
|
|
@@ -26594,7 +26828,14 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26594
26828
|
priority: "high",
|
|
26595
26829
|
senderSessionId: session.id
|
|
26596
26830
|
}).catch((err) => {
|
|
26597
|
-
console.warn(
|
|
26831
|
+
console.warn(
|
|
26832
|
+
JSON.stringify({
|
|
26833
|
+
level: "warn",
|
|
26834
|
+
event: "webui.security_rejection_mailbox_note_failed",
|
|
26835
|
+
message: String(err),
|
|
26836
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
26837
|
+
})
|
|
26838
|
+
);
|
|
26598
26839
|
});
|
|
26599
26840
|
},
|
|
26600
26841
|
goalHandler,
|
|
@@ -26608,7 +26849,9 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26608
26849
|
});
|
|
26609
26850
|
wssPrimary.on("connection", handleConnection);
|
|
26610
26851
|
if (wssSecondary) wssSecondary.on("connection", handleConnection);
|
|
26611
|
-
|
|
26852
|
+
let unregisterShutdown = () => {
|
|
26853
|
+
};
|
|
26854
|
+
unregisterShutdown = registerShutdown({
|
|
26612
26855
|
flushSession: async () => {
|
|
26613
26856
|
await session.append({
|
|
26614
26857
|
type: "session_end",
|
|
@@ -26624,7 +26867,12 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26624
26867
|
wssPrimary,
|
|
26625
26868
|
...wssSecondary ? [wssSecondary] : []
|
|
26626
26869
|
],
|
|
26870
|
+
onPreShutdown: () => {
|
|
26871
|
+
kanbanSupervisorDispose?.();
|
|
26872
|
+
kanbanSupervisorDispose = null;
|
|
26873
|
+
},
|
|
26627
26874
|
onShutdown: async () => {
|
|
26875
|
+
unregisterShutdown();
|
|
26628
26876
|
await todosCheckpoint.detach();
|
|
26629
26877
|
await stopHeapWatchdog();
|
|
26630
26878
|
credentialWatcherClose?.();
|
|
@@ -26883,6 +27131,7 @@ export {
|
|
|
26883
27131
|
isPortFree,
|
|
26884
27132
|
isRegisteredMessageType,
|
|
26885
27133
|
isWildcardBind,
|
|
27134
|
+
joinSessionRegistryWithWebUIInstances,
|
|
26886
27135
|
listInstances,
|
|
26887
27136
|
loadManifest,
|
|
26888
27137
|
loadSavedProviders,
|