@wrongstack/webui-server 0.307.1 → 0.308.1
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 +582 -43
- package/dist/protocol/client-integrations.d.ts +1 -1
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/server-integrations.d.ts +1 -1
- package/dist/server/embedded-lifecycle.d.ts +1 -0
- package/dist/server/entry.js +565 -38
- package/dist/server/frontend-static-serve.d.ts +16 -0
- package/dist/server/http-server/api-router.d.ts +18 -0
- package/dist/server/http-server/vector-memory-handlers.d.ts +108 -0
- package/dist/server/http-server.d.ts +11 -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/server-runtime.d.ts +10 -0
- package/dist/server/start-webui-companion.d.ts +9 -1
- package/dist/server/start-webui-shutdown.d.ts +5 -0
- package/package.json +13 -11
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 });
|
|
@@ -8983,7 +8983,7 @@ function verifyClient(input) {
|
|
|
8983
8983
|
|
|
8984
8984
|
// src/server/http-server/api-router.ts
|
|
8985
8985
|
import * as v8 from "node:v8";
|
|
8986
|
-
import { sanitizeApiError as
|
|
8986
|
+
import { sanitizeApiError as sanitizeApiError3 } from "@wrongstack/core/security";
|
|
8987
8987
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
8988
8988
|
|
|
8989
8989
|
// src/server/codemap-handlers.ts
|
|
@@ -10410,6 +10410,297 @@ function strictDecodeParam(segment, res) {
|
|
|
10410
10410
|
}
|
|
10411
10411
|
}
|
|
10412
10412
|
|
|
10413
|
+
// src/server/http-server/vector-memory-handlers.ts
|
|
10414
|
+
import { getSageSurface } from "@wrongstack/sage";
|
|
10415
|
+
import { sanitizeApiError as sanitizeApiError2 } from "@wrongstack/core/security";
|
|
10416
|
+
function snapshotVectorMemory(store, opts = {}) {
|
|
10417
|
+
const stats = store.stats();
|
|
10418
|
+
return {
|
|
10419
|
+
storePath: opts.projectRoot,
|
|
10420
|
+
modelCacheDir: opts.modelCacheDir,
|
|
10421
|
+
stats
|
|
10422
|
+
};
|
|
10423
|
+
}
|
|
10424
|
+
function snapshotVectorMemoryCache(store) {
|
|
10425
|
+
return store.cacheStats();
|
|
10426
|
+
}
|
|
10427
|
+
async function handleVectorMemoryStatus(res, getStore, opts = {}) {
|
|
10428
|
+
const store = getStore();
|
|
10429
|
+
if (!store) {
|
|
10430
|
+
const body = { enabled: false };
|
|
10431
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10432
|
+
res.end(JSON.stringify(body));
|
|
10433
|
+
return;
|
|
10434
|
+
}
|
|
10435
|
+
try {
|
|
10436
|
+
const snap = snapshotVectorMemory(store, opts);
|
|
10437
|
+
const body = {
|
|
10438
|
+
enabled: true,
|
|
10439
|
+
storePath: snap.storePath,
|
|
10440
|
+
modelCacheDir: snap.modelCacheDir,
|
|
10441
|
+
providerId: snap.stats.modelId,
|
|
10442
|
+
modelId: snap.stats.modelId,
|
|
10443
|
+
dimensions: snap.stats.dimensions,
|
|
10444
|
+
entries: snap.stats.entries,
|
|
10445
|
+
vectors: snap.stats.vectors,
|
|
10446
|
+
providers: snap.stats.providers,
|
|
10447
|
+
cache: snapshotVectorMemoryCache(store)
|
|
10448
|
+
};
|
|
10449
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10450
|
+
res.end(JSON.stringify(body));
|
|
10451
|
+
} catch (error2) {
|
|
10452
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10453
|
+
res.end(
|
|
10454
|
+
JSON.stringify({
|
|
10455
|
+
error: "Vector memory status failed",
|
|
10456
|
+
detail: sanitizeApiError2(error2)
|
|
10457
|
+
})
|
|
10458
|
+
);
|
|
10459
|
+
}
|
|
10460
|
+
}
|
|
10461
|
+
function parseSearchParams(url) {
|
|
10462
|
+
const query = url.searchParams.get("q") ?? "";
|
|
10463
|
+
const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
|
|
10464
|
+
const limit = Math.min(50, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 10));
|
|
10465
|
+
const rawThreshold = url.searchParams.get("threshold");
|
|
10466
|
+
const threshold = rawThreshold !== null && rawThreshold !== "" ? Math.max(0, Math.min(1, Number.parseFloat(rawThreshold))) : void 0;
|
|
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;
|
|
10490
|
+
}
|
|
10491
|
+
async function handleVectorMemorySearch(res, url, getStore) {
|
|
10492
|
+
const store = getStore();
|
|
10493
|
+
if (!store) {
|
|
10494
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10495
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10496
|
+
return;
|
|
10497
|
+
}
|
|
10498
|
+
const { query, limit, threshold, similarity } = parseSearchParams(url);
|
|
10499
|
+
if (query.trim().length === 0) {
|
|
10500
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10501
|
+
res.end(JSON.stringify({ error: "Missing required query parameter `q`" }));
|
|
10502
|
+
return;
|
|
10503
|
+
}
|
|
10504
|
+
try {
|
|
10505
|
+
const hits = await store.search(query, {
|
|
10506
|
+
limit,
|
|
10507
|
+
...threshold !== void 0 ? { threshold } : {},
|
|
10508
|
+
includeVectors: similarity
|
|
10509
|
+
});
|
|
10510
|
+
const body = {
|
|
10511
|
+
hits: hits.map((h) => ({
|
|
10512
|
+
id: h.entry.id,
|
|
10513
|
+
score: h.score,
|
|
10514
|
+
text: h.entry.text,
|
|
10515
|
+
...h.entry.summary ? { summary: h.entry.summary } : {},
|
|
10516
|
+
tags: h.entry.tags
|
|
10517
|
+
})),
|
|
10518
|
+
count: hits.length
|
|
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
|
+
}
|
|
10526
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10527
|
+
res.end(JSON.stringify(body));
|
|
10528
|
+
} catch (error2) {
|
|
10529
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10530
|
+
res.end(
|
|
10531
|
+
JSON.stringify({
|
|
10532
|
+
error: "Vector memory search failed",
|
|
10533
|
+
detail: sanitizeApiError2(error2)
|
|
10534
|
+
})
|
|
10535
|
+
);
|
|
10536
|
+
}
|
|
10537
|
+
}
|
|
10538
|
+
function parseStoreBody(req) {
|
|
10539
|
+
return new Promise((resolve20) => {
|
|
10540
|
+
let raw = "";
|
|
10541
|
+
req.setEncoding("utf8");
|
|
10542
|
+
req.on("data", (chunk) => {
|
|
10543
|
+
raw += chunk;
|
|
10544
|
+
if (raw.length > 64 * 1024) {
|
|
10545
|
+
req.destroy();
|
|
10546
|
+
resolve20(null);
|
|
10547
|
+
}
|
|
10548
|
+
});
|
|
10549
|
+
req.on("end", () => {
|
|
10550
|
+
try {
|
|
10551
|
+
resolve20(raw ? JSON.parse(raw) : {});
|
|
10552
|
+
} catch {
|
|
10553
|
+
resolve20(null);
|
|
10554
|
+
}
|
|
10555
|
+
});
|
|
10556
|
+
req.on("error", () => resolve20(null));
|
|
10557
|
+
});
|
|
10558
|
+
}
|
|
10559
|
+
async function handleVectorMemoryStore(res, req, getStore) {
|
|
10560
|
+
const store = getStore();
|
|
10561
|
+
if (!store) {
|
|
10562
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10563
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10564
|
+
return;
|
|
10565
|
+
}
|
|
10566
|
+
const body = await parseStoreBody(req);
|
|
10567
|
+
if (!body) {
|
|
10568
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10569
|
+
res.end(JSON.stringify({ error: "Malformed JSON body" }));
|
|
10570
|
+
return;
|
|
10571
|
+
}
|
|
10572
|
+
const text2 = typeof body.text === "string" ? body.text.trim() : "";
|
|
10573
|
+
if (text2.length === 0) {
|
|
10574
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10575
|
+
res.end(JSON.stringify({ error: "Missing required field `text`" }));
|
|
10576
|
+
return;
|
|
10577
|
+
}
|
|
10578
|
+
const tags = Array.isArray(body.tags) ? body.tags.filter((t) => typeof t === "string") : [];
|
|
10579
|
+
try {
|
|
10580
|
+
const entry = await store.remember({ text: text2, tags });
|
|
10581
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10582
|
+
res.end(
|
|
10583
|
+
JSON.stringify({
|
|
10584
|
+
id: entry.id,
|
|
10585
|
+
hasVector: entry.vector !== void 0,
|
|
10586
|
+
dimensions: entry.dimensions
|
|
10587
|
+
})
|
|
10588
|
+
);
|
|
10589
|
+
} catch (error2) {
|
|
10590
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10591
|
+
res.end(
|
|
10592
|
+
JSON.stringify({
|
|
10593
|
+
error: "Vector memory store failed",
|
|
10594
|
+
detail: sanitizeApiError2(error2)
|
|
10595
|
+
})
|
|
10596
|
+
);
|
|
10597
|
+
}
|
|
10598
|
+
}
|
|
10599
|
+
async function handleVectorMemoryForget(res, url, getStore) {
|
|
10600
|
+
const store = getStore();
|
|
10601
|
+
if (!store) {
|
|
10602
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10603
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10604
|
+
return;
|
|
10605
|
+
}
|
|
10606
|
+
const match = /^\/api\/vector-memory\/store\/([^/]+)$/.exec(url.pathname);
|
|
10607
|
+
const id = match ? strictDecodeParam(decodeSessionId(match[1]), res) : null;
|
|
10608
|
+
if (id === null) return;
|
|
10609
|
+
try {
|
|
10610
|
+
const removed = await store.forget(id);
|
|
10611
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10612
|
+
res.end(JSON.stringify({ removed }));
|
|
10613
|
+
} catch (error2) {
|
|
10614
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10615
|
+
res.end(
|
|
10616
|
+
JSON.stringify({
|
|
10617
|
+
error: "Vector memory forget failed",
|
|
10618
|
+
detail: sanitizeApiError2(error2)
|
|
10619
|
+
})
|
|
10620
|
+
);
|
|
10621
|
+
}
|
|
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
|
+
}
|
|
10703
|
+
|
|
10413
10704
|
// src/server/http-server/api-router.ts
|
|
10414
10705
|
async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessTokenOk, getTechStackRuntime) {
|
|
10415
10706
|
if (url.pathname === "/api/fleet/ping" && req.method === "POST") {
|
|
@@ -10748,7 +11039,7 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
10748
11039
|
res.end(
|
|
10749
11040
|
JSON.stringify({
|
|
10750
11041
|
error: "TechStack store unavailable",
|
|
10751
|
-
detail:
|
|
11042
|
+
detail: sanitizeApiError3(error2)
|
|
10752
11043
|
})
|
|
10753
11044
|
);
|
|
10754
11045
|
return true;
|
|
@@ -10811,6 +11102,63 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
10811
11102
|
);
|
|
10812
11103
|
return true;
|
|
10813
11104
|
}
|
|
11105
|
+
const vectorForgetMatch = /^\/api\/vector-memory\/store\/([^/]+)$/.exec(url.pathname);
|
|
11106
|
+
if (vectorForgetMatch && req.method === "DELETE") {
|
|
11107
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11108
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11109
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11110
|
+
return true;
|
|
11111
|
+
}
|
|
11112
|
+
await handleVectorMemoryForget(
|
|
11113
|
+
res,
|
|
11114
|
+
url,
|
|
11115
|
+
() => deps2.getVectorMemoryStore?.()
|
|
11116
|
+
);
|
|
11117
|
+
return true;
|
|
11118
|
+
}
|
|
11119
|
+
if (url.pathname === "/api/vector-memory/status" && req.method === "GET") {
|
|
11120
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11121
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11122
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11123
|
+
return true;
|
|
11124
|
+
}
|
|
11125
|
+
await handleVectorMemoryStatus(
|
|
11126
|
+
res,
|
|
11127
|
+
() => deps2.getVectorMemoryStore?.(),
|
|
11128
|
+
{
|
|
11129
|
+
...deps2.projectRoot ? { projectRoot: deps2.projectRoot } : {},
|
|
11130
|
+
...deps2.vectorMemoryModelCacheDir ? { modelCacheDir: deps2.vectorMemoryModelCacheDir } : {}
|
|
11131
|
+
}
|
|
11132
|
+
);
|
|
11133
|
+
return true;
|
|
11134
|
+
}
|
|
11135
|
+
if (url.pathname === "/api/vector-memory/search" && req.method === "GET") {
|
|
11136
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11137
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11138
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11139
|
+
return true;
|
|
11140
|
+
}
|
|
11141
|
+
await handleVectorMemorySearch(res, url, () => deps2.getVectorMemoryStore?.());
|
|
11142
|
+
return true;
|
|
11143
|
+
}
|
|
11144
|
+
if (url.pathname === "/api/vector-memory/store" && req.method === "POST") {
|
|
11145
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11146
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11147
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11148
|
+
return true;
|
|
11149
|
+
}
|
|
11150
|
+
await handleVectorMemoryStore(res, req, () => deps2.getVectorMemoryStore?.());
|
|
11151
|
+
return true;
|
|
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
|
+
}
|
|
10814
11162
|
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
10815
11163
|
await handleDeadCodeActionPlan(
|
|
10816
11164
|
res,
|
|
@@ -10975,12 +11323,15 @@ function createHttpServer(opts) {
|
|
|
10975
11323
|
distDir,
|
|
10976
11324
|
url,
|
|
10977
11325
|
opts,
|
|
10978
|
-
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,
|
|
10979
11330
|
shouldSetAuthCookie
|
|
10980
11331
|
);
|
|
10981
11332
|
} catch (err) {
|
|
10982
11333
|
if (err.code === "ENOENT") {
|
|
10983
|
-
await handleSpaFallback(res, distDir, opts, port);
|
|
11334
|
+
await handleSpaFallback(res, distDir, opts, res.socket?.localPort ?? port);
|
|
10984
11335
|
} else {
|
|
10985
11336
|
console.error({ url: req.url, err });
|
|
10986
11337
|
res.writeHead(500);
|
|
@@ -13911,13 +14262,13 @@ async function handleMcpRoute(ws, msg, handlers) {
|
|
|
13911
14262
|
}
|
|
13912
14263
|
|
|
13913
14264
|
// src/server/memory-handlers.ts
|
|
13914
|
-
import { getSageSurface } from "@wrongstack/sage";
|
|
14265
|
+
import { getSageSurface as getSageSurface2 } from "@wrongstack/sage";
|
|
13915
14266
|
function requiresSage(command) {
|
|
13916
14267
|
return `\`${command}\` requires the SAGE backend (Sage.enabled).`;
|
|
13917
14268
|
}
|
|
13918
14269
|
async function handleMemoryList(ws, memoryStore) {
|
|
13919
14270
|
try {
|
|
13920
|
-
const Sage =
|
|
14271
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13921
14272
|
if (Sage) {
|
|
13922
14273
|
const [stats, memories] = await Promise.all([Sage.stats(), Sage.listSage()]);
|
|
13923
14274
|
const text3 = memories.length === 0 ? "\u{1F9E0} SAGE is empty." : formatSageText(stats, memories);
|
|
@@ -13952,7 +14303,7 @@ function formatSageText(stats, memories) {
|
|
|
13952
14303
|
return lines.join("\n");
|
|
13953
14304
|
}
|
|
13954
14305
|
async function handleSageList(ws, memoryStore) {
|
|
13955
|
-
const Sage =
|
|
14306
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13956
14307
|
if (!Sage) {
|
|
13957
14308
|
send(ws, {
|
|
13958
14309
|
type: "memory.sage.list",
|
|
@@ -13968,7 +14319,7 @@ async function handleSageList(ws, memoryStore) {
|
|
|
13968
14319
|
}
|
|
13969
14320
|
}
|
|
13970
14321
|
async function handleSageListPage(ws, msg, memoryStore) {
|
|
13971
|
-
const Sage =
|
|
14322
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13972
14323
|
if (!Sage) {
|
|
13973
14324
|
send(ws, {
|
|
13974
14325
|
type: "memory.sage.listPage",
|
|
@@ -14018,8 +14369,50 @@ async function handleSageListPage(ws, msg, memoryStore) {
|
|
|
14018
14369
|
send(ws, { type: "memory.sage.listPage", payload: { error: errMessage(err) } });
|
|
14019
14370
|
}
|
|
14020
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
|
+
}
|
|
14021
14414
|
async function handleSageGet(ws, msg, memoryStore) {
|
|
14022
|
-
const Sage =
|
|
14415
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14023
14416
|
if (!Sage) {
|
|
14024
14417
|
send(ws, {
|
|
14025
14418
|
type: "memory.sage.get",
|
|
@@ -14044,7 +14437,7 @@ async function handleSageGet(ws, msg, memoryStore) {
|
|
|
14044
14437
|
}
|
|
14045
14438
|
}
|
|
14046
14439
|
async function handleSageGraph(ws, msg, memoryStore) {
|
|
14047
|
-
const Sage =
|
|
14440
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14048
14441
|
if (!Sage?.graphFor) {
|
|
14049
14442
|
send(ws, {
|
|
14050
14443
|
type: "memory.sage.graph",
|
|
@@ -14078,7 +14471,7 @@ async function handleSageGraph(ws, msg, memoryStore) {
|
|
|
14078
14471
|
}
|
|
14079
14472
|
}
|
|
14080
14473
|
async function handleSageUpdate(ws, msg, memoryStore) {
|
|
14081
|
-
const Sage =
|
|
14474
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14082
14475
|
if (!Sage) {
|
|
14083
14476
|
send(ws, {
|
|
14084
14477
|
type: "memory.sage.update",
|
|
@@ -14106,7 +14499,7 @@ async function handleSageUpdate(ws, msg, memoryStore) {
|
|
|
14106
14499
|
}
|
|
14107
14500
|
}
|
|
14108
14501
|
async function handleSageRemember(ws, msg, memoryStore) {
|
|
14109
|
-
const Sage =
|
|
14502
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14110
14503
|
if (!Sage) {
|
|
14111
14504
|
send(ws, {
|
|
14112
14505
|
type: "memory.sage.remember",
|
|
@@ -14140,7 +14533,7 @@ async function handleSageRemember(ws, msg, memoryStore) {
|
|
|
14140
14533
|
}
|
|
14141
14534
|
}
|
|
14142
14535
|
async function handleSageDelete(ws, msg, memoryStore) {
|
|
14143
|
-
const Sage =
|
|
14536
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14144
14537
|
if (!Sage) {
|
|
14145
14538
|
send(ws, {
|
|
14146
14539
|
type: "memory.sage.delete",
|
|
@@ -14173,7 +14566,7 @@ async function handleSageDelete(ws, msg, memoryStore) {
|
|
|
14173
14566
|
}
|
|
14174
14567
|
}
|
|
14175
14568
|
async function handleSageRecover(ws, msg, memoryStore) {
|
|
14176
|
-
const Sage =
|
|
14569
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14177
14570
|
if (!Sage?.recoverSage) {
|
|
14178
14571
|
send(ws, {
|
|
14179
14572
|
type: "memory.sage.recover",
|
|
@@ -14217,7 +14610,7 @@ async function handleSageRecover(ws, msg, memoryStore) {
|
|
|
14217
14610
|
}
|
|
14218
14611
|
}
|
|
14219
14612
|
async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
14220
|
-
const Sage =
|
|
14613
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14221
14614
|
if (!Sage) {
|
|
14222
14615
|
send(ws, {
|
|
14223
14616
|
type: "memory.sage.listCandidates",
|
|
@@ -14245,7 +14638,7 @@ async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
|
14245
14638
|
}
|
|
14246
14639
|
}
|
|
14247
14640
|
async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
14248
|
-
const Sage =
|
|
14641
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14249
14642
|
if (!Sage) {
|
|
14250
14643
|
send(ws, {
|
|
14251
14644
|
type: "memory.sage.candidateResolve",
|
|
@@ -14302,7 +14695,7 @@ async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
|
14302
14695
|
}
|
|
14303
14696
|
}
|
|
14304
14697
|
async function handleSageBackfillRecoverable(ws, msg, memoryStore) {
|
|
14305
|
-
const Sage =
|
|
14698
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14306
14699
|
if (!Sage?.backfillRecoverable) {
|
|
14307
14700
|
send(ws, {
|
|
14308
14701
|
type: "memory.sage.backfillRecoverable",
|
|
@@ -14342,7 +14735,7 @@ async function handleSageBackfillRecoverable(ws, msg, memoryStore) {
|
|
|
14342
14735
|
}
|
|
14343
14736
|
}
|
|
14344
14737
|
async function handleSageForFile(ws, msg, memoryStore) {
|
|
14345
|
-
const Sage =
|
|
14738
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14346
14739
|
if (!Sage?.findMemoriesForFile) {
|
|
14347
14740
|
send(ws, {
|
|
14348
14741
|
type: "memory.sage.forFile",
|
|
@@ -14434,6 +14827,9 @@ async function handleMemoryRoute(ctx, ws, message) {
|
|
|
14434
14827
|
case "memory.sage.forFile":
|
|
14435
14828
|
await handleSageForFile(ws, message, store);
|
|
14436
14829
|
return true;
|
|
14830
|
+
case "memory.sage.searchBreakdown":
|
|
14831
|
+
await handleSageSearchBreakdown(ws, message, store);
|
|
14832
|
+
return true;
|
|
14437
14833
|
default:
|
|
14438
14834
|
return false;
|
|
14439
14835
|
}
|
|
@@ -14824,23 +15220,31 @@ var SURFACE_DEFAULT_PORTS = {
|
|
|
14824
15220
|
webui: { http: 3456 },
|
|
14825
15221
|
simpleui: { http: 3466 }
|
|
14826
15222
|
};
|
|
15223
|
+
var MAX_TCP_PORT = 65535;
|
|
14827
15224
|
function surfaceLabel(surface) {
|
|
14828
15225
|
return surface === "webui" ? "WebUI" : "SimpleUI";
|
|
14829
15226
|
}
|
|
15227
|
+
function isStrictPort() {
|
|
15228
|
+
const value = process.env["WEBUI_STRICT_PORT"];
|
|
15229
|
+
return value === "1" || value === "true";
|
|
15230
|
+
}
|
|
14830
15231
|
function getSurfaceDefaultPorts(surface) {
|
|
14831
15232
|
return { http: SURFACE_DEFAULT_PORTS[surface].http };
|
|
14832
15233
|
}
|
|
14833
15234
|
function isPortFree(host, port) {
|
|
15235
|
+
return probePort(host, port).then((err) => err === null);
|
|
15236
|
+
}
|
|
15237
|
+
function probePort(host, port) {
|
|
14834
15238
|
return new Promise((resolve20) => {
|
|
14835
15239
|
const srv = net2.createServer();
|
|
14836
|
-
srv.once("error", () => resolve20(
|
|
15240
|
+
srv.once("error", (err) => resolve20(err));
|
|
14837
15241
|
srv.once("listening", () => {
|
|
14838
|
-
srv.close(() => resolve20(
|
|
15242
|
+
srv.close(() => resolve20(null));
|
|
14839
15243
|
});
|
|
14840
15244
|
try {
|
|
14841
15245
|
srv.listen(port, host);
|
|
14842
|
-
} catch {
|
|
14843
|
-
resolve20(
|
|
15246
|
+
} catch (err) {
|
|
15247
|
+
resolve20(err);
|
|
14844
15248
|
}
|
|
14845
15249
|
});
|
|
14846
15250
|
}
|
|
@@ -14849,7 +15253,7 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
14849
15253
|
const maxTries = opts.maxTries ?? 200;
|
|
14850
15254
|
let port = startPort;
|
|
14851
15255
|
for (let i = 0; i < maxTries; i++) {
|
|
14852
|
-
if (port >
|
|
15256
|
+
if (port > MAX_TCP_PORT) port = 1024 + port % 5e4;
|
|
14853
15257
|
if (!exclude.has(port) && await isPortFree(host, port)) {
|
|
14854
15258
|
return port;
|
|
14855
15259
|
}
|
|
@@ -14860,6 +15264,50 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
14860
15264
|
field: "port"
|
|
14861
15265
|
});
|
|
14862
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
|
+
}
|
|
14863
15311
|
|
|
14864
15312
|
// src/server/frontend-static-serve.ts
|
|
14865
15313
|
import { spawn as spawn2 } from "node:child_process";
|
|
@@ -14988,10 +15436,15 @@ async function startStaticServe(opts, deps2 = {}) {
|
|
|
14988
15436
|
apiToken: opts.apiToken,
|
|
14989
15437
|
requireToken: opts.requireToken,
|
|
14990
15438
|
allowedHostnames: opts.allowedHostnames,
|
|
14991
|
-
intakeService
|
|
15439
|
+
intakeService,
|
|
15440
|
+
...opts.getVectorMemoryStore ? { getVectorMemoryStore: opts.getVectorMemoryStore } : {},
|
|
15441
|
+
...opts.vectorMemoryModelCacheDir ? { vectorMemoryModelCacheDir: opts.vectorMemoryModelCacheDir } : {}
|
|
14992
15442
|
});
|
|
14993
15443
|
if (!opts.deferListen) {
|
|
14994
|
-
server.
|
|
15444
|
+
const boundPort = await listenWithRetry(server, opts.host, opts.httpPort, {
|
|
15445
|
+
maxTries: opts.strictPort ? 1 : 10
|
|
15446
|
+
});
|
|
15447
|
+
return { server, port: boundPort };
|
|
14995
15448
|
}
|
|
14996
15449
|
return { server, port: opts.httpPort };
|
|
14997
15450
|
}
|
|
@@ -15124,7 +15577,7 @@ function announceWebuiReady(p) {
|
|
|
15124
15577
|
token: p.wsToken,
|
|
15125
15578
|
publicUrl: p.publicUrl
|
|
15126
15579
|
});
|
|
15127
|
-
|
|
15580
|
+
const announce = () => {
|
|
15128
15581
|
const extraUrls = formatExternalAccessUrls({
|
|
15129
15582
|
bindHost: p.host,
|
|
15130
15583
|
port: p.httpPort,
|
|
@@ -15141,7 +15594,12 @@ ${extraUrls.join("\n")}
|
|
|
15141
15594
|
${extraBlock}`
|
|
15142
15595
|
);
|
|
15143
15596
|
if (p.open) launch(openUrl);
|
|
15144
|
-
}
|
|
15597
|
+
};
|
|
15598
|
+
if (p.server.listening) {
|
|
15599
|
+
announce();
|
|
15600
|
+
return;
|
|
15601
|
+
}
|
|
15602
|
+
p.server.on("listening", announce);
|
|
15145
15603
|
}
|
|
15146
15604
|
var DEFAULT_CHILD_CLEANUP_TIMEOUT_MS = 1e4;
|
|
15147
15605
|
async function runBounded(work, timeoutMs, label, debug) {
|
|
@@ -18232,6 +18690,7 @@ var CLIENT_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
18232
18690
|
"memory.sage.listPage",
|
|
18233
18691
|
"memory.sage.recover",
|
|
18234
18692
|
"memory.sage.remember",
|
|
18693
|
+
"memory.sage.searchBreakdown",
|
|
18235
18694
|
"memory.sage.update"
|
|
18236
18695
|
];
|
|
18237
18696
|
var CLIENT_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -18519,6 +18978,7 @@ var SERVER_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
18519
18978
|
"memory.sage.listPage",
|
|
18520
18979
|
"memory.sage.recover",
|
|
18521
18980
|
"memory.sage.remember",
|
|
18981
|
+
"memory.sage.searchBreakdown",
|
|
18522
18982
|
"memory.sage.update"
|
|
18523
18983
|
];
|
|
18524
18984
|
var SERVER_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -19396,7 +19856,12 @@ function createSessionHandlers(ctx) {
|
|
|
19396
19856
|
sessionScopedPath(sessionsDirectory(), next.id, ".tasks.json")
|
|
19397
19857
|
);
|
|
19398
19858
|
ctx.tokenCounter.reset?.();
|
|
19399
|
-
if (usage)
|
|
19859
|
+
if (usage) {
|
|
19860
|
+
ctx.tokenCounter.account(usage, currentConfig().model, ctx.context.provider.id);
|
|
19861
|
+
if (typeof usage.input === "number" && usage.input > 0) {
|
|
19862
|
+
ctx.context.lastRequestTokens = usage.input;
|
|
19863
|
+
}
|
|
19864
|
+
}
|
|
19400
19865
|
ctx.setSessionStartedAt?.(Date.now());
|
|
19401
19866
|
await ctx.onSessionSwapped?.(next.id);
|
|
19402
19867
|
};
|
|
@@ -19429,6 +19894,10 @@ function createSessionHandlers(ctx) {
|
|
|
19429
19894
|
return;
|
|
19430
19895
|
}
|
|
19431
19896
|
} else {
|
|
19897
|
+
try {
|
|
19898
|
+
ctx.abortActiveRun?.(clearedSessionId);
|
|
19899
|
+
} catch {
|
|
19900
|
+
}
|
|
19432
19901
|
ctx.context.state.replaceMessages([]);
|
|
19433
19902
|
ctx.context.state.replaceTodos([]);
|
|
19434
19903
|
resetContextAccounting();
|
|
@@ -21424,7 +21893,7 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
21424
21893
|
// background after session.new/resume. The run's own end() cleanup
|
|
21425
21894
|
// removes controllers from the map when it unwinds.
|
|
21426
21895
|
abortActiveRun: (sessionId) => {
|
|
21427
|
-
if (sessionId) {
|
|
21896
|
+
if (sessionId && deps2.conversationCtx.abortControllers.has(sessionId)) {
|
|
21428
21897
|
deps2.conversationCtx.abortControllers.get(sessionId)?.abort();
|
|
21429
21898
|
} else {
|
|
21430
21899
|
for (const controller of [...deps2.conversationCtx.abortControllers.values()]) {
|
|
@@ -24203,6 +24672,11 @@ import {
|
|
|
24203
24672
|
wstackGlobalRoot as wstackGlobalRoot4
|
|
24204
24673
|
} from "@wrongstack/core/utils";
|
|
24205
24674
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
24675
|
+
import {
|
|
24676
|
+
TransformersEmbeddingProvider,
|
|
24677
|
+
VectorMemoryStore,
|
|
24678
|
+
startFirstBootSageSync
|
|
24679
|
+
} from "@wrongstack/vector-memory";
|
|
24206
24680
|
|
|
24207
24681
|
// src/server/backend-services.ts
|
|
24208
24682
|
import { join as join15 } from "node:path";
|
|
@@ -27155,7 +27629,7 @@ async function resolvePorts(opts) {
|
|
|
27155
27629
|
const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
|
|
27156
27630
|
const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
|
|
27157
27631
|
const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
|
|
27158
|
-
const strictPort =
|
|
27632
|
+
const strictPort = isStrictPort();
|
|
27159
27633
|
let httpPort = requestedHttpPort;
|
|
27160
27634
|
if (!strictPort) {
|
|
27161
27635
|
httpPort = await findFreePort(wsHost, requestedHttpPort);
|
|
@@ -27356,7 +27830,9 @@ function startHttpServer(opts) {
|
|
|
27356
27830
|
getLlm: opts.getLlm,
|
|
27357
27831
|
executePackageOperation: opts.executePackageOperation,
|
|
27358
27832
|
projectRoot: opts.projectRoot,
|
|
27359
|
-
intakeService
|
|
27833
|
+
intakeService,
|
|
27834
|
+
...opts.getVectorMemoryStore ? { getVectorMemoryStore: opts.getVectorMemoryStore } : {},
|
|
27835
|
+
...opts.vectorMemoryModelCacheDir ? { vectorMemoryModelCacheDir: opts.vectorMemoryModelCacheDir } : {}
|
|
27360
27836
|
});
|
|
27361
27837
|
return httpServer;
|
|
27362
27838
|
}
|
|
@@ -27372,7 +27848,7 @@ function registerShutdown(deps2) {
|
|
|
27372
27848
|
|
|
27373
27849
|
// src/server/start-webui-companion.ts
|
|
27374
27850
|
import * as http2 from "node:http";
|
|
27375
|
-
function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
27851
|
+
async function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
27376
27852
|
const companion = wsHost === "127.0.0.1" ? "::1" : wsHost === "0.0.0.0" || wsHost === void 0 ? "::" : wsHost === "::" || wsHost === "[::]" ? "0.0.0.0" : null;
|
|
27377
27853
|
if (!companion) return null;
|
|
27378
27854
|
const companionLabel = companion.includes(":") ? `[${companion}]` : companion;
|
|
@@ -27383,16 +27859,23 @@ function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
|
27383
27859
|
(req, socket, head2) => httpServer.emit("upgrade", req, socket, head2)
|
|
27384
27860
|
);
|
|
27385
27861
|
companionServer.on("error", (err) => {
|
|
27386
|
-
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL"
|
|
27862
|
+
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL";
|
|
27387
27863
|
if (!expected) {
|
|
27388
27864
|
console.warn(
|
|
27389
27865
|
`[WebUI] companion listener on ${companionLabel} failed (${err.code ?? "unknown"}): ${err.message}. The primary address is unaffected.`
|
|
27390
27866
|
);
|
|
27391
27867
|
}
|
|
27392
27868
|
});
|
|
27393
|
-
|
|
27394
|
-
|
|
27395
|
-
})
|
|
27869
|
+
try {
|
|
27870
|
+
await listenWithRetry(companionServer, companion, httpPort, { maxTries: 1 });
|
|
27871
|
+
} catch (err) {
|
|
27872
|
+
const code = err?.code ?? "unknown";
|
|
27873
|
+
console.warn(
|
|
27874
|
+
`[WebUI] companion listener on ${companionLabel} not started (${code}): ${err?.message ?? err}. The primary address is unaffected.`
|
|
27875
|
+
);
|
|
27876
|
+
return null;
|
|
27877
|
+
}
|
|
27878
|
+
console.log(`[WebUI] HTTP server running on http://${companionLabel}:${httpPort}`);
|
|
27396
27879
|
return companionServer;
|
|
27397
27880
|
}
|
|
27398
27881
|
|
|
@@ -27587,6 +28070,7 @@ function setupWebuiShutdown(options) {
|
|
|
27587
28070
|
await options.memoryStore.dispose().catch(
|
|
27588
28071
|
(err) => options.logger.warn(`sage connection disposal failed: ${toErrorMessage16(err)}`)
|
|
27589
28072
|
);
|
|
28073
|
+
options.vectorMemoryStore?.close();
|
|
27590
28074
|
await unregisterInstance(process.pid, path37.dirname(options.globalConfigPath));
|
|
27591
28075
|
}
|
|
27592
28076
|
});
|
|
@@ -27651,7 +28135,8 @@ function createStandaloneTodosCheckpointLifecycle(input) {
|
|
|
27651
28135
|
async function startWebUI(opts = {}) {
|
|
27652
28136
|
ensureSessionShell();
|
|
27653
28137
|
const ports = await resolvePorts(opts);
|
|
27654
|
-
const { wsHost,
|
|
28138
|
+
const { wsHost, publicUrl, publicWsUrl, requireToken } = ports;
|
|
28139
|
+
let httpPort = ports.httpPort;
|
|
27655
28140
|
console.log("[WebUI] Starting backend services...");
|
|
27656
28141
|
const boot = await bootConfig();
|
|
27657
28142
|
const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
|
|
@@ -27679,6 +28164,27 @@ async function startWebUI(opts = {}) {
|
|
|
27679
28164
|
);
|
|
27680
28165
|
}
|
|
27681
28166
|
const needsProvider = !config.provider || !config.model;
|
|
28167
|
+
let vectorMemoryStore;
|
|
28168
|
+
const vectorMemoryModelCacheDir = path38.join(
|
|
28169
|
+
projectRoot,
|
|
28170
|
+
".wrongstack",
|
|
28171
|
+
"cache",
|
|
28172
|
+
"transformers-models"
|
|
28173
|
+
);
|
|
28174
|
+
try {
|
|
28175
|
+
vectorMemoryStore = new VectorMemoryStore({
|
|
28176
|
+
provider: new TransformersEmbeddingProvider({
|
|
28177
|
+
cacheDir: vectorMemoryModelCacheDir
|
|
28178
|
+
}),
|
|
28179
|
+
projectRoot
|
|
28180
|
+
});
|
|
28181
|
+
} catch (error2) {
|
|
28182
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
28183
|
+
logger.warn(
|
|
28184
|
+
`vector memory store disabled: ${message} \u2014 standalone WebUI will run on the SAGE-only surface.`
|
|
28185
|
+
);
|
|
28186
|
+
vectorMemoryStore = void 0;
|
|
28187
|
+
}
|
|
27682
28188
|
const preContext = await createPreContextServices({
|
|
27683
28189
|
config,
|
|
27684
28190
|
wpaths,
|
|
@@ -27726,6 +28232,13 @@ async function startWebUI(opts = {}) {
|
|
|
27726
28232
|
let sessionStartedAt = preContext.sessionStartedAt;
|
|
27727
28233
|
let modeId = preContext.modeId;
|
|
27728
28234
|
const needsSetup = preContext.needsSetup;
|
|
28235
|
+
if (vectorMemoryStore) {
|
|
28236
|
+
void startFirstBootSageSync({
|
|
28237
|
+
store: vectorMemoryStore,
|
|
28238
|
+
memoryStore,
|
|
28239
|
+
logger
|
|
28240
|
+
});
|
|
28241
|
+
}
|
|
27729
28242
|
const prefSnapshot2 = () => prefSnapshot(context.meta);
|
|
27730
28243
|
const persistPrefsToConfig2 = async (payload) => persistPrefsToConfig(prefHelperDeps, configWriteLock, payload);
|
|
27731
28244
|
const trustBoundary = opts.trustBoundary ?? createCompatibilityTrustBoundary3({ policyId: "webui-trusted-host-compat-v1" });
|
|
@@ -27846,7 +28359,12 @@ async function startWebUI(opts = {}) {
|
|
|
27846
28359
|
events,
|
|
27847
28360
|
permissionPolicy
|
|
27848
28361
|
}),
|
|
27849
|
-
distDir: opts.distDir
|
|
28362
|
+
distDir: opts.distDir,
|
|
28363
|
+
// Vector memory store — mirrors the CLI host. When `vectorMemoryStore`
|
|
28364
|
+
// construction failed (read-only FS, etc.) we still pass the getter;
|
|
28365
|
+
// it just resolves to `undefined` and the API router answers 503.
|
|
28366
|
+
getVectorMemoryStore: () => vectorMemoryStore,
|
|
28367
|
+
vectorMemoryModelCacheDir
|
|
27850
28368
|
});
|
|
27851
28369
|
const wsResult = createWsServers(httpServer, ports, accessToken);
|
|
27852
28370
|
const { wssPrimary, wssSecondary, clients } = wsResult;
|
|
@@ -27901,7 +28419,25 @@ async function startWebUI(opts = {}) {
|
|
|
27901
28419
|
},
|
|
27902
28420
|
watcherMetricsRef
|
|
27903
28421
|
);
|
|
27904
|
-
|
|
28422
|
+
const strictPort = isStrictPort();
|
|
28423
|
+
const boundPort = await listenWithRetry(httpServer, wsHost, httpPort, {
|
|
28424
|
+
maxTries: strictPort ? 1 : 10
|
|
28425
|
+
});
|
|
28426
|
+
if (boundPort !== httpPort) {
|
|
28427
|
+
console.warn(
|
|
28428
|
+
JSON.stringify({
|
|
28429
|
+
level: "warn",
|
|
28430
|
+
event: "webui.port_reassigned",
|
|
28431
|
+
protocol: "HTTP",
|
|
28432
|
+
requested: httpPort,
|
|
28433
|
+
assigned: boundPort,
|
|
28434
|
+
reason: "bind-time EADDRINUSE retry",
|
|
28435
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
28436
|
+
})
|
|
28437
|
+
);
|
|
28438
|
+
httpPort = boundPort;
|
|
28439
|
+
}
|
|
28440
|
+
{
|
|
27905
28441
|
const authHint = requireToken ? " (authentication required; configure WEBUI_TOKEN out of band)" : "";
|
|
27906
28442
|
console.log(`[WebUI] HTTP server listening on http://${wsHost}:${httpPort}${authHint}`);
|
|
27907
28443
|
const extraUrls = formatExternalAccessUrls({
|
|
@@ -27912,8 +28448,8 @@ async function startWebUI(opts = {}) {
|
|
|
27912
28448
|
if (extraUrls.length > 0) {
|
|
27913
28449
|
console.log("[WebUI] Protected endpoints on external interfaces:\n" + extraUrls.join("\n"));
|
|
27914
28450
|
}
|
|
27915
|
-
}
|
|
27916
|
-
const companionServer = setupCompanionServer(httpServer, wsHost, httpPort);
|
|
28451
|
+
}
|
|
28452
|
+
const companionServer = await setupCompanionServer(httpServer, wsHost, httpPort);
|
|
27917
28453
|
async function touchProjectEntry(root, workDir) {
|
|
27918
28454
|
const resolved = path38.resolve(root);
|
|
27919
28455
|
const manifest = await loadManifest(globalConfigPath);
|
|
@@ -28172,6 +28708,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
28172
28708
|
},
|
|
28173
28709
|
codebaseIndexing,
|
|
28174
28710
|
memoryStore,
|
|
28711
|
+
vectorMemoryStore,
|
|
28175
28712
|
globalConfigPath
|
|
28176
28713
|
});
|
|
28177
28714
|
}
|
|
@@ -28405,9 +28942,11 @@ export {
|
|
|
28405
28942
|
isPidAlive,
|
|
28406
28943
|
isPortFree,
|
|
28407
28944
|
isRegisteredMessageType,
|
|
28945
|
+
isStrictPort,
|
|
28408
28946
|
isWildcardBind,
|
|
28409
28947
|
joinSessionRegistryWithWebUIInstances,
|
|
28410
28948
|
listInstances,
|
|
28949
|
+
listenWithRetry,
|
|
28411
28950
|
loadManifest,
|
|
28412
28951
|
loadSavedProviders,
|
|
28413
28952
|
markConnectionActivity,
|