@wrongstack/webui-server 0.308.0 → 0.308.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +700 -67
- package/dist/protocol/client-integrations.d.ts +1 -1
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/server-integrations.d.ts +1 -1
- package/dist/protocol/server-workspace.d.ts +1 -1
- package/dist/server/connections/auto-healer.d.ts +85 -0
- package/dist/server/embedded-lifecycle.d.ts +1 -0
- package/dist/server/embedded-message-router.d.ts +8 -0
- package/dist/server/entry.js +671 -64
- package/dist/server/frontend-static-serve.d.ts +6 -0
- package/dist/server/http-server/api-router.d.ts +7 -0
- package/dist/server/http-server/vector-memory-handlers.d.ts +53 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/memory-handlers.d.ts +19 -0
- package/dist/server/port-utils.d.ts +23 -3
- package/dist/server/start-webui-companion.d.ts +9 -1
- package/dist/server/start-webui-shutdown.d.ts +6 -1
- package/package.json +13 -12
package/dist/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";
|
|
@@ -14994,9 +15217,19 @@ async function handleBrainAsk(ctx, ws, question) {
|
|
|
14994
15217
|
risk: "medium",
|
|
14995
15218
|
fallback: "ask_human"
|
|
14996
15219
|
});
|
|
15220
|
+
const answerSessionId = ctx.getSessionId?.();
|
|
14997
15221
|
ctx.send(ws, {
|
|
14998
15222
|
type: "brain.answer",
|
|
14999
|
-
|
|
15223
|
+
// Omit sessionId when there is no session: this is a direct reply to
|
|
15224
|
+
// the asker, not a broadcast, but the client's session gate
|
|
15225
|
+
// (isActiveSessionMessage) is fail-closed on a present-but-empty
|
|
15226
|
+
// sessionId — stamping '' would hide the answer from its own asker
|
|
15227
|
+
// in an embedded host with an unbound agent context.
|
|
15228
|
+
payload: {
|
|
15229
|
+
...answerSessionId ? { sessionId: answerSessionId } : {},
|
|
15230
|
+
question: q,
|
|
15231
|
+
decision
|
|
15232
|
+
}
|
|
15000
15233
|
});
|
|
15001
15234
|
} catch (err) {
|
|
15002
15235
|
sendResult6(ctx, ws, false, `Brain consultation failed: ${toErrorMessage6(err)}`);
|
|
@@ -17080,7 +17313,12 @@ function createProviderHandlers(deps2) {
|
|
|
17080
17313
|
|
|
17081
17314
|
// src/server/session-handlers.ts
|
|
17082
17315
|
import { loadTodosCheckpoint } from "@wrongstack/core/storage";
|
|
17083
|
-
import {
|
|
17316
|
+
import {
|
|
17317
|
+
CONTEXT_WINDOW_MODE_PINNED_META_KEY,
|
|
17318
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
17319
|
+
isContextWindowModeId,
|
|
17320
|
+
resolveContextWindowPolicy
|
|
17321
|
+
} from "@wrongstack/core/types";
|
|
17084
17322
|
import { repairToolUseAdjacency as repairToolUseAdjacency2, sessionScopedPath } from "@wrongstack/core/utils";
|
|
17085
17323
|
|
|
17086
17324
|
// src/protocol/client-conversation.ts
|
|
@@ -17177,6 +17415,7 @@ var CLIENT_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
17177
17415
|
"memory.sage.listPage",
|
|
17178
17416
|
"memory.sage.recover",
|
|
17179
17417
|
"memory.sage.remember",
|
|
17418
|
+
"memory.sage.searchBreakdown",
|
|
17180
17419
|
"memory.sage.update"
|
|
17181
17420
|
];
|
|
17182
17421
|
var CLIENT_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -17464,6 +17703,7 @@ var SERVER_KNOWLEDGE_MESSAGE_TYPES = [
|
|
|
17464
17703
|
"memory.sage.listPage",
|
|
17465
17704
|
"memory.sage.recover",
|
|
17466
17705
|
"memory.sage.remember",
|
|
17706
|
+
"memory.sage.searchBreakdown",
|
|
17467
17707
|
"memory.sage.update"
|
|
17468
17708
|
];
|
|
17469
17709
|
var SERVER_EXTENSION_MESSAGE_TYPES = [
|
|
@@ -17600,6 +17840,7 @@ var SERVER_WORKSPACE_MESSAGE_TYPES = [
|
|
|
17600
17840
|
var SERVER_CONFIGURATION_MESSAGE_TYPES = [
|
|
17601
17841
|
"auth.oauth.status",
|
|
17602
17842
|
"codebase.index.server.shutdown_result",
|
|
17843
|
+
"connections.auto_heal_status",
|
|
17603
17844
|
"connections.health_error",
|
|
17604
17845
|
"connections.health_result",
|
|
17605
17846
|
"connections.service_action_result",
|
|
@@ -18150,7 +18391,12 @@ function createSessionHandlers(ctx) {
|
|
|
18150
18391
|
sessionScopedPath(sessionsDirectory(), next.id, ".tasks.json")
|
|
18151
18392
|
);
|
|
18152
18393
|
ctx.tokenCounter.reset?.();
|
|
18153
|
-
if (usage)
|
|
18394
|
+
if (usage) {
|
|
18395
|
+
ctx.tokenCounter.account(usage, currentConfig().model, ctx.context.provider.id);
|
|
18396
|
+
if (typeof usage.input === "number" && usage.input > 0) {
|
|
18397
|
+
ctx.context.lastRequestTokens = usage.input;
|
|
18398
|
+
}
|
|
18399
|
+
}
|
|
18154
18400
|
ctx.setSessionStartedAt?.(Date.now());
|
|
18155
18401
|
await ctx.onSessionSwapped?.(next.id);
|
|
18156
18402
|
};
|
|
@@ -18373,8 +18619,8 @@ function createSessionHandlers(ctx) {
|
|
|
18373
18619
|
return;
|
|
18374
18620
|
}
|
|
18375
18621
|
const { id } = parsed.value;
|
|
18376
|
-
let policy = resolveContextWindowPolicy({}, id);
|
|
18377
|
-
if (policy.id !== id) {
|
|
18622
|
+
let policy = resolveContextWindowPolicy({}, id, readSessionWindowTokens(ctx.context));
|
|
18623
|
+
if (!isContextWindowModeId(id) && policy.id !== id) {
|
|
18378
18624
|
const customModes = (await modeStore()).list().filter((m) => m.custom === true);
|
|
18379
18625
|
const custom = customModes.find((m) => m.id === id);
|
|
18380
18626
|
if (!custom) {
|
|
@@ -18385,6 +18631,7 @@ function createSessionHandlers(ctx) {
|
|
|
18385
18631
|
}
|
|
18386
18632
|
ctx.context.meta["contextWindowMode"] = policy.id;
|
|
18387
18633
|
ctx.context.meta["contextWindowPolicy"] = policy;
|
|
18634
|
+
ctx.context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY] = true;
|
|
18388
18635
|
result(ws, true, `Context mode switched to ${policy.id}`);
|
|
18389
18636
|
broadcastToAll({
|
|
18390
18637
|
type: "context.mode.changed",
|
|
@@ -18448,11 +18695,14 @@ function createSessionHandlers(ctx) {
|
|
|
18448
18695
|
}
|
|
18449
18696
|
const { id } = parsed.value;
|
|
18450
18697
|
if (String(ctx.context.meta["contextWindowMode"] ?? "") === id) {
|
|
18451
|
-
|
|
18452
|
-
ctx.context.meta["contextWindowPolicy"] = resolveContextWindowPolicy(
|
|
18698
|
+
const policy = resolveContextWindowPolicy(
|
|
18453
18699
|
{},
|
|
18454
|
-
DEFAULT_CONTEXT_WINDOW_MODE_ID
|
|
18700
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
18701
|
+
readSessionWindowTokens(ctx.context)
|
|
18455
18702
|
);
|
|
18703
|
+
ctx.context.meta["contextWindowMode"] = policy.id;
|
|
18704
|
+
ctx.context.meta["contextWindowPolicy"] = policy;
|
|
18705
|
+
delete ctx.context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY];
|
|
18456
18706
|
}
|
|
18457
18707
|
const store = await modeStore();
|
|
18458
18708
|
const operation = store.remove(id);
|
|
@@ -18659,6 +18909,12 @@ function createSessionHandlers(ctx) {
|
|
|
18659
18909
|
}
|
|
18660
18910
|
};
|
|
18661
18911
|
}
|
|
18912
|
+
function readSessionWindowTokens(context) {
|
|
18913
|
+
const meta = context.meta?.["effectiveMaxContext"];
|
|
18914
|
+
if (typeof meta === "number" && Number.isFinite(meta) && meta > 0) return meta;
|
|
18915
|
+
const cap = context.provider?.capabilities?.maxContext;
|
|
18916
|
+
return typeof cap === "number" && Number.isFinite(cap) && cap > 0 ? cap : 0;
|
|
18917
|
+
}
|
|
18662
18918
|
|
|
18663
18919
|
// src/server/agent-roster-handlers.ts
|
|
18664
18920
|
import {
|
|
@@ -19157,6 +19413,259 @@ async function handleCodebaseIndexServerControl(ws, message, deps2) {
|
|
|
19157
19413
|
return true;
|
|
19158
19414
|
}
|
|
19159
19415
|
|
|
19416
|
+
// src/server/connections/auto-healer.ts
|
|
19417
|
+
var AUTO_HEAL_ENV_FLAG = "WRONGSTACK_AUTO_HEAL_SERVICES";
|
|
19418
|
+
var AUTO_HEAL_DEFAULT_INTERVAL_MS = 3e4;
|
|
19419
|
+
var AUTO_HEAL_DEFAULT_COOLDOWN_MS = 5 * 6e4;
|
|
19420
|
+
var AUTO_HEAL_DEFAULT_MAX_ATTEMPTS = 3;
|
|
19421
|
+
var RESTARTABLE_SERVICE_IDS = /* @__PURE__ */ new Set([
|
|
19422
|
+
"kanban",
|
|
19423
|
+
"sage",
|
|
19424
|
+
"chronicle",
|
|
19425
|
+
"codebase-index",
|
|
19426
|
+
"mailbox"
|
|
19427
|
+
]);
|
|
19428
|
+
function isAutoHealEnabled() {
|
|
19429
|
+
return process.env[AUTO_HEAL_ENV_FLAG] === "1";
|
|
19430
|
+
}
|
|
19431
|
+
function createAutoHealer(options) {
|
|
19432
|
+
const enabled = options.enabled ?? isAutoHealEnabled();
|
|
19433
|
+
const intervalMs = options.intervalMs ?? AUTO_HEAL_DEFAULT_INTERVAL_MS;
|
|
19434
|
+
const cooldownMs = options.cooldownMs ?? AUTO_HEAL_DEFAULT_COOLDOWN_MS;
|
|
19435
|
+
const maxAttempts = options.maxAttempts ?? AUTO_HEAL_DEFAULT_MAX_ATTEMPTS;
|
|
19436
|
+
const collect = options.collect ?? (() => collectConnectionsHealth({
|
|
19437
|
+
projectRoot: options.projectRoot(),
|
|
19438
|
+
indexDir: options.indexDir(),
|
|
19439
|
+
backend: "standalone"
|
|
19440
|
+
}));
|
|
19441
|
+
const execute = options.execute ?? executeServiceAction;
|
|
19442
|
+
const services = /* @__PURE__ */ new Map();
|
|
19443
|
+
let timer = null;
|
|
19444
|
+
let running = false;
|
|
19445
|
+
let ticking = false;
|
|
19446
|
+
let disposed = false;
|
|
19447
|
+
let inFlightTick = null;
|
|
19448
|
+
let lastTickAt = null;
|
|
19449
|
+
let warnedNoBoundary = false;
|
|
19450
|
+
function stateFor(serviceId) {
|
|
19451
|
+
let state = services.get(serviceId);
|
|
19452
|
+
if (!state) {
|
|
19453
|
+
state = {
|
|
19454
|
+
lastAttemptAt: null,
|
|
19455
|
+
consecutiveFailures: 0,
|
|
19456
|
+
lastSuccess: null,
|
|
19457
|
+
lastMessage: null,
|
|
19458
|
+
inFlight: false,
|
|
19459
|
+
escalated: false
|
|
19460
|
+
};
|
|
19461
|
+
services.set(serviceId, state);
|
|
19462
|
+
}
|
|
19463
|
+
return state;
|
|
19464
|
+
}
|
|
19465
|
+
function snapshot() {
|
|
19466
|
+
return {
|
|
19467
|
+
enabled,
|
|
19468
|
+
running,
|
|
19469
|
+
lastTickAt,
|
|
19470
|
+
services: Object.fromEntries(services)
|
|
19471
|
+
};
|
|
19472
|
+
}
|
|
19473
|
+
function emitStatus(event) {
|
|
19474
|
+
try {
|
|
19475
|
+
options.onStatus?.({ ...event, at: Date.now() });
|
|
19476
|
+
} catch (error2) {
|
|
19477
|
+
options.logger?.warn?.(
|
|
19478
|
+
`[AutoHeal] onStatus hook threw: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
19479
|
+
);
|
|
19480
|
+
}
|
|
19481
|
+
}
|
|
19482
|
+
async function tick() {
|
|
19483
|
+
if (!enabled || disposed) return snapshot();
|
|
19484
|
+
if (!options.trustBoundary) {
|
|
19485
|
+
if (!warnedNoBoundary) {
|
|
19486
|
+
warnedNoBoundary = true;
|
|
19487
|
+
options.logger?.warn?.(
|
|
19488
|
+
"[AutoHeal] Disabled: no policy authority (trust boundary) is configured."
|
|
19489
|
+
);
|
|
19490
|
+
}
|
|
19491
|
+
return snapshot();
|
|
19492
|
+
}
|
|
19493
|
+
if (ticking) return snapshot();
|
|
19494
|
+
ticking = true;
|
|
19495
|
+
try {
|
|
19496
|
+
const report = await collect();
|
|
19497
|
+
const now = Date.now();
|
|
19498
|
+
const projectRoot = options.projectRoot();
|
|
19499
|
+
const indexDir = options.indexDir();
|
|
19500
|
+
for (const service of report.services) {
|
|
19501
|
+
if (disposed) break;
|
|
19502
|
+
const state = stateFor(service.id);
|
|
19503
|
+
if (service.status !== "error") {
|
|
19504
|
+
state.consecutiveFailures = 0;
|
|
19505
|
+
state.escalated = false;
|
|
19506
|
+
continue;
|
|
19507
|
+
}
|
|
19508
|
+
if (!RESTARTABLE_SERVICE_IDS.has(service.id) || service.control === "none") {
|
|
19509
|
+
continue;
|
|
19510
|
+
}
|
|
19511
|
+
if (state.lastAttemptAt !== null && now - state.lastAttemptAt < cooldownMs) continue;
|
|
19512
|
+
if (state.consecutiveFailures >= maxAttempts) {
|
|
19513
|
+
state.escalated = true;
|
|
19514
|
+
options.logger?.warn?.(
|
|
19515
|
+
`[AutoHeal] ${service.id} left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage ?? "unknown"}`
|
|
19516
|
+
);
|
|
19517
|
+
continue;
|
|
19518
|
+
}
|
|
19519
|
+
if (state.inFlight) continue;
|
|
19520
|
+
const authorization = await authorizeWebUIAction(
|
|
19521
|
+
options.trustBoundary,
|
|
19522
|
+
{
|
|
19523
|
+
capability: "connections.service.restart",
|
|
19524
|
+
subject: { kind: "process", id: `${service.id}@${projectRoot}` },
|
|
19525
|
+
risk: "elevated",
|
|
19526
|
+
cwd: projectRoot,
|
|
19527
|
+
metadata: { transport: "auto-heal", serviceId: service.id, action: "restart" }
|
|
19528
|
+
},
|
|
19529
|
+
options.logger
|
|
19530
|
+
);
|
|
19531
|
+
if (disposed) break;
|
|
19532
|
+
if (!authorization.allowed) {
|
|
19533
|
+
state.lastAttemptAt = now;
|
|
19534
|
+
state.lastMessage = `refused by policy: ${authorization.reason}`;
|
|
19535
|
+
emitStatus({
|
|
19536
|
+
serviceId: service.id,
|
|
19537
|
+
phase: "refused",
|
|
19538
|
+
message: `refused by policy: ${authorization.reason}`,
|
|
19539
|
+
attempt: state.consecutiveFailures + 1
|
|
19540
|
+
});
|
|
19541
|
+
options.logger?.warn?.(
|
|
19542
|
+
`[AutoHeal] ${service.id} restart refused by policy: ${authorization.reason}`
|
|
19543
|
+
);
|
|
19544
|
+
continue;
|
|
19545
|
+
}
|
|
19546
|
+
state.inFlight = true;
|
|
19547
|
+
const attempt = state.consecutiveFailures + 1;
|
|
19548
|
+
emitStatus({
|
|
19549
|
+
serviceId: service.id,
|
|
19550
|
+
phase: "restarting",
|
|
19551
|
+
message: `Auto-restarting ${service.id}`,
|
|
19552
|
+
attempt
|
|
19553
|
+
});
|
|
19554
|
+
try {
|
|
19555
|
+
const result = await execute(service.id, "restart", projectRoot, indexDir);
|
|
19556
|
+
state.consecutiveFailures = result.success ? 0 : state.consecutiveFailures + 1;
|
|
19557
|
+
state.lastSuccess = result.success;
|
|
19558
|
+
state.lastMessage = result.message;
|
|
19559
|
+
emitStatus({
|
|
19560
|
+
serviceId: service.id,
|
|
19561
|
+
phase: result.success ? "restarted" : "failed",
|
|
19562
|
+
message: result.message,
|
|
19563
|
+
attempt
|
|
19564
|
+
});
|
|
19565
|
+
if (!result.success && state.consecutiveFailures >= maxAttempts) {
|
|
19566
|
+
state.escalated = true;
|
|
19567
|
+
emitStatus({
|
|
19568
|
+
serviceId: service.id,
|
|
19569
|
+
phase: "escalated",
|
|
19570
|
+
message: `left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${result.message}`,
|
|
19571
|
+
attempt
|
|
19572
|
+
});
|
|
19573
|
+
options.logger?.warn?.(
|
|
19574
|
+
`[AutoHeal] ${service.id} escalated after ${state.consecutiveFailures} failed auto-restart(s): ${result.message}`
|
|
19575
|
+
);
|
|
19576
|
+
}
|
|
19577
|
+
options.logger?.[result.success ? "info" : "warn"]?.(
|
|
19578
|
+
`[AutoHeal] ${service.id} auto-restart ${result.success ? "succeeded" : "failed"}: ${result.message}`
|
|
19579
|
+
);
|
|
19580
|
+
} catch (error2) {
|
|
19581
|
+
state.consecutiveFailures += 1;
|
|
19582
|
+
state.lastSuccess = false;
|
|
19583
|
+
state.lastMessage = error2 instanceof Error ? error2.message : String(error2);
|
|
19584
|
+
emitStatus({
|
|
19585
|
+
serviceId: service.id,
|
|
19586
|
+
phase: "failed",
|
|
19587
|
+
message: state.lastMessage,
|
|
19588
|
+
attempt
|
|
19589
|
+
});
|
|
19590
|
+
if (state.consecutiveFailures >= maxAttempts) {
|
|
19591
|
+
state.escalated = true;
|
|
19592
|
+
emitStatus({
|
|
19593
|
+
serviceId: service.id,
|
|
19594
|
+
phase: "escalated",
|
|
19595
|
+
message: `left to manual intervention after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage}`,
|
|
19596
|
+
attempt
|
|
19597
|
+
});
|
|
19598
|
+
options.logger?.warn?.(
|
|
19599
|
+
`[AutoHeal] ${service.id} escalated after ${state.consecutiveFailures} failed auto-restart(s): ${state.lastMessage}`
|
|
19600
|
+
);
|
|
19601
|
+
}
|
|
19602
|
+
options.logger?.warn?.(
|
|
19603
|
+
`[AutoHeal] ${service.id} auto-restart threw: ${state.lastMessage}`
|
|
19604
|
+
);
|
|
19605
|
+
} finally {
|
|
19606
|
+
state.lastAttemptAt = Date.now();
|
|
19607
|
+
state.inFlight = false;
|
|
19608
|
+
}
|
|
19609
|
+
}
|
|
19610
|
+
lastTickAt = Date.now();
|
|
19611
|
+
} catch (error2) {
|
|
19612
|
+
options.logger?.warn?.(
|
|
19613
|
+
`[AutoHeal] health collect failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
19614
|
+
);
|
|
19615
|
+
} finally {
|
|
19616
|
+
ticking = false;
|
|
19617
|
+
}
|
|
19618
|
+
return snapshot();
|
|
19619
|
+
}
|
|
19620
|
+
function runTick() {
|
|
19621
|
+
if (!enabled || disposed || running === false || ticking) return;
|
|
19622
|
+
const pending = tick();
|
|
19623
|
+
const tracked = pending.then(
|
|
19624
|
+
() => void 0,
|
|
19625
|
+
() => void 0
|
|
19626
|
+
);
|
|
19627
|
+
inFlightTick = tracked;
|
|
19628
|
+
void tracked.finally(() => {
|
|
19629
|
+
if (inFlightTick === tracked) inFlightTick = null;
|
|
19630
|
+
});
|
|
19631
|
+
}
|
|
19632
|
+
function stopInternal() {
|
|
19633
|
+
if (timer) {
|
|
19634
|
+
clearInterval(timer);
|
|
19635
|
+
timer = null;
|
|
19636
|
+
}
|
|
19637
|
+
running = false;
|
|
19638
|
+
}
|
|
19639
|
+
return {
|
|
19640
|
+
start() {
|
|
19641
|
+
if (!enabled || running || disposed) return;
|
|
19642
|
+
running = true;
|
|
19643
|
+
runTick();
|
|
19644
|
+
timer = setInterval(runTick, intervalMs);
|
|
19645
|
+
timer.unref?.();
|
|
19646
|
+
},
|
|
19647
|
+
stop: stopInternal,
|
|
19648
|
+
async dispose() {
|
|
19649
|
+
stopInternal();
|
|
19650
|
+
disposed = true;
|
|
19651
|
+
const pending = inFlightTick;
|
|
19652
|
+
if (pending) {
|
|
19653
|
+
await Promise.race([
|
|
19654
|
+
pending,
|
|
19655
|
+
new Promise((resolve19) => {
|
|
19656
|
+
const t = setTimeout(resolve19, 3e4);
|
|
19657
|
+
t.unref?.();
|
|
19658
|
+
})
|
|
19659
|
+
]);
|
|
19660
|
+
}
|
|
19661
|
+
disposed = true;
|
|
19662
|
+
},
|
|
19663
|
+
tick,
|
|
19664
|
+
getSnapshot: snapshot,
|
|
19665
|
+
isRunning: () => running
|
|
19666
|
+
};
|
|
19667
|
+
}
|
|
19668
|
+
|
|
19160
19669
|
// src/server/fallback-choice.ts
|
|
19161
19670
|
function emitFallbackChoice(events, msg) {
|
|
19162
19671
|
const parsed = validateModelFallbackChoicePayload(msg.payload);
|
|
@@ -22310,6 +22819,11 @@ import {
|
|
|
22310
22819
|
wstackGlobalRoot as wstackGlobalRoot2
|
|
22311
22820
|
} from "@wrongstack/core/utils";
|
|
22312
22821
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
22822
|
+
import {
|
|
22823
|
+
TransformersEmbeddingProvider,
|
|
22824
|
+
VectorMemoryStore,
|
|
22825
|
+
startFirstBootSageSync
|
|
22826
|
+
} from "@wrongstack/vector-memory";
|
|
22313
22827
|
|
|
22314
22828
|
// src/server/backend-services.ts
|
|
22315
22829
|
import { join as join12 } from "node:path";
|
|
@@ -22337,6 +22851,7 @@ import {
|
|
|
22337
22851
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
22338
22852
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
22339
22853
|
import {
|
|
22854
|
+
CONTEXT_WINDOW_MODE_PINNED_META_KEY as CONTEXT_WINDOW_MODE_PINNED_META_KEY2,
|
|
22340
22855
|
DEFAULT_TOOLS_CONFIG,
|
|
22341
22856
|
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
22342
22857
|
} from "@wrongstack/core/types";
|
|
@@ -23257,22 +23772,26 @@ async function createAgentServices(input) {
|
|
|
23257
23772
|
summarizerModel: config.context?.summarizerModel,
|
|
23258
23773
|
llmSelector: config.context?.llmSelector
|
|
23259
23774
|
});
|
|
23260
|
-
|
|
23775
|
+
let effectiveMaxContext = 0;
|
|
23776
|
+
try {
|
|
23777
|
+
const m = await resolveProviderModelMetadata(
|
|
23778
|
+
modelsRegistry,
|
|
23779
|
+
config.provider,
|
|
23780
|
+
context.model,
|
|
23781
|
+
config.providers?.[config.provider]
|
|
23782
|
+
);
|
|
23783
|
+
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
23784
|
+
} catch {
|
|
23785
|
+
}
|
|
23786
|
+
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
23787
|
+
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
23788
|
+
const initialContextPolicy = resolveContextWindowPolicy2(
|
|
23789
|
+
config.context,
|
|
23790
|
+
void 0,
|
|
23791
|
+
effectiveMaxContext
|
|
23792
|
+
);
|
|
23261
23793
|
let autoCompactor;
|
|
23262
23794
|
if (config.context?.autoCompact !== false) {
|
|
23263
|
-
let effectiveMaxContext = 0;
|
|
23264
|
-
try {
|
|
23265
|
-
const m = await resolveProviderModelMetadata(
|
|
23266
|
-
modelsRegistry,
|
|
23267
|
-
config.provider,
|
|
23268
|
-
context.model,
|
|
23269
|
-
config.providers?.[config.provider]
|
|
23270
|
-
);
|
|
23271
|
-
effectiveMaxContext = m?.capabilities?.maxContext ?? 0;
|
|
23272
|
-
} catch {
|
|
23273
|
-
}
|
|
23274
|
-
if (!effectiveMaxContext) effectiveMaxContext = config.context?.effectiveMaxContext ?? 0;
|
|
23275
|
-
if (!effectiveMaxContext) effectiveMaxContext = provider.capabilities.maxContext;
|
|
23276
23795
|
autoCompactor = new AutoCompactionMiddlewareCtor(
|
|
23277
23796
|
compactor,
|
|
23278
23797
|
effectiveMaxContext,
|
|
@@ -23327,6 +23846,15 @@ async function createAgentServices(input) {
|
|
|
23327
23846
|
context.meta["effectiveMaxContext"] = newMaxContext;
|
|
23328
23847
|
autoCompactor?.setMaxContext(newMaxContext);
|
|
23329
23848
|
autoCompactor?.setEnabled(config.context?.autoCompact !== false);
|
|
23849
|
+
if (context.meta[CONTEXT_WINDOW_MODE_PINNED_META_KEY2] !== true) {
|
|
23850
|
+
const policy = resolveContextWindowPolicy2(
|
|
23851
|
+
currentConfig.context ?? {},
|
|
23852
|
+
void 0,
|
|
23853
|
+
newMaxContext
|
|
23854
|
+
);
|
|
23855
|
+
context.meta["contextWindowMode"] = policy.id;
|
|
23856
|
+
context.meta["contextWindowPolicy"] = policy;
|
|
23857
|
+
}
|
|
23330
23858
|
} else {
|
|
23331
23859
|
delete context.meta["effectiveMaxContext"];
|
|
23332
23860
|
autoCompactor?.setEnabled(false);
|
|
@@ -23958,8 +24486,22 @@ function createMessageDispatcher(opts) {
|
|
|
23958
24486
|
broadcast: (message) => broadcast(state.getClients(), message),
|
|
23959
24487
|
log: (message) => deps2.logger.warn?.(`[KanbanSupervisor] ${message}`)
|
|
23960
24488
|
});
|
|
24489
|
+
const autoHealer = createAutoHealer({
|
|
24490
|
+
projectRoot: () => state.getProjectRoot(),
|
|
24491
|
+
indexDir: () => typeof deps2.context.meta["codebaseIndexDir"] === "string" ? deps2.context.meta["codebaseIndexDir"] : void 0,
|
|
24492
|
+
trustBoundary: deps2.trustBoundary,
|
|
24493
|
+
logger: deps2.logger,
|
|
24494
|
+
onStatus: (event) => broadcast(state.getClients(), {
|
|
24495
|
+
type: "connections.auto_heal_status",
|
|
24496
|
+
payload: event
|
|
24497
|
+
})
|
|
24498
|
+
});
|
|
24499
|
+
autoHealer.start();
|
|
23961
24500
|
if (opts.onDispose) {
|
|
23962
|
-
const dispose = () =>
|
|
24501
|
+
const dispose = async () => {
|
|
24502
|
+
kanbanSupervisor.dispose();
|
|
24503
|
+
await autoHealer.dispose();
|
|
24504
|
+
};
|
|
23963
24505
|
opts.onDispose(dispose);
|
|
23964
24506
|
}
|
|
23965
24507
|
const kanbanContext = () => ({
|
|
@@ -24823,7 +25365,11 @@ async function createPreContextServices(input) {
|
|
|
24823
25365
|
model: config.model
|
|
24824
25366
|
});
|
|
24825
25367
|
context.meta["promptOnlineAgents"] = onlineAgents;
|
|
24826
|
-
const initialContextPolicy = resolveContextWindowPolicy3(
|
|
25368
|
+
const initialContextPolicy = resolveContextWindowPolicy3(
|
|
25369
|
+
config.context,
|
|
25370
|
+
void 0,
|
|
25371
|
+
provider.capabilities?.maxContext
|
|
25372
|
+
);
|
|
24827
25373
|
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
24828
25374
|
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
24829
25375
|
context.state.setMeta(
|
|
@@ -25262,7 +25808,7 @@ async function resolvePorts(opts) {
|
|
|
25262
25808
|
const publicUrl = opts.publicUrl ?? process.env["WEBUI_PUBLIC_URL"];
|
|
25263
25809
|
const publicWsUrl = opts.publicWsUrl ?? process.env["WEBUI_PUBLIC_WS_URL"];
|
|
25264
25810
|
const requireToken = opts.requireToken ?? envFlag("WEBUI_REQUIRE_TOKEN");
|
|
25265
|
-
const strictPort =
|
|
25811
|
+
const strictPort = isStrictPort();
|
|
25266
25812
|
let httpPort = requestedHttpPort;
|
|
25267
25813
|
if (!strictPort) {
|
|
25268
25814
|
httpPort = await findFreePort(wsHost, requestedHttpPort);
|
|
@@ -25481,7 +26027,7 @@ function registerShutdown(deps2) {
|
|
|
25481
26027
|
|
|
25482
26028
|
// src/server/start-webui-companion.ts
|
|
25483
26029
|
import * as http2 from "node:http";
|
|
25484
|
-
function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
26030
|
+
async function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
25485
26031
|
const companion = wsHost === "127.0.0.1" ? "::1" : wsHost === "0.0.0.0" || wsHost === void 0 ? "::" : wsHost === "::" || wsHost === "[::]" ? "0.0.0.0" : null;
|
|
25486
26032
|
if (!companion) return null;
|
|
25487
26033
|
const companionLabel = companion.includes(":") ? `[${companion}]` : companion;
|
|
@@ -25492,16 +26038,23 @@ function setupCompanionServer(httpServer, wsHost, httpPort) {
|
|
|
25492
26038
|
(req, socket, head2) => httpServer.emit("upgrade", req, socket, head2)
|
|
25493
26039
|
);
|
|
25494
26040
|
companionServer.on("error", (err) => {
|
|
25495
|
-
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL"
|
|
26041
|
+
const expected = err.code === "EAFNOSUPPORT" || err.code === "EADDRNOTAVAIL";
|
|
25496
26042
|
if (!expected) {
|
|
25497
26043
|
console.warn(
|
|
25498
26044
|
`[WebUI] companion listener on ${companionLabel} failed (${err.code ?? "unknown"}): ${err.message}. The primary address is unaffected.`
|
|
25499
26045
|
);
|
|
25500
26046
|
}
|
|
25501
26047
|
});
|
|
25502
|
-
|
|
25503
|
-
|
|
25504
|
-
})
|
|
26048
|
+
try {
|
|
26049
|
+
await listenWithRetry(companionServer, companion, httpPort, { maxTries: 1 });
|
|
26050
|
+
} catch (err) {
|
|
26051
|
+
const code = err?.code ?? "unknown";
|
|
26052
|
+
console.warn(
|
|
26053
|
+
`[WebUI] companion listener on ${companionLabel} not started (${code}): ${err?.message ?? err}. The primary address is unaffected.`
|
|
26054
|
+
);
|
|
26055
|
+
return null;
|
|
26056
|
+
}
|
|
26057
|
+
console.log(`[WebUI] HTTP server running on http://${companionLabel}:${httpPort}`);
|
|
25505
26058
|
return companionServer;
|
|
25506
26059
|
}
|
|
25507
26060
|
|
|
@@ -25661,7 +26214,7 @@ function setupWebuiShutdown(options) {
|
|
|
25661
26214
|
onPreShutdown: async () => {
|
|
25662
26215
|
await options.stopEmptySessionCleanup.dispose();
|
|
25663
26216
|
const disposeKanban = options.getKanbanSupervisorDispose();
|
|
25664
|
-
disposeKanban?.();
|
|
26217
|
+
await disposeKanban?.();
|
|
25665
26218
|
},
|
|
25666
26219
|
onShutdown: async () => {
|
|
25667
26220
|
unregister();
|
|
@@ -25696,6 +26249,7 @@ function setupWebuiShutdown(options) {
|
|
|
25696
26249
|
await options.memoryStore.dispose().catch(
|
|
25697
26250
|
(err) => options.logger.warn(`sage connection disposal failed: ${toErrorMessage15(err)}`)
|
|
25698
26251
|
);
|
|
26252
|
+
options.vectorMemoryStore?.close();
|
|
25699
26253
|
await unregisterInstance(process.pid, path32.dirname(options.globalConfigPath));
|
|
25700
26254
|
}
|
|
25701
26255
|
});
|
|
@@ -25760,7 +26314,8 @@ function createStandaloneTodosCheckpointLifecycle(input) {
|
|
|
25760
26314
|
async function startWebUI(opts = {}) {
|
|
25761
26315
|
ensureSessionShell();
|
|
25762
26316
|
const ports = await resolvePorts(opts);
|
|
25763
|
-
const { wsHost,
|
|
26317
|
+
const { wsHost, publicUrl, publicWsUrl, requireToken } = ports;
|
|
26318
|
+
let httpPort = ports.httpPort;
|
|
25764
26319
|
console.log("[WebUI] Starting backend services...");
|
|
25765
26320
|
const boot = await bootConfig();
|
|
25766
26321
|
const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
|
|
@@ -25788,6 +26343,27 @@ async function startWebUI(opts = {}) {
|
|
|
25788
26343
|
);
|
|
25789
26344
|
}
|
|
25790
26345
|
const needsProvider = !config.provider || !config.model;
|
|
26346
|
+
let vectorMemoryStore;
|
|
26347
|
+
const vectorMemoryModelCacheDir = path33.join(
|
|
26348
|
+
projectRoot,
|
|
26349
|
+
".wrongstack",
|
|
26350
|
+
"cache",
|
|
26351
|
+
"transformers-models"
|
|
26352
|
+
);
|
|
26353
|
+
try {
|
|
26354
|
+
vectorMemoryStore = new VectorMemoryStore({
|
|
26355
|
+
provider: new TransformersEmbeddingProvider({
|
|
26356
|
+
cacheDir: vectorMemoryModelCacheDir
|
|
26357
|
+
}),
|
|
26358
|
+
projectRoot
|
|
26359
|
+
});
|
|
26360
|
+
} catch (error2) {
|
|
26361
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
26362
|
+
logger.warn(
|
|
26363
|
+
`vector memory store disabled: ${message} \u2014 standalone WebUI will run on the SAGE-only surface.`
|
|
26364
|
+
);
|
|
26365
|
+
vectorMemoryStore = void 0;
|
|
26366
|
+
}
|
|
25791
26367
|
const preContext = await createPreContextServices({
|
|
25792
26368
|
config,
|
|
25793
26369
|
wpaths,
|
|
@@ -25835,6 +26411,13 @@ async function startWebUI(opts = {}) {
|
|
|
25835
26411
|
let sessionStartedAt = preContext.sessionStartedAt;
|
|
25836
26412
|
let modeId = preContext.modeId;
|
|
25837
26413
|
const needsSetup = preContext.needsSetup;
|
|
26414
|
+
if (vectorMemoryStore) {
|
|
26415
|
+
void startFirstBootSageSync({
|
|
26416
|
+
store: vectorMemoryStore,
|
|
26417
|
+
memoryStore,
|
|
26418
|
+
logger
|
|
26419
|
+
});
|
|
26420
|
+
}
|
|
25838
26421
|
const prefSnapshot2 = () => prefSnapshot(context.meta);
|
|
25839
26422
|
const persistPrefsToConfig2 = async (payload) => persistPrefsToConfig(prefHelperDeps, configWriteLock, payload);
|
|
25840
26423
|
const trustBoundary = opts.trustBoundary ?? createCompatibilityTrustBoundary3({ policyId: "webui-trusted-host-compat-v1" });
|
|
@@ -25955,7 +26538,12 @@ async function startWebUI(opts = {}) {
|
|
|
25955
26538
|
events,
|
|
25956
26539
|
permissionPolicy
|
|
25957
26540
|
}),
|
|
25958
|
-
distDir: opts.distDir
|
|
26541
|
+
distDir: opts.distDir,
|
|
26542
|
+
// Vector memory store — mirrors the CLI host. When `vectorMemoryStore`
|
|
26543
|
+
// construction failed (read-only FS, etc.) we still pass the getter;
|
|
26544
|
+
// it just resolves to `undefined` and the API router answers 503.
|
|
26545
|
+
getVectorMemoryStore: () => vectorMemoryStore,
|
|
26546
|
+
vectorMemoryModelCacheDir
|
|
25959
26547
|
});
|
|
25960
26548
|
const wsResult = createWsServers(httpServer, ports, accessToken);
|
|
25961
26549
|
const { wssPrimary, wssSecondary, clients } = wsResult;
|
|
@@ -26010,7 +26598,25 @@ async function startWebUI(opts = {}) {
|
|
|
26010
26598
|
},
|
|
26011
26599
|
watcherMetricsRef
|
|
26012
26600
|
);
|
|
26013
|
-
|
|
26601
|
+
const strictPort = isStrictPort();
|
|
26602
|
+
const boundPort = await listenWithRetry(httpServer, wsHost, httpPort, {
|
|
26603
|
+
maxTries: strictPort ? 1 : 10
|
|
26604
|
+
});
|
|
26605
|
+
if (boundPort !== httpPort) {
|
|
26606
|
+
console.warn(
|
|
26607
|
+
JSON.stringify({
|
|
26608
|
+
level: "warn",
|
|
26609
|
+
event: "webui.port_reassigned",
|
|
26610
|
+
protocol: "HTTP",
|
|
26611
|
+
requested: httpPort,
|
|
26612
|
+
assigned: boundPort,
|
|
26613
|
+
reason: "bind-time EADDRINUSE retry",
|
|
26614
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
26615
|
+
})
|
|
26616
|
+
);
|
|
26617
|
+
httpPort = boundPort;
|
|
26618
|
+
}
|
|
26619
|
+
{
|
|
26014
26620
|
const authHint = requireToken ? " (authentication required; configure WEBUI_TOKEN out of band)" : "";
|
|
26015
26621
|
console.log(`[WebUI] HTTP server listening on http://${wsHost}:${httpPort}${authHint}`);
|
|
26016
26622
|
const extraUrls = formatExternalAccessUrls({
|
|
@@ -26021,8 +26627,8 @@ async function startWebUI(opts = {}) {
|
|
|
26021
26627
|
if (extraUrls.length > 0) {
|
|
26022
26628
|
console.log("[WebUI] Protected endpoints on external interfaces:\n" + extraUrls.join("\n"));
|
|
26023
26629
|
}
|
|
26024
|
-
}
|
|
26025
|
-
const companionServer = setupCompanionServer(httpServer, wsHost, httpPort);
|
|
26630
|
+
}
|
|
26631
|
+
const companionServer = await setupCompanionServer(httpServer, wsHost, httpPort);
|
|
26026
26632
|
async function touchProjectEntry(root, workDir) {
|
|
26027
26633
|
const resolved = path33.resolve(root);
|
|
26028
26634
|
const manifest = await loadManifest(globalConfigPath);
|
|
@@ -26281,6 +26887,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26281
26887
|
},
|
|
26282
26888
|
codebaseIndexing,
|
|
26283
26889
|
memoryStore,
|
|
26890
|
+
vectorMemoryStore,
|
|
26284
26891
|
globalConfigPath
|
|
26285
26892
|
});
|
|
26286
26893
|
}
|