@wrongstack/webui-server 0.308.0 → 0.308.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 +700 -67
- package/dist/protocol/client-integrations.d.ts +1 -1
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/server-integrations.d.ts +1 -1
- package/dist/protocol/server-workspace.d.ts +1 -1
- package/dist/server/connections/auto-healer.d.ts +85 -0
- package/dist/server/embedded-lifecycle.d.ts +1 -0
- package/dist/server/embedded-message-router.d.ts +8 -0
- package/dist/server/entry.js +671 -64
- package/dist/server/frontend-static-serve.d.ts +6 -0
- package/dist/server/http-server/api-router.d.ts +7 -0
- package/dist/server/http-server/vector-memory-handlers.d.ts +53 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/memory-handlers.d.ts +19 -0
- package/dist/server/port-utils.d.ts +23 -3
- package/dist/server/start-webui-companion.d.ts +9 -1
- package/dist/server/start-webui-shutdown.d.ts +6 -1
- package/package.json +13 -12
package/dist/index.js
CHANGED
|
@@ -7376,11 +7376,11 @@ async function handleGitDiff(ws, projectRoot, path39) {
|
|
|
7376
7376
|
try {
|
|
7377
7377
|
const git = makeGit(cwd);
|
|
7378
7378
|
const { readFile: readFile13 } = await import("node:fs/promises");
|
|
7379
|
-
const { join:
|
|
7379
|
+
const { join: join19 } = await import("node:path");
|
|
7380
7380
|
const oldText = await git(["show", `HEAD:${path39}`]);
|
|
7381
7381
|
let newText = "";
|
|
7382
7382
|
try {
|
|
7383
|
-
const abs = cwd ?
|
|
7383
|
+
const abs = cwd ? join19(cwd, path39) : path39;
|
|
7384
7384
|
const buf = await readFile13(abs);
|
|
7385
7385
|
if (buf.includes(0)) {
|
|
7386
7386
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -10411,6 +10411,7 @@ function strictDecodeParam(segment, res) {
|
|
|
10411
10411
|
}
|
|
10412
10412
|
|
|
10413
10413
|
// src/server/http-server/vector-memory-handlers.ts
|
|
10414
|
+
import { getSageSurface } from "@wrongstack/sage";
|
|
10414
10415
|
import { sanitizeApiError as sanitizeApiError2 } from "@wrongstack/core/security";
|
|
10415
10416
|
function snapshotVectorMemory(store, opts = {}) {
|
|
10416
10417
|
const stats = store.stats();
|
|
@@ -10420,6 +10421,9 @@ function snapshotVectorMemory(store, opts = {}) {
|
|
|
10420
10421
|
stats
|
|
10421
10422
|
};
|
|
10422
10423
|
}
|
|
10424
|
+
function snapshotVectorMemoryCache(store) {
|
|
10425
|
+
return store.cacheStats();
|
|
10426
|
+
}
|
|
10423
10427
|
async function handleVectorMemoryStatus(res, getStore, opts = {}) {
|
|
10424
10428
|
const store = getStore();
|
|
10425
10429
|
if (!store) {
|
|
@@ -10439,7 +10443,8 @@ async function handleVectorMemoryStatus(res, getStore, opts = {}) {
|
|
|
10439
10443
|
dimensions: snap.stats.dimensions,
|
|
10440
10444
|
entries: snap.stats.entries,
|
|
10441
10445
|
vectors: snap.stats.vectors,
|
|
10442
|
-
providers: snap.stats.providers
|
|
10446
|
+
providers: snap.stats.providers,
|
|
10447
|
+
cache: snapshotVectorMemoryCache(store)
|
|
10443
10448
|
};
|
|
10444
10449
|
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10445
10450
|
res.end(JSON.stringify(body));
|
|
@@ -10459,7 +10464,29 @@ function parseSearchParams(url) {
|
|
|
10459
10464
|
const limit = Math.min(50, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 10));
|
|
10460
10465
|
const rawThreshold = url.searchParams.get("threshold");
|
|
10461
10466
|
const threshold = rawThreshold !== null && rawThreshold !== "" ? Math.max(0, Math.min(1, Number.parseFloat(rawThreshold))) : void 0;
|
|
10462
|
-
|
|
10467
|
+
const similarity = url.searchParams.get("similarity") === "1";
|
|
10468
|
+
return { query, limit, threshold: Number.isFinite(threshold) ? threshold : void 0, similarity };
|
|
10469
|
+
}
|
|
10470
|
+
function cosineMatrix(vectors) {
|
|
10471
|
+
const n = vectors.length;
|
|
10472
|
+
const out = new Array(n);
|
|
10473
|
+
for (let i = 0; i < n; i++) {
|
|
10474
|
+
out[i] = new Array(n).fill(0);
|
|
10475
|
+
}
|
|
10476
|
+
for (let i = 0; i < n; i++) {
|
|
10477
|
+
out[i][i] = 1;
|
|
10478
|
+
for (let j = i + 1; j < n; j++) {
|
|
10479
|
+
const a = vectors[i];
|
|
10480
|
+
const b = vectors[j];
|
|
10481
|
+
let dot = 0;
|
|
10482
|
+
const len = Math.min(a.length, b.length);
|
|
10483
|
+
for (let k = 0; k < len; k++) dot += (a[k] ?? 0) * (b[k] ?? 0);
|
|
10484
|
+
const score = Math.max(0, Math.min(1, dot));
|
|
10485
|
+
out[i][j] = score;
|
|
10486
|
+
out[j][i] = score;
|
|
10487
|
+
}
|
|
10488
|
+
}
|
|
10489
|
+
return out;
|
|
10463
10490
|
}
|
|
10464
10491
|
async function handleVectorMemorySearch(res, url, getStore) {
|
|
10465
10492
|
const store = getStore();
|
|
@@ -10468,7 +10495,7 @@ async function handleVectorMemorySearch(res, url, getStore) {
|
|
|
10468
10495
|
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10469
10496
|
return;
|
|
10470
10497
|
}
|
|
10471
|
-
const { query, limit, threshold } = parseSearchParams(url);
|
|
10498
|
+
const { query, limit, threshold, similarity } = parseSearchParams(url);
|
|
10472
10499
|
if (query.trim().length === 0) {
|
|
10473
10500
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10474
10501
|
res.end(JSON.stringify({ error: "Missing required query parameter `q`" }));
|
|
@@ -10477,7 +10504,8 @@ async function handleVectorMemorySearch(res, url, getStore) {
|
|
|
10477
10504
|
try {
|
|
10478
10505
|
const hits = await store.search(query, {
|
|
10479
10506
|
limit,
|
|
10480
|
-
...threshold !== void 0 ? { threshold } : {}
|
|
10507
|
+
...threshold !== void 0 ? { threshold } : {},
|
|
10508
|
+
includeVectors: similarity
|
|
10481
10509
|
});
|
|
10482
10510
|
const body = {
|
|
10483
10511
|
hits: hits.map((h) => ({
|
|
@@ -10489,6 +10517,12 @@ async function handleVectorMemorySearch(res, url, getStore) {
|
|
|
10489
10517
|
})),
|
|
10490
10518
|
count: hits.length
|
|
10491
10519
|
};
|
|
10520
|
+
if (similarity && hits.length > 1) {
|
|
10521
|
+
const vecs = hits.map((h) => h.vector).filter((v) => v !== void 0);
|
|
10522
|
+
if (vecs.length === hits.length) {
|
|
10523
|
+
body.similarity = cosineMatrix(vecs);
|
|
10524
|
+
}
|
|
10525
|
+
}
|
|
10492
10526
|
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10493
10527
|
res.end(JSON.stringify(body));
|
|
10494
10528
|
} catch (error2) {
|
|
@@ -10573,7 +10607,7 @@ async function handleVectorMemoryForget(res, url, getStore) {
|
|
|
10573
10607
|
const id = match ? strictDecodeParam(decodeSessionId(match[1]), res) : null;
|
|
10574
10608
|
if (id === null) return;
|
|
10575
10609
|
try {
|
|
10576
|
-
const removed = store.forget(id);
|
|
10610
|
+
const removed = await store.forget(id);
|
|
10577
10611
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10578
10612
|
res.end(JSON.stringify({ removed }));
|
|
10579
10613
|
} catch (error2) {
|
|
@@ -10586,6 +10620,86 @@ async function handleVectorMemoryForget(res, url, getStore) {
|
|
|
10586
10620
|
);
|
|
10587
10621
|
}
|
|
10588
10622
|
}
|
|
10623
|
+
function parseMemorySearchParams(url) {
|
|
10624
|
+
const query = url.searchParams.get("q") ?? "";
|
|
10625
|
+
const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "20", 10);
|
|
10626
|
+
const limit = Math.min(50, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 20));
|
|
10627
|
+
const explain = url.searchParams.get("explain") === "1";
|
|
10628
|
+
return { query, limit, explain };
|
|
10629
|
+
}
|
|
10630
|
+
async function handleMemorySearch(res, url, getStore) {
|
|
10631
|
+
const store = getStore();
|
|
10632
|
+
if (!store) {
|
|
10633
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10634
|
+
res.end(JSON.stringify({ error: "Memory store not enabled in this host" }));
|
|
10635
|
+
return;
|
|
10636
|
+
}
|
|
10637
|
+
const { query, limit, explain } = parseMemorySearchParams(url);
|
|
10638
|
+
if (query.trim().length === 0) {
|
|
10639
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10640
|
+
res.end(JSON.stringify({ error: "Missing required query parameter `q`" }));
|
|
10641
|
+
return;
|
|
10642
|
+
}
|
|
10643
|
+
const Sage = getSageSurface(store);
|
|
10644
|
+
if (!Sage) {
|
|
10645
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10646
|
+
res.end(
|
|
10647
|
+
JSON.stringify({
|
|
10648
|
+
error: "Memory search requires the SAGE surface (this host does not expose it)."
|
|
10649
|
+
})
|
|
10650
|
+
);
|
|
10651
|
+
return;
|
|
10652
|
+
}
|
|
10653
|
+
try {
|
|
10654
|
+
let payload;
|
|
10655
|
+
if (explain && typeof Sage.searchSageWithBreakdown === "function") {
|
|
10656
|
+
const hits = await Sage.searchSageWithBreakdown(query, { limit });
|
|
10657
|
+
payload = {
|
|
10658
|
+
count: hits.length,
|
|
10659
|
+
channel: "breakdown",
|
|
10660
|
+
hits: hits.map((h) => ({
|
|
10661
|
+
id: h.memory.id,
|
|
10662
|
+
text: h.memory.text,
|
|
10663
|
+
kind: h.memory.kind,
|
|
10664
|
+
status: h.memory.status,
|
|
10665
|
+
tags: h.memory.tags ?? [],
|
|
10666
|
+
lexicalScore: h.lexicalScore,
|
|
10667
|
+
vectorScore: h.vectorScore,
|
|
10668
|
+
finalScore: h.finalScore,
|
|
10669
|
+
source: h.source
|
|
10670
|
+
}))
|
|
10671
|
+
};
|
|
10672
|
+
} else {
|
|
10673
|
+
const rows = await Sage.searchSage(query, { limit });
|
|
10674
|
+
const total = rows.length;
|
|
10675
|
+
payload = {
|
|
10676
|
+
count: total,
|
|
10677
|
+
channel: "lexical",
|
|
10678
|
+
hits: rows.map((memory, index) => ({
|
|
10679
|
+
id: memory.id,
|
|
10680
|
+
text: memory.text,
|
|
10681
|
+
kind: memory.kind,
|
|
10682
|
+
status: memory.status,
|
|
10683
|
+
tags: memory.tags ?? [],
|
|
10684
|
+
lexicalScore: total <= 1 ? 1 : 1 - index / Math.max(1, total - 1),
|
|
10685
|
+
vectorScore: null,
|
|
10686
|
+
finalScore: total <= 1 ? 1 : 1 - index / Math.max(1, total - 1),
|
|
10687
|
+
source: "lexical"
|
|
10688
|
+
}))
|
|
10689
|
+
};
|
|
10690
|
+
}
|
|
10691
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10692
|
+
res.end(JSON.stringify(payload));
|
|
10693
|
+
} catch (error2) {
|
|
10694
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10695
|
+
res.end(
|
|
10696
|
+
JSON.stringify({
|
|
10697
|
+
error: "Memory search failed",
|
|
10698
|
+
detail: sanitizeApiError2(error2)
|
|
10699
|
+
})
|
|
10700
|
+
);
|
|
10701
|
+
}
|
|
10702
|
+
}
|
|
10589
10703
|
|
|
10590
10704
|
// src/server/http-server/api-router.ts
|
|
10591
10705
|
async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessTokenOk, getTechStackRuntime) {
|
|
@@ -11036,6 +11150,15 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
11036
11150
|
await handleVectorMemoryStore(res, req, () => deps2.getVectorMemoryStore?.());
|
|
11037
11151
|
return true;
|
|
11038
11152
|
}
|
|
11153
|
+
if (url.pathname === "/api/memory/search" && req.method === "GET") {
|
|
11154
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11155
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11156
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11157
|
+
return true;
|
|
11158
|
+
}
|
|
11159
|
+
await handleMemorySearch(res, url, () => deps2.getMemoryStore?.());
|
|
11160
|
+
return true;
|
|
11161
|
+
}
|
|
11039
11162
|
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
11040
11163
|
await handleDeadCodeActionPlan(
|
|
11041
11164
|
res,
|
|
@@ -11200,12 +11323,15 @@ function createHttpServer(opts) {
|
|
|
11200
11323
|
distDir,
|
|
11201
11324
|
url,
|
|
11202
11325
|
opts,
|
|
11203
|
-
port
|
|
11326
|
+
// Live port from the socket: the bind may have advanced past an
|
|
11327
|
+
// EADDRINUSE (listenWithRetry) after this server was constructed,
|
|
11328
|
+
// and the CSP must advertise the port actually serving this request.
|
|
11329
|
+
res.socket?.localPort ?? port,
|
|
11204
11330
|
shouldSetAuthCookie
|
|
11205
11331
|
);
|
|
11206
11332
|
} catch (err) {
|
|
11207
11333
|
if (err.code === "ENOENT") {
|
|
11208
|
-
await handleSpaFallback(res, distDir, opts, port);
|
|
11334
|
+
await handleSpaFallback(res, distDir, opts, res.socket?.localPort ?? port);
|
|
11209
11335
|
} else {
|
|
11210
11336
|
console.error({ url: req.url, err });
|
|
11211
11337
|
res.writeHead(500);
|
|
@@ -14136,13 +14262,13 @@ async function handleMcpRoute(ws, msg, handlers) {
|
|
|
14136
14262
|
}
|
|
14137
14263
|
|
|
14138
14264
|
// src/server/memory-handlers.ts
|
|
14139
|
-
import { getSageSurface } from "@wrongstack/sage";
|
|
14265
|
+
import { getSageSurface as getSageSurface2 } from "@wrongstack/sage";
|
|
14140
14266
|
function requiresSage(command) {
|
|
14141
14267
|
return `\`${command}\` requires the SAGE backend (Sage.enabled).`;
|
|
14142
14268
|
}
|
|
14143
14269
|
async function handleMemoryList(ws, memoryStore) {
|
|
14144
14270
|
try {
|
|
14145
|
-
const Sage =
|
|
14271
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14146
14272
|
if (Sage) {
|
|
14147
14273
|
const [stats, memories] = await Promise.all([Sage.stats(), Sage.listSage()]);
|
|
14148
14274
|
const text3 = memories.length === 0 ? "\u{1F9E0} SAGE is empty." : formatSageText(stats, memories);
|
|
@@ -14177,7 +14303,7 @@ function formatSageText(stats, memories) {
|
|
|
14177
14303
|
return lines.join("\n");
|
|
14178
14304
|
}
|
|
14179
14305
|
async function handleSageList(ws, memoryStore) {
|
|
14180
|
-
const Sage =
|
|
14306
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14181
14307
|
if (!Sage) {
|
|
14182
14308
|
send(ws, {
|
|
14183
14309
|
type: "memory.sage.list",
|
|
@@ -14193,7 +14319,7 @@ async function handleSageList(ws, memoryStore) {
|
|
|
14193
14319
|
}
|
|
14194
14320
|
}
|
|
14195
14321
|
async function handleSageListPage(ws, msg, memoryStore) {
|
|
14196
|
-
const Sage =
|
|
14322
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14197
14323
|
if (!Sage) {
|
|
14198
14324
|
send(ws, {
|
|
14199
14325
|
type: "memory.sage.listPage",
|
|
@@ -14243,8 +14369,50 @@ async function handleSageListPage(ws, msg, memoryStore) {
|
|
|
14243
14369
|
send(ws, { type: "memory.sage.listPage", payload: { error: errMessage(err) } });
|
|
14244
14370
|
}
|
|
14245
14371
|
}
|
|
14372
|
+
async function handleSageSearchBreakdown(ws, msg, memoryStore) {
|
|
14373
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14374
|
+
if (!Sage) {
|
|
14375
|
+
send(ws, {
|
|
14376
|
+
type: "memory.sage.searchBreakdown",
|
|
14377
|
+
payload: { error: requiresSage("memory.sage.searchBreakdown") }
|
|
14378
|
+
});
|
|
14379
|
+
return;
|
|
14380
|
+
}
|
|
14381
|
+
try {
|
|
14382
|
+
const payload = msg.payload ?? {};
|
|
14383
|
+
const query = typeof payload["query"] === "string" ? payload["query"] : "";
|
|
14384
|
+
if (query.trim().length === 0) {
|
|
14385
|
+
send(ws, {
|
|
14386
|
+
type: "memory.sage.searchBreakdown",
|
|
14387
|
+
payload: { error: "Missing required field `query`" }
|
|
14388
|
+
});
|
|
14389
|
+
return;
|
|
14390
|
+
}
|
|
14391
|
+
const limit = typeof payload["limit"] === "number" ? payload["limit"] : 20;
|
|
14392
|
+
const includeStale = payload["includeStale"] === true;
|
|
14393
|
+
const includeStatuses = includeStale ? ["active", "stale"] : ["active"];
|
|
14394
|
+
const options = { limit, includeStatuses };
|
|
14395
|
+
if (typeof Sage.searchSageWithBreakdown === "function") {
|
|
14396
|
+
const hits2 = await Sage.searchSageWithBreakdown(query, options);
|
|
14397
|
+
send(ws, { type: "memory.sage.searchBreakdown", payload: { hits: hits2, source: "breakdown" } });
|
|
14398
|
+
return;
|
|
14399
|
+
}
|
|
14400
|
+
const rows = await Sage.searchSage(query, options);
|
|
14401
|
+
const total = rows.length;
|
|
14402
|
+
const hits = rows.map((memory, index) => ({
|
|
14403
|
+
memory,
|
|
14404
|
+
vectorScore: null,
|
|
14405
|
+
lexicalScore: total <= 1 ? 1 : 1 - index / Math.max(1, total - 1),
|
|
14406
|
+
finalScore: total <= 1 ? 1 : 1 - index / Math.max(1, total - 1),
|
|
14407
|
+
source: "lexical"
|
|
14408
|
+
}));
|
|
14409
|
+
send(ws, { type: "memory.sage.searchBreakdown", payload: { hits, source: "lexical" } });
|
|
14410
|
+
} catch (err) {
|
|
14411
|
+
send(ws, { type: "memory.sage.searchBreakdown", payload: { error: errMessage(err) } });
|
|
14412
|
+
}
|
|
14413
|
+
}
|
|
14246
14414
|
async function handleSageGet(ws, msg, memoryStore) {
|
|
14247
|
-
const Sage =
|
|
14415
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14248
14416
|
if (!Sage) {
|
|
14249
14417
|
send(ws, {
|
|
14250
14418
|
type: "memory.sage.get",
|
|
@@ -14269,7 +14437,7 @@ async function handleSageGet(ws, msg, memoryStore) {
|
|
|
14269
14437
|
}
|
|
14270
14438
|
}
|
|
14271
14439
|
async function handleSageGraph(ws, msg, memoryStore) {
|
|
14272
|
-
const Sage =
|
|
14440
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14273
14441
|
if (!Sage?.graphFor) {
|
|
14274
14442
|
send(ws, {
|
|
14275
14443
|
type: "memory.sage.graph",
|
|
@@ -14303,7 +14471,7 @@ async function handleSageGraph(ws, msg, memoryStore) {
|
|
|
14303
14471
|
}
|
|
14304
14472
|
}
|
|
14305
14473
|
async function handleSageUpdate(ws, msg, memoryStore) {
|
|
14306
|
-
const Sage =
|
|
14474
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14307
14475
|
if (!Sage) {
|
|
14308
14476
|
send(ws, {
|
|
14309
14477
|
type: "memory.sage.update",
|
|
@@ -14331,7 +14499,7 @@ async function handleSageUpdate(ws, msg, memoryStore) {
|
|
|
14331
14499
|
}
|
|
14332
14500
|
}
|
|
14333
14501
|
async function handleSageRemember(ws, msg, memoryStore) {
|
|
14334
|
-
const Sage =
|
|
14502
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14335
14503
|
if (!Sage) {
|
|
14336
14504
|
send(ws, {
|
|
14337
14505
|
type: "memory.sage.remember",
|
|
@@ -14365,7 +14533,7 @@ async function handleSageRemember(ws, msg, memoryStore) {
|
|
|
14365
14533
|
}
|
|
14366
14534
|
}
|
|
14367
14535
|
async function handleSageDelete(ws, msg, memoryStore) {
|
|
14368
|
-
const Sage =
|
|
14536
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14369
14537
|
if (!Sage) {
|
|
14370
14538
|
send(ws, {
|
|
14371
14539
|
type: "memory.sage.delete",
|
|
@@ -14398,7 +14566,7 @@ async function handleSageDelete(ws, msg, memoryStore) {
|
|
|
14398
14566
|
}
|
|
14399
14567
|
}
|
|
14400
14568
|
async function handleSageRecover(ws, msg, memoryStore) {
|
|
14401
|
-
const Sage =
|
|
14569
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14402
14570
|
if (!Sage?.recoverSage) {
|
|
14403
14571
|
send(ws, {
|
|
14404
14572
|
type: "memory.sage.recover",
|
|
@@ -14442,7 +14610,7 @@ async function handleSageRecover(ws, msg, memoryStore) {
|
|
|
14442
14610
|
}
|
|
14443
14611
|
}
|
|
14444
14612
|
async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
14445
|
-
const Sage =
|
|
14613
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14446
14614
|
if (!Sage) {
|
|
14447
14615
|
send(ws, {
|
|
14448
14616
|
type: "memory.sage.listCandidates",
|
|
@@ -14470,7 +14638,7 @@ async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
|
14470
14638
|
}
|
|
14471
14639
|
}
|
|
14472
14640
|
async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
14473
|
-
const Sage =
|
|
14641
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14474
14642
|
if (!Sage) {
|
|
14475
14643
|
send(ws, {
|
|
14476
14644
|
type: "memory.sage.candidateResolve",
|
|
@@ -14527,7 +14695,7 @@ async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
|
14527
14695
|
}
|
|
14528
14696
|
}
|
|
14529
14697
|
async function handleSageBackfillRecoverable(ws, msg, memoryStore) {
|
|
14530
|
-
const Sage =
|
|
14698
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14531
14699
|
if (!Sage?.backfillRecoverable) {
|
|
14532
14700
|
send(ws, {
|
|
14533
14701
|
type: "memory.sage.backfillRecoverable",
|
|
@@ -14567,7 +14735,7 @@ async function handleSageBackfillRecoverable(ws, msg, memoryStore) {
|
|
|
14567
14735
|
}
|
|
14568
14736
|
}
|
|
14569
14737
|
async function handleSageForFile(ws, msg, memoryStore) {
|
|
14570
|
-
const Sage =
|
|
14738
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14571
14739
|
if (!Sage?.findMemoriesForFile) {
|
|
14572
14740
|
send(ws, {
|
|
14573
14741
|
type: "memory.sage.forFile",
|
|
@@ -14659,6 +14827,9 @@ async function handleMemoryRoute(ctx, ws, message) {
|
|
|
14659
14827
|
case "memory.sage.forFile":
|
|
14660
14828
|
await handleSageForFile(ws, message, store);
|
|
14661
14829
|
return true;
|
|
14830
|
+
case "memory.sage.searchBreakdown":
|
|
14831
|
+
await handleSageSearchBreakdown(ws, message, store);
|
|
14832
|
+
return true;
|
|
14662
14833
|
default:
|
|
14663
14834
|
return false;
|
|
14664
14835
|
}
|
|
@@ -15049,23 +15220,31 @@ var SURFACE_DEFAULT_PORTS = {
|
|
|
15049
15220
|
webui: { http: 3456 },
|
|
15050
15221
|
simpleui: { http: 3466 }
|
|
15051
15222
|
};
|
|
15223
|
+
var MAX_TCP_PORT = 65535;
|
|
15052
15224
|
function surfaceLabel(surface) {
|
|
15053
15225
|
return surface === "webui" ? "WebUI" : "SimpleUI";
|
|
15054
15226
|
}
|
|
15227
|
+
function isStrictPort() {
|
|
15228
|
+
const value = process.env["WEBUI_STRICT_PORT"];
|
|
15229
|
+
return value === "1" || value === "true";
|
|
15230
|
+
}
|
|
15055
15231
|
function getSurfaceDefaultPorts(surface) {
|
|
15056
15232
|
return { http: SURFACE_DEFAULT_PORTS[surface].http };
|
|
15057
15233
|
}
|
|
15058
15234
|
function isPortFree(host, port) {
|
|
15235
|
+
return probePort(host, port).then((err) => err === null);
|
|
15236
|
+
}
|
|
15237
|
+
function probePort(host, port) {
|
|
15059
15238
|
return new Promise((resolve20) => {
|
|
15060
15239
|
const srv = net2.createServer();
|
|
15061
|
-
srv.once("error", () => resolve20(
|
|
15240
|
+
srv.once("error", (err) => resolve20(err));
|
|
15062
15241
|
srv.once("listening", () => {
|
|
15063
|
-
srv.close(() => resolve20(
|
|
15242
|
+
srv.close(() => resolve20(null));
|
|
15064
15243
|
});
|
|
15065
15244
|
try {
|
|
15066
15245
|
srv.listen(port, host);
|
|
15067
|
-
} catch {
|
|
15068
|
-
resolve20(
|
|
15246
|
+
} catch (err) {
|
|
15247
|
+
resolve20(err);
|
|
15069
15248
|
}
|
|
15070
15249
|
});
|
|
15071
15250
|
}
|
|
@@ -15074,7 +15253,7 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
15074
15253
|
const maxTries = opts.maxTries ?? 200;
|
|
15075
15254
|
let port = startPort;
|
|
15076
15255
|
for (let i = 0; i < maxTries; i++) {
|
|
15077
|
-
if (port >
|
|
15256
|
+
if (port > MAX_TCP_PORT) port = 1024 + port % 5e4;
|
|
15078
15257
|
if (!exclude.has(port) && await isPortFree(host, port)) {
|
|
15079
15258
|
return port;
|
|
15080
15259
|
}
|
|
@@ -15085,6 +15264,50 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
15085
15264
|
field: "port"
|
|
15086
15265
|
});
|
|
15087
15266
|
}
|
|
15267
|
+
function listenWithRetry(server, host, port, opts = {}) {
|
|
15268
|
+
const maxTries = opts.maxTries ?? 10;
|
|
15269
|
+
return new Promise((resolve20, reject) => {
|
|
15270
|
+
const canAdvance = (candidate) => candidate < MAX_TCP_PORT;
|
|
15271
|
+
const probeable = (candidate) => Number.isInteger(candidate) && candidate >= 0 && candidate <= MAX_TCP_PORT;
|
|
15272
|
+
const attempt = (candidate, remaining) => {
|
|
15273
|
+
void (async () => {
|
|
15274
|
+
if (probeable(candidate)) {
|
|
15275
|
+
const probeErr = await probePort(host, candidate);
|
|
15276
|
+
if (probeErr !== null) {
|
|
15277
|
+
if (probeErr.code === "EADDRINUSE" && remaining > 1 && canAdvance(candidate)) {
|
|
15278
|
+
attempt(candidate + 1, remaining - 1);
|
|
15279
|
+
return;
|
|
15280
|
+
}
|
|
15281
|
+
reject(probeErr);
|
|
15282
|
+
return;
|
|
15283
|
+
}
|
|
15284
|
+
}
|
|
15285
|
+
const onError = (err) => {
|
|
15286
|
+
server.off("listening", onListening);
|
|
15287
|
+
server.off("error", onError);
|
|
15288
|
+
if (err.code === "EADDRINUSE" && remaining > 1 && canAdvance(candidate)) {
|
|
15289
|
+
attempt(candidate + 1, remaining - 1);
|
|
15290
|
+
return;
|
|
15291
|
+
}
|
|
15292
|
+
reject(err);
|
|
15293
|
+
};
|
|
15294
|
+
const onListening = () => {
|
|
15295
|
+
server.off("error", onError);
|
|
15296
|
+
const address = server.address();
|
|
15297
|
+
resolve20(address && typeof address === "object" ? address.port : candidate);
|
|
15298
|
+
};
|
|
15299
|
+
server.once("error", onError);
|
|
15300
|
+
server.once("listening", onListening);
|
|
15301
|
+
try {
|
|
15302
|
+
server.listen(candidate, host);
|
|
15303
|
+
} catch (err) {
|
|
15304
|
+
onError(err);
|
|
15305
|
+
}
|
|
15306
|
+
})();
|
|
15307
|
+
};
|
|
15308
|
+
attempt(port, maxTries);
|
|
15309
|
+
});
|
|
15310
|
+
}
|
|
15088
15311
|
|
|
15089
15312
|
// src/server/frontend-static-serve.ts
|
|
15090
15313
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -15218,7 +15441,10 @@ async function startStaticServe(opts, deps2 = {}) {
|
|
|
15218
15441
|
...opts.vectorMemoryModelCacheDir ? { vectorMemoryModelCacheDir: opts.vectorMemoryModelCacheDir } : {}
|
|
15219
15442
|
});
|
|
15220
15443
|
if (!opts.deferListen) {
|
|
15221
|
-
server.
|
|
15444
|
+
const boundPort = await listenWithRetry(server, opts.host, opts.httpPort, {
|
|
15445
|
+
maxTries: opts.strictPort ? 1 : 10
|
|
15446
|
+
});
|
|
15447
|
+
return { server, port: boundPort };
|
|
15222
15448
|
}
|
|
15223
15449
|
return { server, port: opts.httpPort };
|
|
15224
15450
|
}
|
|
@@ -15351,7 +15577,7 @@ function announceWebuiReady(p) {
|
|
|
15351
15577
|
token: p.wsToken,
|
|
15352
15578
|
publicUrl: p.publicUrl
|
|
15353
15579
|
});
|
|
15354
|
-
|
|
15580
|
+
const announce = () => {
|
|
15355
15581
|
const extraUrls = formatExternalAccessUrls({
|
|
15356
15582
|
bindHost: p.host,
|
|
15357
15583
|
port: p.httpPort,
|
|
@@ -15368,7 +15594,12 @@ ${extraUrls.join("\n")}
|
|
|
15368
15594
|
${extraBlock}`
|
|
15369
15595
|
);
|
|
15370
15596
|
if (p.open) launch(openUrl);
|
|
15371
|
-
}
|
|
15597
|
+
};
|
|
15598
|
+
if (p.server.listening) {
|
|
15599
|
+
announce();
|
|
15600
|
+
return;
|
|
15601
|
+
}
|
|
15602
|
+
p.server.on("listening", announce);
|
|
15372
15603
|
}
|
|
15373
15604
|
var DEFAULT_CHILD_CLEANUP_TIMEOUT_MS = 1e4;
|
|
15374
15605
|
async function runBounded(work, timeoutMs, label, debug) {
|
|
@@ -15811,9 +16042,19 @@ async function handleBrainAsk(ctx, ws, question) {
|
|
|
15811
16042
|
risk: "medium",
|
|
15812
16043
|
fallback: "ask_human"
|
|
15813
16044
|
});
|
|
16045
|
+
const answerSessionId = ctx.getSessionId?.();
|
|
15814
16046
|
ctx.send(ws, {
|
|
15815
16047
|
type: "brain.answer",
|
|
15816
|
-
|
|
16048
|
+
// Omit sessionId when there is no session: this is a direct reply to
|
|
16049
|
+
// the asker, not a broadcast, but the client's session gate
|
|
16050
|
+
// (isActiveSessionMessage) is fail-closed on a present-but-empty
|
|
16051
|
+
// sessionId — stamping '' would hide the answer from its own asker
|
|
16052
|
+
// in an embedded host with an unbound agent context.
|
|
16053
|
+
payload: {
|
|
16054
|
+
...answerSessionId ? { sessionId: answerSessionId } : {},
|
|
16055
|
+
question: q,
|
|
16056
|
+
decision
|
|
16057
|
+
}
|
|
15817
16058
|
});
|
|
15818
16059
|
} catch (err) {
|
|
15819
16060
|
sendResult6(ctx, ws, false, `Brain consultation failed: ${toErrorMessage6(err)}`);
|
|
@@ -18299,7 +18540,12 @@ function createProviderHandlers(deps2) {
|
|
|
18299
18540
|
|
|
18300
18541
|
// src/server/session-handlers.ts
|
|
18301
18542
|
import { loadTodosCheckpoint } from "@wrongstack/core/storage";
|
|
18302
|
-
import {
|
|
18543
|
+
import {
|
|
18544
|
+
CONTEXT_WINDOW_MODE_PINNED_META_KEY,
|
|
18545
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
18546
|
+
isContextWindowModeId,
|
|
18547
|
+
resolveContextWindowPolicy
|
|
18548
|
+
} from "@wrongstack/core/types";
|
|
18303
18549
|
import { repairToolUseAdjacency as repairToolUseAdjacency2, sessionScopedPath } from "@wrongstack/core/utils";
|
|
18304
18550
|
|
|
18305
18551
|
// src/protocol/connection-fsm.ts
|
|
@@ -18459,6 +18705,7 @@ var CLIENT_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
18459
18705
|
"memory.sage.listPage",
|
|
18460
18706
|
"memory.sage.recover",
|
|
18461
18707
|
"memory.sage.remember",
|
|
18708
|
+
"memory.sage.searchBreakdown",
|
|
18462
18709
|
"memory.sage.update"
|
|
18463
18710
|
];
|
|
18464
18711
|
var CLIENT_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -18746,6 +18993,7 @@ var SERVER_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
18746
18993
|
"memory.sage.listPage",
|
|
18747
18994
|
"memory.sage.recover",
|
|
18748
18995
|
"memory.sage.remember",
|
|
18996
|
+
"memory.sage.searchBreakdown",
|
|
18749
18997
|
"memory.sage.update"
|
|
18750
18998
|
];
|
|
18751
18999
|
var SERVER_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -18882,6 +19130,7 @@ var SERVER_WORKSPACE_MESSAGE_TYPES = [
|
|
|
18882
19130
|
var SERVER_CONFIGURATION_MESSAGE_TYPES = [
|
|
18883
19131
|
"auth.oauth.status",
|
|
18884
19132
|
"codebase.index.server.shutdown_result",
|
|
19133
|
+
"connections.auto_heal_status",
|
|
18885
19134
|
"connections.health_error",
|
|
18886
19135
|
"connections.health_result",
|
|
18887
19136
|
"connections.service_action_result",
|
|
@@ -19623,7 +19872,12 @@ function createSessionHandlers(ctx) {
|
|
|
19623
19872
|
sessionScopedPath(sessionsDirectory(), next.id, ".tasks.json")
|
|
19624
19873
|
);
|
|
19625
19874
|
ctx.tokenCounter.reset?.();
|
|
19626
|
-
if (usage)
|
|
19875
|
+
if (usage) {
|
|
19876
|
+
ctx.tokenCounter.account(usage, currentConfig().model, ctx.context.provider.id);
|
|
19877
|
+
if (typeof usage.input === "number" && usage.input > 0) {
|
|
19878
|
+
ctx.context.lastRequestTokens = usage.input;
|
|
19879
|
+
}
|
|
19880
|
+
}
|
|
19627
19881
|
ctx.setSessionStartedAt?.(Date.now());
|
|
19628
19882
|
await ctx.onSessionSwapped?.(next.id);
|
|
19629
19883
|
};
|
|
@@ -19846,8 +20100,8 @@ function createSessionHandlers(ctx) {
|
|
|
19846
20100
|
return;
|
|
19847
20101
|
}
|
|
19848
20102
|
const { id } = parsed.value;
|
|
19849
|
-
let policy = resolveContextWindowPolicy({}, id);
|
|
19850
|
-
if (policy.id !== id) {
|
|
20103
|
+
let policy = resolveContextWindowPolicy({}, id, readSessionWindowTokens(ctx.context));
|
|
20104
|
+
if (!isContextWindowModeId(id) && policy.id !== id) {
|
|
19851
20105
|
const customModes = (await modeStore()).list().filter((m) => m.custom === true);
|
|
19852
20106
|
const custom = customModes.find((m) => m.id === id);
|
|
19853
20107
|
if (!custom) {
|
|
@@ -19858,6 +20112,7 @@ function createSessionHandlers(ctx) {
|
|
|
19858
20112
|
}
|
|
19859
20113
|
ctx.context.meta["contextWindowMode"] = policy.id;
|
|
19860
20114
|
ctx.context.meta["contextWindowPolicy"] = policy;
|
|
20115
|
+
ctx.context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY] = true;
|
|
19861
20116
|
result(ws, true, `Context mode switched to ${policy.id}`);
|
|
19862
20117
|
broadcastToAll({
|
|
19863
20118
|
type: "context.mode.changed",
|
|
@@ -19921,11 +20176,14 @@ function createSessionHandlers(ctx) {
|
|
|
19921
20176
|
}
|
|
19922
20177
|
const { id } = parsed.value;
|
|
19923
20178
|
if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
|
|
19924
|
-
|
|
19925
|
-
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy(
|
|
20179
|
+
const policy = resolveContextWindowPolicy(
|
|
19926
20180
|
{},
|
|
19927
|
-
DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
20181
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
20182
|
+
readSessionWindowTokens(ctx.context)
|
|
19928
20183
|
);
|
|
20184
|
+
ctx.context.meta["contextWindowMode"] = policy.id;
|
|
20185
|
+
ctx.context.meta["contextWindowPolicy"] = policy;
|
|
20186
|
+
delete ctx.context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY];
|
|
19929
20187
|
}
|
|
19930
20188
|
const store = await modeStore();
|
|
19931
20189
|
const operation = store.remove(id);
|
|
@@ -20132,6 +20390,12 @@ function createSessionHandlers(ctx) {
|
|
|
20132
20390
|
}
|
|
20133
20391
|
};
|
|
20134
20392
|
}
|
|
20393
|
+
function readSessionWindowTokens(context) {
|
|
20394
|
+
const meta = context.meta?.["effectiveMaxContext"];
|
|
20395
|
+
if (typeof meta === "number" && Number.isFinite(meta) && meta > 0) return meta;
|
|
20396
|
+
const cap = context.provider?.capabilities?.maxContext;
|
|
20397
|
+
return typeof cap === "number" && Number.isFinite(cap) && cap > 0 ? cap : 0;
|
|
20398
|
+
}
|
|
20135
20399
|
|
|
20136
20400
|
// src/server/embedded-host-adapters.ts
|
|
20137
20401
|
async function applyEmbeddedModelSwitch(ctx, providerId, modelId) {
|
|
@@ -20794,6 +21058,259 @@ async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
|
20794
21058
|
return true;
|
|
20795
21059
|
}
|
|
20796
21060
|
|
|
21061
|
+
// src/server/connections/auto-healer.ts
|
|
21062
|
+
var AUTO_HEAL_ENV_FLAG = "WRONGSTACK_AUTO_HEAL_SERVICES";
|
|
21063
|
+
var AUTO_HEAL_DEFAULT_INTERVAL_MS = 3e4;
|
|
21064
|
+
var AUTO_HEAL_DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
21065
|
+
var AUTO_HEAL_DEFAULT_MAX_ATTEMPTS = 3;
|
|
21066
|
+
var RESTARTABLE_SERVICE_IDS = /* @__PURE__ */ new Set([
|
|
21067
|
+
"kanban",
|
|
21068
|
+
"sage",
|
|
21069
|
+
"chronicle",
|
|
21070
|
+
"codebase-index",
|
|
21071
|
+
"mailbox"
|
|
21072
|
+
]);
|
|
21073
|
+
function isAutoHealEnabled() {
|
|
21074
|
+
return process.env[AUTO_HEAL_ENV_FLAG] === "1";
|
|
21075
|
+
}
|
|
21076
|
+
function createAutoHealer(options) {
|
|
21077
|
+
const enabled = options.enabled ?? isAutoHealEnabled();
|
|
21078
|
+
const intervalMs = options.intervalMs ?? AUTO_HEAL_DEFAULT_INTERVAL_MS;
|
|
21079
|
+
const cooldownMs = options.cooldownMs ?? AUTO_HEAL_DEFAULT_COOLDOWN_MS;
|
|
21080
|
+
const maxAttempts = options.maxAttempts ?? AUTO_HEAL_DEFAULT_MAX_ATTEMPTS;
|
|
21081
|
+
const collect = options.collect ?? (() => collectConnectionsHealth({
|
|
21082
|
+
projectRoot: options.projectRoot(),
|
|
21083
|
+
indexDir: options.indexDir(),
|
|
21084
|
+
backend: "standalone"
|
|
21085
|
+
}));
|
|
21086
|
+
const execute = options.execute ?? executeServiceAction;
|
|
21087
|
+
const services = /* @__PURE__ */ new Map();
|
|
21088
|
+
let timer = null;
|
|
21089
|
+
let running = false;
|
|
21090
|
+
let ticking = false;
|
|
21091
|
+
let disposed = false;
|
|
21092
|
+
let inFlightTick = null;
|
|
21093
|
+
let lastTickAt = null;
|
|
21094
|
+
let warnedNoBoundary = false;
|
|
21095
|
+
function stateFor(serviceId) {
|
|
21096
|
+
let state = services.get(serviceId);
|
|
21097
|
+
if (!state) {
|
|
21098
|
+
state = {
|
|
21099
|
+
lastAttemptAt: null,
|
|
21100
|
+
consecutiveFailures: 0,
|
|
21101
|
+
lastSuccess: null,
|
|
21102
|
+
lastMessage: null,
|
|
21103
|
+
inFlight: false,
|
|
21104
|
+
escalated: false
|
|
21105
|
+
};
|
|
21106
|
+
services.set(serviceId, state);
|
|
21107
|
+
}
|
|
21108
|
+
return state;
|
|
21109
|
+
}
|
|
21110
|
+
function snapshot() {
|
|
21111
|
+
return {
|
|
21112
|
+
enabled,
|
|
21113
|
+
running,
|
|
21114
|
+
lastTickAt,
|
|
21115
|
+
services: Object.fromEntries(services)
|
|
21116
|
+
};
|
|
21117
|
+
}
|
|
21118
|
+
function emitStatus(event) {
|
|
21119
|
+
try {
|
|
21120
|
+
options.onStatus?.({ ...event, at: Date.now() });
|
|
21121
|
+
} catch (error2) {
|
|
21122
|
+
options.logger?.warn?.(
|
|
21123
|
+
`[AutoHeal] onStatus hook threw: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21124
|
+
);
|
|
21125
|
+
}
|
|
21126
|
+
}
|
|
21127
|
+
async function tick() {
|
|
21128
|
+
if (!enabled || disposed) return snapshot();
|
|
21129
|
+
if (!options.trustBoundary) {
|
|
21130
|
+
if (!warnedNoBoundary) {
|
|
21131
|
+
warnedNoBoundary = true;
|
|
21132
|
+
options.logger?.warn?.(
|
|
21133
|
+
"[AutoHeal] Disabled: no policy authority (trust boundary) is configured."
|
|
21134
|
+
);
|
|
21135
|
+
}
|
|
21136
|
+
return snapshot();
|
|
21137
|
+
}
|
|
21138
|
+
if (ticking) return snapshot();
|
|
21139
|
+
ticking = true;
|
|
21140
|
+
try {
|
|
21141
|
+
const report = await collect();
|
|
21142
|
+
const now = Date.now();
|
|
21143
|
+
const projectRoot = options.projectRoot();
|
|
21144
|
+
const indexDir = options.indexDir();
|
|
21145
|
+
for (const service of report.services) {
|
|
21146
|
+
if (disposed) break;
|
|
21147
|
+
const state = stateFor(service.id);
|
|
21148
|
+
if (service.status !== "error") {
|
|
21149
|
+
state.consecutiveFailures = 0;
|
|
21150
|
+
state.escalated = false;
|
|
21151
|
+
continue;
|
|
21152
|
+
}
|
|
21153
|
+
if (!RESTARTABLE_SERVICE_IDS.has(service.id) || service.control === "none") {
|
|
21154
|
+
continue;
|
|
21155
|
+
}
|
|
21156
|
+
if (state.lastAttemptAt !== null && now - state.lastAttemptAt < cooldownMs) continue;
|
|
21157
|
+
if (state.consecutiveFailures >= maxAttempts) {
|
|
21158
|
+
state.escalated = true;
|
|
21159
|
+
options.logger?.warn?.(
|
|
21160
|
+
`[AutoHeal] ${service.id} left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage ?? "unknown"}`
|
|
21161
|
+
);
|
|
21162
|
+
continue;
|
|
21163
|
+
}
|
|
21164
|
+
if (state.inFlight) continue;
|
|
21165
|
+
const authorization = await authorizeWebUIAction(
|
|
21166
|
+
options.trustBoundary,
|
|
21167
|
+
{
|
|
21168
|
+
capability: "connections.service.restart",
|
|
21169
|
+
subject: { kind: "process", id: `${service.id}@${projectRoot}` },
|
|
21170
|
+
risk: "elevated",
|
|
21171
|
+
cwd: projectRoot,
|
|
21172
|
+
metadata: { transport: "auto-heal", serviceId: service.id, action: "restart" }
|
|
21173
|
+
},
|
|
21174
|
+
options.logger
|
|
21175
|
+
);
|
|
21176
|
+
if (disposed) break;
|
|
21177
|
+
if (!authorization.allowed) {
|
|
21178
|
+
state.lastAttemptAt = now;
|
|
21179
|
+
state.lastMessage = `refused by policy: ${authorization.reason}`;
|
|
21180
|
+
emitStatus({
|
|
21181
|
+
serviceId: service.id,
|
|
21182
|
+
phase: "refused",
|
|
21183
|
+
message: `refused by policy: ${authorization.reason}`,
|
|
21184
|
+
attempt: state.consecutiveFailures + 1
|
|
21185
|
+
});
|
|
21186
|
+
options.logger?.warn?.(
|
|
21187
|
+
`[AutoHeal] ${service.id} restart refused by policy: ${authorization.reason}`
|
|
21188
|
+
);
|
|
21189
|
+
continue;
|
|
21190
|
+
}
|
|
21191
|
+
state.inFlight = true;
|
|
21192
|
+
const attempt = state.consecutiveFailures + 1;
|
|
21193
|
+
emitStatus({
|
|
21194
|
+
serviceId: service.id,
|
|
21195
|
+
phase: "restarting",
|
|
21196
|
+
message: `Auto-restarting ${service.id}`,
|
|
21197
|
+
attempt
|
|
21198
|
+
});
|
|
21199
|
+
try {
|
|
21200
|
+
const result = await execute(service.id, "restart", projectRoot, indexDir);
|
|
21201
|
+
state.consecutiveFailures = result.success ? 0 : state.consecutiveFailures + 1;
|
|
21202
|
+
state.lastSuccess = result.success;
|
|
21203
|
+
state.lastMessage = result.message;
|
|
21204
|
+
emitStatus({
|
|
21205
|
+
serviceId: service.id,
|
|
21206
|
+
phase: result.success ? "restarted" : "failed",
|
|
21207
|
+
message: result.message,
|
|
21208
|
+
attempt
|
|
21209
|
+
});
|
|
21210
|
+
if (!result.success && state.consecutiveFailures >= maxAttempts) {
|
|
21211
|
+
state.escalated = true;
|
|
21212
|
+
emitStatus({
|
|
21213
|
+
serviceId: service.id,
|
|
21214
|
+
phase: "escalated",
|
|
21215
|
+
message: `left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${result.message}`,
|
|
21216
|
+
attempt
|
|
21217
|
+
});
|
|
21218
|
+
options.logger?.warn?.(
|
|
21219
|
+
`[AutoHeal] ${service.id} escalated after ${state.consecutiveFailures} failed auto-restart(s): ${result.message}`
|
|
21220
|
+
);
|
|
21221
|
+
}
|
|
21222
|
+
options.logger?.[result.success ? "info" : "warn"]?.(
|
|
21223
|
+
`[AutoHeal] ${service.id} auto-restart ${result.success ? "succeeded" : "failed"}: ${result.message}`
|
|
21224
|
+
);
|
|
21225
|
+
} catch (error2) {
|
|
21226
|
+
state.consecutiveFailures += 1;
|
|
21227
|
+
state.lastSuccess = false;
|
|
21228
|
+
state.lastMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
21229
|
+
emitStatus({
|
|
21230
|
+
serviceId: service.id,
|
|
21231
|
+
phase: "failed",
|
|
21232
|
+
message: state.lastMessage,
|
|
21233
|
+
attempt
|
|
21234
|
+
});
|
|
21235
|
+
if (state.consecutiveFailures >= maxAttempts) {
|
|
21236
|
+
state.escalated = true;
|
|
21237
|
+
emitStatus({
|
|
21238
|
+
serviceId: service.id,
|
|
21239
|
+
phase: "escalated",
|
|
21240
|
+
message: `left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage}`,
|
|
21241
|
+
attempt
|
|
21242
|
+
});
|
|
21243
|
+
options.logger?.warn?.(
|
|
21244
|
+
`[AutoHeal] ${service.id} escalated after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage}`
|
|
21245
|
+
);
|
|
21246
|
+
}
|
|
21247
|
+
options.logger?.warn?.(
|
|
21248
|
+
`[AutoHeal] ${service.id} auto-restart threw: ${state.lastMessage}`
|
|
21249
|
+
);
|
|
21250
|
+
} finally {
|
|
21251
|
+
state.lastAttemptAt = Date.now();
|
|
21252
|
+
state.inFlight = false;
|
|
21253
|
+
}
|
|
21254
|
+
}
|
|
21255
|
+
lastTickAt = Date.now();
|
|
21256
|
+
} catch (error2) {
|
|
21257
|
+
options.logger?.warn?.(
|
|
21258
|
+
`[AutoHeal] health collect failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21259
|
+
);
|
|
21260
|
+
} finally {
|
|
21261
|
+
ticking = false;
|
|
21262
|
+
}
|
|
21263
|
+
return snapshot();
|
|
21264
|
+
}
|
|
21265
|
+
function runTick() {
|
|
21266
|
+
if (!enabled || disposed || running === false || ticking) return;
|
|
21267
|
+
const pending = tick();
|
|
21268
|
+
const tracked = pending.then(
|
|
21269
|
+
() => void 0,
|
|
21270
|
+
() => void 0
|
|
21271
|
+
);
|
|
21272
|
+
inFlightTick = tracked;
|
|
21273
|
+
void tracked.finally(() => {
|
|
21274
|
+
if (inFlightTick === tracked) inFlightTick = null;
|
|
21275
|
+
});
|
|
21276
|
+
}
|
|
21277
|
+
function stopInternal() {
|
|
21278
|
+
if (timer) {
|
|
21279
|
+
clearInterval(timer);
|
|
21280
|
+
timer = null;
|
|
21281
|
+
}
|
|
21282
|
+
running = false;
|
|
21283
|
+
}
|
|
21284
|
+
return {
|
|
21285
|
+
start() {
|
|
21286
|
+
if (!enabled || running || disposed) return;
|
|
21287
|
+
running = true;
|
|
21288
|
+
runTick();
|
|
21289
|
+
timer = setInterval(runTick, intervalMs);
|
|
21290
|
+
timer.unref?.();
|
|
21291
|
+
},
|
|
21292
|
+
stop: stopInternal,
|
|
21293
|
+
async dispose() {
|
|
21294
|
+
stopInternal();
|
|
21295
|
+
disposed = true;
|
|
21296
|
+
const pending = inFlightTick;
|
|
21297
|
+
if (pending) {
|
|
21298
|
+
await Promise.race([
|
|
21299
|
+
pending,
|
|
21300
|
+
new Promise((resolve20) => {
|
|
21301
|
+
const t = setTimeout(resolve20, 3e4);
|
|
21302
|
+
t.unref?.();
|
|
21303
|
+
})
|
|
21304
|
+
]);
|
|
21305
|
+
}
|
|
21306
|
+
disposed = true;
|
|
21307
|
+
},
|
|
21308
|
+
tick,
|
|
21309
|
+
getSnapshot: snapshot,
|
|
21310
|
+
isRunning: () => running
|
|
21311
|
+
};
|
|
21312
|
+
}
|
|
21313
|
+
|
|
20797
21314
|
// src/server/fallback-choice.ts
|
|
20798
21315
|
function emitFallbackChoice(events, msg) {
|
|
20799
21316
|
const parsed = validateModelFallbackChoicePayload(msg.payload);
|
|
@@ -21483,6 +22000,22 @@ async function handleShellOpen(req, logger, options) {
|
|
|
21483
22000
|
function createEmbeddedMessageRouter(deps2) {
|
|
21484
22001
|
const { opts, send: send2, sendResult: sendResult7 } = deps2;
|
|
21485
22002
|
const projectRoot = () => opts.projectRoot ?? opts.agent.ctx.projectRoot ?? "";
|
|
22003
|
+
const autoHealer = createAutoHealer({
|
|
22004
|
+
projectRoot,
|
|
22005
|
+
indexDir: () => typeof opts.agent.ctx.meta["codebaseIndexDir"] === "string" ? opts.agent.ctx.meta["codebaseIndexDir"] : void 0,
|
|
22006
|
+
trustBoundary: deps2.trustBoundary,
|
|
22007
|
+
logger: deps2.logger,
|
|
22008
|
+
onStatus: (event) => deps2.providerCtx.broadcast({
|
|
22009
|
+
type: "connections.auto_heal_status",
|
|
22010
|
+
payload: event
|
|
22011
|
+
})
|
|
22012
|
+
});
|
|
22013
|
+
autoHealer.start();
|
|
22014
|
+
if (deps2.onDispose) {
|
|
22015
|
+
deps2.onDispose(async () => {
|
|
22016
|
+
await autoHealer.dispose();
|
|
22017
|
+
});
|
|
22018
|
+
}
|
|
21486
22019
|
const terminal = async (ws, message) => {
|
|
21487
22020
|
await deps2.terminalHandler.handleMessage(ws, message).catch((error2) => {
|
|
21488
22021
|
const text2 = error2 instanceof Error ? error2.message : String(error2);
|
|
@@ -24434,6 +24967,11 @@ import {
|
|
|
24434
24967
|
wstackGlobalRoot as wstackGlobalRoot4
|
|
24435
24968
|
} from "@wrongstack/core/utils";
|
|
24436
24969
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
24970
|
+
import {
|
|
24971
|
+
TransformersEmbeddingProvider,
|
|
24972
|
+
VectorMemoryStore,
|
|
24973
|
+
startFirstBootSageSync
|
|
24974
|
+
} from "@wrongstack/vector-memory";
|
|
24437
24975
|
|
|
24438
24976
|
// src/server/backend-services.ts
|
|
24439
24977
|
import { join as join15 } from "node:path";
|
|
@@ -24461,6 +24999,7 @@ import {
|
|
|
24461
24999
|
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
24462
25000
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
24463
25001
|
import {
|
|
25002
|
+
CONTEXT_WINDOW_MODE_PINNED_META_KEY as CONTEXT_WINDOW_MODE_PINNED_META_KEY2,
|
|
24464
25003
|
DEFAULT_TOOLS_CONFIG,
|
|
24465
25004
|
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
24466
25005
|
} from "@wrongstack/core/types";
|
|
@@ -25381,22 +25920,26 @@ async function createAgentServices(input) {
|
|
|
25381
25920
|
summarizerModel: config.context?.summarizerModel,
|
|
25382
25921
|
llmSelector: config.context?.llmSelector
|
|
25383
25922
|
});
|
|
25384
|
-
|
|
25923
|
+
let effectiveMaxContext = 0;
|
|
25924
|
+
try {
|
|
25925
|
+
const m = await resolveProviderModelMetadata(
|
|
25926
|
+
modelsRegistry,
|
|
25927
|
+
config.provider,
|
|
25928
|
+
context.model,
|
|
25929
|
+
config.providers?.[config.provider]
|
|
25930
|
+
);
|
|
25931
|
+
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
25932
|
+
} catch {
|
|
25933
|
+
}
|
|
25934
|
+
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
25935
|
+
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
25936
|
+
const initialContextPolicy = resolveContextWindowPolicy2(
|
|
25937
|
+
config.context,
|
|
25938
|
+
void 0,
|
|
25939
|
+
effectiveMaxContext
|
|
25940
|
+
);
|
|
25385
25941
|
let autoCompactor;
|
|
25386
25942
|
if (config.context?.autoCompact !== false) {
|
|
25387
|
-
let effectiveMaxContext = 0;
|
|
25388
|
-
try {
|
|
25389
|
-
const m = await resolveProviderModelMetadata(
|
|
25390
|
-
modelsRegistry,
|
|
25391
|
-
config.provider,
|
|
25392
|
-
context.model,
|
|
25393
|
-
config.providers?.[config.provider]
|
|
25394
|
-
);
|
|
25395
|
-
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
25396
|
-
} catch {
|
|
25397
|
-
}
|
|
25398
|
-
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
25399
|
-
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
25400
25943
|
autoCompactor = new AutoCompactionMiddlewareCtor(
|
|
25401
25944
|
compactor,
|
|
25402
25945
|
effectiveMaxContext,
|
|
@@ -25451,6 +25994,15 @@ async function createAgentServices(input) {
|
|
|
25451
25994
|
context.meta["effectiveMaxContext"] = newMaxContext;
|
|
25452
25995
|
autoCompactor?.setMaxContext(newMaxContext);
|
|
25453
25996
|
autoCompactor?.setEnabled(config.context?.autoCompact !== false);
|
|
25997
|
+
if (context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY2] !== true) {
|
|
25998
|
+
const policy = resolveContextWindowPolicy2(
|
|
25999
|
+
currentConfig.context ?? {},
|
|
26000
|
+
void 0,
|
|
26001
|
+
newMaxContext
|
|
26002
|
+
);
|
|
26003
|
+
context.meta["contextWindowMode"] = policy.id;
|
|
26004
|
+
context.meta["contextWindowPolicy"] = policy;
|
|
26005
|
+
}
|
|
25454
26006
|
} else {
|
|
25455
26007
|
delete context.meta["effectiveMaxContext"];
|
|
25456
26008
|
autoCompactor?.setEnabled(false);
|
|
@@ -26082,8 +26634,22 @@ function createMessageDispatcher(opts) {
|
|
|
26082
26634
|
broadcast: (message) => broadcast(state.getClients(), message),
|
|
26083
26635
|
log: (message) => deps2.logger.warn?.(`[KanbanSupervisor] ${message}`)
|
|
26084
26636
|
});
|
|
26637
|
+
const autoHealer = createAutoHealer({
|
|
26638
|
+
projectRoot: () => state.getProjectRoot(),
|
|
26639
|
+
indexDir: () => typeof deps2.context.meta["codebaseIndexDir"] === "string" ? deps2.context.meta["codebaseIndexDir"] : void 0,
|
|
26640
|
+
trustBoundary: deps2.trustBoundary,
|
|
26641
|
+
logger: deps2.logger,
|
|
26642
|
+
onStatus: (event) => broadcast(state.getClients(), {
|
|
26643
|
+
type: "connections.auto_heal_status",
|
|
26644
|
+
payload: event
|
|
26645
|
+
})
|
|
26646
|
+
});
|
|
26647
|
+
autoHealer.start();
|
|
26085
26648
|
if (opts.onDispose) {
|
|
26086
|
-
const dispose = () =>
|
|
26649
|
+
const dispose = async () => {
|
|
26650
|
+
kanbanSupervisor.dispose();
|
|
26651
|
+
await autoHealer.dispose();
|
|
26652
|
+
};
|
|
26087
26653
|
opts.onDispose(dispose);
|
|
26088
26654
|
}
|
|
26089
26655
|
const kanbanContext = () => ({
|
|
@@ -26947,7 +27513,11 @@ async function createPreContextServices(input) {
|
|
|
26947
27513
|
model: config.model
|
|
26948
27514
|
});
|
|
26949
27515
|
context.meta["promptOnlineAgents"] = onlineAgents;
|
|
26950
|
-
const initialContextPolicy = resolveContextWindowPolicy3(
|
|
27516
|
+
const initialContextPolicy = resolveContextWindowPolicy3(
|
|
27517
|
+
config.context,
|
|
27518
|
+
void 0,
|
|
27519
|
+
provider.capabilities?.maxContext
|
|
27520
|
+
);
|
|
26951
27521
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
26952
27522
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
26953
27523
|
context.state.setMeta(
|
|
@@ -27386,7 +27956,7 @@ async function resolvePorts(opts) {
|
|
|
27386
27956
|
const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
|
|
27387
27957
|
const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
|
|
27388
27958
|
const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
|
|
27389
|
-
const strictPort =
|
|
27959
|
+
const strictPort = isStrictPort();
|
|
27390
27960
|
let httpPort = requestedHttpPort;
|
|
27391
27961
|
if (!strictPort) {
|
|
27392
27962
|
httpPort = await findFreePort(wsHost, requestedHttpPort);
|
|
@@ -27605,7 +28175,7 @@ function registerShutdown(deps2) {
|
|
|
27605
28175
|
|
|
27606
28176
|
// src/server/start-webui-companion.ts
|
|
27607
28177
|
import * as http2 from "node:http";
|
|
27608
|
-
function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
28178
|
+
async function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
27609
28179
|
const companion = wsHost === "127.0.0.1" ? "::1" : wsHost === "0.0.0.0" || wsHost === void 0 ? "::" : wsHost === "::" || wsHost === "[::]" ? "0.0.0.0" : null;
|
|
27610
28180
|
if (!companion) return null;
|
|
27611
28181
|
const companionLabel = companion.includes(":") ? `[${companion}]` : companion;
|
|
@@ -27616,16 +28186,23 @@ function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
|
27616
28186
|
(req, socket, head2) => httpServer.emit("upgrade", req, socket, head2)
|
|
27617
28187
|
);
|
|
27618
28188
|
companionServer.on("error", (err) => {
|
|
27619
|
-
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL"
|
|
28189
|
+
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL";
|
|
27620
28190
|
if (!expected) {
|
|
27621
28191
|
console.warn(
|
|
27622
28192
|
`[WebUI] companion listener on ${companionLabel} failed (${err.code ?? "unknown"}): ${err.message}. The primary address is unaffected.`
|
|
27623
28193
|
);
|
|
27624
28194
|
}
|
|
27625
28195
|
});
|
|
27626
|
-
|
|
27627
|
-
|
|
27628
|
-
})
|
|
28196
|
+
try {
|
|
28197
|
+
await listenWithRetry(companionServer, companion, httpPort, { maxTries: 1 });
|
|
28198
|
+
} catch (err) {
|
|
28199
|
+
const code = err?.code ?? "unknown";
|
|
28200
|
+
console.warn(
|
|
28201
|
+
`[WebUI] companion listener on ${companionLabel} not started (${code}): ${err?.message ?? err}. The primary address is unaffected.`
|
|
28202
|
+
);
|
|
28203
|
+
return null;
|
|
28204
|
+
}
|
|
28205
|
+
console.log(`[WebUI] HTTP server running on http://${companionLabel}:${httpPort}`);
|
|
27629
28206
|
return companionServer;
|
|
27630
28207
|
}
|
|
27631
28208
|
|
|
@@ -27785,7 +28362,7 @@ function setupWebuiShutdown(options) {
|
|
|
27785
28362
|
onPreShutdown: async () => {
|
|
27786
28363
|
await options.stopEmptySessionCleanup.dispose();
|
|
27787
28364
|
const disposeKanban = options.getKanbanSupervisorDispose();
|
|
27788
|
-
disposeKanban?.();
|
|
28365
|
+
await disposeKanban?.();
|
|
27789
28366
|
},
|
|
27790
28367
|
onShutdown: async () => {
|
|
27791
28368
|
unregister();
|
|
@@ -27820,6 +28397,7 @@ function setupWebuiShutdown(options) {
|
|
|
27820
28397
|
await options.memoryStore.dispose().catch(
|
|
27821
28398
|
(err) => options.logger.warn(`sage connection disposal failed: ${toErrorMessage16(err)}`)
|
|
27822
28399
|
);
|
|
28400
|
+
options.vectorMemoryStore?.close();
|
|
27823
28401
|
await unregisterInstance(process.pid, path37.dirname(options.globalConfigPath));
|
|
27824
28402
|
}
|
|
27825
28403
|
});
|
|
@@ -27884,7 +28462,8 @@ function createStandaloneTodosCheckpointLifecycle(input) {
|
|
|
27884
28462
|
async function startWebUI(opts = {}) {
|
|
27885
28463
|
ensureSessionShell();
|
|
27886
28464
|
const ports = await resolvePorts(opts);
|
|
27887
|
-
const { wsHost,
|
|
28465
|
+
const { wsHost, publicUrl, publicWsUrl, requireToken } = ports;
|
|
28466
|
+
let httpPort = ports.httpPort;
|
|
27888
28467
|
console.log("[WebUI] Starting backend services...");
|
|
27889
28468
|
const boot = await bootConfig();
|
|
27890
28469
|
const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
|
|
@@ -27912,6 +28491,27 @@ async function startWebUI(opts = {}) {
|
|
|
27912
28491
|
);
|
|
27913
28492
|
}
|
|
27914
28493
|
const needsProvider = !config.provider || !config.model;
|
|
28494
|
+
let vectorMemoryStore;
|
|
28495
|
+
const vectorMemoryModelCacheDir = path38.join(
|
|
28496
|
+
projectRoot,
|
|
28497
|
+
".wrongstack",
|
|
28498
|
+
"cache",
|
|
28499
|
+
"transformers-models"
|
|
28500
|
+
);
|
|
28501
|
+
try {
|
|
28502
|
+
vectorMemoryStore = new VectorMemoryStore({
|
|
28503
|
+
provider: new TransformersEmbeddingProvider({
|
|
28504
|
+
cacheDir: vectorMemoryModelCacheDir
|
|
28505
|
+
}),
|
|
28506
|
+
projectRoot
|
|
28507
|
+
});
|
|
28508
|
+
} catch (error2) {
|
|
28509
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
28510
|
+
logger.warn(
|
|
28511
|
+
`vector memory store disabled: ${message} \u2014 standalone WebUI will run on the SAGE-only surface.`
|
|
28512
|
+
);
|
|
28513
|
+
vectorMemoryStore = void 0;
|
|
28514
|
+
}
|
|
27915
28515
|
const preContext = await createPreContextServices({
|
|
27916
28516
|
config,
|
|
27917
28517
|
wpaths,
|
|
@@ -27959,6 +28559,13 @@ async function startWebUI(opts = {}) {
|
|
|
27959
28559
|
let sessionStartedAt = preContext.sessionStartedAt;
|
|
27960
28560
|
let modeId = preContext.modeId;
|
|
27961
28561
|
const needsSetup = preContext.needsSetup;
|
|
28562
|
+
if (vectorMemoryStore) {
|
|
28563
|
+
void startFirstBootSageSync({
|
|
28564
|
+
store: vectorMemoryStore,
|
|
28565
|
+
memoryStore,
|
|
28566
|
+
logger
|
|
28567
|
+
});
|
|
28568
|
+
}
|
|
27962
28569
|
const prefSnapshot2 = () => prefSnapshot(context.meta);
|
|
27963
28570
|
const persistPrefsToConfig2 = async (payload) => persistPrefsToConfig(prefHelperDeps, configWriteLock, payload);
|
|
27964
28571
|
const trustBoundary = opts.trustBoundary ?? createCompatibilityTrustBoundary3({ policyId: "webui-trusted-host-compat-v1" });
|
|
@@ -28079,7 +28686,12 @@ async function startWebUI(opts = {}) {
|
|
|
28079
28686
|
events,
|
|
28080
28687
|
permissionPolicy
|
|
28081
28688
|
}),
|
|
28082
|
-
distDir: opts.distDir
|
|
28689
|
+
distDir: opts.distDir,
|
|
28690
|
+
// Vector memory store — mirrors the CLI host. When `vectorMemoryStore`
|
|
28691
|
+
// construction failed (read-only FS, etc.) we still pass the getter;
|
|
28692
|
+
// it just resolves to `undefined` and the API router answers 503.
|
|
28693
|
+
getVectorMemoryStore: () => vectorMemoryStore,
|
|
28694
|
+
vectorMemoryModelCacheDir
|
|
28083
28695
|
});
|
|
28084
28696
|
const wsResult = createWsServers(httpServer, ports, accessToken);
|
|
28085
28697
|
const { wssPrimary, wssSecondary, clients } = wsResult;
|
|
@@ -28134,7 +28746,25 @@ async function startWebUI(opts = {}) {
|
|
|
28134
28746
|
},
|
|
28135
28747
|
watcherMetricsRef
|
|
28136
28748
|
);
|
|
28137
|
-
|
|
28749
|
+
const strictPort = isStrictPort();
|
|
28750
|
+
const boundPort = await listenWithRetry(httpServer, wsHost, httpPort, {
|
|
28751
|
+
maxTries: strictPort ? 1 : 10
|
|
28752
|
+
});
|
|
28753
|
+
if (boundPort !== httpPort) {
|
|
28754
|
+
console.warn(
|
|
28755
|
+
JSON.stringify({
|
|
28756
|
+
level: "warn",
|
|
28757
|
+
event: "webui.port_reassigned",
|
|
28758
|
+
protocol: "HTTP",
|
|
28759
|
+
requested: httpPort,
|
|
28760
|
+
assigned: boundPort,
|
|
28761
|
+
reason: "bind-time EADDRINUSE retry",
|
|
28762
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
28763
|
+
})
|
|
28764
|
+
);
|
|
28765
|
+
httpPort = boundPort;
|
|
28766
|
+
}
|
|
28767
|
+
{
|
|
28138
28768
|
const authHint = requireToken ? " (authentication required; configure WEBUI_TOKEN out of band)" : "";
|
|
28139
28769
|
console.log(`[WebUI] HTTP server listening on http://${wsHost}:${httpPort}${authHint}`);
|
|
28140
28770
|
const extraUrls = formatExternalAccessUrls({
|
|
@@ -28145,8 +28775,8 @@ async function startWebUI(opts = {}) {
|
|
|
28145
28775
|
if (extraUrls.length > 0) {
|
|
28146
28776
|
console.log("[WebUI] Protected endpoints on external interfaces:\n" + extraUrls.join("\n"));
|
|
28147
28777
|
}
|
|
28148
|
-
}
|
|
28149
|
-
const companionServer = setupCompanionServer(httpServer, wsHost, httpPort);
|
|
28778
|
+
}
|
|
28779
|
+
const companionServer = await setupCompanionServer(httpServer, wsHost, httpPort);
|
|
28150
28780
|
async function touchProjectEntry(root, workDir) {
|
|
28151
28781
|
const resolved = path38.resolve(root);
|
|
28152
28782
|
const manifest = await loadManifest(globalConfigPath);
|
|
@@ -28405,6 +29035,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
28405
29035
|
},
|
|
28406
29036
|
codebaseIndexing,
|
|
28407
29037
|
memoryStore,
|
|
29038
|
+
vectorMemoryStore,
|
|
28408
29039
|
globalConfigPath
|
|
28409
29040
|
});
|
|
28410
29041
|
}
|
|
@@ -28638,9 +29269,11 @@ export {
|
|
|
28638
29269
|
isPidAlive,
|
|
28639
29270
|
isPortFree,
|
|
28640
29271
|
isRegisteredMessageType,
|
|
29272
|
+
isStrictPort,
|
|
28641
29273
|
isWildcardBind,
|
|
28642
29274
|
joinSessionRegistryWithWebUIInstances,
|
|
28643
29275
|
listInstances,
|
|
29276
|
+
listenWithRetry,
|
|
28644
29277
|
loadManifest,
|
|
28645
29278
|
loadSavedProviders,
|
|
28646
29279
|
markConnectionActivity,
|