@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/server/entry.js
CHANGED
|
@@ -7290,11 +7290,11 @@ async function handleGitDiff(ws, projectRoot, path34) {
|
|
|
7290
7290
|
try {
|
|
7291
7291
|
const git = makeGit(cwd);
|
|
7292
7292
|
const { readFile: readFile11 } = await import("node:fs/promises");
|
|
7293
|
-
const { join:
|
|
7293
|
+
const { join: join16 } = await import("node:path");
|
|
7294
7294
|
const oldText = await git(["show", `HEAD:${path34}`]);
|
|
7295
7295
|
let newText = "";
|
|
7296
7296
|
try {
|
|
7297
|
-
const abs = cwd ?
|
|
7297
|
+
const abs = cwd ? join16(cwd, path34) : path34;
|
|
7298
7298
|
const buf = await readFile11(abs);
|
|
7299
7299
|
if (buf.includes(0)) {
|
|
7300
7300
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -8889,7 +8889,7 @@ function verifyClient(input) {
|
|
|
8889
8889
|
|
|
8890
8890
|
// src/server/http-server/api-router.ts
|
|
8891
8891
|
import * as v8 from "node:v8";
|
|
8892
|
-
import { sanitizeApiError as
|
|
8892
|
+
import { sanitizeApiError as sanitizeApiError3 } from "@wrongstack/core/security";
|
|
8893
8893
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
8894
8894
|
|
|
8895
8895
|
// src/server/codemap-handlers.ts
|
|
@@ -10316,6 +10316,297 @@ function strictDecodeParam(segment, res) {
|
|
|
10316
10316
|
}
|
|
10317
10317
|
}
|
|
10318
10318
|
|
|
10319
|
+
// src/server/http-server/vector-memory-handlers.ts
|
|
10320
|
+
import { getSageSurface } from "@wrongstack/sage";
|
|
10321
|
+
import { sanitizeApiError as sanitizeApiError2 } from "@wrongstack/core/security";
|
|
10322
|
+
function snapshotVectorMemory(store, opts = {}) {
|
|
10323
|
+
const stats = store.stats();
|
|
10324
|
+
return {
|
|
10325
|
+
storePath: opts.projectRoot,
|
|
10326
|
+
modelCacheDir: opts.modelCacheDir,
|
|
10327
|
+
stats
|
|
10328
|
+
};
|
|
10329
|
+
}
|
|
10330
|
+
function snapshotVectorMemoryCache(store) {
|
|
10331
|
+
return store.cacheStats();
|
|
10332
|
+
}
|
|
10333
|
+
async function handleVectorMemoryStatus(res, getStore, opts = {}) {
|
|
10334
|
+
const store = getStore();
|
|
10335
|
+
if (!store) {
|
|
10336
|
+
const body = { enabled: false };
|
|
10337
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10338
|
+
res.end(JSON.stringify(body));
|
|
10339
|
+
return;
|
|
10340
|
+
}
|
|
10341
|
+
try {
|
|
10342
|
+
const snap = snapshotVectorMemory(store, opts);
|
|
10343
|
+
const body = {
|
|
10344
|
+
enabled: true,
|
|
10345
|
+
storePath: snap.storePath,
|
|
10346
|
+
modelCacheDir: snap.modelCacheDir,
|
|
10347
|
+
providerId: snap.stats.modelId,
|
|
10348
|
+
modelId: snap.stats.modelId,
|
|
10349
|
+
dimensions: snap.stats.dimensions,
|
|
10350
|
+
entries: snap.stats.entries,
|
|
10351
|
+
vectors: snap.stats.vectors,
|
|
10352
|
+
providers: snap.stats.providers,
|
|
10353
|
+
cache: snapshotVectorMemoryCache(store)
|
|
10354
|
+
};
|
|
10355
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10356
|
+
res.end(JSON.stringify(body));
|
|
10357
|
+
} catch (error2) {
|
|
10358
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10359
|
+
res.end(
|
|
10360
|
+
JSON.stringify({
|
|
10361
|
+
error: "Vector memory status failed",
|
|
10362
|
+
detail: sanitizeApiError2(error2)
|
|
10363
|
+
})
|
|
10364
|
+
);
|
|
10365
|
+
}
|
|
10366
|
+
}
|
|
10367
|
+
function parseSearchParams(url) {
|
|
10368
|
+
const query = url.searchParams.get("q") ?? "";
|
|
10369
|
+
const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
|
|
10370
|
+
const limit = Math.min(50, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 10));
|
|
10371
|
+
const rawThreshold = url.searchParams.get("threshold");
|
|
10372
|
+
const threshold = rawThreshold !== null && rawThreshold !== "" ? Math.max(0, Math.min(1, Number.parseFloat(rawThreshold))) : void 0;
|
|
10373
|
+
const similarity = url.searchParams.get("similarity") === "1";
|
|
10374
|
+
return { query, limit, threshold: Number.isFinite(threshold) ? threshold : void 0, similarity };
|
|
10375
|
+
}
|
|
10376
|
+
function cosineMatrix(vectors) {
|
|
10377
|
+
const n = vectors.length;
|
|
10378
|
+
const out = new Array(n);
|
|
10379
|
+
for (let i = 0; i < n; i++) {
|
|
10380
|
+
out[i] = new Array(n).fill(0);
|
|
10381
|
+
}
|
|
10382
|
+
for (let i = 0; i < n; i++) {
|
|
10383
|
+
out[i][i] = 1;
|
|
10384
|
+
for (let j = i + 1; j < n; j++) {
|
|
10385
|
+
const a = vectors[i];
|
|
10386
|
+
const b = vectors[j];
|
|
10387
|
+
let dot = 0;
|
|
10388
|
+
const len = Math.min(a.length, b.length);
|
|
10389
|
+
for (let k = 0; k < len; k++) dot += (a[k] ?? 0) * (b[k] ?? 0);
|
|
10390
|
+
const score = Math.max(0, Math.min(1, dot));
|
|
10391
|
+
out[i][j] = score;
|
|
10392
|
+
out[j][i] = score;
|
|
10393
|
+
}
|
|
10394
|
+
}
|
|
10395
|
+
return out;
|
|
10396
|
+
}
|
|
10397
|
+
async function handleVectorMemorySearch(res, url, getStore) {
|
|
10398
|
+
const store = getStore();
|
|
10399
|
+
if (!store) {
|
|
10400
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10401
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10402
|
+
return;
|
|
10403
|
+
}
|
|
10404
|
+
const { query, limit, threshold, similarity } = parseSearchParams(url);
|
|
10405
|
+
if (query.trim().length === 0) {
|
|
10406
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10407
|
+
res.end(JSON.stringify({ error: "Missing required query parameter `q`" }));
|
|
10408
|
+
return;
|
|
10409
|
+
}
|
|
10410
|
+
try {
|
|
10411
|
+
const hits = await store.search(query, {
|
|
10412
|
+
limit,
|
|
10413
|
+
...threshold !== void 0 ? { threshold } : {},
|
|
10414
|
+
includeVectors: similarity
|
|
10415
|
+
});
|
|
10416
|
+
const body = {
|
|
10417
|
+
hits: hits.map((h) => ({
|
|
10418
|
+
id: h.entry.id,
|
|
10419
|
+
score: h.score,
|
|
10420
|
+
text: h.entry.text,
|
|
10421
|
+
...h.entry.summary ? { summary: h.entry.summary } : {},
|
|
10422
|
+
tags: h.entry.tags
|
|
10423
|
+
})),
|
|
10424
|
+
count: hits.length
|
|
10425
|
+
};
|
|
10426
|
+
if (similarity && hits.length > 1) {
|
|
10427
|
+
const vecs = hits.map((h) => h.vector).filter((v) => v !== void 0);
|
|
10428
|
+
if (vecs.length === hits.length) {
|
|
10429
|
+
body.similarity = cosineMatrix(vecs);
|
|
10430
|
+
}
|
|
10431
|
+
}
|
|
10432
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10433
|
+
res.end(JSON.stringify(body));
|
|
10434
|
+
} catch (error2) {
|
|
10435
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10436
|
+
res.end(
|
|
10437
|
+
JSON.stringify({
|
|
10438
|
+
error: "Vector memory search failed",
|
|
10439
|
+
detail: sanitizeApiError2(error2)
|
|
10440
|
+
})
|
|
10441
|
+
);
|
|
10442
|
+
}
|
|
10443
|
+
}
|
|
10444
|
+
function parseStoreBody(req) {
|
|
10445
|
+
return new Promise((resolve19) => {
|
|
10446
|
+
let raw = "";
|
|
10447
|
+
req.setEncoding("utf8");
|
|
10448
|
+
req.on("data", (chunk) => {
|
|
10449
|
+
raw += chunk;
|
|
10450
|
+
if (raw.length > 64 * 1024) {
|
|
10451
|
+
req.destroy();
|
|
10452
|
+
resolve19(null);
|
|
10453
|
+
}
|
|
10454
|
+
});
|
|
10455
|
+
req.on("end", () => {
|
|
10456
|
+
try {
|
|
10457
|
+
resolve19(raw ? JSON.parse(raw) : {});
|
|
10458
|
+
} catch {
|
|
10459
|
+
resolve19(null);
|
|
10460
|
+
}
|
|
10461
|
+
});
|
|
10462
|
+
req.on("error", () => resolve19(null));
|
|
10463
|
+
});
|
|
10464
|
+
}
|
|
10465
|
+
async function handleVectorMemoryStore(res, req, getStore) {
|
|
10466
|
+
const store = getStore();
|
|
10467
|
+
if (!store) {
|
|
10468
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10469
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10470
|
+
return;
|
|
10471
|
+
}
|
|
10472
|
+
const body = await parseStoreBody(req);
|
|
10473
|
+
if (!body) {
|
|
10474
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10475
|
+
res.end(JSON.stringify({ error: "Malformed JSON body" }));
|
|
10476
|
+
return;
|
|
10477
|
+
}
|
|
10478
|
+
const text = typeof body.text === "string" ? body.text.trim() : "";
|
|
10479
|
+
if (text.length === 0) {
|
|
10480
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10481
|
+
res.end(JSON.stringify({ error: "Missing required field `text`" }));
|
|
10482
|
+
return;
|
|
10483
|
+
}
|
|
10484
|
+
const tags = Array.isArray(body.tags) ? body.tags.filter((t) => typeof t === "string") : [];
|
|
10485
|
+
try {
|
|
10486
|
+
const entry = await store.remember({ text, tags });
|
|
10487
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10488
|
+
res.end(
|
|
10489
|
+
JSON.stringify({
|
|
10490
|
+
id: entry.id,
|
|
10491
|
+
hasVector: entry.vector !== void 0,
|
|
10492
|
+
dimensions: entry.dimensions
|
|
10493
|
+
})
|
|
10494
|
+
);
|
|
10495
|
+
} catch (error2) {
|
|
10496
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10497
|
+
res.end(
|
|
10498
|
+
JSON.stringify({
|
|
10499
|
+
error: "Vector memory store failed",
|
|
10500
|
+
detail: sanitizeApiError2(error2)
|
|
10501
|
+
})
|
|
10502
|
+
);
|
|
10503
|
+
}
|
|
10504
|
+
}
|
|
10505
|
+
async function handleVectorMemoryForget(res, url, getStore) {
|
|
10506
|
+
const store = getStore();
|
|
10507
|
+
if (!store) {
|
|
10508
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10509
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10510
|
+
return;
|
|
10511
|
+
}
|
|
10512
|
+
const match = /^\/api\/vector-memory\/store\/([^/]+)$/.exec(url.pathname);
|
|
10513
|
+
const id = match ? strictDecodeParam(decodeSessionId(match[1]), res) : null;
|
|
10514
|
+
if (id === null) return;
|
|
10515
|
+
try {
|
|
10516
|
+
const removed = await store.forget(id);
|
|
10517
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10518
|
+
res.end(JSON.stringify({ removed }));
|
|
10519
|
+
} catch (error2) {
|
|
10520
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10521
|
+
res.end(
|
|
10522
|
+
JSON.stringify({
|
|
10523
|
+
error: "Vector memory forget failed",
|
|
10524
|
+
detail: sanitizeApiError2(error2)
|
|
10525
|
+
})
|
|
10526
|
+
);
|
|
10527
|
+
}
|
|
10528
|
+
}
|
|
10529
|
+
function parseMemorySearchParams(url) {
|
|
10530
|
+
const query = url.searchParams.get("q") ?? "";
|
|
10531
|
+
const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "20", 10);
|
|
10532
|
+
const limit = Math.min(50, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 20));
|
|
10533
|
+
const explain = url.searchParams.get("explain") === "1";
|
|
10534
|
+
return { query, limit, explain };
|
|
10535
|
+
}
|
|
10536
|
+
async function handleMemorySearch(res, url, getStore) {
|
|
10537
|
+
const store = getStore();
|
|
10538
|
+
if (!store) {
|
|
10539
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10540
|
+
res.end(JSON.stringify({ error: "Memory store not enabled in this host" }));
|
|
10541
|
+
return;
|
|
10542
|
+
}
|
|
10543
|
+
const { query, limit, explain } = parseMemorySearchParams(url);
|
|
10544
|
+
if (query.trim().length === 0) {
|
|
10545
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10546
|
+
res.end(JSON.stringify({ error: "Missing required query parameter `q`" }));
|
|
10547
|
+
return;
|
|
10548
|
+
}
|
|
10549
|
+
const Sage = getSageSurface(store);
|
|
10550
|
+
if (!Sage) {
|
|
10551
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10552
|
+
res.end(
|
|
10553
|
+
JSON.stringify({
|
|
10554
|
+
error: "Memory search requires the SAGE surface (this host does not expose it)."
|
|
10555
|
+
})
|
|
10556
|
+
);
|
|
10557
|
+
return;
|
|
10558
|
+
}
|
|
10559
|
+
try {
|
|
10560
|
+
let payload;
|
|
10561
|
+
if (explain && typeof Sage.searchSageWithBreakdown === "function") {
|
|
10562
|
+
const hits = await Sage.searchSageWithBreakdown(query, { limit });
|
|
10563
|
+
payload = {
|
|
10564
|
+
count: hits.length,
|
|
10565
|
+
channel: "breakdown",
|
|
10566
|
+
hits: hits.map((h) => ({
|
|
10567
|
+
id: h.memory.id,
|
|
10568
|
+
text: h.memory.text,
|
|
10569
|
+
kind: h.memory.kind,
|
|
10570
|
+
status: h.memory.status,
|
|
10571
|
+
tags: h.memory.tags ?? [],
|
|
10572
|
+
lexicalScore: h.lexicalScore,
|
|
10573
|
+
vectorScore: h.vectorScore,
|
|
10574
|
+
finalScore: h.finalScore,
|
|
10575
|
+
source: h.source
|
|
10576
|
+
}))
|
|
10577
|
+
};
|
|
10578
|
+
} else {
|
|
10579
|
+
const rows = await Sage.searchSage(query, { limit });
|
|
10580
|
+
const total = rows.length;
|
|
10581
|
+
payload = {
|
|
10582
|
+
count: total,
|
|
10583
|
+
channel: "lexical",
|
|
10584
|
+
hits: rows.map((memory, index) => ({
|
|
10585
|
+
id: memory.id,
|
|
10586
|
+
text: memory.text,
|
|
10587
|
+
kind: memory.kind,
|
|
10588
|
+
status: memory.status,
|
|
10589
|
+
tags: memory.tags ?? [],
|
|
10590
|
+
lexicalScore: total <= 1 ? 1 : 1 - index / Math.max(1, total - 1),
|
|
10591
|
+
vectorScore: null,
|
|
10592
|
+
finalScore: total <= 1 ? 1 : 1 - index / Math.max(1, total - 1),
|
|
10593
|
+
source: "lexical"
|
|
10594
|
+
}))
|
|
10595
|
+
};
|
|
10596
|
+
}
|
|
10597
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10598
|
+
res.end(JSON.stringify(payload));
|
|
10599
|
+
} catch (error2) {
|
|
10600
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10601
|
+
res.end(
|
|
10602
|
+
JSON.stringify({
|
|
10603
|
+
error: "Memory search failed",
|
|
10604
|
+
detail: sanitizeApiError2(error2)
|
|
10605
|
+
})
|
|
10606
|
+
);
|
|
10607
|
+
}
|
|
10608
|
+
}
|
|
10609
|
+
|
|
10319
10610
|
// src/server/http-server/api-router.ts
|
|
10320
10611
|
async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessTokenOk, getTechStackRuntime) {
|
|
10321
10612
|
if (url.pathname === "/api/fleet/ping" && req.method === "POST") {
|
|
@@ -10654,7 +10945,7 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
10654
10945
|
res.end(
|
|
10655
10946
|
JSON.stringify({
|
|
10656
10947
|
error: "TechStack store unavailable",
|
|
10657
|
-
detail:
|
|
10948
|
+
detail: sanitizeApiError3(error2)
|
|
10658
10949
|
})
|
|
10659
10950
|
);
|
|
10660
10951
|
return true;
|
|
@@ -10717,6 +11008,63 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
10717
11008
|
);
|
|
10718
11009
|
return true;
|
|
10719
11010
|
}
|
|
11011
|
+
const vectorForgetMatch = /^\/api\/vector-memory\/store\/([^/]+)$/.exec(url.pathname);
|
|
11012
|
+
if (vectorForgetMatch && req.method === "DELETE") {
|
|
11013
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11014
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11015
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11016
|
+
return true;
|
|
11017
|
+
}
|
|
11018
|
+
await handleVectorMemoryForget(
|
|
11019
|
+
res,
|
|
11020
|
+
url,
|
|
11021
|
+
() => deps2.getVectorMemoryStore?.()
|
|
11022
|
+
);
|
|
11023
|
+
return true;
|
|
11024
|
+
}
|
|
11025
|
+
if (url.pathname === "/api/vector-memory/status" && req.method === "GET") {
|
|
11026
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11027
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11028
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11029
|
+
return true;
|
|
11030
|
+
}
|
|
11031
|
+
await handleVectorMemoryStatus(
|
|
11032
|
+
res,
|
|
11033
|
+
() => deps2.getVectorMemoryStore?.(),
|
|
11034
|
+
{
|
|
11035
|
+
...deps2.projectRoot ? { projectRoot: deps2.projectRoot } : {},
|
|
11036
|
+
...deps2.vectorMemoryModelCacheDir ? { modelCacheDir: deps2.vectorMemoryModelCacheDir } : {}
|
|
11037
|
+
}
|
|
11038
|
+
);
|
|
11039
|
+
return true;
|
|
11040
|
+
}
|
|
11041
|
+
if (url.pathname === "/api/vector-memory/search" && req.method === "GET") {
|
|
11042
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11043
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11044
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11045
|
+
return true;
|
|
11046
|
+
}
|
|
11047
|
+
await handleVectorMemorySearch(res, url, () => deps2.getVectorMemoryStore?.());
|
|
11048
|
+
return true;
|
|
11049
|
+
}
|
|
11050
|
+
if (url.pathname === "/api/vector-memory/store" && req.method === "POST") {
|
|
11051
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11052
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11053
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11054
|
+
return true;
|
|
11055
|
+
}
|
|
11056
|
+
await handleVectorMemoryStore(res, req, () => deps2.getVectorMemoryStore?.());
|
|
11057
|
+
return true;
|
|
11058
|
+
}
|
|
11059
|
+
if (url.pathname === "/api/memory/search" && req.method === "GET") {
|
|
11060
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11061
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11062
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11063
|
+
return true;
|
|
11064
|
+
}
|
|
11065
|
+
await handleMemorySearch(res, url, () => deps2.getMemoryStore?.());
|
|
11066
|
+
return true;
|
|
11067
|
+
}
|
|
10720
11068
|
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
10721
11069
|
await handleDeadCodeActionPlan(
|
|
10722
11070
|
res,
|
|
@@ -10881,12 +11229,15 @@ function createHttpServer(opts) {
|
|
|
10881
11229
|
distDir,
|
|
10882
11230
|
url,
|
|
10883
11231
|
opts,
|
|
10884
|
-
port
|
|
11232
|
+
// Live port from the socket: the bind may have advanced past an
|
|
11233
|
+
// EADDRINUSE (listenWithRetry) after this server was constructed,
|
|
11234
|
+
// and the CSP must advertise the port actually serving this request.
|
|
11235
|
+
res.socket?.localPort ?? port,
|
|
10885
11236
|
shouldSetAuthCookie
|
|
10886
11237
|
);
|
|
10887
11238
|
} catch (err) {
|
|
10888
11239
|
if (err.code === "ENOENT") {
|
|
10889
|
-
await handleSpaFallback(res, distDir, opts, port);
|
|
11240
|
+
await handleSpaFallback(res, distDir, opts, res.socket?.localPort ?? port);
|
|
10890
11241
|
} else {
|
|
10891
11242
|
console.error({ url: req.url, err });
|
|
10892
11243
|
res.writeHead(500);
|
|
@@ -13671,13 +14022,13 @@ async function handleMcpRoute(ws, msg, handlers) {
|
|
|
13671
14022
|
}
|
|
13672
14023
|
|
|
13673
14024
|
// src/server/memory-handlers.ts
|
|
13674
|
-
import { getSageSurface } from "@wrongstack/sage";
|
|
14025
|
+
import { getSageSurface as getSageSurface2 } from "@wrongstack/sage";
|
|
13675
14026
|
function requiresSage(command) {
|
|
13676
14027
|
return `\`${command}\` requires the SAGE backend (Sage.enabled).`;
|
|
13677
14028
|
}
|
|
13678
14029
|
async function handleMemoryList(ws, memoryStore) {
|
|
13679
14030
|
try {
|
|
13680
|
-
const Sage =
|
|
14031
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13681
14032
|
if (Sage) {
|
|
13682
14033
|
const [stats, memories] = await Promise.all([Sage.stats(), Sage.listSage()]);
|
|
13683
14034
|
const text2 = memories.length === 0 ? "\u{1F9E0} SAGE is empty." : formatSageText(stats, memories);
|
|
@@ -13712,7 +14063,7 @@ function formatSageText(stats, memories) {
|
|
|
13712
14063
|
return lines.join("\n");
|
|
13713
14064
|
}
|
|
13714
14065
|
async function handleSageList(ws, memoryStore) {
|
|
13715
|
-
const Sage =
|
|
14066
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13716
14067
|
if (!Sage) {
|
|
13717
14068
|
send(ws, {
|
|
13718
14069
|
type: "memory.sage.list",
|
|
@@ -13728,7 +14079,7 @@ async function handleSageList(ws, memoryStore) {
|
|
|
13728
14079
|
}
|
|
13729
14080
|
}
|
|
13730
14081
|
async function handleSageListPage(ws, msg, memoryStore) {
|
|
13731
|
-
const Sage =
|
|
14082
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13732
14083
|
if (!Sage) {
|
|
13733
14084
|
send(ws, {
|
|
13734
14085
|
type: "memory.sage.listPage",
|
|
@@ -13778,8 +14129,50 @@ async function handleSageListPage(ws, msg, memoryStore) {
|
|
|
13778
14129
|
send(ws, { type: "memory.sage.listPage", payload: { error: errMessage(err) } });
|
|
13779
14130
|
}
|
|
13780
14131
|
}
|
|
14132
|
+
async function handleSageSearchBreakdown(ws, msg, memoryStore) {
|
|
14133
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14134
|
+
if (!Sage) {
|
|
14135
|
+
send(ws, {
|
|
14136
|
+
type: "memory.sage.searchBreakdown",
|
|
14137
|
+
payload: { error: requiresSage("memory.sage.searchBreakdown") }
|
|
14138
|
+
});
|
|
14139
|
+
return;
|
|
14140
|
+
}
|
|
14141
|
+
try {
|
|
14142
|
+
const payload = msg.payload ?? {};
|
|
14143
|
+
const query = typeof payload["query"] === "string" ? payload["query"] : "";
|
|
14144
|
+
if (query.trim().length === 0) {
|
|
14145
|
+
send(ws, {
|
|
14146
|
+
type: "memory.sage.searchBreakdown",
|
|
14147
|
+
payload: { error: "Missing required field `query`" }
|
|
14148
|
+
});
|
|
14149
|
+
return;
|
|
14150
|
+
}
|
|
14151
|
+
const limit = typeof payload["limit"] === "number" ? payload["limit"] : 20;
|
|
14152
|
+
const includeStale = payload["includeStale"] === true;
|
|
14153
|
+
const includeStatuses = includeStale ? ["active", "stale"] : ["active"];
|
|
14154
|
+
const options = { limit, includeStatuses };
|
|
14155
|
+
if (typeof Sage.searchSageWithBreakdown === "function") {
|
|
14156
|
+
const hits2 = await Sage.searchSageWithBreakdown(query, options);
|
|
14157
|
+
send(ws, { type: "memory.sage.searchBreakdown", payload: { hits: hits2, source: "breakdown" } });
|
|
14158
|
+
return;
|
|
14159
|
+
}
|
|
14160
|
+
const rows = await Sage.searchSage(query, options);
|
|
14161
|
+
const total = rows.length;
|
|
14162
|
+
const hits = rows.map((memory, index) => ({
|
|
14163
|
+
memory,
|
|
14164
|
+
vectorScore: null,
|
|
14165
|
+
lexicalScore: total <= 1 ? 1 : 1 - index / Math.max(1, total - 1),
|
|
14166
|
+
finalScore: total <= 1 ? 1 : 1 - index / Math.max(1, total - 1),
|
|
14167
|
+
source: "lexical"
|
|
14168
|
+
}));
|
|
14169
|
+
send(ws, { type: "memory.sage.searchBreakdown", payload: { hits, source: "lexical" } });
|
|
14170
|
+
} catch (err) {
|
|
14171
|
+
send(ws, { type: "memory.sage.searchBreakdown", payload: { error: errMessage(err) } });
|
|
14172
|
+
}
|
|
14173
|
+
}
|
|
13781
14174
|
async function handleSageGet(ws, msg, memoryStore) {
|
|
13782
|
-
const Sage =
|
|
14175
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13783
14176
|
if (!Sage) {
|
|
13784
14177
|
send(ws, {
|
|
13785
14178
|
type: "memory.sage.get",
|
|
@@ -13804,7 +14197,7 @@ async function handleSageGet(ws, msg, memoryStore) {
|
|
|
13804
14197
|
}
|
|
13805
14198
|
}
|
|
13806
14199
|
async function handleSageGraph(ws, msg, memoryStore) {
|
|
13807
|
-
const Sage =
|
|
14200
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13808
14201
|
if (!Sage?.graphFor) {
|
|
13809
14202
|
send(ws, {
|
|
13810
14203
|
type: "memory.sage.graph",
|
|
@@ -13838,7 +14231,7 @@ async function handleSageGraph(ws, msg, memoryStore) {
|
|
|
13838
14231
|
}
|
|
13839
14232
|
}
|
|
13840
14233
|
async function handleSageUpdate(ws, msg, memoryStore) {
|
|
13841
|
-
const Sage =
|
|
14234
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13842
14235
|
if (!Sage) {
|
|
13843
14236
|
send(ws, {
|
|
13844
14237
|
type: "memory.sage.update",
|
|
@@ -13866,7 +14259,7 @@ async function handleSageUpdate(ws, msg, memoryStore) {
|
|
|
13866
14259
|
}
|
|
13867
14260
|
}
|
|
13868
14261
|
async function handleSageRemember(ws, msg, memoryStore) {
|
|
13869
|
-
const Sage =
|
|
14262
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13870
14263
|
if (!Sage) {
|
|
13871
14264
|
send(ws, {
|
|
13872
14265
|
type: "memory.sage.remember",
|
|
@@ -13900,7 +14293,7 @@ async function handleSageRemember(ws, msg, memoryStore) {
|
|
|
13900
14293
|
}
|
|
13901
14294
|
}
|
|
13902
14295
|
async function handleSageDelete(ws, msg, memoryStore) {
|
|
13903
|
-
const Sage =
|
|
14296
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13904
14297
|
if (!Sage) {
|
|
13905
14298
|
send(ws, {
|
|
13906
14299
|
type: "memory.sage.delete",
|
|
@@ -13933,7 +14326,7 @@ async function handleSageDelete(ws, msg, memoryStore) {
|
|
|
13933
14326
|
}
|
|
13934
14327
|
}
|
|
13935
14328
|
async function handleSageRecover(ws, msg, memoryStore) {
|
|
13936
|
-
const Sage =
|
|
14329
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13937
14330
|
if (!Sage?.recoverSage) {
|
|
13938
14331
|
send(ws, {
|
|
13939
14332
|
type: "memory.sage.recover",
|
|
@@ -13977,7 +14370,7 @@ async function handleSageRecover(ws, msg, memoryStore) {
|
|
|
13977
14370
|
}
|
|
13978
14371
|
}
|
|
13979
14372
|
async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
13980
|
-
const Sage =
|
|
14373
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13981
14374
|
if (!Sage) {
|
|
13982
14375
|
send(ws, {
|
|
13983
14376
|
type: "memory.sage.listCandidates",
|
|
@@ -14005,7 +14398,7 @@ async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
|
14005
14398
|
}
|
|
14006
14399
|
}
|
|
14007
14400
|
async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
14008
|
-
const Sage =
|
|
14401
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14009
14402
|
if (!Sage) {
|
|
14010
14403
|
send(ws, {
|
|
14011
14404
|
type: "memory.sage.candidateResolve",
|
|
@@ -14062,7 +14455,7 @@ async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
|
14062
14455
|
}
|
|
14063
14456
|
}
|
|
14064
14457
|
async function handleSageBackfillRecoverable(ws, msg, memoryStore) {
|
|
14065
|
-
const Sage =
|
|
14458
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14066
14459
|
if (!Sage?.backfillRecoverable) {
|
|
14067
14460
|
send(ws, {
|
|
14068
14461
|
type: "memory.sage.backfillRecoverable",
|
|
@@ -14102,7 +14495,7 @@ async function handleSageBackfillRecoverable(ws, msg, memoryStore) {
|
|
|
14102
14495
|
}
|
|
14103
14496
|
}
|
|
14104
14497
|
async function handleSageForFile(ws, msg, memoryStore) {
|
|
14105
|
-
const Sage =
|
|
14498
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14106
14499
|
if (!Sage?.findMemoriesForFile) {
|
|
14107
14500
|
send(ws, {
|
|
14108
14501
|
type: "memory.sage.forFile",
|
|
@@ -14194,6 +14587,9 @@ async function handleMemoryRoute(ctx, ws, message) {
|
|
|
14194
14587
|
case "memory.sage.forFile":
|
|
14195
14588
|
await handleSageForFile(ws, message, store);
|
|
14196
14589
|
return true;
|
|
14590
|
+
case "memory.sage.searchBreakdown":
|
|
14591
|
+
await handleSageSearchBreakdown(ws, message, store);
|
|
14592
|
+
return true;
|
|
14197
14593
|
default:
|
|
14198
14594
|
return false;
|
|
14199
14595
|
}
|
|
@@ -14537,17 +14933,25 @@ function createModelOperations(context) {
|
|
|
14537
14933
|
// src/server/port-utils.ts
|
|
14538
14934
|
import * as net2 from "node:net";
|
|
14539
14935
|
import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
|
|
14936
|
+
var MAX_TCP_PORT = 65535;
|
|
14937
|
+
function isStrictPort() {
|
|
14938
|
+
const value = process.env["WEBUI_STRICT_PORT"];
|
|
14939
|
+
return value === "1" || value === "true";
|
|
14940
|
+
}
|
|
14540
14941
|
function isPortFree(host, port) {
|
|
14942
|
+
return probePort(host, port).then((err) => err === null);
|
|
14943
|
+
}
|
|
14944
|
+
function probePort(host, port) {
|
|
14541
14945
|
return new Promise((resolve19) => {
|
|
14542
14946
|
const srv = net2.createServer();
|
|
14543
|
-
srv.once("error", () => resolve19(
|
|
14947
|
+
srv.once("error", (err) => resolve19(err));
|
|
14544
14948
|
srv.once("listening", () => {
|
|
14545
|
-
srv.close(() => resolve19(
|
|
14949
|
+
srv.close(() => resolve19(null));
|
|
14546
14950
|
});
|
|
14547
14951
|
try {
|
|
14548
14952
|
srv.listen(port, host);
|
|
14549
|
-
} catch {
|
|
14550
|
-
resolve19(
|
|
14953
|
+
} catch (err) {
|
|
14954
|
+
resolve19(err);
|
|
14551
14955
|
}
|
|
14552
14956
|
});
|
|
14553
14957
|
}
|
|
@@ -14556,7 +14960,7 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
14556
14960
|
const maxTries = opts.maxTries ?? 200;
|
|
14557
14961
|
let port = startPort;
|
|
14558
14962
|
for (let i = 0; i < maxTries; i++) {
|
|
14559
|
-
if (port >
|
|
14963
|
+
if (port > MAX_TCP_PORT) port = 1024 + port % 5e4;
|
|
14560
14964
|
if (!exclude.has(port) && await isPortFree(host, port)) {
|
|
14561
14965
|
return port;
|
|
14562
14966
|
}
|
|
@@ -14567,6 +14971,50 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
14567
14971
|
field: "port"
|
|
14568
14972
|
});
|
|
14569
14973
|
}
|
|
14974
|
+
function listenWithRetry(server, host, port, opts = {}) {
|
|
14975
|
+
const maxTries = opts.maxTries ?? 10;
|
|
14976
|
+
return new Promise((resolve19, reject) => {
|
|
14977
|
+
const canAdvance = (candidate) => candidate < MAX_TCP_PORT;
|
|
14978
|
+
const probeable = (candidate) => Number.isInteger(candidate) && candidate >= 0 && candidate <= MAX_TCP_PORT;
|
|
14979
|
+
const attempt = (candidate, remaining) => {
|
|
14980
|
+
void (async () => {
|
|
14981
|
+
if (probeable(candidate)) {
|
|
14982
|
+
const probeErr = await probePort(host, candidate);
|
|
14983
|
+
if (probeErr !== null) {
|
|
14984
|
+
if (probeErr.code === "EADDRINUSE" && remaining > 1 && canAdvance(candidate)) {
|
|
14985
|
+
attempt(candidate + 1, remaining - 1);
|
|
14986
|
+
return;
|
|
14987
|
+
}
|
|
14988
|
+
reject(probeErr);
|
|
14989
|
+
return;
|
|
14990
|
+
}
|
|
14991
|
+
}
|
|
14992
|
+
const onError = (err) => {
|
|
14993
|
+
server.off("listening", onListening);
|
|
14994
|
+
server.off("error", onError);
|
|
14995
|
+
if (err.code === "EADDRINUSE" && remaining > 1 && canAdvance(candidate)) {
|
|
14996
|
+
attempt(candidate + 1, remaining - 1);
|
|
14997
|
+
return;
|
|
14998
|
+
}
|
|
14999
|
+
reject(err);
|
|
15000
|
+
};
|
|
15001
|
+
const onListening = () => {
|
|
15002
|
+
server.off("error", onError);
|
|
15003
|
+
const address = server.address();
|
|
15004
|
+
resolve19(address && typeof address === "object" ? address.port : candidate);
|
|
15005
|
+
};
|
|
15006
|
+
server.once("error", onError);
|
|
15007
|
+
server.once("listening", onListening);
|
|
15008
|
+
try {
|
|
15009
|
+
server.listen(candidate, host);
|
|
15010
|
+
} catch (err) {
|
|
15011
|
+
onError(err);
|
|
15012
|
+
}
|
|
15013
|
+
})();
|
|
15014
|
+
};
|
|
15015
|
+
attempt(port, maxTries);
|
|
15016
|
+
});
|
|
15017
|
+
}
|
|
14570
15018
|
|
|
14571
15019
|
// src/server/intake-service.ts
|
|
14572
15020
|
import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
@@ -16952,6 +17400,7 @@ var CLIENT_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
16952
17400
|
"memory.sage.listPage",
|
|
16953
17401
|
"memory.sage.recover",
|
|
16954
17402
|
"memory.sage.remember",
|
|
17403
|
+
"memory.sage.searchBreakdown",
|
|
16955
17404
|
"memory.sage.update"
|
|
16956
17405
|
];
|
|
16957
17406
|
var CLIENT_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -17239,6 +17688,7 @@ var SERVER_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
17239
17688
|
"memory.sage.listPage",
|
|
17240
17689
|
"memory.sage.recover",
|
|
17241
17690
|
"memory.sage.remember",
|
|
17691
|
+
"memory.sage.searchBreakdown",
|
|
17242
17692
|
"memory.sage.update"
|
|
17243
17693
|
];
|
|
17244
17694
|
var SERVER_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -17925,7 +18375,12 @@ function createSessionHandlers(ctx) {
|
|
|
17925
18375
|
sessionScopedPath(sessionsDirectory(), next.id, ".tasks.json")
|
|
17926
18376
|
);
|
|
17927
18377
|
ctx.tokenCounter.reset?.();
|
|
17928
|
-
if (usage)
|
|
18378
|
+
if (usage) {
|
|
18379
|
+
ctx.tokenCounter.account(usage, currentConfig().model, ctx.context.provider.id);
|
|
18380
|
+
if (typeof usage.input === "number" && usage.input > 0) {
|
|
18381
|
+
ctx.context.lastRequestTokens = usage.input;
|
|
18382
|
+
}
|
|
18383
|
+
}
|
|
17929
18384
|
ctx.setSessionStartedAt?.(Date.now());
|
|
17930
18385
|
await ctx.onSessionSwapped?.(next.id);
|
|
17931
18386
|
};
|
|
@@ -17958,6 +18413,10 @@ function createSessionHandlers(ctx) {
|
|
|
17958
18413
|
return;
|
|
17959
18414
|
}
|
|
17960
18415
|
} else {
|
|
18416
|
+
try {
|
|
18417
|
+
ctx.abortActiveRun?.(clearedSessionId);
|
|
18418
|
+
} catch {
|
|
18419
|
+
}
|
|
17961
18420
|
ctx.context.state.replaceMessages([]);
|
|
17962
18421
|
ctx.context.state.replaceTodos([]);
|
|
17963
18422
|
resetContextAccounting();
|
|
@@ -22081,6 +22540,11 @@ import {
|
|
|
22081
22540
|
wstackGlobalRoot as wstackGlobalRoot2
|
|
22082
22541
|
} from "@wrongstack/core/utils";
|
|
22083
22542
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
22543
|
+
import {
|
|
22544
|
+
TransformersEmbeddingProvider,
|
|
22545
|
+
VectorMemoryStore,
|
|
22546
|
+
startFirstBootSageSync
|
|
22547
|
+
} from "@wrongstack/vector-memory";
|
|
22084
22548
|
|
|
22085
22549
|
// src/server/backend-services.ts
|
|
22086
22550
|
import { join as join12 } from "node:path";
|
|
@@ -25033,7 +25497,7 @@ async function resolvePorts(opts) {
|
|
|
25033
25497
|
const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
|
|
25034
25498
|
const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
|
|
25035
25499
|
const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
|
|
25036
|
-
const strictPort =
|
|
25500
|
+
const strictPort = isStrictPort();
|
|
25037
25501
|
let httpPort = requestedHttpPort;
|
|
25038
25502
|
if (!strictPort) {
|
|
25039
25503
|
httpPort = await findFreePort(wsHost, requestedHttpPort);
|
|
@@ -25234,7 +25698,9 @@ function startHttpServer(opts) {
|
|
|
25234
25698
|
getLlm: opts.getLlm,
|
|
25235
25699
|
executePackageOperation: opts.executePackageOperation,
|
|
25236
25700
|
projectRoot: opts.projectRoot,
|
|
25237
|
-
intakeService
|
|
25701
|
+
intakeService,
|
|
25702
|
+
...opts.getVectorMemoryStore ? { getVectorMemoryStore: opts.getVectorMemoryStore } : {},
|
|
25703
|
+
...opts.vectorMemoryModelCacheDir ? { vectorMemoryModelCacheDir: opts.vectorMemoryModelCacheDir } : {}
|
|
25238
25704
|
});
|
|
25239
25705
|
return httpServer;
|
|
25240
25706
|
}
|
|
@@ -25250,7 +25716,7 @@ function registerShutdown(deps2) {
|
|
|
25250
25716
|
|
|
25251
25717
|
// src/server/start-webui-companion.ts
|
|
25252
25718
|
import * as http2 from "node:http";
|
|
25253
|
-
function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
25719
|
+
async function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
25254
25720
|
const companion = wsHost === "127.0.0.1" ? "::1" : wsHost === "0.0.0.0" || wsHost === void 0 ? "::" : wsHost === "::" || wsHost === "[::]" ? "0.0.0.0" : null;
|
|
25255
25721
|
if (!companion) return null;
|
|
25256
25722
|
const companionLabel = companion.includes(":") ? `[${companion}]` : companion;
|
|
@@ -25261,16 +25727,23 @@ function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
|
25261
25727
|
(req, socket, head2) => httpServer.emit("upgrade", req, socket, head2)
|
|
25262
25728
|
);
|
|
25263
25729
|
companionServer.on("error", (err) => {
|
|
25264
|
-
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL"
|
|
25730
|
+
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL";
|
|
25265
25731
|
if (!expected) {
|
|
25266
25732
|
console.warn(
|
|
25267
25733
|
`[WebUI] companion listener on ${companionLabel} failed (${err.code ?? "unknown"}): ${err.message}. The primary address is unaffected.`
|
|
25268
25734
|
);
|
|
25269
25735
|
}
|
|
25270
25736
|
});
|
|
25271
|
-
|
|
25272
|
-
|
|
25273
|
-
})
|
|
25737
|
+
try {
|
|
25738
|
+
await listenWithRetry(companionServer, companion, httpPort, { maxTries: 1 });
|
|
25739
|
+
} catch (err) {
|
|
25740
|
+
const code = err?.code ?? "unknown";
|
|
25741
|
+
console.warn(
|
|
25742
|
+
`[WebUI] companion listener on ${companionLabel} not started (${code}): ${err?.message ?? err}. The primary address is unaffected.`
|
|
25743
|
+
);
|
|
25744
|
+
return null;
|
|
25745
|
+
}
|
|
25746
|
+
console.log(`[WebUI] HTTP server running on http://${companionLabel}:${httpPort}`);
|
|
25274
25747
|
return companionServer;
|
|
25275
25748
|
}
|
|
25276
25749
|
|
|
@@ -25465,6 +25938,7 @@ function setupWebuiShutdown(options) {
|
|
|
25465
25938
|
await options.memoryStore.dispose().catch(
|
|
25466
25939
|
(err) => options.logger.warn(`sage connection disposal failed: ${toErrorMessage15(err)}`)
|
|
25467
25940
|
);
|
|
25941
|
+
options.vectorMemoryStore?.close();
|
|
25468
25942
|
await unregisterInstance(process.pid, path32.dirname(options.globalConfigPath));
|
|
25469
25943
|
}
|
|
25470
25944
|
});
|
|
@@ -25529,7 +26003,8 @@ function createStandaloneTodosCheckpointLifecycle(input) {
|
|
|
25529
26003
|
async function startWebUI(opts = {}) {
|
|
25530
26004
|
ensureSessionShell();
|
|
25531
26005
|
const ports = await resolvePorts(opts);
|
|
25532
|
-
const { wsHost,
|
|
26006
|
+
const { wsHost, publicUrl, publicWsUrl, requireToken } = ports;
|
|
26007
|
+
let httpPort = ports.httpPort;
|
|
25533
26008
|
console.log("[WebUI] Starting backend services...");
|
|
25534
26009
|
const boot = await bootConfig();
|
|
25535
26010
|
const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
|
|
@@ -25557,6 +26032,27 @@ async function startWebUI(opts = {}) {
|
|
|
25557
26032
|
);
|
|
25558
26033
|
}
|
|
25559
26034
|
const needsProvider = !config.provider || !config.model;
|
|
26035
|
+
let vectorMemoryStore;
|
|
26036
|
+
const vectorMemoryModelCacheDir = path33.join(
|
|
26037
|
+
projectRoot,
|
|
26038
|
+
".wrongstack",
|
|
26039
|
+
"cache",
|
|
26040
|
+
"transformers-models"
|
|
26041
|
+
);
|
|
26042
|
+
try {
|
|
26043
|
+
vectorMemoryStore = new VectorMemoryStore({
|
|
26044
|
+
provider: new TransformersEmbeddingProvider({
|
|
26045
|
+
cacheDir: vectorMemoryModelCacheDir
|
|
26046
|
+
}),
|
|
26047
|
+
projectRoot
|
|
26048
|
+
});
|
|
26049
|
+
} catch (error2) {
|
|
26050
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
26051
|
+
logger.warn(
|
|
26052
|
+
`vector memory store disabled: ${message} \u2014 standalone WebUI will run on the SAGE-only surface.`
|
|
26053
|
+
);
|
|
26054
|
+
vectorMemoryStore = void 0;
|
|
26055
|
+
}
|
|
25560
26056
|
const preContext = await createPreContextServices({
|
|
25561
26057
|
config,
|
|
25562
26058
|
wpaths,
|
|
@@ -25604,6 +26100,13 @@ async function startWebUI(opts = {}) {
|
|
|
25604
26100
|
let sessionStartedAt = preContext.sessionStartedAt;
|
|
25605
26101
|
let modeId = preContext.modeId;
|
|
25606
26102
|
const needsSetup = preContext.needsSetup;
|
|
26103
|
+
if (vectorMemoryStore) {
|
|
26104
|
+
void startFirstBootSageSync({
|
|
26105
|
+
store: vectorMemoryStore,
|
|
26106
|
+
memoryStore,
|
|
26107
|
+
logger
|
|
26108
|
+
});
|
|
26109
|
+
}
|
|
25607
26110
|
const prefSnapshot2 = () => prefSnapshot(context.meta);
|
|
25608
26111
|
const persistPrefsToConfig2 = async (payload) => persistPrefsToConfig(prefHelperDeps, configWriteLock, payload);
|
|
25609
26112
|
const trustBoundary = opts.trustBoundary ?? createCompatibilityTrustBoundary3({ policyId: "webui-trusted-host-compat-v1" });
|
|
@@ -25724,7 +26227,12 @@ async function startWebUI(opts = {}) {
|
|
|
25724
26227
|
events,
|
|
25725
26228
|
permissionPolicy
|
|
25726
26229
|
}),
|
|
25727
|
-
distDir: opts.distDir
|
|
26230
|
+
distDir: opts.distDir,
|
|
26231
|
+
// Vector memory store — mirrors the CLI host. When `vectorMemoryStore`
|
|
26232
|
+
// construction failed (read-only FS, etc.) we still pass the getter;
|
|
26233
|
+
// it just resolves to `undefined` and the API router answers 503.
|
|
26234
|
+
getVectorMemoryStore: () => vectorMemoryStore,
|
|
26235
|
+
vectorMemoryModelCacheDir
|
|
25728
26236
|
});
|
|
25729
26237
|
const wsResult = createWsServers(httpServer, ports, accessToken);
|
|
25730
26238
|
const { wssPrimary, wssSecondary, clients } = wsResult;
|
|
@@ -25779,7 +26287,25 @@ async function startWebUI(opts = {}) {
|
|
|
25779
26287
|
},
|
|
25780
26288
|
watcherMetricsRef
|
|
25781
26289
|
);
|
|
25782
|
-
|
|
26290
|
+
const strictPort = isStrictPort();
|
|
26291
|
+
const boundPort = await listenWithRetry(httpServer, wsHost, httpPort, {
|
|
26292
|
+
maxTries: strictPort ? 1 : 10
|
|
26293
|
+
});
|
|
26294
|
+
if (boundPort !== httpPort) {
|
|
26295
|
+
console.warn(
|
|
26296
|
+
JSON.stringify({
|
|
26297
|
+
level: "warn",
|
|
26298
|
+
event: "webui.port_reassigned",
|
|
26299
|
+
protocol: "HTTP",
|
|
26300
|
+
requested: httpPort,
|
|
26301
|
+
assigned: boundPort,
|
|
26302
|
+
reason: "bind-time EADDRINUSE retry",
|
|
26303
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
26304
|
+
})
|
|
26305
|
+
);
|
|
26306
|
+
httpPort = boundPort;
|
|
26307
|
+
}
|
|
26308
|
+
{
|
|
25783
26309
|
const authHint = requireToken ? " (authentication required; configure WEBUI_TOKEN out of band)" : "";
|
|
25784
26310
|
console.log(`[WebUI] HTTP server listening on http://${wsHost}:${httpPort}${authHint}`);
|
|
25785
26311
|
const extraUrls = formatExternalAccessUrls({
|
|
@@ -25790,8 +26316,8 @@ async function startWebUI(opts = {}) {
|
|
|
25790
26316
|
if (extraUrls.length > 0) {
|
|
25791
26317
|
console.log("[WebUI] Protected endpoints on external interfaces:\n" + extraUrls.join("\n"));
|
|
25792
26318
|
}
|
|
25793
|
-
}
|
|
25794
|
-
const companionServer = setupCompanionServer(httpServer, wsHost, httpPort);
|
|
26319
|
+
}
|
|
26320
|
+
const companionServer = await setupCompanionServer(httpServer, wsHost, httpPort);
|
|
25795
26321
|
async function touchProjectEntry(root, workDir) {
|
|
25796
26322
|
const resolved = path33.resolve(root);
|
|
25797
26323
|
const manifest = await loadManifest(globalConfigPath);
|
|
@@ -26050,6 +26576,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26050
26576
|
},
|
|
26051
26577
|
codebaseIndexing,
|
|
26052
26578
|
memoryStore,
|
|
26579
|
+
vectorMemoryStore,
|
|
26053
26580
|
globalConfigPath
|
|
26054
26581
|
});
|
|
26055
26582
|
}
|