@wrongstack/webui-server 0.308.0 → 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 +349 -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 +336 -40
- 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 +5 -0
- package/package.json +13 -12
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 });
|
|
@@ -10317,6 +10317,7 @@ function strictDecodeParam(segment, res) {
|
|
|
10317
10317
|
}
|
|
10318
10318
|
|
|
10319
10319
|
// src/server/http-server/vector-memory-handlers.ts
|
|
10320
|
+
import { getSageSurface } from "@wrongstack/sage";
|
|
10320
10321
|
import { sanitizeApiError as sanitizeApiError2 } from "@wrongstack/core/security";
|
|
10321
10322
|
function snapshotVectorMemory(store, opts = {}) {
|
|
10322
10323
|
const stats = store.stats();
|
|
@@ -10326,6 +10327,9 @@ function snapshotVectorMemory(store, opts = {}) {
|
|
|
10326
10327
|
stats
|
|
10327
10328
|
};
|
|
10328
10329
|
}
|
|
10330
|
+
function snapshotVectorMemoryCache(store) {
|
|
10331
|
+
return store.cacheStats();
|
|
10332
|
+
}
|
|
10329
10333
|
async function handleVectorMemoryStatus(res, getStore, opts = {}) {
|
|
10330
10334
|
const store = getStore();
|
|
10331
10335
|
if (!store) {
|
|
@@ -10345,7 +10349,8 @@ async function handleVectorMemoryStatus(res, getStore, opts = {}) {
|
|
|
10345
10349
|
dimensions: snap.stats.dimensions,
|
|
10346
10350
|
entries: snap.stats.entries,
|
|
10347
10351
|
vectors: snap.stats.vectors,
|
|
10348
|
-
providers: snap.stats.providers
|
|
10352
|
+
providers: snap.stats.providers,
|
|
10353
|
+
cache: snapshotVectorMemoryCache(store)
|
|
10349
10354
|
};
|
|
10350
10355
|
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10351
10356
|
res.end(JSON.stringify(body));
|
|
@@ -10365,7 +10370,29 @@ function parseSearchParams(url) {
|
|
|
10365
10370
|
const limit = Math.min(50, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 10));
|
|
10366
10371
|
const rawThreshold = url.searchParams.get("threshold");
|
|
10367
10372
|
const threshold = rawThreshold !== null && rawThreshold !== "" ? Math.max(0, Math.min(1, Number.parseFloat(rawThreshold))) : void 0;
|
|
10368
|
-
|
|
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;
|
|
10369
10396
|
}
|
|
10370
10397
|
async function handleVectorMemorySearch(res, url, getStore) {
|
|
10371
10398
|
const store = getStore();
|
|
@@ -10374,7 +10401,7 @@ async function handleVectorMemorySearch(res, url, getStore) {
|
|
|
10374
10401
|
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10375
10402
|
return;
|
|
10376
10403
|
}
|
|
10377
|
-
const { query, limit, threshold } = parseSearchParams(url);
|
|
10404
|
+
const { query, limit, threshold, similarity } = parseSearchParams(url);
|
|
10378
10405
|
if (query.trim().length === 0) {
|
|
10379
10406
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10380
10407
|
res.end(JSON.stringify({ error: "Missing required query parameter `q`" }));
|
|
@@ -10383,7 +10410,8 @@ async function handleVectorMemorySearch(res, url, getStore) {
|
|
|
10383
10410
|
try {
|
|
10384
10411
|
const hits = await store.search(query, {
|
|
10385
10412
|
limit,
|
|
10386
|
-
...threshold !== void 0 ? { threshold } : {}
|
|
10413
|
+
...threshold !== void 0 ? { threshold } : {},
|
|
10414
|
+
includeVectors: similarity
|
|
10387
10415
|
});
|
|
10388
10416
|
const body = {
|
|
10389
10417
|
hits: hits.map((h) => ({
|
|
@@ -10395,6 +10423,12 @@ async function handleVectorMemorySearch(res, url, getStore) {
|
|
|
10395
10423
|
})),
|
|
10396
10424
|
count: hits.length
|
|
10397
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
|
+
}
|
|
10398
10432
|
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10399
10433
|
res.end(JSON.stringify(body));
|
|
10400
10434
|
} catch (error2) {
|
|
@@ -10479,7 +10513,7 @@ async function handleVectorMemoryForget(res, url, getStore) {
|
|
|
10479
10513
|
const id = match ? strictDecodeParam(decodeSessionId(match[1]), res) : null;
|
|
10480
10514
|
if (id === null) return;
|
|
10481
10515
|
try {
|
|
10482
|
-
const removed = store.forget(id);
|
|
10516
|
+
const removed = await store.forget(id);
|
|
10483
10517
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10484
10518
|
res.end(JSON.stringify({ removed }));
|
|
10485
10519
|
} catch (error2) {
|
|
@@ -10492,6 +10526,86 @@ async function handleVectorMemoryForget(res, url, getStore) {
|
|
|
10492
10526
|
);
|
|
10493
10527
|
}
|
|
10494
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
|
+
}
|
|
10495
10609
|
|
|
10496
10610
|
// src/server/http-server/api-router.ts
|
|
10497
10611
|
async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessTokenOk, getTechStackRuntime) {
|
|
@@ -10942,6 +11056,15 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
10942
11056
|
await handleVectorMemoryStore(res, req, () => deps2.getVectorMemoryStore?.());
|
|
10943
11057
|
return true;
|
|
10944
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
|
+
}
|
|
10945
11068
|
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
10946
11069
|
await handleDeadCodeActionPlan(
|
|
10947
11070
|
res,
|
|
@@ -11106,12 +11229,15 @@ function createHttpServer(opts) {
|
|
|
11106
11229
|
distDir,
|
|
11107
11230
|
url,
|
|
11108
11231
|
opts,
|
|
11109
|
-
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,
|
|
11110
11236
|
shouldSetAuthCookie
|
|
11111
11237
|
);
|
|
11112
11238
|
} catch (err) {
|
|
11113
11239
|
if (err.code === "ENOENT") {
|
|
11114
|
-
await handleSpaFallback(res, distDir, opts, port);
|
|
11240
|
+
await handleSpaFallback(res, distDir, opts, res.socket?.localPort ?? port);
|
|
11115
11241
|
} else {
|
|
11116
11242
|
console.error({ url: req.url, err });
|
|
11117
11243
|
res.writeHead(500);
|
|
@@ -13896,13 +14022,13 @@ async function handleMcpRoute(ws, msg, handlers) {
|
|
|
13896
14022
|
}
|
|
13897
14023
|
|
|
13898
14024
|
// src/server/memory-handlers.ts
|
|
13899
|
-
import { getSageSurface } from "@wrongstack/sage";
|
|
14025
|
+
import { getSageSurface as getSageSurface2 } from "@wrongstack/sage";
|
|
13900
14026
|
function requiresSage(command) {
|
|
13901
14027
|
return `\`${command}\` requires the SAGE backend (Sage.enabled).`;
|
|
13902
14028
|
}
|
|
13903
14029
|
async function handleMemoryList(ws, memoryStore) {
|
|
13904
14030
|
try {
|
|
13905
|
-
const Sage =
|
|
14031
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13906
14032
|
if (Sage) {
|
|
13907
14033
|
const [stats, memories] = await Promise.all([Sage.stats(), Sage.listSage()]);
|
|
13908
14034
|
const text2 = memories.length === 0 ? "\u{1F9E0} SAGE is empty." : formatSageText(stats, memories);
|
|
@@ -13937,7 +14063,7 @@ function formatSageText(stats, memories) {
|
|
|
13937
14063
|
return lines.join("\n");
|
|
13938
14064
|
}
|
|
13939
14065
|
async function handleSageList(ws, memoryStore) {
|
|
13940
|
-
const Sage =
|
|
14066
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13941
14067
|
if (!Sage) {
|
|
13942
14068
|
send(ws, {
|
|
13943
14069
|
type: "memory.sage.list",
|
|
@@ -13953,7 +14079,7 @@ async function handleSageList(ws, memoryStore) {
|
|
|
13953
14079
|
}
|
|
13954
14080
|
}
|
|
13955
14081
|
async function handleSageListPage(ws, msg, memoryStore) {
|
|
13956
|
-
const Sage =
|
|
14082
|
+
const Sage = getSageSurface2(memoryStore);
|
|
13957
14083
|
if (!Sage) {
|
|
13958
14084
|
send(ws, {
|
|
13959
14085
|
type: "memory.sage.listPage",
|
|
@@ -14003,8 +14129,50 @@ async function handleSageListPage(ws, msg, memoryStore) {
|
|
|
14003
14129
|
send(ws, { type: "memory.sage.listPage", payload: { error: errMessage(err) } });
|
|
14004
14130
|
}
|
|
14005
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
|
+
}
|
|
14006
14174
|
async function handleSageGet(ws, msg, memoryStore) {
|
|
14007
|
-
const Sage =
|
|
14175
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14008
14176
|
if (!Sage) {
|
|
14009
14177
|
send(ws, {
|
|
14010
14178
|
type: "memory.sage.get",
|
|
@@ -14029,7 +14197,7 @@ async function handleSageGet(ws, msg, memoryStore) {
|
|
|
14029
14197
|
}
|
|
14030
14198
|
}
|
|
14031
14199
|
async function handleSageGraph(ws, msg, memoryStore) {
|
|
14032
|
-
const Sage =
|
|
14200
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14033
14201
|
if (!Sage?.graphFor) {
|
|
14034
14202
|
send(ws, {
|
|
14035
14203
|
type: "memory.sage.graph",
|
|
@@ -14063,7 +14231,7 @@ async function handleSageGraph(ws, msg, memoryStore) {
|
|
|
14063
14231
|
}
|
|
14064
14232
|
}
|
|
14065
14233
|
async function handleSageUpdate(ws, msg, memoryStore) {
|
|
14066
|
-
const Sage =
|
|
14234
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14067
14235
|
if (!Sage) {
|
|
14068
14236
|
send(ws, {
|
|
14069
14237
|
type: "memory.sage.update",
|
|
@@ -14091,7 +14259,7 @@ async function handleSageUpdate(ws, msg, memoryStore) {
|
|
|
14091
14259
|
}
|
|
14092
14260
|
}
|
|
14093
14261
|
async function handleSageRemember(ws, msg, memoryStore) {
|
|
14094
|
-
const Sage =
|
|
14262
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14095
14263
|
if (!Sage) {
|
|
14096
14264
|
send(ws, {
|
|
14097
14265
|
type: "memory.sage.remember",
|
|
@@ -14125,7 +14293,7 @@ async function handleSageRemember(ws, msg, memoryStore) {
|
|
|
14125
14293
|
}
|
|
14126
14294
|
}
|
|
14127
14295
|
async function handleSageDelete(ws, msg, memoryStore) {
|
|
14128
|
-
const Sage =
|
|
14296
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14129
14297
|
if (!Sage) {
|
|
14130
14298
|
send(ws, {
|
|
14131
14299
|
type: "memory.sage.delete",
|
|
@@ -14158,7 +14326,7 @@ async function handleSageDelete(ws, msg, memoryStore) {
|
|
|
14158
14326
|
}
|
|
14159
14327
|
}
|
|
14160
14328
|
async function handleSageRecover(ws, msg, memoryStore) {
|
|
14161
|
-
const Sage =
|
|
14329
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14162
14330
|
if (!Sage?.recoverSage) {
|
|
14163
14331
|
send(ws, {
|
|
14164
14332
|
type: "memory.sage.recover",
|
|
@@ -14202,7 +14370,7 @@ async function handleSageRecover(ws, msg, memoryStore) {
|
|
|
14202
14370
|
}
|
|
14203
14371
|
}
|
|
14204
14372
|
async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
14205
|
-
const Sage =
|
|
14373
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14206
14374
|
if (!Sage) {
|
|
14207
14375
|
send(ws, {
|
|
14208
14376
|
type: "memory.sage.listCandidates",
|
|
@@ -14230,7 +14398,7 @@ async function handleSageListCandidates(ws, msg, memoryStore) {
|
|
|
14230
14398
|
}
|
|
14231
14399
|
}
|
|
14232
14400
|
async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
14233
|
-
const Sage =
|
|
14401
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14234
14402
|
if (!Sage) {
|
|
14235
14403
|
send(ws, {
|
|
14236
14404
|
type: "memory.sage.candidateResolve",
|
|
@@ -14287,7 +14455,7 @@ async function handleSageCandidateResolve(ws, msg, memoryStore) {
|
|
|
14287
14455
|
}
|
|
14288
14456
|
}
|
|
14289
14457
|
async function handleSageBackfillRecoverable(ws, msg, memoryStore) {
|
|
14290
|
-
const Sage =
|
|
14458
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14291
14459
|
if (!Sage?.backfillRecoverable) {
|
|
14292
14460
|
send(ws, {
|
|
14293
14461
|
type: "memory.sage.backfillRecoverable",
|
|
@@ -14327,7 +14495,7 @@ async function handleSageBackfillRecoverable(ws, msg, memoryStore) {
|
|
|
14327
14495
|
}
|
|
14328
14496
|
}
|
|
14329
14497
|
async function handleSageForFile(ws, msg, memoryStore) {
|
|
14330
|
-
const Sage =
|
|
14498
|
+
const Sage = getSageSurface2(memoryStore);
|
|
14331
14499
|
if (!Sage?.findMemoriesForFile) {
|
|
14332
14500
|
send(ws, {
|
|
14333
14501
|
type: "memory.sage.forFile",
|
|
@@ -14419,6 +14587,9 @@ async function handleMemoryRoute(ctx, ws, message) {
|
|
|
14419
14587
|
case "memory.sage.forFile":
|
|
14420
14588
|
await handleSageForFile(ws, message, store);
|
|
14421
14589
|
return true;
|
|
14590
|
+
case "memory.sage.searchBreakdown":
|
|
14591
|
+
await handleSageSearchBreakdown(ws, message, store);
|
|
14592
|
+
return true;
|
|
14422
14593
|
default:
|
|
14423
14594
|
return false;
|
|
14424
14595
|
}
|
|
@@ -14762,17 +14933,25 @@ function createModelOperations(context) {
|
|
|
14762
14933
|
// src/server/port-utils.ts
|
|
14763
14934
|
import * as net2 from "node:net";
|
|
14764
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
|
+
}
|
|
14765
14941
|
function isPortFree(host, port) {
|
|
14942
|
+
return probePort(host, port).then((err) => err === null);
|
|
14943
|
+
}
|
|
14944
|
+
function probePort(host, port) {
|
|
14766
14945
|
return new Promise((resolve19) => {
|
|
14767
14946
|
const srv = net2.createServer();
|
|
14768
|
-
srv.once("error", () => resolve19(
|
|
14947
|
+
srv.once("error", (err) => resolve19(err));
|
|
14769
14948
|
srv.once("listening", () => {
|
|
14770
|
-
srv.close(() => resolve19(
|
|
14949
|
+
srv.close(() => resolve19(null));
|
|
14771
14950
|
});
|
|
14772
14951
|
try {
|
|
14773
14952
|
srv.listen(port, host);
|
|
14774
|
-
} catch {
|
|
14775
|
-
resolve19(
|
|
14953
|
+
} catch (err) {
|
|
14954
|
+
resolve19(err);
|
|
14776
14955
|
}
|
|
14777
14956
|
});
|
|
14778
14957
|
}
|
|
@@ -14781,7 +14960,7 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
14781
14960
|
const maxTries = opts.maxTries ?? 200;
|
|
14782
14961
|
let port = startPort;
|
|
14783
14962
|
for (let i = 0; i < maxTries; i++) {
|
|
14784
|
-
if (port >
|
|
14963
|
+
if (port > MAX_TCP_PORT) port = 1024 + port % 5e4;
|
|
14785
14964
|
if (!exclude.has(port) && await isPortFree(host, port)) {
|
|
14786
14965
|
return port;
|
|
14787
14966
|
}
|
|
@@ -14792,6 +14971,50 @@ async function findFreePort(host, startPort, opts = {}) {
|
|
|
14792
14971
|
field: "port"
|
|
14793
14972
|
});
|
|
14794
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
|
+
}
|
|
14795
15018
|
|
|
14796
15019
|
// src/server/intake-service.ts
|
|
14797
15020
|
import { resolveWstackPaths as resolveWstackPaths5 } from "@wrongstack/core/utils";
|
|
@@ -17177,6 +17400,7 @@ var CLIENT_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
17177
17400
|
"memory.sage.listPage",
|
|
17178
17401
|
"memory.sage.recover",
|
|
17179
17402
|
"memory.sage.remember",
|
|
17403
|
+
"memory.sage.searchBreakdown",
|
|
17180
17404
|
"memory.sage.update"
|
|
17181
17405
|
];
|
|
17182
17406
|
var CLIENT_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -17464,6 +17688,7 @@ var SERVER_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
17464
17688
|
"memory.sage.listPage",
|
|
17465
17689
|
"memory.sage.recover",
|
|
17466
17690
|
"memory.sage.remember",
|
|
17691
|
+
"memory.sage.searchBreakdown",
|
|
17467
17692
|
"memory.sage.update"
|
|
17468
17693
|
];
|
|
17469
17694
|
var SERVER_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -18150,7 +18375,12 @@ function createSessionHandlers(ctx) {
|
|
|
18150
18375
|
sessionScopedPath(sessionsDirectory(), next.id, ".tasks.json")
|
|
18151
18376
|
);
|
|
18152
18377
|
ctx.tokenCounter.reset?.();
|
|
18153
|
-
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
|
+
}
|
|
18154
18384
|
ctx.setSessionStartedAt?.(Date.now());
|
|
18155
18385
|
await ctx.onSessionSwapped?.(next.id);
|
|
18156
18386
|
};
|
|
@@ -22310,6 +22540,11 @@ import {
|
|
|
22310
22540
|
wstackGlobalRoot as wstackGlobalRoot2
|
|
22311
22541
|
} from "@wrongstack/core/utils";
|
|
22312
22542
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
22543
|
+
import {
|
|
22544
|
+
TransformersEmbeddingProvider,
|
|
22545
|
+
VectorMemoryStore,
|
|
22546
|
+
startFirstBootSageSync
|
|
22547
|
+
} from "@wrongstack/vector-memory";
|
|
22313
22548
|
|
|
22314
22549
|
// src/server/backend-services.ts
|
|
22315
22550
|
import { join as join12 } from "node:path";
|
|
@@ -25262,7 +25497,7 @@ async function resolvePorts(opts) {
|
|
|
25262
25497
|
const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
|
|
25263
25498
|
const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
|
|
25264
25499
|
const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
|
|
25265
|
-
const strictPort =
|
|
25500
|
+
const strictPort = isStrictPort();
|
|
25266
25501
|
let httpPort = requestedHttpPort;
|
|
25267
25502
|
if (!strictPort) {
|
|
25268
25503
|
httpPort = await findFreePort(wsHost, requestedHttpPort);
|
|
@@ -25481,7 +25716,7 @@ function registerShutdown(deps2) {
|
|
|
25481
25716
|
|
|
25482
25717
|
// src/server/start-webui-companion.ts
|
|
25483
25718
|
import * as http2 from "node:http";
|
|
25484
|
-
function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
25719
|
+
async function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
25485
25720
|
const companion = wsHost === "127.0.0.1" ? "::1" : wsHost === "0.0.0.0" || wsHost === void 0 ? "::" : wsHost === "::" || wsHost === "[::]" ? "0.0.0.0" : null;
|
|
25486
25721
|
if (!companion) return null;
|
|
25487
25722
|
const companionLabel = companion.includes(":") ? `[${companion}]` : companion;
|
|
@@ -25492,16 +25727,23 @@ function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
|
25492
25727
|
(req, socket, head2) => httpServer.emit("upgrade", req, socket, head2)
|
|
25493
25728
|
);
|
|
25494
25729
|
companionServer.on("error", (err) => {
|
|
25495
|
-
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL"
|
|
25730
|
+
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL";
|
|
25496
25731
|
if (!expected) {
|
|
25497
25732
|
console.warn(
|
|
25498
25733
|
`[WebUI] companion listener on ${companionLabel} failed (${err.code ?? "unknown"}): ${err.message}. The primary address is unaffected.`
|
|
25499
25734
|
);
|
|
25500
25735
|
}
|
|
25501
25736
|
});
|
|
25502
|
-
|
|
25503
|
-
|
|
25504
|
-
})
|
|
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}`);
|
|
25505
25747
|
return companionServer;
|
|
25506
25748
|
}
|
|
25507
25749
|
|
|
@@ -25696,6 +25938,7 @@ function setupWebuiShutdown(options) {
|
|
|
25696
25938
|
await options.memoryStore.dispose().catch(
|
|
25697
25939
|
(err) => options.logger.warn(`sage connection disposal failed: ${toErrorMessage15(err)}`)
|
|
25698
25940
|
);
|
|
25941
|
+
options.vectorMemoryStore?.close();
|
|
25699
25942
|
await unregisterInstance(process.pid, path32.dirname(options.globalConfigPath));
|
|
25700
25943
|
}
|
|
25701
25944
|
});
|
|
@@ -25760,7 +26003,8 @@ function createStandaloneTodosCheckpointLifecycle(input) {
|
|
|
25760
26003
|
async function startWebUI(opts = {}) {
|
|
25761
26004
|
ensureSessionShell();
|
|
25762
26005
|
const ports = await resolvePorts(opts);
|
|
25763
|
-
const { wsHost,
|
|
26006
|
+
const { wsHost, publicUrl, publicWsUrl, requireToken } = ports;
|
|
26007
|
+
let httpPort = ports.httpPort;
|
|
25764
26008
|
console.log("[WebUI] Starting backend services...");
|
|
25765
26009
|
const boot = await bootConfig();
|
|
25766
26010
|
const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
|
|
@@ -25788,6 +26032,27 @@ async function startWebUI(opts = {}) {
|
|
|
25788
26032
|
);
|
|
25789
26033
|
}
|
|
25790
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
|
+
}
|
|
25791
26056
|
const preContext = await createPreContextServices({
|
|
25792
26057
|
config,
|
|
25793
26058
|
wpaths,
|
|
@@ -25835,6 +26100,13 @@ async function startWebUI(opts = {}) {
|
|
|
25835
26100
|
let sessionStartedAt = preContext.sessionStartedAt;
|
|
25836
26101
|
let modeId = preContext.modeId;
|
|
25837
26102
|
const needsSetup = preContext.needsSetup;
|
|
26103
|
+
if (vectorMemoryStore) {
|
|
26104
|
+
void startFirstBootSageSync({
|
|
26105
|
+
store: vectorMemoryStore,
|
|
26106
|
+
memoryStore,
|
|
26107
|
+
logger
|
|
26108
|
+
});
|
|
26109
|
+
}
|
|
25838
26110
|
const prefSnapshot2 = () => prefSnapshot(context.meta);
|
|
25839
26111
|
const persistPrefsToConfig2 = async (payload) => persistPrefsToConfig(prefHelperDeps, configWriteLock, payload);
|
|
25840
26112
|
const trustBoundary = opts.trustBoundary ?? createCompatibilityTrustBoundary3({ policyId: "webui-trusted-host-compat-v1" });
|
|
@@ -25955,7 +26227,12 @@ async function startWebUI(opts = {}) {
|
|
|
25955
26227
|
events,
|
|
25956
26228
|
permissionPolicy
|
|
25957
26229
|
}),
|
|
25958
|
-
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
|
|
25959
26236
|
});
|
|
25960
26237
|
const wsResult = createWsServers(httpServer, ports, accessToken);
|
|
25961
26238
|
const { wssPrimary, wssSecondary, clients } = wsResult;
|
|
@@ -26010,7 +26287,25 @@ async function startWebUI(opts = {}) {
|
|
|
26010
26287
|
},
|
|
26011
26288
|
watcherMetricsRef
|
|
26012
26289
|
);
|
|
26013
|
-
|
|
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
|
+
{
|
|
26014
26309
|
const authHint = requireToken ? " (authentication required; configure WEBUI_TOKEN out of band)" : "";
|
|
26015
26310
|
console.log(`[WebUI] HTTP server listening on http://${wsHost}:${httpPort}${authHint}`);
|
|
26016
26311
|
const extraUrls = formatExternalAccessUrls({
|
|
@@ -26021,8 +26316,8 @@ async function startWebUI(opts = {}) {
|
|
|
26021
26316
|
if (extraUrls.length > 0) {
|
|
26022
26317
|
console.log("[WebUI] Protected endpoints on external interfaces:\n" + extraUrls.join("\n"));
|
|
26023
26318
|
}
|
|
26024
|
-
}
|
|
26025
|
-
const companionServer = setupCompanionServer(httpServer, wsHost, httpPort);
|
|
26319
|
+
}
|
|
26320
|
+
const companionServer = await setupCompanionServer(httpServer, wsHost, httpPort);
|
|
26026
26321
|
async function touchProjectEntry(root, workDir) {
|
|
26027
26322
|
const resolved = path33.resolve(root);
|
|
26028
26323
|
const manifest = await loadManifest(globalConfigPath);
|
|
@@ -26281,6 +26576,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26281
26576
|
},
|
|
26282
26577
|
codebaseIndexing,
|
|
26283
26578
|
memoryStore,
|
|
26579
|
+
vectorMemoryStore,
|
|
26284
26580
|
globalConfigPath
|
|
26285
26581
|
});
|
|
26286
26582
|
}
|