prism-mcp-server 20.21.7 → 20.21.8
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/README.md +17 -0
- package/dist/dashboard/accountRouter.js +328 -0
- package/dist/dashboard/graphRouter.js +44 -0
- package/dist/dashboard/server.js +8 -2
- package/dist/dashboard/ui.js +283 -47
- package/dist/storage/index.js +7 -0
- package/dist/storage/synalux.js +170 -3
- package/dist/tools/graphHandlers.js +81 -18
- package/dist/tools/hygieneHandlers.js +10 -8
- package/dist/tools/ledgerHandlers.js +5 -5
- package/dist/tools/sessionMemoryDefinitions.js +18 -3
- package/dist/utils/imageCaptioner.js +3 -3
- package/dist/utils/llm/adapters/synalux.js +98 -0
- package/dist/utils/llm/factory.js +31 -0
- package/dist/utils/synaluxCredentialState.js +19 -0
- package/dist/utils/synaluxJwt.js +22 -3
- package/dist/utils/synaluxSearch.js +23 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -149,6 +149,23 @@ or by re-enabling after each run.
|
|
|
149
149
|
<details>
|
|
150
150
|
<summary>Release history (optional)</summary>
|
|
151
151
|
|
|
152
|
+
## What's New in v20.21.8
|
|
153
|
+
|
|
154
|
+
### Dashboard graphs use the authenticated Portal
|
|
155
|
+
|
|
156
|
+
- The selected cloud project now shows its real session nodes and stored links
|
|
157
|
+
instead of collapsing to a keyword-only project bubble.
|
|
158
|
+
- **Synthesize Edges** uses the authenticated Synalux Portal embedding contract;
|
|
159
|
+
the Prism dashboard process does not need a Google or Gemini API key.
|
|
160
|
+
- Existing vectors and links are reused on repeat runs, synthesis fan-out is
|
|
161
|
+
bounded, and Portal failures are shown rather than rendered as an empty graph.
|
|
162
|
+
- **Account & Settings** now shows the current user, role, and Free, Standard,
|
|
163
|
+
Advanced, or Enterprise plan, with visible sign-in, upgrade, billing, and
|
|
164
|
+
sign-out actions.
|
|
165
|
+
- A selected paid plan survives the Portal sign-in round trip, live and test
|
|
166
|
+
Stripe catalogs resolve every paid tier, and localized checkout never reuses
|
|
167
|
+
a USD Price for another currency.
|
|
168
|
+
|
|
152
169
|
## What's New in v20.18.1
|
|
153
170
|
|
|
154
171
|
### Task skills survive compaction and update in place
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { invalidateEntitlements } from "../utils/entitlements.js";
|
|
2
|
+
import { getSynaluxJwt, invalidateSynaluxJwt } from "../utils/synaluxJwt.js";
|
|
3
|
+
import { resolvePortalBaseUrl, usablePortalKey } from "../utils/synaluxSearch.js";
|
|
4
|
+
import { isSynaluxSignedOut, setSynaluxSignedOut } from "../utils/synaluxCredentialState.js";
|
|
5
|
+
import { upgradeInsecureCloudUrl } from "../utils/secureUrl.js";
|
|
6
|
+
const DEFAULT_PORTAL_URL = "https://synalux.ai";
|
|
7
|
+
const MAX_BODY_BYTES = 16 * 1024;
|
|
8
|
+
const VALID_PLANS = new Set(["free", "standard", "advanced", "enterprise"]);
|
|
9
|
+
let accountMutationQueue = Promise.resolve();
|
|
10
|
+
async function serializeAccountMutation(operation) {
|
|
11
|
+
const previous = accountMutationQueue;
|
|
12
|
+
let release;
|
|
13
|
+
accountMutationQueue = new Promise((resolve) => { release = resolve; });
|
|
14
|
+
await previous.catch(() => { });
|
|
15
|
+
try {
|
|
16
|
+
return await operation();
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
release();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const defaultDeps = {
|
|
23
|
+
fetcher: fetch,
|
|
24
|
+
getSetting: async (key, defaultValue) => (await import("../storage/configStorage.js")).getSetting(key, defaultValue),
|
|
25
|
+
setSetting: async (key, value) => (await import("../storage/configStorage.js")).setSetting(key, value),
|
|
26
|
+
getJwt: getSynaluxJwt,
|
|
27
|
+
invalidateJwt: invalidateSynaluxJwt,
|
|
28
|
+
invalidateEntitlements,
|
|
29
|
+
closeStorage: async () => (await import("../storage/index.js")).closeStorage(),
|
|
30
|
+
resolvePortalBaseUrl,
|
|
31
|
+
usablePortalKey,
|
|
32
|
+
};
|
|
33
|
+
function json(res, status, body) {
|
|
34
|
+
res.writeHead(status, {
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
"Cache-Control": "private, no-store",
|
|
37
|
+
});
|
|
38
|
+
res.end(JSON.stringify(body));
|
|
39
|
+
}
|
|
40
|
+
async function readJson(req) {
|
|
41
|
+
const chunks = [];
|
|
42
|
+
let total = 0;
|
|
43
|
+
for await (const chunk of req) {
|
|
44
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
45
|
+
total += buffer.length;
|
|
46
|
+
if (total > MAX_BODY_BYTES)
|
|
47
|
+
throw new Error("Request body too large");
|
|
48
|
+
chunks.push(buffer);
|
|
49
|
+
}
|
|
50
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
51
|
+
if (!raw)
|
|
52
|
+
return {};
|
|
53
|
+
const value = JSON.parse(raw);
|
|
54
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
55
|
+
throw new Error("Body must be a JSON object");
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
function safePortalOrigin(raw) {
|
|
60
|
+
if (!raw)
|
|
61
|
+
return null;
|
|
62
|
+
try {
|
|
63
|
+
const upgraded = upgradeInsecureCloudUrl(raw.trim());
|
|
64
|
+
const parsed = new URL(upgraded);
|
|
65
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && ["localhost", "127.0.0.1", "::1", "[::1]"].includes(parsed.hostname))) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return parsed.origin;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function portalOrigin(deps) {
|
|
75
|
+
return safePortalOrigin(deps.resolvePortalBaseUrl())
|
|
76
|
+
?? safePortalOrigin(await deps.getSetting("PRISM_SYNALUX_BASE_URL", ""))
|
|
77
|
+
?? safePortalOrigin(await deps.getSetting("SYNALUX_BASE_URL", ""))
|
|
78
|
+
?? DEFAULT_PORTAL_URL;
|
|
79
|
+
}
|
|
80
|
+
async function applyStoredSignOut(deps) {
|
|
81
|
+
const signedOut = (await deps.getSetting("PRISM_SYNALUX_SIGNED_OUT", "")) === "true";
|
|
82
|
+
setSynaluxSignedOut(signedOut);
|
|
83
|
+
if (signedOut)
|
|
84
|
+
delete process.env.PRISM_SYNALUX_API_KEY;
|
|
85
|
+
return signedOut;
|
|
86
|
+
}
|
|
87
|
+
function authUrl(origin) {
|
|
88
|
+
return `${origin}/auth?source=prism`;
|
|
89
|
+
}
|
|
90
|
+
function signedOutAccount(origin) {
|
|
91
|
+
return {
|
|
92
|
+
signed_in: false,
|
|
93
|
+
configured: false,
|
|
94
|
+
name: null,
|
|
95
|
+
role_key: null,
|
|
96
|
+
plan: "free",
|
|
97
|
+
billing: { action: "upgrade", url: `${origin}/pricing` },
|
|
98
|
+
auth_url: authUrl(origin),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function normalizeAccount(value, origin) {
|
|
102
|
+
if (!value || typeof value !== "object")
|
|
103
|
+
throw new Error("Portal returned an invalid account response");
|
|
104
|
+
const raw = value;
|
|
105
|
+
const plan = typeof raw.plan === "string" && VALID_PLANS.has(raw.plan) ? raw.plan : "free";
|
|
106
|
+
const billingRaw = raw.billing && typeof raw.billing === "object"
|
|
107
|
+
? raw.billing
|
|
108
|
+
: {};
|
|
109
|
+
const action = billingRaw.action === "manage" || billingRaw.action === "included"
|
|
110
|
+
? billingRaw.action
|
|
111
|
+
: "upgrade";
|
|
112
|
+
const url = typeof billingRaw.url === "string" && isAllowedBillingUrl(billingRaw.url, origin)
|
|
113
|
+
? billingRaw.url
|
|
114
|
+
: null;
|
|
115
|
+
return {
|
|
116
|
+
signed_in: raw.signed_in === true,
|
|
117
|
+
configured: true,
|
|
118
|
+
name: typeof raw.name === "string" ? raw.name.slice(0, 160) : null,
|
|
119
|
+
role_key: typeof raw.role_key === "string" ? raw.role_key.slice(0, 80) : null,
|
|
120
|
+
plan: plan,
|
|
121
|
+
plan_source: raw.plan_source === "managed" ? "managed" : "stripe",
|
|
122
|
+
billing: { action, url },
|
|
123
|
+
auth_url: authUrl(origin),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function isAllowedBillingUrl(raw, origin) {
|
|
127
|
+
try {
|
|
128
|
+
const url = new URL(raw);
|
|
129
|
+
return url.origin === origin
|
|
130
|
+
|| (url.protocol === "https:" && url.hostname === "billing.stripe.com");
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function accountWithJwt(deps, origin) {
|
|
137
|
+
let jwt = await deps.getJwt();
|
|
138
|
+
if (!jwt || isSynaluxSignedOut())
|
|
139
|
+
throw new Error("Synalux sign-in expired. Sign in again.");
|
|
140
|
+
const send = (token) => deps.fetcher(`${origin}/api/v1/prism/account`, {
|
|
141
|
+
method: "GET",
|
|
142
|
+
headers: { Authorization: `Bearer ${token}`, "X-Prism-Client": "prism-dashboard" },
|
|
143
|
+
signal: AbortSignal.timeout(10_000),
|
|
144
|
+
redirect: "error",
|
|
145
|
+
});
|
|
146
|
+
let response = await send(jwt);
|
|
147
|
+
if (isSynaluxSignedOut())
|
|
148
|
+
throw new Error("Synalux account is signed out");
|
|
149
|
+
if (response.status === 401) {
|
|
150
|
+
deps.invalidateJwt();
|
|
151
|
+
jwt = await deps.getJwt();
|
|
152
|
+
if (!jwt)
|
|
153
|
+
throw new Error("Synalux sign-in expired. Sign in again.");
|
|
154
|
+
response = await send(jwt);
|
|
155
|
+
if (isSynaluxSignedOut())
|
|
156
|
+
throw new Error("Synalux account is signed out");
|
|
157
|
+
}
|
|
158
|
+
if (!response.ok)
|
|
159
|
+
throw new Error(`Unable to load Synalux account (HTTP ${response.status})`);
|
|
160
|
+
const data = await response.json();
|
|
161
|
+
if (isSynaluxSignedOut())
|
|
162
|
+
throw new Error("Synalux account is signed out");
|
|
163
|
+
return normalizeAccount(data, origin);
|
|
164
|
+
}
|
|
165
|
+
export async function loadDashboardAccount(overrides = {}) {
|
|
166
|
+
const deps = { ...defaultDeps, ...overrides };
|
|
167
|
+
const origin = await portalOrigin(deps);
|
|
168
|
+
if (await applyStoredSignOut(deps))
|
|
169
|
+
return signedOutAccount(origin);
|
|
170
|
+
if (!deps.usablePortalKey())
|
|
171
|
+
return signedOutAccount(origin);
|
|
172
|
+
return accountWithJwt(deps, origin);
|
|
173
|
+
}
|
|
174
|
+
async function revokeRawToken(deps, origin, token) {
|
|
175
|
+
try {
|
|
176
|
+
const response = await deps.fetcher(`${origin}/api/v1/prism/account`, {
|
|
177
|
+
method: "DELETE",
|
|
178
|
+
headers: { Authorization: `Bearer ${token}`, "X-Prism-Client": "prism-dashboard" },
|
|
179
|
+
signal: AbortSignal.timeout(10_000),
|
|
180
|
+
redirect: "error",
|
|
181
|
+
});
|
|
182
|
+
return response.ok || response.status === 401;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export async function connectDashboardAccount(code, overrides = {}) {
|
|
189
|
+
return serializeAccountMutation(async () => {
|
|
190
|
+
const deps = { ...defaultDeps, ...overrides };
|
|
191
|
+
if (typeof code !== "string" || !code.startsWith("synalux_code_") || code.length > 256) {
|
|
192
|
+
throw new Error("Enter a valid one-time Prism sign-in code");
|
|
193
|
+
}
|
|
194
|
+
const origin = await portalOrigin(deps);
|
|
195
|
+
const response = await deps.fetcher(`${origin}/api/v1/auth/code-exchange`, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: { "Content-Type": "application/json", "X-Prism-Client": "prism-dashboard" },
|
|
198
|
+
body: JSON.stringify({ code, client: "prism" }),
|
|
199
|
+
signal: AbortSignal.timeout(10_000),
|
|
200
|
+
redirect: "error",
|
|
201
|
+
});
|
|
202
|
+
const data = await response.json().catch(() => ({}));
|
|
203
|
+
if (!response.ok)
|
|
204
|
+
throw new Error(typeof data.error === "string" ? data.error : "Prism sign-in failed");
|
|
205
|
+
const token = typeof data.api_token === "string" ? data.api_token : "";
|
|
206
|
+
if (!token.startsWith("synalux_sk_") || token.length > 512) {
|
|
207
|
+
throw new Error("Portal returned an invalid Prism credential");
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
await deps.setSetting("PRISM_SYNALUX_BASE_URL", origin);
|
|
211
|
+
await deps.setSetting("PRISM_SYNALUX_API_KEY", token);
|
|
212
|
+
await deps.setSetting("PRISM_SYNALUX_SIGNED_OUT", "false");
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
await revokeRawToken(deps, origin, token);
|
|
216
|
+
await deps.setSetting("PRISM_SYNALUX_SIGNED_OUT", "true").catch(() => { });
|
|
217
|
+
await deps.setSetting("PRISM_SYNALUX_API_KEY", "").catch(() => { });
|
|
218
|
+
throw new Error(`Could not save Prism sign-in: ${error instanceof Error ? error.message : String(error)}`);
|
|
219
|
+
}
|
|
220
|
+
setSynaluxSignedOut(false);
|
|
221
|
+
process.env.PRISM_SYNALUX_BASE_URL = origin;
|
|
222
|
+
process.env.PRISM_SYNALUX_API_KEY = token;
|
|
223
|
+
deps.invalidateJwt();
|
|
224
|
+
deps.invalidateEntitlements();
|
|
225
|
+
await deps.closeStorage();
|
|
226
|
+
return accountWithJwt(deps, origin);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
export async function openDashboardBilling(overrides = {}) {
|
|
230
|
+
const deps = { ...defaultDeps, ...overrides };
|
|
231
|
+
const origin = await portalOrigin(deps);
|
|
232
|
+
if (await applyStoredSignOut(deps) || !deps.usablePortalKey()) {
|
|
233
|
+
return { url: `${origin}/pricing`, action: "upgrade" };
|
|
234
|
+
}
|
|
235
|
+
let jwt = await deps.getJwt();
|
|
236
|
+
if (!jwt || isSynaluxSignedOut())
|
|
237
|
+
throw new Error("Synalux sign-in expired. Sign in again.");
|
|
238
|
+
const send = (token) => deps.fetcher(`${origin}/api/v1/prism/account`, {
|
|
239
|
+
method: "POST",
|
|
240
|
+
headers: { Authorization: `Bearer ${token}`, "X-Prism-Client": "prism-dashboard" },
|
|
241
|
+
signal: AbortSignal.timeout(10_000),
|
|
242
|
+
redirect: "error",
|
|
243
|
+
});
|
|
244
|
+
let response = await send(jwt);
|
|
245
|
+
if (isSynaluxSignedOut())
|
|
246
|
+
throw new Error("Synalux account is signed out");
|
|
247
|
+
if (response.status === 401) {
|
|
248
|
+
deps.invalidateJwt();
|
|
249
|
+
jwt = await deps.getJwt();
|
|
250
|
+
if (!jwt)
|
|
251
|
+
throw new Error("Synalux sign-in expired. Sign in again.");
|
|
252
|
+
response = await send(jwt);
|
|
253
|
+
if (isSynaluxSignedOut())
|
|
254
|
+
throw new Error("Synalux account is signed out");
|
|
255
|
+
}
|
|
256
|
+
const data = await response.json().catch(() => ({}));
|
|
257
|
+
if (isSynaluxSignedOut())
|
|
258
|
+
throw new Error("Synalux account is signed out");
|
|
259
|
+
if (!response.ok)
|
|
260
|
+
throw new Error(typeof data.error === "string" ? data.error : `Billing unavailable (HTTP ${response.status})`);
|
|
261
|
+
if (typeof data.url !== "string" || !isAllowedBillingUrl(data.url, origin)) {
|
|
262
|
+
throw new Error("Portal returned an unsafe billing URL");
|
|
263
|
+
}
|
|
264
|
+
return { url: data.url, action: typeof data.action === "string" ? data.action : "manage" };
|
|
265
|
+
}
|
|
266
|
+
export async function signOutDashboardAccount(overrides = {}) {
|
|
267
|
+
return serializeAccountMutation(async () => {
|
|
268
|
+
const deps = { ...defaultDeps, ...overrides };
|
|
269
|
+
const origin = await portalOrigin(deps);
|
|
270
|
+
const token = deps.usablePortalKey() ?? (await deps.getSetting("PRISM_SYNALUX_API_KEY", "")).trim();
|
|
271
|
+
// Cut off every local credential path before waiting on the network. The
|
|
272
|
+
// storage singleton captures its refresh token at construction, so clearing
|
|
273
|
+
// only process.env would leave an already-created backend signed in.
|
|
274
|
+
setSynaluxSignedOut(true);
|
|
275
|
+
delete process.env.PRISM_SYNALUX_API_KEY;
|
|
276
|
+
deps.invalidateJwt();
|
|
277
|
+
deps.invalidateEntitlements();
|
|
278
|
+
await deps.setSetting("PRISM_SYNALUX_SIGNED_OUT", "true");
|
|
279
|
+
await deps.setSetting("PRISM_SYNALUX_API_KEY", "");
|
|
280
|
+
await deps.closeStorage();
|
|
281
|
+
const revoked = token ? await revokeRawToken(deps, origin, token) : true;
|
|
282
|
+
return { signed_out: true, revoked };
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
export async function handleAccountRoutes(url, req, res) {
|
|
286
|
+
if (url.pathname === "/api/account" && req.method === "GET") {
|
|
287
|
+
try {
|
|
288
|
+
json(res, 200, await loadDashboardAccount());
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
json(res, 502, { error: error instanceof Error ? error.message : "Unable to load account" });
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
if (url.pathname === "/api/account/connect" && req.method === "POST") {
|
|
296
|
+
try {
|
|
297
|
+
const body = await readJson(req);
|
|
298
|
+
json(res, 200, await connectDashboardAccount(body.code));
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
json(res, 400, { error: error instanceof Error ? error.message : "Prism sign-in failed" });
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
if (url.pathname === "/api/account/billing" && req.method === "POST") {
|
|
306
|
+
try {
|
|
307
|
+
json(res, 200, await openDashboardBilling());
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
json(res, 502, { error: error instanceof Error ? error.message : "Billing unavailable" });
|
|
311
|
+
}
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
if (url.pathname === "/api/account/signout" && req.method === "POST") {
|
|
315
|
+
try {
|
|
316
|
+
const result = await signOutDashboardAccount();
|
|
317
|
+
json(res, result.revoked ? 200 : 502, {
|
|
318
|
+
...result,
|
|
319
|
+
...(result.revoked ? {} : { warning: "Signed out locally, but remote token revocation could not be confirmed" }),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
json(res, 500, { error: error instanceof Error ? error.message : "Sign-out failed" });
|
|
324
|
+
}
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
@@ -77,6 +77,50 @@ export async function handleGraphRoutes(url, req, res, getStorageSafe) {
|
|
|
77
77
|
if (min_importance)
|
|
78
78
|
params.importance = `gte.${parseInt(min_importance, 10)}`;
|
|
79
79
|
const graphLimit = !project && !days && !min_importance ? 30 : 200;
|
|
80
|
+
if (project && s.getDashboardMemoryGraph) {
|
|
81
|
+
const graph = await s.getDashboardMemoryGraph({
|
|
82
|
+
project,
|
|
83
|
+
...(createdAfter ? { createdAfter } : {}),
|
|
84
|
+
...(min_importance ? { minImportance: parseInt(min_importance, 10) } : {}),
|
|
85
|
+
limit: graphLimit,
|
|
86
|
+
});
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const nodes = graph.nodes.map((row) => {
|
|
89
|
+
const accessedAt = row.last_accessed_at || row.created_at;
|
|
90
|
+
const daysSince = accessedAt
|
|
91
|
+
? Math.max(0, Math.floor((now - new Date(accessedAt).getTime()) / (1000 * 60 * 60 * 24)))
|
|
92
|
+
: 999;
|
|
93
|
+
const baseImportance = typeof row.importance === "number" ? row.importance : 0;
|
|
94
|
+
return {
|
|
95
|
+
id: row.id,
|
|
96
|
+
label: typeof row.summary === "string" && row.summary.trim() ? row.summary.trim() : String(row.id),
|
|
97
|
+
group: "memory",
|
|
98
|
+
days_since_access: daysSince,
|
|
99
|
+
decayed_importance: baseImportance > 0
|
|
100
|
+
? Math.round(baseImportance * Math.pow(0.95, daysSince) * 100) / 100
|
|
101
|
+
: 0,
|
|
102
|
+
base_importance: baseImportance,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
const nodeIds = new Set(nodes.map(node => node.id));
|
|
106
|
+
const edges = graph.edges
|
|
107
|
+
.filter((edge) => nodeIds.has(edge.source_id) && nodeIds.has(edge.target_id))
|
|
108
|
+
.map((edge) => ({
|
|
109
|
+
from: edge.source_id,
|
|
110
|
+
to: edge.target_id,
|
|
111
|
+
label: edge.link_type,
|
|
112
|
+
value: typeof edge.strength === "number" ? edge.strength : 1,
|
|
113
|
+
}));
|
|
114
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
115
|
+
res.end(JSON.stringify({
|
|
116
|
+
graphType: "memory",
|
|
117
|
+
entryCount: nodes.length,
|
|
118
|
+
truncated: graph.truncated,
|
|
119
|
+
nodes,
|
|
120
|
+
edges,
|
|
121
|
+
}));
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
80
124
|
const entries = s.getDashboardGraphEntries
|
|
81
125
|
? await s.getDashboardGraphEntries({
|
|
82
126
|
...(project ? { project } : {}),
|
package/dist/dashboard/server.js
CHANGED
|
@@ -29,10 +29,11 @@ import { renderDashboardHTML } from "./ui.js";
|
|
|
29
29
|
import { computeIntentHealth } from "./intentHealth.js";
|
|
30
30
|
import { getAllSettings, setSetting, getSetting, getSettingSync } from "../storage/configStorage.js";
|
|
31
31
|
import { compactLedgerHandler } from "../tools/compactionHandler.js";
|
|
32
|
-
import {
|
|
32
|
+
import { getEmbeddingProvider } from "../utils/llm/factory.js";
|
|
33
33
|
import { buildVaultDirectory } from "../utils/vaultExporter.js";
|
|
34
34
|
import { redactSettings } from "../tools/commonHelpers.js";
|
|
35
35
|
import { handleGraphRoutes } from "./graphRouter.js";
|
|
36
|
+
import { handleAccountRoutes } from "./accountRouter.js";
|
|
36
37
|
import { isDashboardSettingKeyAllowed, isDashboardSettingValueAllowed } from "./settingsPolicy.js";
|
|
37
38
|
import { isTrustedRequest, isRebindGuardedPath } from "./hostGuard.js";
|
|
38
39
|
import { resolveDashboardToken, requestHasToken, buildTokenCookie, dashboardTokenCookieName, } from "./dashboardToken.js";
|
|
@@ -640,6 +641,11 @@ return false;}
|
|
|
640
641
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
641
642
|
return res.end(JSON.stringify({ ok: true, role, namespace: "user_skill" }));
|
|
642
643
|
}
|
|
644
|
+
// ─── API: Synalux Account & Subscription ───
|
|
645
|
+
if (url.pathname.startsWith("/api/account")) {
|
|
646
|
+
if (await handleAccountRoutes(url, req, res))
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
643
649
|
// ─── API: Knowledge Graph (v6.2 — extracted to graphRouter.ts) ───
|
|
644
650
|
if (url.pathname.startsWith("/api/graph")) {
|
|
645
651
|
if (await handleGraphRoutes(url, req, res, getStorageSafe))
|
|
@@ -1007,7 +1013,7 @@ return false;}
|
|
|
1007
1013
|
// Check LLM provider availability before attempting embedding
|
|
1008
1014
|
let llm;
|
|
1009
1015
|
try {
|
|
1010
|
-
llm =
|
|
1016
|
+
llm = getEmbeddingProvider();
|
|
1011
1017
|
}
|
|
1012
1018
|
catch {
|
|
1013
1019
|
res.writeHead(503, { "Content-Type": "application/json" });
|