@wrongstack/webui-server 0.302.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 +289 -123
- 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/entry.js +578 -106
- 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",
|
|
@@ -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 = [];
|
|
@@ -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);
|
|
@@ -11304,6 +11360,14 @@ async function handleKanbanRoute(ws, msg, ctx) {
|
|
|
11304
11360
|
fail(ws, type, `Board not found: ${boardId}`);
|
|
11305
11361
|
return true;
|
|
11306
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
|
+
}
|
|
11307
11371
|
const reconciled = await reconcileKanbanBoard(ctx.projectRoot, boardId);
|
|
11308
11372
|
let health = await getKanbanQueueHealth(ctx.projectRoot, { boardId });
|
|
11309
11373
|
const recovered = health.staleAssignments.count ? await recoverStaleTaskAssignments(ctx.projectRoot, boardId, {
|
|
@@ -12068,6 +12132,15 @@ function createShutdown(res) {
|
|
|
12068
12132
|
} catch {
|
|
12069
12133
|
}
|
|
12070
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
|
+
}
|
|
12071
12144
|
for (const server of res.servers) server?.close();
|
|
12072
12145
|
if (res.onShutdown) {
|
|
12073
12146
|
try {
|
|
@@ -14956,6 +15029,10 @@ import {
|
|
|
14956
15029
|
recoverStaleTaskAssignments as recoverStaleTaskAssignments2,
|
|
14957
15030
|
resolveGateEnforcement
|
|
14958
15031
|
} from "@wrongstack/kanban";
|
|
15032
|
+
function resolveProjectRoot(deps2) {
|
|
15033
|
+
const root = deps2.projectRoot;
|
|
15034
|
+
return typeof root === "function" ? root() : root;
|
|
15035
|
+
}
|
|
14959
15036
|
var DEFAULT_INTERVAL_MS = 1e4;
|
|
14960
15037
|
var MIN_INTERVAL_MS = 2e3;
|
|
14961
15038
|
var DEFAULT_AGENT_COOLDOWN_MS = 5 * 6e4;
|
|
@@ -15020,14 +15097,14 @@ function createKanbanSupervisor(deps2) {
|
|
|
15020
15097
|
publish(snapshot2);
|
|
15021
15098
|
return snapshot2;
|
|
15022
15099
|
}
|
|
15023
|
-
const reconciled = await reconcileKanbanBoard2(deps2
|
|
15100
|
+
const reconciled = await reconcileKanbanBoard2(resolveProjectRoot(deps2), board.id);
|
|
15024
15101
|
const gateSwept = await sweepGateParkedTasks(deps2, reconciled?.board ?? board);
|
|
15025
|
-
let health = await getKanbanQueueHealth2(deps2
|
|
15026
|
-
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, {
|
|
15027
15104
|
mode: config.recoveryMode ?? "auto",
|
|
15028
15105
|
reason: "Kanban supervisor found an expired worker lease."
|
|
15029
15106
|
}) : null;
|
|
15030
|
-
if (recovered) health = await getKanbanQueueHealth2(deps2
|
|
15107
|
+
if (recovered) health = await getKanbanQueueHealth2(resolveProjectRoot(deps2), { boardId: board.id });
|
|
15031
15108
|
const anomalyCount = countAnomalies(health);
|
|
15032
15109
|
const snapshot = {
|
|
15033
15110
|
boardId: board.id,
|
|
@@ -15046,7 +15123,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
15046
15123
|
await publishKanbanBoard(
|
|
15047
15124
|
deps2.broadcast,
|
|
15048
15125
|
changedBoard,
|
|
15049
|
-
() => listBoards4(deps2
|
|
15126
|
+
() => listBoards4(resolveProjectRoot(deps2))
|
|
15050
15127
|
);
|
|
15051
15128
|
}
|
|
15052
15129
|
if (config.mode === "agentic" && anomalyCount > 0) {
|
|
@@ -15084,11 +15161,11 @@ function createKanbanSupervisor(deps2) {
|
|
|
15084
15161
|
// tool-runtime boundary gate (`evaluateToolKanbanBoundary`) can resolve
|
|
15085
15162
|
// the live board policy instead of failing open. Whole-board agentic
|
|
15086
15163
|
// runs have no taskId, so only boardId is propagated.
|
|
15087
|
-
context: { kanban: { boardId: board.id, projectRoot: deps2
|
|
15164
|
+
context: { kanban: { boardId: board.id, projectRoot: resolveProjectRoot(deps2) } },
|
|
15088
15165
|
onDone: async (result) => {
|
|
15089
15166
|
clearTimeout(watchdog);
|
|
15090
15167
|
agentRunning.delete(board.id);
|
|
15091
|
-
if (await getBoard3(deps2
|
|
15168
|
+
if (await getBoard3(resolveProjectRoot(deps2), board.id) === null) return;
|
|
15092
15169
|
const current3 = snapshots.get(board.id) ?? snapshot;
|
|
15093
15170
|
publish({
|
|
15094
15171
|
...current3,
|
|
@@ -15112,7 +15189,7 @@ function createKanbanSupervisor(deps2) {
|
|
|
15112
15189
|
const auditNow = async (boardId) => {
|
|
15113
15190
|
let boards;
|
|
15114
15191
|
if (boardId) {
|
|
15115
|
-
const board = await getBoard3(deps2
|
|
15192
|
+
const board = await getBoard3(resolveProjectRoot(deps2), boardId);
|
|
15116
15193
|
if (board === null) {
|
|
15117
15194
|
forgetBoard(boardId);
|
|
15118
15195
|
boards = [];
|
|
@@ -15120,9 +15197,9 @@ function createKanbanSupervisor(deps2) {
|
|
|
15120
15197
|
boards = [board];
|
|
15121
15198
|
}
|
|
15122
15199
|
} else {
|
|
15123
|
-
const summaries = await listBoards4(deps2
|
|
15200
|
+
const summaries = await listBoards4(resolveProjectRoot(deps2));
|
|
15124
15201
|
pruneAbsentBoards(new Set(summaries.map((summary) => summary.id)));
|
|
15125
|
-
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));
|
|
15126
15203
|
}
|
|
15127
15204
|
const results = [];
|
|
15128
15205
|
for (const board of boards) results.push(await auditBoard(board));
|
|
@@ -15157,11 +15234,11 @@ function createKanbanSupervisor(deps2) {
|
|
|
15157
15234
|
if (disposed) return;
|
|
15158
15235
|
try {
|
|
15159
15236
|
const now = Date.now();
|
|
15160
|
-
const summaries = await listBoards4(deps2
|
|
15237
|
+
const summaries = await listBoards4(resolveProjectRoot(deps2));
|
|
15161
15238
|
pruneAbsentBoards(new Set(summaries.map((summary) => summary.id)));
|
|
15162
15239
|
for (const summary of summaries) {
|
|
15163
15240
|
if ((nextDue.get(summary.id) ?? 0) > now) continue;
|
|
15164
|
-
const board = await getBoard3(deps2
|
|
15241
|
+
const board = await getBoard3(resolveProjectRoot(deps2), summary.id);
|
|
15165
15242
|
if (board) await auditBoard(board);
|
|
15166
15243
|
}
|
|
15167
15244
|
} catch (error2) {
|
|
@@ -15204,7 +15281,7 @@ async function sweepGateParkedTasks(deps2, board) {
|
|
|
15204
15281
|
let lastBoard;
|
|
15205
15282
|
for (const task of parked) {
|
|
15206
15283
|
try {
|
|
15207
|
-
const finalized = await finalizeTaskCompletion(deps2
|
|
15284
|
+
const finalized = await finalizeTaskCompletion(resolveProjectRoot(deps2), board.id, task.id, {
|
|
15208
15285
|
eventContext: { actor: "kanban-supervisor" }
|
|
15209
15286
|
});
|
|
15210
15287
|
if (finalized) lastBoard = finalized.board;
|
|
@@ -17994,6 +18071,8 @@ function labelForEvent(e) {
|
|
|
17994
18071
|
const count = e.messagesOmitted ?? e.messages.length;
|
|
17995
18072
|
return `Messages replaced (${e.messagesOmitted ? "~" : ""}${count} msgs)`;
|
|
17996
18073
|
}
|
|
18074
|
+
case "messages_dropped":
|
|
18075
|
+
return `Oldest ${e.count} message${e.count === 1 ? "" : "s"} evicted`;
|
|
17997
18076
|
case "message_truncated":
|
|
17998
18077
|
return `Message truncated: ${e.before} \u2192 ${e.after}`;
|
|
17999
18078
|
case "file_event":
|
|
@@ -18081,6 +18160,8 @@ function detailForEvent(e) {
|
|
|
18081
18160
|
return `at index ${e.index}`;
|
|
18082
18161
|
case "messages_replaced":
|
|
18083
18162
|
return `${e.messagesOmitted ?? e.messages.length} total`;
|
|
18163
|
+
case "messages_dropped":
|
|
18164
|
+
return `dropped ${e.count} from the front`;
|
|
18084
18165
|
case "message_truncated":
|
|
18085
18166
|
return `truncated to ${e.after} tokens`;
|
|
18086
18167
|
case "mode_changed":
|
|
@@ -18298,7 +18379,7 @@ function createSessionHandlers(ctx) {
|
|
|
18298
18379
|
const current2 = ctx.getSession();
|
|
18299
18380
|
if (current2 !== next) {
|
|
18300
18381
|
try {
|
|
18301
|
-
ctx.abortActiveRun?.();
|
|
18382
|
+
ctx.abortActiveRun?.(current2.id);
|
|
18302
18383
|
} catch {
|
|
18303
18384
|
}
|
|
18304
18385
|
await finalizeSession(current2);
|
|
@@ -18885,16 +18966,17 @@ function createEmbeddedConversationRoutes(ctx) {
|
|
|
18885
18966
|
getAgent: () => ctx.agent,
|
|
18886
18967
|
getSessionId: () => ctx.agent.ctx.session?.id ?? "",
|
|
18887
18968
|
runControl: {
|
|
18888
|
-
begin: (
|
|
18889
|
-
if (ctx.abortControllers.has(
|
|
18969
|
+
begin: (_ws, sessionId) => {
|
|
18970
|
+
if (ctx.abortControllers.has(sessionId)) return void 0;
|
|
18890
18971
|
const controller = new AbortController();
|
|
18891
|
-
ctx.abortControllers.set(
|
|
18972
|
+
ctx.abortControllers.set(sessionId, controller);
|
|
18892
18973
|
return controller;
|
|
18893
18974
|
},
|
|
18894
|
-
end: (
|
|
18895
|
-
if (ctx.abortControllers.get(
|
|
18975
|
+
end: (_ws, sessionId, controller) => {
|
|
18976
|
+
if (ctx.abortControllers.get(sessionId) === controller)
|
|
18977
|
+
ctx.abortControllers.delete(sessionId);
|
|
18896
18978
|
},
|
|
18897
|
-
abort: (
|
|
18979
|
+
abort: (_ws, sessionId) => ctx.abortControllers.get(sessionId)?.abort()
|
|
18898
18980
|
},
|
|
18899
18981
|
pendingConfirms: ctx.pendingConfirms,
|
|
18900
18982
|
send: ctx.send,
|
|
@@ -20316,9 +20398,13 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
20316
20398
|
// stream from the previous session would otherwise keep running in the
|
|
20317
20399
|
// background after session.new/resume. The run's own end() cleanup
|
|
20318
20400
|
// removes controllers from the map when it unwinds.
|
|
20319
|
-
abortActiveRun: () => {
|
|
20320
|
-
|
|
20321
|
-
|
|
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
|
+
}
|
|
20322
20408
|
}
|
|
20323
20409
|
},
|
|
20324
20410
|
isRunActive: () => deps2.conversationCtx.abortControllers.size > 0
|
|
@@ -21496,7 +21582,6 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
21496
21582
|
}
|
|
21497
21583
|
|
|
21498
21584
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
21499
|
-
import { watch as fsWatch } from "node:fs";
|
|
21500
21585
|
import * as path25 from "node:path";
|
|
21501
21586
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
21502
21587
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
@@ -21505,8 +21590,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
21505
21590
|
const disposers = [];
|
|
21506
21591
|
const broadcastSessions = async () => {
|
|
21507
21592
|
try {
|
|
21508
|
-
const {
|
|
21509
|
-
const registry =
|
|
21593
|
+
const { getSessionRegistry: getSessionRegistry3 } = await import("@wrongstack/core/storage");
|
|
21594
|
+
const registry = getSessionRegistry3(globalRoot);
|
|
21510
21595
|
const sessions = await registry.list();
|
|
21511
21596
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
21512
21597
|
const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
|
|
@@ -21553,7 +21638,7 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
21553
21638
|
}
|
|
21554
21639
|
};
|
|
21555
21640
|
onFleetBroadcaster?.(broadcastSessions);
|
|
21556
|
-
let
|
|
21641
|
+
let subscriptionLive = false;
|
|
21557
21642
|
let statusTimer;
|
|
21558
21643
|
const scheduleStatusPoll = () => {
|
|
21559
21644
|
if (isDisposed()) return;
|
|
@@ -21562,35 +21647,31 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
21562
21647
|
void broadcastSessions();
|
|
21563
21648
|
scheduleStatusPoll();
|
|
21564
21649
|
},
|
|
21565
|
-
|
|
21650
|
+
subscriptionLive ? 3e4 : 5e3
|
|
21566
21651
|
);
|
|
21567
21652
|
if (statusTimer.unref) statusTimer.unref();
|
|
21568
21653
|
};
|
|
21569
21654
|
disposers.push(() => {
|
|
21570
21655
|
if (statusTimer) clearTimeout(statusTimer);
|
|
21571
21656
|
});
|
|
21572
|
-
let
|
|
21573
|
-
|
|
21574
|
-
|
|
21575
|
-
|
|
21576
|
-
|
|
21577
|
-
|
|
21578
|
-
|
|
21579
|
-
|
|
21580
|
-
|
|
21581
|
-
|
|
21582
|
-
|
|
21583
|
-
|
|
21584
|
-
|
|
21585
|
-
|
|
21586
|
-
|
|
21587
|
-
|
|
21588
|
-
|
|
21589
|
-
|
|
21590
|
-
regWatcher.close();
|
|
21591
|
-
});
|
|
21592
|
-
} catch {
|
|
21593
|
-
}
|
|
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
|
+
});
|
|
21594
21675
|
scheduleStatusPoll();
|
|
21595
21676
|
void broadcastSessions();
|
|
21596
21677
|
return () => {
|
|
@@ -21703,7 +21784,7 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
21703
21784
|
}
|
|
21704
21785
|
|
|
21705
21786
|
// src/server/setup-events-status-watcher.ts
|
|
21706
|
-
import { watch as
|
|
21787
|
+
import { watch as fsWatch } from "node:fs";
|
|
21707
21788
|
import * as fs20 from "node:fs/promises";
|
|
21708
21789
|
import * as path27 from "node:path";
|
|
21709
21790
|
|
|
@@ -21793,7 +21874,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
21793
21874
|
try {
|
|
21794
21875
|
await fs20.mkdir(projectsDir, { recursive: true });
|
|
21795
21876
|
if (isDisposed()) return;
|
|
21796
|
-
watcher =
|
|
21877
|
+
watcher = fsWatch(
|
|
21797
21878
|
projectsDir,
|
|
21798
21879
|
{ persistent: true, recursive: true },
|
|
21799
21880
|
async (eventType, filename) => {
|
|
@@ -22955,7 +23036,6 @@ import {
|
|
|
22955
23036
|
mailboxSessionTag,
|
|
22956
23037
|
ObservableBrainArbiter as ObservableBrainArbiterCtor
|
|
22957
23038
|
} from "@wrongstack/core/coordination";
|
|
22958
|
-
import { DEFAULT_TOOLS_CONFIG } from "@wrongstack/core/types";
|
|
22959
23039
|
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
22960
23040
|
import {
|
|
22961
23041
|
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
@@ -22967,6 +23047,7 @@ import {
|
|
|
22967
23047
|
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
22968
23048
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
22969
23049
|
import {
|
|
23050
|
+
DEFAULT_TOOLS_CONFIG,
|
|
22970
23051
|
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
22971
23052
|
} from "@wrongstack/core/types";
|
|
22972
23053
|
import {
|
|
@@ -23848,6 +23929,11 @@ async function createAgentServices(input) {
|
|
|
23848
23929
|
taskAware: config.Sage?.inject?.taskAware,
|
|
23849
23930
|
minScore: config.Sage?.inject?.minScore,
|
|
23850
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,
|
|
23851
23937
|
repeatCooldownMs: config.Sage?.inject?.repeatCooldownMs,
|
|
23852
23938
|
verifyOnMutation: config.Sage?.hygiene?.autoOnFileChange,
|
|
23853
23939
|
triggers: config.Sage?.inject?.triggers,
|
|
@@ -23869,6 +23955,10 @@ async function createAgentServices(input) {
|
|
|
23869
23955
|
maxMemories: config.Sage?.inject?.maxTurnMemories,
|
|
23870
23956
|
maxChars: config.Sage?.inject?.maxCharsPerTurn,
|
|
23871
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,
|
|
23872
23962
|
getSessionId: getSageSessionId,
|
|
23873
23963
|
tracker: sageInjectionTracker
|
|
23874
23964
|
})
|
|
@@ -23893,25 +23983,29 @@ async function createAgentServices(input) {
|
|
|
23893
23983
|
strategy: config.context?.strategy,
|
|
23894
23984
|
preserveK: config.context?.preserveK ?? 10,
|
|
23895
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,
|
|
23896
23991
|
summarizerModel: config.context?.summarizerModel,
|
|
23897
23992
|
llmSelector: config.context?.llmSelector
|
|
23898
23993
|
});
|
|
23899
23994
|
const initialContextPolicy = resolveContextWindowPolicy2(config.context);
|
|
23900
23995
|
let autoCompactor;
|
|
23901
23996
|
if (config.context?.autoCompact !== false) {
|
|
23902
|
-
let effectiveMaxContext =
|
|
23903
|
-
|
|
23904
|
-
|
|
23905
|
-
|
|
23906
|
-
|
|
23907
|
-
|
|
23908
|
-
|
|
23909
|
-
|
|
23910
|
-
|
|
23911
|
-
|
|
23912
|
-
} catch {
|
|
23913
|
-
}
|
|
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 {
|
|
23914
24007
|
}
|
|
24008
|
+
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
23915
24009
|
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
23916
24010
|
autoCompactor = new AutoCompactionMiddlewareCtor(
|
|
23917
24011
|
compactor,
|
|
@@ -24533,16 +24627,24 @@ function createMessageDispatcher(opts) {
|
|
|
24533
24627
|
getAgent: () => deps2.agent,
|
|
24534
24628
|
getSessionId: () => state.getSession().id,
|
|
24535
24629
|
runControl: {
|
|
24536
|
-
begin: () => {
|
|
24630
|
+
begin: (_ws, sessionId) => {
|
|
24537
24631
|
if (runLock.get()) return void 0;
|
|
24538
24632
|
const controller = new AbortController();
|
|
24539
24633
|
runLock.set(controller);
|
|
24634
|
+
runLock.setSession(sessionId);
|
|
24540
24635
|
return controller;
|
|
24541
24636
|
},
|
|
24542
|
-
end: (_ws, controller) => {
|
|
24543
|
-
if (runLock.get() === controller)
|
|
24637
|
+
end: (_ws, _sessionId, controller) => {
|
|
24638
|
+
if (runLock.get() === controller) {
|
|
24639
|
+
runLock.set(null);
|
|
24640
|
+
runLock.setSession(null);
|
|
24641
|
+
}
|
|
24544
24642
|
},
|
|
24545
|
-
abort: () =>
|
|
24643
|
+
abort: (_ws, sessionId) => {
|
|
24644
|
+
if (runLock.getSession() === sessionId || !runLock.getSession()) {
|
|
24645
|
+
runLock.get()?.abort();
|
|
24646
|
+
}
|
|
24647
|
+
}
|
|
24546
24648
|
},
|
|
24547
24649
|
pendingConfirms,
|
|
24548
24650
|
send,
|
|
@@ -24564,10 +24666,20 @@ function createMessageDispatcher(opts) {
|
|
|
24564
24666
|
const goalSnapshotRoutes = {
|
|
24565
24667
|
getSnapshot: () => handleGoalGet(state.getProjectRoot(), (message) => broadcast(state.getClients(), message))
|
|
24566
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
|
+
}
|
|
24567
24678
|
const kanbanContext = () => ({
|
|
24568
24679
|
projectRoot: state.getProjectRoot(),
|
|
24569
24680
|
context: deps2.context,
|
|
24570
|
-
broadcast: (message) => broadcast(state.getClients(), message)
|
|
24681
|
+
broadcast: (message) => broadcast(state.getClients(), message),
|
|
24682
|
+
supervisor: kanbanSupervisor
|
|
24571
24683
|
});
|
|
24572
24684
|
const kanbanHostRoutes = {
|
|
24573
24685
|
meta: async (ws) => {
|
|
@@ -25083,6 +25195,14 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
25083
25195
|
transition = transition.then(async () => {
|
|
25084
25196
|
if (stopped) return;
|
|
25085
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
|
+
});
|
|
25086
25206
|
pendingClaim = void 0;
|
|
25087
25207
|
} else {
|
|
25088
25208
|
await register(sessionId, true, target);
|
|
@@ -25106,14 +25226,35 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
25106
25226
|
}
|
|
25107
25227
|
if (sessionId === activeSessionId) return async () => {
|
|
25108
25228
|
};
|
|
25109
|
-
const previousSessionId = activeSessionId;
|
|
25110
|
-
const previousTarget = activeTarget;
|
|
25111
25229
|
const token = Symbol(sessionId);
|
|
25112
|
-
|
|
25113
|
-
|
|
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
|
+
}
|
|
25114
25255
|
return async () => {
|
|
25115
25256
|
if (pendingClaim?.token !== token) return;
|
|
25116
|
-
await
|
|
25257
|
+
await pendingClaim.claim.cancel();
|
|
25117
25258
|
pendingClaim = void 0;
|
|
25118
25259
|
};
|
|
25119
25260
|
};
|
|
@@ -26040,10 +26181,11 @@ function startHttpServer(opts) {
|
|
|
26040
26181
|
return httpServer;
|
|
26041
26182
|
}
|
|
26042
26183
|
function registerShutdown(deps2) {
|
|
26043
|
-
registerShutdownHandlers({
|
|
26184
|
+
return registerShutdownHandlers({
|
|
26044
26185
|
flushSession: deps2.flushSession,
|
|
26045
26186
|
clients: deps2.clients,
|
|
26046
26187
|
servers: deps2.servers,
|
|
26188
|
+
onPreShutdown: deps2.onPreShutdown,
|
|
26047
26189
|
onShutdown: deps2.onShutdown
|
|
26048
26190
|
});
|
|
26049
26191
|
}
|
|
@@ -26358,10 +26500,15 @@ async function startWebUI(opts = {}) {
|
|
|
26358
26500
|
);
|
|
26359
26501
|
}
|
|
26360
26502
|
let _runLock = null;
|
|
26503
|
+
let _runLockSession = null;
|
|
26361
26504
|
const runLockControl = {
|
|
26362
26505
|
get: () => _runLock,
|
|
26363
26506
|
set: (ctrl) => {
|
|
26364
26507
|
_runLock = ctrl;
|
|
26508
|
+
},
|
|
26509
|
+
getSession: () => _runLockSession,
|
|
26510
|
+
setSession: (id) => {
|
|
26511
|
+
_runLockSession = id;
|
|
26365
26512
|
}
|
|
26366
26513
|
};
|
|
26367
26514
|
const pendingConfirms = /* @__PURE__ */ new Map();
|
|
@@ -26486,6 +26633,7 @@ async function startWebUI(opts = {}) {
|
|
|
26486
26633
|
if (ctrl) {
|
|
26487
26634
|
ctrl.abort();
|
|
26488
26635
|
runLockControl.set(null);
|
|
26636
|
+
runLockControl.setSession(null);
|
|
26489
26637
|
}
|
|
26490
26638
|
},
|
|
26491
26639
|
isRunActive: () => runLockControl.get() !== null,
|
|
@@ -26635,6 +26783,7 @@ async function startWebUI(opts = {}) {
|
|
|
26635
26783
|
})
|
|
26636
26784
|
});
|
|
26637
26785
|
const routes = buildRoutes(state, deps2, cb);
|
|
26786
|
+
let kanbanSupervisorDispose = null;
|
|
26638
26787
|
const handleMessage = createMessageDispatcher({
|
|
26639
26788
|
state,
|
|
26640
26789
|
deps: deps2,
|
|
@@ -26642,7 +26791,10 @@ async function startWebUI(opts = {}) {
|
|
|
26642
26791
|
promptsCtx,
|
|
26643
26792
|
codebaseIndexing,
|
|
26644
26793
|
runLock: runLockControl,
|
|
26645
|
-
pendingConfirms
|
|
26794
|
+
pendingConfirms,
|
|
26795
|
+
onDispose: (dispose) => {
|
|
26796
|
+
kanbanSupervisorDispose = dispose;
|
|
26797
|
+
}
|
|
26646
26798
|
});
|
|
26647
26799
|
const mailbox = getSharedProjectMailbox5(
|
|
26648
26800
|
resolveProjectDir4(context.projectRoot, wstackGlobalRoot4()),
|
|
@@ -26676,7 +26828,14 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26676
26828
|
priority: "high",
|
|
26677
26829
|
senderSessionId: session.id
|
|
26678
26830
|
}).catch((err) => {
|
|
26679
|
-
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
|
+
);
|
|
26680
26839
|
});
|
|
26681
26840
|
},
|
|
26682
26841
|
goalHandler,
|
|
@@ -26690,7 +26849,9 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26690
26849
|
});
|
|
26691
26850
|
wssPrimary.on("connection", handleConnection);
|
|
26692
26851
|
if (wssSecondary) wssSecondary.on("connection", handleConnection);
|
|
26693
|
-
|
|
26852
|
+
let unregisterShutdown = () => {
|
|
26853
|
+
};
|
|
26854
|
+
unregisterShutdown = registerShutdown({
|
|
26694
26855
|
flushSession: async () => {
|
|
26695
26856
|
await session.append({
|
|
26696
26857
|
type: "session_end",
|
|
@@ -26706,7 +26867,12 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26706
26867
|
wssPrimary,
|
|
26707
26868
|
...wssSecondary ? [wssSecondary] : []
|
|
26708
26869
|
],
|
|
26870
|
+
onPreShutdown: () => {
|
|
26871
|
+
kanbanSupervisorDispose?.();
|
|
26872
|
+
kanbanSupervisorDispose = null;
|
|
26873
|
+
},
|
|
26709
26874
|
onShutdown: async () => {
|
|
26875
|
+
unregisterShutdown();
|
|
26710
26876
|
await todosCheckpoint.detach();
|
|
26711
26877
|
await stopHeapWatchdog();
|
|
26712
26878
|
credentialWatcherClose?.();
|