@wrongstack/webui-server 0.307.1 → 0.308.0
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 +238 -5
- package/dist/server/entry.js +234 -3
- package/dist/server/frontend-static-serve.d.ts +10 -0
- package/dist/server/http-server/api-router.d.ts +11 -0
- package/dist/server/http-server/vector-memory-handlers.d.ts +55 -0
- package/dist/server/http-server.d.ts +11 -0
- package/dist/server/server-runtime.d.ts +10 -0
- package/package.json +12 -11
package/dist/index.js
CHANGED
|
@@ -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,183 @@ function strictDecodeParam(segment, res) {
|
|
|
10410
10410
|
}
|
|
10411
10411
|
}
|
|
10412
10412
|
|
|
10413
|
+
// src/server/http-server/vector-memory-handlers.ts
|
|
10414
|
+
import { sanitizeApiError as sanitizeApiError2 } from "@wrongstack/core/security";
|
|
10415
|
+
function snapshotVectorMemory(store, opts = {}) {
|
|
10416
|
+
const stats = store.stats();
|
|
10417
|
+
return {
|
|
10418
|
+
storePath: opts.projectRoot,
|
|
10419
|
+
modelCacheDir: opts.modelCacheDir,
|
|
10420
|
+
stats
|
|
10421
|
+
};
|
|
10422
|
+
}
|
|
10423
|
+
async function handleVectorMemoryStatus(res, getStore, opts = {}) {
|
|
10424
|
+
const store = getStore();
|
|
10425
|
+
if (!store) {
|
|
10426
|
+
const body = { enabled: false };
|
|
10427
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10428
|
+
res.end(JSON.stringify(body));
|
|
10429
|
+
return;
|
|
10430
|
+
}
|
|
10431
|
+
try {
|
|
10432
|
+
const snap = snapshotVectorMemory(store, opts);
|
|
10433
|
+
const body = {
|
|
10434
|
+
enabled: true,
|
|
10435
|
+
storePath: snap.storePath,
|
|
10436
|
+
modelCacheDir: snap.modelCacheDir,
|
|
10437
|
+
providerId: snap.stats.modelId,
|
|
10438
|
+
modelId: snap.stats.modelId,
|
|
10439
|
+
dimensions: snap.stats.dimensions,
|
|
10440
|
+
entries: snap.stats.entries,
|
|
10441
|
+
vectors: snap.stats.vectors,
|
|
10442
|
+
providers: snap.stats.providers
|
|
10443
|
+
};
|
|
10444
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10445
|
+
res.end(JSON.stringify(body));
|
|
10446
|
+
} catch (error2) {
|
|
10447
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10448
|
+
res.end(
|
|
10449
|
+
JSON.stringify({
|
|
10450
|
+
error: "Vector memory status failed",
|
|
10451
|
+
detail: sanitizeApiError2(error2)
|
|
10452
|
+
})
|
|
10453
|
+
);
|
|
10454
|
+
}
|
|
10455
|
+
}
|
|
10456
|
+
function parseSearchParams(url) {
|
|
10457
|
+
const query = url.searchParams.get("q") ?? "";
|
|
10458
|
+
const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
|
|
10459
|
+
const limit = Math.min(50, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 10));
|
|
10460
|
+
const rawThreshold = url.searchParams.get("threshold");
|
|
10461
|
+
const threshold = rawThreshold !== null && rawThreshold !== "" ? Math.max(0, Math.min(1, Number.parseFloat(rawThreshold))) : void 0;
|
|
10462
|
+
return { query, limit, threshold: Number.isFinite(threshold) ? threshold : void 0 };
|
|
10463
|
+
}
|
|
10464
|
+
async function handleVectorMemorySearch(res, url, getStore) {
|
|
10465
|
+
const store = getStore();
|
|
10466
|
+
if (!store) {
|
|
10467
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10468
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10469
|
+
return;
|
|
10470
|
+
}
|
|
10471
|
+
const { query, limit, threshold } = parseSearchParams(url);
|
|
10472
|
+
if (query.trim().length === 0) {
|
|
10473
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10474
|
+
res.end(JSON.stringify({ error: "Missing required query parameter `q`" }));
|
|
10475
|
+
return;
|
|
10476
|
+
}
|
|
10477
|
+
try {
|
|
10478
|
+
const hits = await store.search(query, {
|
|
10479
|
+
limit,
|
|
10480
|
+
...threshold !== void 0 ? { threshold } : {}
|
|
10481
|
+
});
|
|
10482
|
+
const body = {
|
|
10483
|
+
hits: hits.map((h) => ({
|
|
10484
|
+
id: h.entry.id,
|
|
10485
|
+
score: h.score,
|
|
10486
|
+
text: h.entry.text,
|
|
10487
|
+
...h.entry.summary ? { summary: h.entry.summary } : {},
|
|
10488
|
+
tags: h.entry.tags
|
|
10489
|
+
})),
|
|
10490
|
+
count: hits.length
|
|
10491
|
+
};
|
|
10492
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10493
|
+
res.end(JSON.stringify(body));
|
|
10494
|
+
} catch (error2) {
|
|
10495
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10496
|
+
res.end(
|
|
10497
|
+
JSON.stringify({
|
|
10498
|
+
error: "Vector memory search failed",
|
|
10499
|
+
detail: sanitizeApiError2(error2)
|
|
10500
|
+
})
|
|
10501
|
+
);
|
|
10502
|
+
}
|
|
10503
|
+
}
|
|
10504
|
+
function parseStoreBody(req) {
|
|
10505
|
+
return new Promise((resolve20) => {
|
|
10506
|
+
let raw = "";
|
|
10507
|
+
req.setEncoding("utf8");
|
|
10508
|
+
req.on("data", (chunk) => {
|
|
10509
|
+
raw += chunk;
|
|
10510
|
+
if (raw.length > 64 * 1024) {
|
|
10511
|
+
req.destroy();
|
|
10512
|
+
resolve20(null);
|
|
10513
|
+
}
|
|
10514
|
+
});
|
|
10515
|
+
req.on("end", () => {
|
|
10516
|
+
try {
|
|
10517
|
+
resolve20(raw ? JSON.parse(raw) : {});
|
|
10518
|
+
} catch {
|
|
10519
|
+
resolve20(null);
|
|
10520
|
+
}
|
|
10521
|
+
});
|
|
10522
|
+
req.on("error", () => resolve20(null));
|
|
10523
|
+
});
|
|
10524
|
+
}
|
|
10525
|
+
async function handleVectorMemoryStore(res, req, getStore) {
|
|
10526
|
+
const store = getStore();
|
|
10527
|
+
if (!store) {
|
|
10528
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10529
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10530
|
+
return;
|
|
10531
|
+
}
|
|
10532
|
+
const body = await parseStoreBody(req);
|
|
10533
|
+
if (!body) {
|
|
10534
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10535
|
+
res.end(JSON.stringify({ error: "Malformed JSON body" }));
|
|
10536
|
+
return;
|
|
10537
|
+
}
|
|
10538
|
+
const text2 = typeof body.text === "string" ? body.text.trim() : "";
|
|
10539
|
+
if (text2.length === 0) {
|
|
10540
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10541
|
+
res.end(JSON.stringify({ error: "Missing required field `text`" }));
|
|
10542
|
+
return;
|
|
10543
|
+
}
|
|
10544
|
+
const tags = Array.isArray(body.tags) ? body.tags.filter((t) => typeof t === "string") : [];
|
|
10545
|
+
try {
|
|
10546
|
+
const entry = await store.remember({ text: text2, tags });
|
|
10547
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10548
|
+
res.end(
|
|
10549
|
+
JSON.stringify({
|
|
10550
|
+
id: entry.id,
|
|
10551
|
+
hasVector: entry.vector !== void 0,
|
|
10552
|
+
dimensions: entry.dimensions
|
|
10553
|
+
})
|
|
10554
|
+
);
|
|
10555
|
+
} catch (error2) {
|
|
10556
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10557
|
+
res.end(
|
|
10558
|
+
JSON.stringify({
|
|
10559
|
+
error: "Vector memory store failed",
|
|
10560
|
+
detail: sanitizeApiError2(error2)
|
|
10561
|
+
})
|
|
10562
|
+
);
|
|
10563
|
+
}
|
|
10564
|
+
}
|
|
10565
|
+
async function handleVectorMemoryForget(res, url, getStore) {
|
|
10566
|
+
const store = getStore();
|
|
10567
|
+
if (!store) {
|
|
10568
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10569
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10570
|
+
return;
|
|
10571
|
+
}
|
|
10572
|
+
const match = /^\/api\/vector-memory\/store\/([^/]+)$/.exec(url.pathname);
|
|
10573
|
+
const id = match ? strictDecodeParam(decodeSessionId(match[1]), res) : null;
|
|
10574
|
+
if (id === null) return;
|
|
10575
|
+
try {
|
|
10576
|
+
const removed = store.forget(id);
|
|
10577
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10578
|
+
res.end(JSON.stringify({ removed }));
|
|
10579
|
+
} catch (error2) {
|
|
10580
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10581
|
+
res.end(
|
|
10582
|
+
JSON.stringify({
|
|
10583
|
+
error: "Vector memory forget failed",
|
|
10584
|
+
detail: sanitizeApiError2(error2)
|
|
10585
|
+
})
|
|
10586
|
+
);
|
|
10587
|
+
}
|
|
10588
|
+
}
|
|
10589
|
+
|
|
10413
10590
|
// src/server/http-server/api-router.ts
|
|
10414
10591
|
async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessTokenOk, getTechStackRuntime) {
|
|
10415
10592
|
if (url.pathname === "/api/fleet/ping" && req.method === "POST") {
|
|
@@ -10748,7 +10925,7 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
10748
10925
|
res.end(
|
|
10749
10926
|
JSON.stringify({
|
|
10750
10927
|
error: "TechStack store unavailable",
|
|
10751
|
-
detail:
|
|
10928
|
+
detail: sanitizeApiError3(error2)
|
|
10752
10929
|
})
|
|
10753
10930
|
);
|
|
10754
10931
|
return true;
|
|
@@ -10811,6 +10988,54 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
10811
10988
|
);
|
|
10812
10989
|
return true;
|
|
10813
10990
|
}
|
|
10991
|
+
const vectorForgetMatch = /^\/api\/vector-memory\/store\/([^/]+)$/.exec(url.pathname);
|
|
10992
|
+
if (vectorForgetMatch && req.method === "DELETE") {
|
|
10993
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
10994
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
10995
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
10996
|
+
return true;
|
|
10997
|
+
}
|
|
10998
|
+
await handleVectorMemoryForget(
|
|
10999
|
+
res,
|
|
11000
|
+
url,
|
|
11001
|
+
() => deps2.getVectorMemoryStore?.()
|
|
11002
|
+
);
|
|
11003
|
+
return true;
|
|
11004
|
+
}
|
|
11005
|
+
if (url.pathname === "/api/vector-memory/status" && req.method === "GET") {
|
|
11006
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11007
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11008
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11009
|
+
return true;
|
|
11010
|
+
}
|
|
11011
|
+
await handleVectorMemoryStatus(
|
|
11012
|
+
res,
|
|
11013
|
+
() => deps2.getVectorMemoryStore?.(),
|
|
11014
|
+
{
|
|
11015
|
+
...deps2.projectRoot ? { projectRoot: deps2.projectRoot } : {},
|
|
11016
|
+
...deps2.vectorMemoryModelCacheDir ? { modelCacheDir: deps2.vectorMemoryModelCacheDir } : {}
|
|
11017
|
+
}
|
|
11018
|
+
);
|
|
11019
|
+
return true;
|
|
11020
|
+
}
|
|
11021
|
+
if (url.pathname === "/api/vector-memory/search" && req.method === "GET") {
|
|
11022
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11023
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11024
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11025
|
+
return true;
|
|
11026
|
+
}
|
|
11027
|
+
await handleVectorMemorySearch(res, url, () => deps2.getVectorMemoryStore?.());
|
|
11028
|
+
return true;
|
|
11029
|
+
}
|
|
11030
|
+
if (url.pathname === "/api/vector-memory/store" && req.method === "POST") {
|
|
11031
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
11032
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
11033
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
11034
|
+
return true;
|
|
11035
|
+
}
|
|
11036
|
+
await handleVectorMemoryStore(res, req, () => deps2.getVectorMemoryStore?.());
|
|
11037
|
+
return true;
|
|
11038
|
+
}
|
|
10814
11039
|
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
10815
11040
|
await handleDeadCodeActionPlan(
|
|
10816
11041
|
res,
|
|
@@ -14988,7 +15213,9 @@ async function startStaticServe(opts, deps2 = {}) {
|
|
|
14988
15213
|
apiToken: opts.apiToken,
|
|
14989
15214
|
requireToken: opts.requireToken,
|
|
14990
15215
|
allowedHostnames: opts.allowedHostnames,
|
|
14991
|
-
intakeService
|
|
15216
|
+
intakeService,
|
|
15217
|
+
...opts.getVectorMemoryStore ? { getVectorMemoryStore: opts.getVectorMemoryStore } : {},
|
|
15218
|
+
...opts.vectorMemoryModelCacheDir ? { vectorMemoryModelCacheDir: opts.vectorMemoryModelCacheDir } : {}
|
|
14992
15219
|
});
|
|
14993
15220
|
if (!opts.deferListen) {
|
|
14994
15221
|
server.listen(opts.httpPort, opts.host);
|
|
@@ -19429,6 +19656,10 @@ function createSessionHandlers(ctx) {
|
|
|
19429
19656
|
return;
|
|
19430
19657
|
}
|
|
19431
19658
|
} else {
|
|
19659
|
+
try {
|
|
19660
|
+
ctx.abortActiveRun?.(clearedSessionId);
|
|
19661
|
+
} catch {
|
|
19662
|
+
}
|
|
19432
19663
|
ctx.context.state.replaceMessages([]);
|
|
19433
19664
|
ctx.context.state.replaceTodos([]);
|
|
19434
19665
|
resetContextAccounting();
|
|
@@ -21424,7 +21655,7 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
21424
21655
|
// background after session.new/resume. The run's own end() cleanup
|
|
21425
21656
|
// removes controllers from the map when it unwinds.
|
|
21426
21657
|
abortActiveRun: (sessionId) => {
|
|
21427
|
-
if (sessionId) {
|
|
21658
|
+
if (sessionId && deps2.conversationCtx.abortControllers.has(sessionId)) {
|
|
21428
21659
|
deps2.conversationCtx.abortControllers.get(sessionId)?.abort();
|
|
21429
21660
|
} else {
|
|
21430
21661
|
for (const controller of [...deps2.conversationCtx.abortControllers.values()]) {
|
|
@@ -27356,7 +27587,9 @@ function startHttpServer(opts) {
|
|
|
27356
27587
|
getLlm: opts.getLlm,
|
|
27357
27588
|
executePackageOperation: opts.executePackageOperation,
|
|
27358
27589
|
projectRoot: opts.projectRoot,
|
|
27359
|
-
intakeService
|
|
27590
|
+
intakeService,
|
|
27591
|
+
...opts.getVectorMemoryStore ? { getVectorMemoryStore: opts.getVectorMemoryStore } : {},
|
|
27592
|
+
...opts.vectorMemoryModelCacheDir ? { vectorMemoryModelCacheDir: opts.vectorMemoryModelCacheDir } : {}
|
|
27360
27593
|
});
|
|
27361
27594
|
return httpServer;
|
|
27362
27595
|
}
|
package/dist/server/entry.js
CHANGED
|
@@ -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,183 @@ function strictDecodeParam(segment, res) {
|
|
|
10316
10316
|
}
|
|
10317
10317
|
}
|
|
10318
10318
|
|
|
10319
|
+
// src/server/http-server/vector-memory-handlers.ts
|
|
10320
|
+
import { sanitizeApiError as sanitizeApiError2 } from "@wrongstack/core/security";
|
|
10321
|
+
function snapshotVectorMemory(store, opts = {}) {
|
|
10322
|
+
const stats = store.stats();
|
|
10323
|
+
return {
|
|
10324
|
+
storePath: opts.projectRoot,
|
|
10325
|
+
modelCacheDir: opts.modelCacheDir,
|
|
10326
|
+
stats
|
|
10327
|
+
};
|
|
10328
|
+
}
|
|
10329
|
+
async function handleVectorMemoryStatus(res, getStore, opts = {}) {
|
|
10330
|
+
const store = getStore();
|
|
10331
|
+
if (!store) {
|
|
10332
|
+
const body = { enabled: false };
|
|
10333
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10334
|
+
res.end(JSON.stringify(body));
|
|
10335
|
+
return;
|
|
10336
|
+
}
|
|
10337
|
+
try {
|
|
10338
|
+
const snap = snapshotVectorMemory(store, opts);
|
|
10339
|
+
const body = {
|
|
10340
|
+
enabled: true,
|
|
10341
|
+
storePath: snap.storePath,
|
|
10342
|
+
modelCacheDir: snap.modelCacheDir,
|
|
10343
|
+
providerId: snap.stats.modelId,
|
|
10344
|
+
modelId: snap.stats.modelId,
|
|
10345
|
+
dimensions: snap.stats.dimensions,
|
|
10346
|
+
entries: snap.stats.entries,
|
|
10347
|
+
vectors: snap.stats.vectors,
|
|
10348
|
+
providers: snap.stats.providers
|
|
10349
|
+
};
|
|
10350
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10351
|
+
res.end(JSON.stringify(body));
|
|
10352
|
+
} catch (error2) {
|
|
10353
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10354
|
+
res.end(
|
|
10355
|
+
JSON.stringify({
|
|
10356
|
+
error: "Vector memory status failed",
|
|
10357
|
+
detail: sanitizeApiError2(error2)
|
|
10358
|
+
})
|
|
10359
|
+
);
|
|
10360
|
+
}
|
|
10361
|
+
}
|
|
10362
|
+
function parseSearchParams(url) {
|
|
10363
|
+
const query = url.searchParams.get("q") ?? "";
|
|
10364
|
+
const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "10", 10);
|
|
10365
|
+
const limit = Math.min(50, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 10));
|
|
10366
|
+
const rawThreshold = url.searchParams.get("threshold");
|
|
10367
|
+
const threshold = rawThreshold !== null && rawThreshold !== "" ? Math.max(0, Math.min(1, Number.parseFloat(rawThreshold))) : void 0;
|
|
10368
|
+
return { query, limit, threshold: Number.isFinite(threshold) ? threshold : void 0 };
|
|
10369
|
+
}
|
|
10370
|
+
async function handleVectorMemorySearch(res, url, getStore) {
|
|
10371
|
+
const store = getStore();
|
|
10372
|
+
if (!store) {
|
|
10373
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10374
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10375
|
+
return;
|
|
10376
|
+
}
|
|
10377
|
+
const { query, limit, threshold } = parseSearchParams(url);
|
|
10378
|
+
if (query.trim().length === 0) {
|
|
10379
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10380
|
+
res.end(JSON.stringify({ error: "Missing required query parameter `q`" }));
|
|
10381
|
+
return;
|
|
10382
|
+
}
|
|
10383
|
+
try {
|
|
10384
|
+
const hits = await store.search(query, {
|
|
10385
|
+
limit,
|
|
10386
|
+
...threshold !== void 0 ? { threshold } : {}
|
|
10387
|
+
});
|
|
10388
|
+
const body = {
|
|
10389
|
+
hits: hits.map((h) => ({
|
|
10390
|
+
id: h.entry.id,
|
|
10391
|
+
score: h.score,
|
|
10392
|
+
text: h.entry.text,
|
|
10393
|
+
...h.entry.summary ? { summary: h.entry.summary } : {},
|
|
10394
|
+
tags: h.entry.tags
|
|
10395
|
+
})),
|
|
10396
|
+
count: hits.length
|
|
10397
|
+
};
|
|
10398
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
10399
|
+
res.end(JSON.stringify(body));
|
|
10400
|
+
} catch (error2) {
|
|
10401
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10402
|
+
res.end(
|
|
10403
|
+
JSON.stringify({
|
|
10404
|
+
error: "Vector memory search failed",
|
|
10405
|
+
detail: sanitizeApiError2(error2)
|
|
10406
|
+
})
|
|
10407
|
+
);
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
10410
|
+
function parseStoreBody(req) {
|
|
10411
|
+
return new Promise((resolve19) => {
|
|
10412
|
+
let raw = "";
|
|
10413
|
+
req.setEncoding("utf8");
|
|
10414
|
+
req.on("data", (chunk) => {
|
|
10415
|
+
raw += chunk;
|
|
10416
|
+
if (raw.length > 64 * 1024) {
|
|
10417
|
+
req.destroy();
|
|
10418
|
+
resolve19(null);
|
|
10419
|
+
}
|
|
10420
|
+
});
|
|
10421
|
+
req.on("end", () => {
|
|
10422
|
+
try {
|
|
10423
|
+
resolve19(raw ? JSON.parse(raw) : {});
|
|
10424
|
+
} catch {
|
|
10425
|
+
resolve19(null);
|
|
10426
|
+
}
|
|
10427
|
+
});
|
|
10428
|
+
req.on("error", () => resolve19(null));
|
|
10429
|
+
});
|
|
10430
|
+
}
|
|
10431
|
+
async function handleVectorMemoryStore(res, req, getStore) {
|
|
10432
|
+
const store = getStore();
|
|
10433
|
+
if (!store) {
|
|
10434
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10435
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10436
|
+
return;
|
|
10437
|
+
}
|
|
10438
|
+
const body = await parseStoreBody(req);
|
|
10439
|
+
if (!body) {
|
|
10440
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10441
|
+
res.end(JSON.stringify({ error: "Malformed JSON body" }));
|
|
10442
|
+
return;
|
|
10443
|
+
}
|
|
10444
|
+
const text = typeof body.text === "string" ? body.text.trim() : "";
|
|
10445
|
+
if (text.length === 0) {
|
|
10446
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
10447
|
+
res.end(JSON.stringify({ error: "Missing required field `text`" }));
|
|
10448
|
+
return;
|
|
10449
|
+
}
|
|
10450
|
+
const tags = Array.isArray(body.tags) ? body.tags.filter((t) => typeof t === "string") : [];
|
|
10451
|
+
try {
|
|
10452
|
+
const entry = await store.remember({ text, tags });
|
|
10453
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10454
|
+
res.end(
|
|
10455
|
+
JSON.stringify({
|
|
10456
|
+
id: entry.id,
|
|
10457
|
+
hasVector: entry.vector !== void 0,
|
|
10458
|
+
dimensions: entry.dimensions
|
|
10459
|
+
})
|
|
10460
|
+
);
|
|
10461
|
+
} catch (error2) {
|
|
10462
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10463
|
+
res.end(
|
|
10464
|
+
JSON.stringify({
|
|
10465
|
+
error: "Vector memory store failed",
|
|
10466
|
+
detail: sanitizeApiError2(error2)
|
|
10467
|
+
})
|
|
10468
|
+
);
|
|
10469
|
+
}
|
|
10470
|
+
}
|
|
10471
|
+
async function handleVectorMemoryForget(res, url, getStore) {
|
|
10472
|
+
const store = getStore();
|
|
10473
|
+
if (!store) {
|
|
10474
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
10475
|
+
res.end(JSON.stringify({ error: "Vector memory not enabled in this host" }));
|
|
10476
|
+
return;
|
|
10477
|
+
}
|
|
10478
|
+
const match = /^\/api\/vector-memory\/store\/([^/]+)$/.exec(url.pathname);
|
|
10479
|
+
const id = match ? strictDecodeParam(decodeSessionId(match[1]), res) : null;
|
|
10480
|
+
if (id === null) return;
|
|
10481
|
+
try {
|
|
10482
|
+
const removed = store.forget(id);
|
|
10483
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10484
|
+
res.end(JSON.stringify({ removed }));
|
|
10485
|
+
} catch (error2) {
|
|
10486
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10487
|
+
res.end(
|
|
10488
|
+
JSON.stringify({
|
|
10489
|
+
error: "Vector memory forget failed",
|
|
10490
|
+
detail: sanitizeApiError2(error2)
|
|
10491
|
+
})
|
|
10492
|
+
);
|
|
10493
|
+
}
|
|
10494
|
+
}
|
|
10495
|
+
|
|
10319
10496
|
// src/server/http-server/api-router.ts
|
|
10320
10497
|
async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessTokenOk, getTechStackRuntime) {
|
|
10321
10498
|
if (url.pathname === "/api/fleet/ping" && req.method === "POST") {
|
|
@@ -10654,7 +10831,7 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
10654
10831
|
res.end(
|
|
10655
10832
|
JSON.stringify({
|
|
10656
10833
|
error: "TechStack store unavailable",
|
|
10657
|
-
detail:
|
|
10834
|
+
detail: sanitizeApiError3(error2)
|
|
10658
10835
|
})
|
|
10659
10836
|
);
|
|
10660
10837
|
return true;
|
|
@@ -10717,6 +10894,54 @@ async function handleApiRoutes(req, res, url, deps2, requireAccessToken, accessT
|
|
|
10717
10894
|
);
|
|
10718
10895
|
return true;
|
|
10719
10896
|
}
|
|
10897
|
+
const vectorForgetMatch = /^\/api\/vector-memory\/store\/([^/]+)$/.exec(url.pathname);
|
|
10898
|
+
if (vectorForgetMatch && req.method === "DELETE") {
|
|
10899
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
10900
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
10901
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
10902
|
+
return true;
|
|
10903
|
+
}
|
|
10904
|
+
await handleVectorMemoryForget(
|
|
10905
|
+
res,
|
|
10906
|
+
url,
|
|
10907
|
+
() => deps2.getVectorMemoryStore?.()
|
|
10908
|
+
);
|
|
10909
|
+
return true;
|
|
10910
|
+
}
|
|
10911
|
+
if (url.pathname === "/api/vector-memory/status" && req.method === "GET") {
|
|
10912
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
10913
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
10914
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
10915
|
+
return true;
|
|
10916
|
+
}
|
|
10917
|
+
await handleVectorMemoryStatus(
|
|
10918
|
+
res,
|
|
10919
|
+
() => deps2.getVectorMemoryStore?.(),
|
|
10920
|
+
{
|
|
10921
|
+
...deps2.projectRoot ? { projectRoot: deps2.projectRoot } : {},
|
|
10922
|
+
...deps2.vectorMemoryModelCacheDir ? { modelCacheDir: deps2.vectorMemoryModelCacheDir } : {}
|
|
10923
|
+
}
|
|
10924
|
+
);
|
|
10925
|
+
return true;
|
|
10926
|
+
}
|
|
10927
|
+
if (url.pathname === "/api/vector-memory/search" && req.method === "GET") {
|
|
10928
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
10929
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
10930
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
10931
|
+
return true;
|
|
10932
|
+
}
|
|
10933
|
+
await handleVectorMemorySearch(res, url, () => deps2.getVectorMemoryStore?.());
|
|
10934
|
+
return true;
|
|
10935
|
+
}
|
|
10936
|
+
if (url.pathname === "/api/vector-memory/store" && req.method === "POST") {
|
|
10937
|
+
if (requireAccessToken && !accessTokenOk) {
|
|
10938
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
10939
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
10940
|
+
return true;
|
|
10941
|
+
}
|
|
10942
|
+
await handleVectorMemoryStore(res, req, () => deps2.getVectorMemoryStore?.());
|
|
10943
|
+
return true;
|
|
10944
|
+
}
|
|
10720
10945
|
if (url.pathname === "/api/deadcode/action-plan" && req.method === "POST") {
|
|
10721
10946
|
await handleDeadCodeActionPlan(
|
|
10722
10947
|
res,
|
|
@@ -17958,6 +18183,10 @@ function createSessionHandlers(ctx) {
|
|
|
17958
18183
|
return;
|
|
17959
18184
|
}
|
|
17960
18185
|
} else {
|
|
18186
|
+
try {
|
|
18187
|
+
ctx.abortActiveRun?.(clearedSessionId);
|
|
18188
|
+
} catch {
|
|
18189
|
+
}
|
|
17961
18190
|
ctx.context.state.replaceMessages([]);
|
|
17962
18191
|
ctx.context.state.replaceTodos([]);
|
|
17963
18192
|
resetContextAccounting();
|
|
@@ -25234,7 +25463,9 @@ function startHttpServer(opts) {
|
|
|
25234
25463
|
getLlm: opts.getLlm,
|
|
25235
25464
|
executePackageOperation: opts.executePackageOperation,
|
|
25236
25465
|
projectRoot: opts.projectRoot,
|
|
25237
|
-
intakeService
|
|
25466
|
+
intakeService,
|
|
25467
|
+
...opts.getVectorMemoryStore ? { getVectorMemoryStore: opts.getVectorMemoryStore } : {},
|
|
25468
|
+
...opts.vectorMemoryModelCacheDir ? { vectorMemoryModelCacheDir: opts.vectorMemoryModelCacheDir } : {}
|
|
25238
25469
|
});
|
|
25239
25470
|
return httpServer;
|
|
25240
25471
|
}
|
|
@@ -84,6 +84,16 @@ export interface StaticServeOptions {
|
|
|
84
84
|
* routes correctly answer 503.
|
|
85
85
|
*/
|
|
86
86
|
intakeService?: CreateHttpServerOptions['intakeService'];
|
|
87
|
+
/**
|
|
88
|
+
* Optional vector-memory store. When provided, the four
|
|
89
|
+
* `/api/vector-memory/{status,search,store,store/:id}` endpoints become
|
|
90
|
+
* active. When omitted, the routes respond with `{ enabled: false }` or
|
|
91
|
+
* 503 — a non-CLI webui-server host stays on its existing surface with
|
|
92
|
+
* zero behavior change.
|
|
93
|
+
*/
|
|
94
|
+
getVectorMemoryStore?: CreateHttpServerOptions['getVectorMemoryStore'];
|
|
95
|
+
/** Model cache directory for the vector-memory provider. */
|
|
96
|
+
vectorMemoryModelCacheDir?: string | undefined;
|
|
87
97
|
}
|
|
88
98
|
/**
|
|
89
99
|
* Resolve the webui package's built `dist` directory.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type * as http from 'node:http';
|
|
2
2
|
import { type TechStackEvent } from '../techstack-handlers.js';
|
|
3
|
+
import type { VectorMemoryStore } from '@wrongstack/vector-memory';
|
|
3
4
|
export interface ApiRouterDeps {
|
|
4
5
|
globalRoot?: string | undefined;
|
|
5
6
|
projectRoot?: string | undefined;
|
|
@@ -13,6 +14,16 @@ export interface ApiRouterDeps {
|
|
|
13
14
|
model: string;
|
|
14
15
|
} | undefined) | undefined;
|
|
15
16
|
executePackageOperation?: import('../techstack-handlers.js').TechStackHandlerDeps['executePackageOperation'];
|
|
17
|
+
/**
|
|
18
|
+
* Optional vector memory store. When provided, the four
|
|
19
|
+
* `GET /api/vector-memory/status|search` and `POST /api/vector-memory/store`
|
|
20
|
+
* / `DELETE /api/vector-memory/store/:id` routes become active.
|
|
21
|
+
* Defaults to undefined so non-CLI webui-server hosts (e.g. a headless
|
|
22
|
+
* fleet dashboard) are unaffected.
|
|
23
|
+
*/
|
|
24
|
+
getVectorMemoryStore?: (() => VectorMemoryStore | undefined) | undefined;
|
|
25
|
+
/** Model cache directory for the vector memory provider. */
|
|
26
|
+
vectorMemoryModelCacheDir?: string | undefined;
|
|
16
27
|
}
|
|
17
28
|
export declare function handleApiRoutes(req: http.IncomingMessage, res: http.ServerResponse, url: URL, deps: ApiRouterDeps, requireAccessToken: boolean, accessTokenOk: boolean, getTechStackRuntime: () => Promise<{
|
|
18
29
|
store: import('@wrongstack/techstack').TechStackStore;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector memory HTTP handlers — minimal visibility surface for the
|
|
3
|
+
* WebUI/SimpleUI. Exposes the active store/provider, the model cache
|
|
4
|
+
* location, entry counts, and a search endpoint. Strictly opt-in:
|
|
5
|
+
* `getVectorMemoryStore` defaults to undefined so non-CLI webui-server
|
|
6
|
+
* hosts (e.g. a headless fleet dashboard) are unaffected.
|
|
7
|
+
*/
|
|
8
|
+
import type * as http from 'node:http';
|
|
9
|
+
import type { VectorMemoryStore } from '@wrongstack/vector-memory';
|
|
10
|
+
export interface VectorMemoryStatusResponse {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
storePath?: string | undefined;
|
|
13
|
+
modelCacheDir?: string | undefined;
|
|
14
|
+
providerId?: string | undefined;
|
|
15
|
+
modelId?: string | undefined;
|
|
16
|
+
dimensions?: number | undefined;
|
|
17
|
+
entries?: number | undefined;
|
|
18
|
+
vectors?: number | undefined;
|
|
19
|
+
providers?: string[] | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface VectorMemorySearchHit {
|
|
22
|
+
id: string;
|
|
23
|
+
score: number;
|
|
24
|
+
text: string;
|
|
25
|
+
summary?: string | undefined;
|
|
26
|
+
tags: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface VectorMemorySearchResponse {
|
|
29
|
+
hits: VectorMemorySearchHit[];
|
|
30
|
+
count: number;
|
|
31
|
+
}
|
|
32
|
+
/** Shape the store exposes. Kept narrow so we don't leak the full class. */
|
|
33
|
+
export interface VectorMemorySnapshot {
|
|
34
|
+
storePath?: string | undefined;
|
|
35
|
+
modelCacheDir?: string | undefined;
|
|
36
|
+
stats: {
|
|
37
|
+
entries: number;
|
|
38
|
+
vectors: number;
|
|
39
|
+
providers: string[];
|
|
40
|
+
modelId: string;
|
|
41
|
+
dimensions: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Handle `GET /api/vector-memory/status`. */
|
|
45
|
+
export declare function handleVectorMemoryStatus(res: http.ServerResponse, getStore: () => VectorMemoryStore | undefined, opts?: {
|
|
46
|
+
projectRoot?: string;
|
|
47
|
+
modelCacheDir?: string;
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
/** Handle `GET /api/vector-memory/search?q=…&limit=…&threshold=…`. */
|
|
50
|
+
export declare function handleVectorMemorySearch(res: http.ServerResponse, url: URL, getStore: () => VectorMemoryStore | undefined): Promise<void>;
|
|
51
|
+
/** Handle `POST /api/vector-memory/store`. */
|
|
52
|
+
export declare function handleVectorMemoryStore(res: http.ServerResponse, req: http.IncomingMessage, getStore: () => VectorMemoryStore | undefined): Promise<void>;
|
|
53
|
+
/** Handle `DELETE /api/vector-memory/store/:id`. */
|
|
54
|
+
export declare function handleVectorMemoryForget(res: http.ServerResponse, url: URL, getStore: () => VectorMemoryStore | undefined): Promise<void>;
|
|
55
|
+
//# sourceMappingURL=vector-memory-handlers.d.ts.map
|
|
@@ -121,6 +121,17 @@ export interface CreateHttpServerOptions {
|
|
|
121
121
|
* respond 503.
|
|
122
122
|
*/
|
|
123
123
|
intakeService?: import('@wrongstack/requirement-intake').RequirementIntakeService | undefined;
|
|
124
|
+
/**
|
|
125
|
+
* Optional vector-memory store. When provided, the four
|
|
126
|
+
* `/api/vector-memory/{status,search,store,store/:id}` endpoints become
|
|
127
|
+
* active and return live data. When omitted, the route responds with
|
|
128
|
+
* `{ enabled: false }` (status) or 503 (search/store/forget), so a
|
|
129
|
+
* non-CLI webui-server host stays on its existing surface with zero
|
|
130
|
+
* behavior change.
|
|
131
|
+
*/
|
|
132
|
+
getVectorMemoryStore?: (() => import('@wrongstack/vector-memory').VectorMemoryStore | undefined) | undefined;
|
|
133
|
+
/** Model cache directory for the vector-memory provider. */
|
|
134
|
+
vectorMemoryModelCacheDir?: string | undefined;
|
|
124
135
|
}
|
|
125
136
|
/**
|
|
126
137
|
* Create the static-file HTTP server. Returns the `http.Server` (not
|
|
@@ -117,6 +117,16 @@ export declare function startHttpServer(opts: {
|
|
|
117
117
|
/** Optional pre-built intake service (tests/embeds). Defaults to a fresh
|
|
118
118
|
* per-project service backed by `projectRequirementIntakes`. */
|
|
119
119
|
intakeService?: import('@wrongstack/requirement-intake').RequirementIntakeService | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Optional vector-memory store. When provided, the four
|
|
122
|
+
* `/api/vector-memory/{status,search,store,store/:id}` endpoints become
|
|
123
|
+
* active. When omitted, the routes respond with `{ enabled: false }` or
|
|
124
|
+
* 503 — a non-CLI webui-server host stays on its existing surface with
|
|
125
|
+
* zero behavior change.
|
|
126
|
+
*/
|
|
127
|
+
getVectorMemoryStore?: (() => import('@wrongstack/vector-memory').VectorMemoryStore | undefined) | undefined;
|
|
128
|
+
/** Model cache directory for the vector-memory provider. */
|
|
129
|
+
vectorMemoryModelCacheDir?: string | undefined;
|
|
120
130
|
}): import('node:http').Server;
|
|
121
131
|
interface ShutdownDeps {
|
|
122
132
|
flushSession: () => Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/webui-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.308.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack WebUI HTTP/WebSocket server module — extracted from @wrongstack/webui in PR #243/244 to remove the CLI -> @wrongstack/webui/server cross-package edge (audit §3.1.1). Pure backend: HTTP routes, WebSocket handlers, MCP tool wrappers, HTML serving. The web frontend lives in @wrongstack/webui; this package is the standalone server it can run on.",
|
|
6
6
|
"keywords": [
|
|
@@ -40,16 +40,17 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"ws": "^8.21.3",
|
|
43
|
-
"@wrongstack/
|
|
44
|
-
"@wrongstack/
|
|
45
|
-
"@wrongstack/
|
|
46
|
-
"@wrongstack/
|
|
47
|
-
"@wrongstack/
|
|
48
|
-
"@wrongstack/
|
|
49
|
-
"@wrongstack/
|
|
50
|
-
"@wrongstack/
|
|
51
|
-
"@wrongstack/tools": "0.
|
|
52
|
-
"@wrongstack/
|
|
43
|
+
"@wrongstack/runtime": "0.308.0",
|
|
44
|
+
"@wrongstack/kanban": "0.308.0",
|
|
45
|
+
"@wrongstack/core": "0.308.0",
|
|
46
|
+
"@wrongstack/sdd": "0.308.0",
|
|
47
|
+
"@wrongstack/providers": "0.308.0",
|
|
48
|
+
"@wrongstack/sage": "0.308.0",
|
|
49
|
+
"@wrongstack/mcp": "0.308.0",
|
|
50
|
+
"@wrongstack/techstack": "0.308.0",
|
|
51
|
+
"@wrongstack/tools": "0.308.0",
|
|
52
|
+
"@wrongstack/vector-memory": "0.308.0",
|
|
53
|
+
"@wrongstack/requirement-intake": "0.308.0"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@types/node": "^26.2.0",
|