trellis 3.2.2 → 3.2.4
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/CHANGELOG.md +10 -0
- package/README.md +4 -0
- package/dist/better-sqlite-backend-CDYOAJDF.js +209 -0
- package/dist/boot-middleware-LBJX4N2S.js +9 -0
- package/dist/boot-middleware-PATBLD52.js +9 -0
- package/dist/bridge-G5DPW6TY.js +2361 -0
- package/dist/bridge-JSSPQPRH.js +2389 -0
- package/dist/bridge-LNXE4HAW.js +2389 -0
- package/dist/bridge-OAAXJWSA.js +2389 -0
- package/dist/bridge-SFS63RHC.js +2367 -0
- package/dist/bridge-U5TOUBVM.js +2389 -0
- package/dist/browser/index.js +9 -9
- package/dist/{chunk-KSU2GW22.js → chunk-276W52FK.js} +8 -0
- package/dist/{chunk-KFQGP6VL.js → chunk-2ESYSVXG.js} +15 -0
- package/dist/{chunk-L5N5PR2S.js → chunk-2WW2X2BR.js} +313 -77
- package/dist/chunk-3XCOAP6G.js +48 -0
- package/dist/chunk-53PDCTJ3.js +1856 -0
- package/dist/{chunk-N6MYZT4O.js → chunk-547FZFKO.js} +1 -1
- package/dist/{chunk-H6JB3PZ3.js → chunk-5KSGGKET.js} +1 -1
- package/dist/{chunk-J2TXUFCZ.js → chunk-6RWGFC3N.js} +134 -20
- package/dist/{chunk-QB5ISE47.js → chunk-7WVOPXJV.js} +3 -3
- package/dist/chunk-CMSFD6OV.js +11801 -0
- package/dist/{chunk-RSWUFTO2.js → chunk-CPUYCCLT.js} +2 -2
- package/dist/{chunk-4HYPLFJD.js → chunk-FA5TFVA5.js} +1 -1
- package/dist/{chunk-4XIVMVHC.js → chunk-G3XIHPSQ.js} +1 -1
- package/dist/{chunk-JHHEXEDM.js → chunk-GCQF75T4.js} +3 -3
- package/dist/{chunk-FKQO5A3H.js → chunk-GHG4H3AS.js} +2 -2
- package/dist/{chunk-GLM4HGN4.js → chunk-GRWQPKYK.js} +1 -1
- package/dist/{chunk-PZ4XYZ4J.js → chunk-GXA73CNC.js} +33 -8
- package/dist/{chunk-6GEZ6DN5.js → chunk-H6VTC5SS.js} +3 -3
- package/dist/{chunk-S3XLFZ27.js → chunk-IHHSOBJS.js} +1 -1
- package/dist/chunk-IJNC25UD.js +2243 -0
- package/dist/{chunk-VDAKEOML.js → chunk-J6OUIION.js} +1 -1
- package/dist/chunk-JAVAYMMS.js +6 -0
- package/dist/{chunk-S6GBJ2ZB.js → chunk-JCQQFLO5.js} +1 -1
- package/dist/{chunk-FWRS4CSC.js → chunk-KQG6ZYXP.js} +3 -3
- package/dist/chunk-KXAISKZT.js +119 -0
- package/dist/chunk-N3R74ZSR.js +7949 -0
- package/dist/{chunk-GU374KWZ.js → chunk-NLFRRGX6.js} +3 -3
- package/dist/{chunk-XMQ7G77X.js → chunk-OB4CMMX6.js} +1 -1
- package/dist/chunk-OKNASCZY.js +182 -0
- package/dist/chunk-ONOPPX5E.js +394 -0
- package/dist/chunk-QMSU2RAH.js +98 -0
- package/dist/{chunk-TCKXSOBP.js → chunk-RARKFVWU.js} +8 -1
- package/dist/{chunk-2W3MHTMG.js → chunk-SJWVDS5M.js} +835 -127
- package/dist/{chunk-SPSPV5BC.js → chunk-SZ3VAB5P.js} +159 -2
- package/dist/chunk-V2ASD6RQ.js +147 -0
- package/dist/{chunk-KQ4A2D2P.js → chunk-V4YCJDYL.js} +58 -38
- package/dist/{chunk-LNUIGRDZ.js → chunk-VWKLKLYB.js} +1 -1
- package/dist/chunk-W5PADCDF.js +1856 -0
- package/dist/{chunk-MLC5BUYO.js → chunk-WMERMSWI.js} +1 -1
- package/dist/{chunk-IZX2PLIB.js → chunk-WXAURJUD.js} +835 -127
- package/dist/chunk-YC5I32PS.js +147 -0
- package/dist/{chunk-VBYUTRXX.js → chunk-YKVB4LUQ.js} +3 -3
- package/dist/{chunk-7ZFRVCUE.js → chunk-YPT6XMS4.js} +48 -64
- package/dist/cli/deploy-cli.d.ts.map +1 -1
- package/dist/{deploy-cli-C35HTITO.js → cli/deploy-cli.js} +45 -6
- package/dist/cli/gateway-deploy-cli.d.ts +19 -0
- package/dist/cli/gateway-deploy-cli.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1363 -884
- package/dist/cli/lane.d.ts.map +1 -1
- package/dist/cli/protocol.d.ts +6 -0
- package/dist/cli/protocol.d.ts.map +1 -0
- package/dist/client/index.browser.js +2 -2
- package/dist/client/index.js +11 -11
- package/dist/client/sdk.browser.d.ts +28 -1
- package/dist/client/sdk.browser.d.ts.map +1 -1
- package/dist/client/sdk.browser.js +2 -2
- package/dist/client/sdk.js +2 -2
- package/dist/cms/index.js +1 -1
- package/dist/config-KMY6RRK3.js +19 -0
- package/dist/core/index.js +17 -17
- package/dist/core/kernel/boot-middleware.d.ts +1 -1
- package/dist/core/kernel/boot-middleware.d.ts.map +1 -1
- package/dist/core/kernel/schema-middleware.d.ts +2 -1
- package/dist/core/kernel/schema-middleware.d.ts.map +1 -1
- package/dist/core/kernel/trellis-kernel.d.ts +10 -10
- package/dist/core/kernel/trellis-kernel.d.ts.map +1 -1
- package/dist/core/persist/factory.js +2 -2
- package/dist/core/persist/sqljs-backend.js +2 -2
- package/dist/db/index.js +32 -27
- package/dist/decisions/index.js +4 -4
- package/dist/embeddings/index.js +2 -2
- package/dist/engine.d.ts +24 -6
- package/dist/engine.d.ts.map +1 -1
- package/dist/gateway-deploy-cli-4TU4V3QI.js +312 -0
- package/dist/gateway-deploy-cli-A4ISTDHJ.js +298 -0
- package/dist/gateway-deploy-cli-D3XTKJQJ.js +312 -0
- package/dist/gateway-deploy-cli-ECV3OQCN.js +305 -0
- package/dist/gateway-deploy-cli-JUHKVIJE.js +296 -0
- package/dist/gateway-deploy-cli-NSDW2JLT.js +310 -0
- package/dist/gateway-serve-CM5EAZ52.js +113 -0
- package/dist/import-AJLYHFIA.js +10 -0
- package/dist/index.js +11 -11
- package/dist/launch-explorer-FVX6ZABW.js +95 -0
- package/dist/links/index.js +2 -2
- package/dist/mcp/bridge.d.ts +45 -0
- package/dist/mcp/bridge.d.ts.map +1 -0
- package/dist/mcp/discovery.d.ts +15 -0
- package/dist/mcp/discovery.d.ts.map +1 -0
- package/dist/mcp/gateway-config.d.ts +20 -0
- package/dist/mcp/gateway-config.d.ts.map +1 -0
- package/dist/mcp/gateway-serve.d.ts +19 -0
- package/dist/mcp/gateway-serve.d.ts.map +1 -0
- package/dist/mcp/gateway-serve.js +119 -0
- package/dist/mcp/graph-summary.d.ts +47 -0
- package/dist/mcp/graph-summary.d.ts.map +1 -0
- package/dist/mcp/room-audit.d.ts +14 -0
- package/dist/mcp/room-audit.d.ts.map +1 -0
- package/dist/mcp/room-helpers.d.ts +43 -0
- package/dist/mcp/room-helpers.d.ts.map +1 -0
- package/dist/mcp/room-registry.d.ts +32 -0
- package/dist/mcp/room-registry.d.ts.map +1 -0
- package/dist/mcp/room.d.ts +28 -0
- package/dist/mcp/room.d.ts.map +1 -0
- package/dist/plugins/agent-memory/index.js +1 -1
- package/dist/plugins/idea-garden/index.js +1 -1
- package/dist/plugins/plan-approval/index.js +1 -1
- package/dist/plugins/proactive-watcher/index.js +1 -1
- package/dist/protocol/envelope.d.ts +30 -0
- package/dist/protocol/envelope.d.ts.map +1 -0
- package/dist/protocol/index.d.ts +3 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/whereami.d.ts +33 -0
- package/dist/protocol/whereami.d.ts.map +1 -0
- package/dist/query-KHDDIR5G.js +31 -0
- package/dist/react/index.js +3 -3
- package/dist/react/realtime.js +1 -1
- package/dist/react/schema-hooks.js +6 -6
- package/dist/realtime/index.js +3 -3
- package/dist/realtime/relay-server.d.ts.map +1 -1
- package/dist/realtime/room.d.ts.map +1 -1
- package/dist/realtime.bundle.js +2 -0
- package/dist/relay-server-BT6DCJ7F.js +12 -0
- package/dist/relay-server-MPQVGNQU.js +12 -0
- package/dist/relay-server-PAPW2EWC.js +12 -0
- package/dist/remote-manager-6ERQUBSL.js +224 -0
- package/dist/remote-manager-G665WWYY.js +224 -0
- package/dist/remote-manager-HJTEZ4OF.js +224 -0
- package/dist/remote-manager-NYP2Y5UR.js +224 -0
- package/dist/remote-manager-WGLCAMMC.js +224 -0
- package/dist/scaffold/write.d.ts.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/server/cors.d.ts +13 -0
- package/dist/server/cors.d.ts.map +1 -0
- package/dist/server/deploy-gateway.d.ts +27 -0
- package/dist/server/deploy-gateway.d.ts.map +1 -0
- package/dist/server/deploy.d.ts +1 -1
- package/dist/server/deploy.d.ts.map +1 -1
- package/dist/server/deploy.js +10 -0
- package/dist/server/discovery-mcp-gateway.d.ts +13 -0
- package/dist/server/discovery-mcp-gateway.d.ts.map +1 -0
- package/dist/server/index.js +44 -37
- package/dist/server/mcp-gateway.d.ts +27 -0
- package/dist/server/mcp-gateway.d.ts.map +1 -0
- package/dist/server/mcp-oauth-metadata.d.ts +31 -0
- package/dist/server/mcp-oauth-metadata.d.ts.map +1 -0
- package/dist/server/node-adapter.d.ts +7 -0
- package/dist/server/node-adapter.d.ts.map +1 -1
- package/dist/server/node-adapter.js +2 -2
- package/dist/server/realtime.d.ts.map +1 -1
- package/dist/server/server.d.ts +8 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/streamable-mcp-gateway.d.ts +12 -0
- package/dist/server/streamable-mcp-gateway.d.ts.map +1 -0
- package/dist/server/usage-meter.d.ts +11 -0
- package/dist/server/usage-meter.d.ts.map +1 -1
- package/dist/server-4VEZHXXS.js +23 -0
- package/dist/{server-ZVWGLLRF.js → server-556BMK4C.js} +1 -1
- package/dist/server-FQT2VYRE.js +23 -0
- package/dist/server-HGZE4ECC.js +23 -0
- package/dist/server-J4JKEAMH.js +23 -0
- package/dist/server-LXPM5TFR.js +23 -0
- package/dist/{server-MB3OPSPI.js → server-POB4NEUX.js} +2 -2
- package/dist/server-VJKAPGPG.js +23 -0
- package/dist/server-YHS3XIIG.js +23 -0
- package/dist/server-ZHTPWPXS.js +23 -0
- package/dist/sprites-NC6D7YZS.js +27 -0
- package/dist/svelte/index.js +3 -3
- package/dist/svelte/schema-hooks.js +4 -4
- package/dist/sync/index.js +4 -4
- package/dist/tenancy-2SZTP4UD.js +16 -0
- package/dist/tenancy-7MTSESMJ.js +16 -0
- package/dist/tenancy-U4E7ZEAG.js +16 -0
- package/dist/vcs/decompose.d.ts.map +1 -1
- package/dist/vcs/index.js +5 -5
- package/dist/vcs/lane-disk-materialize.d.ts +11 -0
- package/dist/vcs/lane-disk-materialize.d.ts.map +1 -0
- package/dist/vcs/lane-worktree.d.ts +21 -0
- package/dist/vcs/lane-worktree.d.ts.map +1 -0
- package/dist/vcs/store.d.ts +28 -0
- package/dist/vcs/store.d.ts.map +1 -0
- package/dist/vcs/types.d.ts +5 -0
- package/dist/vcs/types.d.ts.map +1 -1
- package/dist/vm-config-YZRJ3KUB.js +21 -0
- package/dist/vue/index.js +3 -3
- package/dist/vue/schema-hooks.js +4 -4
- package/package.json +4 -3
- package/dist/chunk-FF36CQHZ.js +0 -61
- package/dist/chunk-GFZCJ4EH.js +0 -108
- package/dist/chunk-KIJITUZB.js +0 -419
- package/dist/chunk-SGMDTWJJ.js +0 -149
- package/dist/chunk-UQISRW6T.js +0 -203
- package/dist/db/trellis.css +0 -1
- package/dist/deploy-LQ7GUGJ5.js +0 -9
- package/dist/deploy-YNVG5E4E.js +0 -9
- package/dist/deploy-cli-24BCHAXY.js +0 -67
- package/dist/sprites-RU3E4XQN.js +0 -15
- package/dist/tenancy-NBLLGCNJ.js +0 -14
|
@@ -1,6 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
McpServer,
|
|
3
|
+
StreamableMcpGateway,
|
|
4
|
+
corsEnabledForConfig,
|
|
5
|
+
corsPreflightResponse,
|
|
6
|
+
discoveryMcpGateway,
|
|
7
|
+
mcpServiceDocument,
|
|
8
|
+
oauthAuthorizationServerMetadata,
|
|
9
|
+
oauthProtectedResourceMetadata,
|
|
10
|
+
withCors
|
|
11
|
+
} from "./chunk-W5PADCDF.js";
|
|
12
|
+
import {
|
|
13
|
+
McpRateLimitError,
|
|
14
|
+
UsageMeter,
|
|
15
|
+
assertMcpBudget,
|
|
16
|
+
collectionSlugFromMetaId,
|
|
17
|
+
resolveCollectionId,
|
|
18
|
+
resolveMcpTenantId,
|
|
19
|
+
resolveUsageTenantId,
|
|
20
|
+
verifyAdminKey,
|
|
21
|
+
writeContext
|
|
22
|
+
} from "./chunk-CMSFD6OV.js";
|
|
1
23
|
import {
|
|
2
24
|
DEFAULT_TENANT
|
|
3
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-GCQF75T4.js";
|
|
4
26
|
import {
|
|
5
27
|
rel
|
|
6
28
|
} from "./chunk-44FFQKQX.js";
|
|
@@ -14,8 +36,8 @@ import {
|
|
|
14
36
|
} from "./chunk-X5FLTRUB.js";
|
|
15
37
|
|
|
16
38
|
// src/server/server.ts
|
|
17
|
-
import { existsSync
|
|
18
|
-
import { dirname, join
|
|
39
|
+
import { existsSync, readFileSync } from "fs";
|
|
40
|
+
import { dirname, join } from "path";
|
|
19
41
|
import { fileURLToPath } from "url";
|
|
20
42
|
|
|
21
43
|
// src/server/auth.ts
|
|
@@ -370,114 +392,6 @@ async function hydrateAndResolve(kernel, bindings, entityType, resolve) {
|
|
|
370
392
|
});
|
|
371
393
|
}
|
|
372
394
|
|
|
373
|
-
// src/server/usage-meter.ts
|
|
374
|
-
import { existsSync, readdirSync, statSync } from "fs";
|
|
375
|
-
import { join } from "path";
|
|
376
|
-
var EMPTY_BUCKET = () => ({
|
|
377
|
-
graph_io: 0,
|
|
378
|
-
storage_bytes: 0,
|
|
379
|
-
egress_bytes: 0
|
|
380
|
-
});
|
|
381
|
-
function resolveUsageTenantId(tenantId) {
|
|
382
|
-
return tenantId ?? DEFAULT_TENANT;
|
|
383
|
-
}
|
|
384
|
-
function dayKey(date) {
|
|
385
|
-
return date.toISOString().slice(0, 10);
|
|
386
|
-
}
|
|
387
|
-
function dirSize(dir) {
|
|
388
|
-
let total = 0;
|
|
389
|
-
for (const ent of readdirSync(dir, { withFileTypes: true })) {
|
|
390
|
-
const p = join(dir, ent.name);
|
|
391
|
-
if (ent.isDirectory()) total += dirSize(p);
|
|
392
|
-
else if (ent.isFile()) total += statSync(p).size;
|
|
393
|
-
}
|
|
394
|
-
return total;
|
|
395
|
-
}
|
|
396
|
-
function sampleTenantStorage(pool, tenantId) {
|
|
397
|
-
let total = 0;
|
|
398
|
-
const sqlitePath = pool.dbFilePath(tenantId);
|
|
399
|
-
if (existsSync(sqlitePath)) {
|
|
400
|
-
total += statSync(sqlitePath).size;
|
|
401
|
-
}
|
|
402
|
-
const blobDir = join(pool.dataPath(), "blobs");
|
|
403
|
-
if (existsSync(blobDir)) {
|
|
404
|
-
total += dirSize(blobDir);
|
|
405
|
-
}
|
|
406
|
-
return total;
|
|
407
|
-
}
|
|
408
|
-
function verifyAdminKey(req) {
|
|
409
|
-
const expected = process.env.TURTLEDB_ADMIN_KEY;
|
|
410
|
-
if (!expected) return false;
|
|
411
|
-
const auth = req.headers.get("authorization");
|
|
412
|
-
if (auth?.startsWith("Bearer ")) {
|
|
413
|
-
return auth.slice("Bearer ".length) === expected;
|
|
414
|
-
}
|
|
415
|
-
return req.headers.get("x-turtledb-admin-key") === expected;
|
|
416
|
-
}
|
|
417
|
-
var UsageMeter = class {
|
|
418
|
-
buckets = /* @__PURE__ */ new Map();
|
|
419
|
-
storageSampledAt = /* @__PURE__ */ new Map();
|
|
420
|
-
now;
|
|
421
|
-
constructor(opts = {}) {
|
|
422
|
-
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
423
|
-
}
|
|
424
|
-
recordGraphIo(tenantId, count = 1) {
|
|
425
|
-
if (count <= 0) return;
|
|
426
|
-
const bucket = this._bucket(tenantId);
|
|
427
|
-
bucket.graph_io += count;
|
|
428
|
-
}
|
|
429
|
-
recordEgress(tenantId, bytes) {
|
|
430
|
-
if (bytes <= 0) return;
|
|
431
|
-
const bucket = this._bucket(tenantId);
|
|
432
|
-
bucket.egress_bytes += bytes;
|
|
433
|
-
}
|
|
434
|
-
/** Set storage gauge for the current day (from sampler, not incremental). */
|
|
435
|
-
recordStorage(tenantId, bytes) {
|
|
436
|
-
const bucket = this._bucket(tenantId);
|
|
437
|
-
bucket.storage_bytes = bytes;
|
|
438
|
-
this.storageSampledAt.set(`${tenantId}:${this._today()}`, this.now().toISOString());
|
|
439
|
-
}
|
|
440
|
-
sampleStorage(pool, tenantId) {
|
|
441
|
-
const id = resolveUsageTenantId(tenantId);
|
|
442
|
-
const bytes = sampleTenantStorage(pool, id);
|
|
443
|
-
this.recordStorage(id, bytes);
|
|
444
|
-
return bytes;
|
|
445
|
-
}
|
|
446
|
-
getUsage(tenantId, day) {
|
|
447
|
-
const d = day ?? this._today();
|
|
448
|
-
const tenantBuckets = this.buckets.get(tenantId);
|
|
449
|
-
const meters = tenantBuckets?.get(d) ?? EMPTY_BUCKET();
|
|
450
|
-
const sampledAt = this.storageSampledAt.get(`${tenantId}:${d}`);
|
|
451
|
-
return {
|
|
452
|
-
tenantId,
|
|
453
|
-
day: d,
|
|
454
|
-
meters: { ...meters },
|
|
455
|
-
...sampledAt ? { storageSampledAt: sampledAt } : {}
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
/** All day keys recorded for a tenant (sorted ascending). */
|
|
459
|
-
listDays(tenantId) {
|
|
460
|
-
const tenantBuckets = this.buckets.get(tenantId);
|
|
461
|
-
if (!tenantBuckets) return [];
|
|
462
|
-
return Array.from(tenantBuckets.keys()).sort();
|
|
463
|
-
}
|
|
464
|
-
_today() {
|
|
465
|
-
return dayKey(this.now());
|
|
466
|
-
}
|
|
467
|
-
_bucket(tenantId) {
|
|
468
|
-
const id = resolveUsageTenantId(tenantId);
|
|
469
|
-
const d = this._today();
|
|
470
|
-
if (!this.buckets.has(id)) {
|
|
471
|
-
this.buckets.set(id, /* @__PURE__ */ new Map());
|
|
472
|
-
}
|
|
473
|
-
const tenantBuckets = this.buckets.get(id);
|
|
474
|
-
if (!tenantBuckets.has(d)) {
|
|
475
|
-
tenantBuckets.set(d, EMPTY_BUCKET());
|
|
476
|
-
}
|
|
477
|
-
return tenantBuckets.get(d);
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
|
|
481
395
|
// src/server/realtime.ts
|
|
482
396
|
var SubscriptionManager = class {
|
|
483
397
|
clients = /* @__PURE__ */ new Map();
|
|
@@ -549,7 +463,6 @@ var SubscriptionManager = class {
|
|
|
549
463
|
dead.push(clientId);
|
|
550
464
|
continue;
|
|
551
465
|
}
|
|
552
|
-
if (client.tenantId !== tid) continue;
|
|
553
466
|
for (const [subId, sub] of client.subscriptions) {
|
|
554
467
|
if (sub.tenantId !== tid) continue;
|
|
555
468
|
await this._pushUpdate(client, sub);
|
|
@@ -606,6 +519,9 @@ var SubscriptionManager = class {
|
|
|
606
519
|
resolve: opts.resolve
|
|
607
520
|
};
|
|
608
521
|
client.subscriptions.set(subId, sub);
|
|
522
|
+
if (tid !== null && client.tenantId !== tid) {
|
|
523
|
+
client.tenantId = tid;
|
|
524
|
+
}
|
|
609
525
|
this._send(client, { type: "subscribed", id: subId });
|
|
610
526
|
this._send(client, {
|
|
611
527
|
type: "data",
|
|
@@ -682,6 +598,641 @@ function computeDiff(prev, next) {
|
|
|
682
598
|
return { added, updated, removed };
|
|
683
599
|
}
|
|
684
600
|
|
|
601
|
+
// src/mcp/room.ts
|
|
602
|
+
import { z as z2 } from "zod";
|
|
603
|
+
|
|
604
|
+
// src/mcp/graph-summary.ts
|
|
605
|
+
var SKIP_ATTRS = /* @__PURE__ */ new Set(["@id", "@type", "id", "type"]);
|
|
606
|
+
function buildRoomGraphSummary(kernel, tenantId, opts = {}) {
|
|
607
|
+
const limit = opts.limit ?? 10;
|
|
608
|
+
const store = kernel.getStore();
|
|
609
|
+
let factCount = 0;
|
|
610
|
+
for (const _ of store.getAllFacts()) factCount++;
|
|
611
|
+
let linkCount = 0;
|
|
612
|
+
for (const _ of store.getAllLinks()) linkCount++;
|
|
613
|
+
const typeCounts = {};
|
|
614
|
+
const entityIds = /* @__PURE__ */ new Set();
|
|
615
|
+
for (const fact of store.getAllFacts()) {
|
|
616
|
+
if (fact.a === "type") {
|
|
617
|
+
const type = String(fact.v);
|
|
618
|
+
typeCounts[type] = (typeCounts[type] ?? 0) + 1;
|
|
619
|
+
entityIds.add(fact.e);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const entityTypes = Object.entries(typeCounts).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([type, count]) => ({ type, count }));
|
|
623
|
+
const systemOntologies = [];
|
|
624
|
+
const userOntologies = [];
|
|
625
|
+
for (const schema of kernel.listOntologies()) {
|
|
626
|
+
const shortId = schema["@id"].replace(/^(trellis:schema\/|core:)/, "");
|
|
627
|
+
if (schema.tier === "user") {
|
|
628
|
+
userOntologies.push(shortId);
|
|
629
|
+
} else if (schema.tier !== "core") {
|
|
630
|
+
systemOntologies.push(shortId);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const topAttributes = store.getCatalog().filter((c) => !SKIP_ATTRS.has(c.attribute)).sort((a, b) => b.distinctCount - a.distinctCount).slice(0, limit).map((c) => ({
|
|
634
|
+
attribute: c.attribute,
|
|
635
|
+
distinctCount: c.distinctCount,
|
|
636
|
+
cardinality: c.cardinality
|
|
637
|
+
}));
|
|
638
|
+
const relations = /* @__PURE__ */ new Set();
|
|
639
|
+
for (const link of store.getAllLinks()) relations.add(link.a);
|
|
640
|
+
const recentMutations = kernel.readAllOps().slice(-limit).reverse().map((op) => ({
|
|
641
|
+
kind: op.kind,
|
|
642
|
+
agentId: op.agentId,
|
|
643
|
+
timestamp: op.timestamp,
|
|
644
|
+
entityId: inferEntityIdFromOp(op)
|
|
645
|
+
}));
|
|
646
|
+
return {
|
|
647
|
+
health: {
|
|
648
|
+
status: "ok",
|
|
649
|
+
factCount,
|
|
650
|
+
linkCount,
|
|
651
|
+
entityCount: entityIds.size,
|
|
652
|
+
ops: kernel.readAllOps().length,
|
|
653
|
+
tenantId
|
|
654
|
+
},
|
|
655
|
+
entityTypes,
|
|
656
|
+
ontologies: {
|
|
657
|
+
total: kernel.listOntologies().length,
|
|
658
|
+
system: systemOntologies.slice(0, limit),
|
|
659
|
+
user: userOntologies.slice(0, limit)
|
|
660
|
+
},
|
|
661
|
+
topAttributes,
|
|
662
|
+
links: { total: linkCount, relations: [...relations].sort().slice(0, limit) },
|
|
663
|
+
recentMutations
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function inferEntityIdFromOp(op) {
|
|
667
|
+
const facts = op.facts ?? op.deleteFacts;
|
|
668
|
+
if (!facts?.length) return void 0;
|
|
669
|
+
const typeFact = facts.find((f) => "a" in f && f.a === "type");
|
|
670
|
+
return typeFact?.e ?? facts[0]?.e;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/mcp/room-audit.ts
|
|
674
|
+
function sanitizeInput(params) {
|
|
675
|
+
const out = {};
|
|
676
|
+
for (const [key, value] of Object.entries(params)) {
|
|
677
|
+
if (typeof value === "string" && value.length > 2e3) {
|
|
678
|
+
out[key] = value.slice(0, 2e3) + "\u2026";
|
|
679
|
+
} else {
|
|
680
|
+
out[key] = value;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return out;
|
|
684
|
+
}
|
|
685
|
+
function summarizeText(text2, max = 500) {
|
|
686
|
+
return text2.length > max ? text2.slice(0, max) + "\u2026" : text2;
|
|
687
|
+
}
|
|
688
|
+
async function recordRoomMcpAudit(kernel, agentId, toolName, params, resultText, wctx, relatedEntities = []) {
|
|
689
|
+
try {
|
|
690
|
+
const id = `decision:mcp-${crypto.randomUUID().slice(0, 8)}`;
|
|
691
|
+
const links = relatedEntities.map((targetEntityId) => ({
|
|
692
|
+
attribute: "relatedTo",
|
|
693
|
+
targetEntityId
|
|
694
|
+
}));
|
|
695
|
+
await kernel.createEntity(
|
|
696
|
+
id,
|
|
697
|
+
"Decision",
|
|
698
|
+
{
|
|
699
|
+
title: `MCP ${toolName}`,
|
|
700
|
+
toolName,
|
|
701
|
+
input: JSON.stringify(sanitizeInput(params)),
|
|
702
|
+
outputSummary: summarizeText(resultText),
|
|
703
|
+
createdBy: agentId,
|
|
704
|
+
source: "room-mcp"
|
|
705
|
+
},
|
|
706
|
+
links.length > 0 ? links : void 0,
|
|
707
|
+
wctx
|
|
708
|
+
);
|
|
709
|
+
return id;
|
|
710
|
+
} catch {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function scheduleRoomMcpAudit(kernelPromise, agentId, toolName, params, resultText, wctx, relatedEntities) {
|
|
715
|
+
void kernelPromise.then(
|
|
716
|
+
(kernel) => recordRoomMcpAudit(
|
|
717
|
+
kernel,
|
|
718
|
+
agentId,
|
|
719
|
+
toolName,
|
|
720
|
+
params,
|
|
721
|
+
resultText,
|
|
722
|
+
wctx,
|
|
723
|
+
relatedEntities
|
|
724
|
+
)
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/mcp/room.ts
|
|
729
|
+
var laneSchema = z2.string().optional().describe(
|
|
730
|
+
"Draft lane for op attribution (e.g. agent:cursor). Defaults to authenticated user or agent:room-mcp."
|
|
731
|
+
);
|
|
732
|
+
var tenantIdSchema = z2.string().optional().describe(
|
|
733
|
+
"Tenant id (e.g. embed-design-review). Overrides session default. Use for Playground multi-tenant rooms."
|
|
734
|
+
);
|
|
735
|
+
var roomSchema = z2.string().optional().describe(
|
|
736
|
+
"Playground ?room= slug \u2014 resolves to tenant embed-{slug} (same as playground.trellis.computer session rooms)."
|
|
737
|
+
);
|
|
738
|
+
function resolveToolTenant(ctx, args) {
|
|
739
|
+
return resolveMcpTenantId({
|
|
740
|
+
defaultTenantId: ctx.tenantId,
|
|
741
|
+
headerTenant: ctx.headerTenant,
|
|
742
|
+
toolTenantId: args.tenantId,
|
|
743
|
+
roomSlug: args.room
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
function text(content) {
|
|
747
|
+
return { content: [{ type: "text", text: content }] };
|
|
748
|
+
}
|
|
749
|
+
function jsonText(data) {
|
|
750
|
+
return text(JSON.stringify(data, null, 2));
|
|
751
|
+
}
|
|
752
|
+
function toolError(err) {
|
|
753
|
+
if (err instanceof PermissionError || err instanceof McpRateLimitError) {
|
|
754
|
+
return text(err.message);
|
|
755
|
+
}
|
|
756
|
+
return text(err instanceof Error ? err.message : String(err));
|
|
757
|
+
}
|
|
758
|
+
async function withMcpIo(ctx, tenantId, fn) {
|
|
759
|
+
assertMcpBudget(ctx.meter, tenantId);
|
|
760
|
+
const result = await fn();
|
|
761
|
+
ctx.meter?.recordGraphIo(resolveUsageTenantId(tenantId));
|
|
762
|
+
return result;
|
|
763
|
+
}
|
|
764
|
+
function auditWrite(kernel, wctx, toolName, params, payload, relatedEntities = []) {
|
|
765
|
+
const agentId = wctx.agentId ?? "agent:room-mcp";
|
|
766
|
+
const resultText = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
767
|
+
scheduleRoomMcpAudit(
|
|
768
|
+
Promise.resolve(kernel),
|
|
769
|
+
agentId,
|
|
770
|
+
toolName,
|
|
771
|
+
params,
|
|
772
|
+
resultText,
|
|
773
|
+
wctx,
|
|
774
|
+
relatedEntities
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
function createRoomMcpServer(ctx) {
|
|
778
|
+
const server = new McpServer({
|
|
779
|
+
name: "trellis-room",
|
|
780
|
+
version: "0.2.0"
|
|
781
|
+
});
|
|
782
|
+
server.registerTool(
|
|
783
|
+
"get_graph_summary",
|
|
784
|
+
{
|
|
785
|
+
description: "Compact graph overview \u2014 health, entity types, ontologies, top attributes, links, recent ops. Call first.",
|
|
786
|
+
inputSchema: {
|
|
787
|
+
limit: z2.number().optional().describe("Max items per section (default: 10)"),
|
|
788
|
+
tenantId: tenantIdSchema,
|
|
789
|
+
room: roomSchema
|
|
790
|
+
}
|
|
791
|
+
},
|
|
792
|
+
async ({ limit, tenantId, room }) => {
|
|
793
|
+
try {
|
|
794
|
+
const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
|
|
795
|
+
return await withMcpIo(ctx, effectiveTenant, async () => {
|
|
796
|
+
const kernel = await ctx.pool.preload(effectiveTenant);
|
|
797
|
+
return jsonText(
|
|
798
|
+
buildRoomGraphSummary(kernel, effectiveTenant, { limit })
|
|
799
|
+
);
|
|
800
|
+
});
|
|
801
|
+
} catch (err) {
|
|
802
|
+
return toolError(err);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
);
|
|
806
|
+
server.registerTool(
|
|
807
|
+
"graph_health",
|
|
808
|
+
{
|
|
809
|
+
description: "Quick liveness check for the Trellis room graph (ops + entity counts).",
|
|
810
|
+
inputSchema: {
|
|
811
|
+
tenantId: tenantIdSchema,
|
|
812
|
+
room: roomSchema
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
async ({ tenantId, room }) => {
|
|
816
|
+
try {
|
|
817
|
+
const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
|
|
818
|
+
return await withMcpIo(ctx, effectiveTenant, async () => {
|
|
819
|
+
const kernel = await ctx.pool.preload(effectiveTenant);
|
|
820
|
+
const entities = kernel.listEntities();
|
|
821
|
+
const byType = {};
|
|
822
|
+
for (const e of entities) {
|
|
823
|
+
byType[e.type] = (byType[e.type] ?? 0) + 1;
|
|
824
|
+
}
|
|
825
|
+
return jsonText({
|
|
826
|
+
status: "ok",
|
|
827
|
+
ops: kernel.readAllOps().length,
|
|
828
|
+
entities: entities.length,
|
|
829
|
+
byType,
|
|
830
|
+
tenantId: effectiveTenant
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
} catch (err) {
|
|
834
|
+
return toolError(err);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
server.registerTool(
|
|
839
|
+
"query_graph",
|
|
840
|
+
{
|
|
841
|
+
description: "Run an EQL-S query against the room graph.",
|
|
842
|
+
inputSchema: {
|
|
843
|
+
query: z2.string().describe("EQL-S query string"),
|
|
844
|
+
tenantId: tenantIdSchema,
|
|
845
|
+
room: roomSchema
|
|
846
|
+
}
|
|
847
|
+
},
|
|
848
|
+
async ({ query, tenantId, room }) => {
|
|
849
|
+
try {
|
|
850
|
+
const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
|
|
851
|
+
return await withMcpIo(ctx, effectiveTenant, async () => {
|
|
852
|
+
const parsed = parseSimple(query);
|
|
853
|
+
const kernel = await ctx.pool.preload(effectiveTenant);
|
|
854
|
+
const result = await kernel.query(parsed);
|
|
855
|
+
const bindings = hydrateBindings(
|
|
856
|
+
kernel,
|
|
857
|
+
result.bindings
|
|
858
|
+
);
|
|
859
|
+
return jsonText({
|
|
860
|
+
bindings,
|
|
861
|
+
executionTime: result.executionTime
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
} catch (err) {
|
|
865
|
+
return toolError(err);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
);
|
|
869
|
+
server.registerTool(
|
|
870
|
+
"get_node",
|
|
871
|
+
{
|
|
872
|
+
description: "Read a single entity by ID.",
|
|
873
|
+
inputSchema: {
|
|
874
|
+
id: z2.string().describe("Entity ID"),
|
|
875
|
+
tenantId: tenantIdSchema,
|
|
876
|
+
room: roomSchema
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
async ({ id, tenantId, room }) => {
|
|
880
|
+
try {
|
|
881
|
+
const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
|
|
882
|
+
return await withMcpIo(ctx, effectiveTenant, async () => {
|
|
883
|
+
const kernel = await ctx.pool.preload(effectiveTenant);
|
|
884
|
+
const entity = kernel.getEntity(id);
|
|
885
|
+
if (!entity) return text(`Not found: ${id}`);
|
|
886
|
+
ctx.permissions?.assert(ctx.auth, entity.type, "read", entity);
|
|
887
|
+
return jsonText(entityRecordToPlain(entity));
|
|
888
|
+
});
|
|
889
|
+
} catch (err) {
|
|
890
|
+
return toolError(err);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
);
|
|
894
|
+
server.registerTool(
|
|
895
|
+
"create_node",
|
|
896
|
+
{
|
|
897
|
+
description: "Create a new entity in the room graph. Optionally pass links for relations.",
|
|
898
|
+
inputSchema: {
|
|
899
|
+
type: z2.string().describe("Entity type name"),
|
|
900
|
+
id: z2.string().optional().describe("Optional explicit entity ID"),
|
|
901
|
+
attributes: z2.record(z2.string(), z2.unknown()).optional().describe("Entity attributes"),
|
|
902
|
+
links: z2.array(
|
|
903
|
+
z2.object({
|
|
904
|
+
attribute: z2.string(),
|
|
905
|
+
targetEntityId: z2.string()
|
|
906
|
+
})
|
|
907
|
+
).optional().describe("Relation links to create with the entity"),
|
|
908
|
+
lane: laneSchema,
|
|
909
|
+
tenantId: tenantIdSchema,
|
|
910
|
+
room: roomSchema
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
async ({ type, id, attributes, links, lane, tenantId, room }) => {
|
|
914
|
+
try {
|
|
915
|
+
const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
|
|
916
|
+
ctx.permissions?.assert(ctx.auth, type, "create");
|
|
917
|
+
const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
|
|
918
|
+
return await withMcpIo(ctx, effectiveTenant, async () => {
|
|
919
|
+
const kernel = await ctx.pool.preload(effectiveTenant);
|
|
920
|
+
const entityId2 = id ?? `${type.toLowerCase()}:${crypto.randomUUID()}`;
|
|
921
|
+
const attrs = { ...attributes ?? {} };
|
|
922
|
+
if (ctx.auth.userId) attrs.createdBy = ctx.auth.userId;
|
|
923
|
+
if (effectiveTenant) attrs.tenantId = effectiveTenant;
|
|
924
|
+
attrs.laneId = wctx.agentId;
|
|
925
|
+
const result = await kernel.createEntity(
|
|
926
|
+
entityId2,
|
|
927
|
+
type,
|
|
928
|
+
attrs,
|
|
929
|
+
links,
|
|
930
|
+
wctx
|
|
931
|
+
);
|
|
932
|
+
await ctx.subs.notify(effectiveTenant);
|
|
933
|
+
const payload = {
|
|
934
|
+
id: entityId2,
|
|
935
|
+
op: result.op.hash,
|
|
936
|
+
lane: wctx.agentId,
|
|
937
|
+
tenantId: effectiveTenant
|
|
938
|
+
};
|
|
939
|
+
auditWrite(
|
|
940
|
+
kernel,
|
|
941
|
+
wctx,
|
|
942
|
+
"create_node",
|
|
943
|
+
{ type, id, attributes, links, lane, tenantId, room },
|
|
944
|
+
payload,
|
|
945
|
+
[entityId2]
|
|
946
|
+
);
|
|
947
|
+
return jsonText(payload);
|
|
948
|
+
});
|
|
949
|
+
} catch (err) {
|
|
950
|
+
return toolError(err);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
);
|
|
954
|
+
server.registerTool(
|
|
955
|
+
"update_node",
|
|
956
|
+
{
|
|
957
|
+
description: "Partial update of an entity attributes.",
|
|
958
|
+
inputSchema: {
|
|
959
|
+
id: z2.string().describe("Entity ID"),
|
|
960
|
+
attributes: z2.record(z2.string(), z2.unknown()).describe("Attributes to merge"),
|
|
961
|
+
lane: laneSchema,
|
|
962
|
+
tenantId: tenantIdSchema,
|
|
963
|
+
room: roomSchema
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
async ({ id, attributes, lane, tenantId, room }) => {
|
|
967
|
+
try {
|
|
968
|
+
const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
|
|
969
|
+
const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
|
|
970
|
+
return await withMcpIo(ctx, effectiveTenant, async () => {
|
|
971
|
+
const kernel = await ctx.pool.preload(effectiveTenant);
|
|
972
|
+
const entity = kernel.getEntity(id);
|
|
973
|
+
if (!entity) return text(`Not found: ${id}`);
|
|
974
|
+
ctx.permissions?.assert(ctx.auth, entity.type, "update", entity);
|
|
975
|
+
await kernel.updateEntity(id, attributes, wctx);
|
|
976
|
+
await ctx.subs.notify(effectiveTenant);
|
|
977
|
+
const payload = {
|
|
978
|
+
id,
|
|
979
|
+
updated: true,
|
|
980
|
+
lane: wctx.agentId,
|
|
981
|
+
tenantId: effectiveTenant
|
|
982
|
+
};
|
|
983
|
+
auditWrite(
|
|
984
|
+
kernel,
|
|
985
|
+
wctx,
|
|
986
|
+
"update_node",
|
|
987
|
+
{ id, attributes, lane, tenantId, room },
|
|
988
|
+
payload,
|
|
989
|
+
[id]
|
|
990
|
+
);
|
|
991
|
+
return jsonText(payload);
|
|
992
|
+
});
|
|
993
|
+
} catch (err) {
|
|
994
|
+
return toolError(err);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
);
|
|
998
|
+
server.registerTool(
|
|
999
|
+
"delete_node",
|
|
1000
|
+
{
|
|
1001
|
+
description: "Delete an entity by ID.",
|
|
1002
|
+
inputSchema: {
|
|
1003
|
+
id: z2.string().describe("Entity ID"),
|
|
1004
|
+
lane: laneSchema,
|
|
1005
|
+
tenantId: tenantIdSchema,
|
|
1006
|
+
room: roomSchema
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
async ({ id, lane, tenantId, room }) => {
|
|
1010
|
+
try {
|
|
1011
|
+
const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
|
|
1012
|
+
const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
|
|
1013
|
+
return await withMcpIo(ctx, effectiveTenant, async () => {
|
|
1014
|
+
const kernel = await ctx.pool.preload(effectiveTenant);
|
|
1015
|
+
const entity = kernel.getEntity(id);
|
|
1016
|
+
if (!entity) return text(`Not found: ${id}`);
|
|
1017
|
+
ctx.permissions?.assert(ctx.auth, entity.type, "delete", entity);
|
|
1018
|
+
await kernel.deleteEntity(id, wctx);
|
|
1019
|
+
await ctx.subs.notify(effectiveTenant);
|
|
1020
|
+
const payload = {
|
|
1021
|
+
id,
|
|
1022
|
+
deleted: true,
|
|
1023
|
+
lane: wctx.agentId,
|
|
1024
|
+
tenantId: effectiveTenant
|
|
1025
|
+
};
|
|
1026
|
+
auditWrite(
|
|
1027
|
+
kernel,
|
|
1028
|
+
wctx,
|
|
1029
|
+
"delete_node",
|
|
1030
|
+
{ id, lane, tenantId, room },
|
|
1031
|
+
payload,
|
|
1032
|
+
[id]
|
|
1033
|
+
);
|
|
1034
|
+
return jsonText(payload);
|
|
1035
|
+
});
|
|
1036
|
+
} catch (err) {
|
|
1037
|
+
return toolError(err);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
);
|
|
1041
|
+
server.registerTool(
|
|
1042
|
+
"create_collection_record",
|
|
1043
|
+
{
|
|
1044
|
+
description: "Create a Playground CollectionRecord row (shows in Collections UI). Sets collectionId to collectionMeta:<slug> so recordBelongsToCollection matches. Optionally ensureCollection to create CollectionMeta when the collection is missing.",
|
|
1045
|
+
inputSchema: {
|
|
1046
|
+
collectionSlug: z2.string().optional().describe("Collection slug (e.g. people, ideas). Required unless collectionId is set."),
|
|
1047
|
+
collectionId: z2.string().optional().describe("Explicit collection id (e.g. collectionMeta:people). Overrides slug."),
|
|
1048
|
+
title: z2.string().min(1).describe("Record title shown in the collection"),
|
|
1049
|
+
body: z2.string().optional().describe("Optional record body / notes"),
|
|
1050
|
+
sortOrder: z2.number().int().optional().describe("Row sort order within the collection"),
|
|
1051
|
+
attributes: z2.record(z2.string(), z2.unknown()).optional().describe("Extra typed fields (status, tags, etc.) merged into the record"),
|
|
1052
|
+
ensureCollection: z2.boolean().optional().describe(
|
|
1053
|
+
"When true, create CollectionMeta with stable id collectionMeta:<slug> if missing."
|
|
1054
|
+
),
|
|
1055
|
+
collectionTitle: z2.string().optional().describe("Title for CollectionMeta when ensureCollection creates it"),
|
|
1056
|
+
lane: laneSchema,
|
|
1057
|
+
tenantId: tenantIdSchema,
|
|
1058
|
+
room: roomSchema
|
|
1059
|
+
}
|
|
1060
|
+
},
|
|
1061
|
+
async ({
|
|
1062
|
+
collectionSlug,
|
|
1063
|
+
collectionId,
|
|
1064
|
+
title,
|
|
1065
|
+
body,
|
|
1066
|
+
sortOrder,
|
|
1067
|
+
attributes,
|
|
1068
|
+
ensureCollection,
|
|
1069
|
+
collectionTitle,
|
|
1070
|
+
lane,
|
|
1071
|
+
tenantId,
|
|
1072
|
+
room
|
|
1073
|
+
}) => {
|
|
1074
|
+
try {
|
|
1075
|
+
const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
|
|
1076
|
+
ctx.permissions?.assert(ctx.auth, "CollectionRecord", "create");
|
|
1077
|
+
const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
|
|
1078
|
+
const resolvedCollectionId = resolveCollectionId({
|
|
1079
|
+
collectionSlug,
|
|
1080
|
+
collectionId
|
|
1081
|
+
});
|
|
1082
|
+
const slug = collectionSlug?.trim() || collectionSlugFromMetaId(resolvedCollectionId) || resolvedCollectionId;
|
|
1083
|
+
return await withMcpIo(ctx, effectiveTenant, async () => {
|
|
1084
|
+
const kernel = await ctx.pool.preload(effectiveTenant);
|
|
1085
|
+
let collectionCreated = false;
|
|
1086
|
+
if (ensureCollection) {
|
|
1087
|
+
const existingMeta = kernel.getEntity(resolvedCollectionId);
|
|
1088
|
+
if (!existingMeta) {
|
|
1089
|
+
ctx.permissions?.assert(ctx.auth, "CollectionMeta", "create");
|
|
1090
|
+
const metaAttrs = {
|
|
1091
|
+
title: collectionTitle?.trim() || slug,
|
|
1092
|
+
slug,
|
|
1093
|
+
sortOrder: 0
|
|
1094
|
+
};
|
|
1095
|
+
if (ctx.auth.userId) metaAttrs.createdBy = ctx.auth.userId;
|
|
1096
|
+
if (effectiveTenant) metaAttrs.tenantId = effectiveTenant;
|
|
1097
|
+
metaAttrs.laneId = wctx.agentId;
|
|
1098
|
+
await kernel.createEntity(
|
|
1099
|
+
resolvedCollectionId,
|
|
1100
|
+
"CollectionMeta",
|
|
1101
|
+
metaAttrs,
|
|
1102
|
+
void 0,
|
|
1103
|
+
wctx
|
|
1104
|
+
);
|
|
1105
|
+
collectionCreated = true;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const recordId = `collectionRecord:${crypto.randomUUID()}`;
|
|
1109
|
+
const recordAttrs = {
|
|
1110
|
+
...attributes ?? {},
|
|
1111
|
+
collectionId: resolvedCollectionId,
|
|
1112
|
+
title
|
|
1113
|
+
};
|
|
1114
|
+
if (body !== void 0) recordAttrs.body = body;
|
|
1115
|
+
if (sortOrder !== void 0) recordAttrs.sortOrder = sortOrder;
|
|
1116
|
+
if (ctx.auth.userId) recordAttrs.createdBy = ctx.auth.userId;
|
|
1117
|
+
if (effectiveTenant) recordAttrs.tenantId = effectiveTenant;
|
|
1118
|
+
recordAttrs.laneId = wctx.agentId;
|
|
1119
|
+
const result = await kernel.createEntity(
|
|
1120
|
+
recordId,
|
|
1121
|
+
"CollectionRecord",
|
|
1122
|
+
recordAttrs,
|
|
1123
|
+
void 0,
|
|
1124
|
+
wctx
|
|
1125
|
+
);
|
|
1126
|
+
await ctx.subs.notify(effectiveTenant);
|
|
1127
|
+
const payload = {
|
|
1128
|
+
id: recordId,
|
|
1129
|
+
collectionId: resolvedCollectionId,
|
|
1130
|
+
collectionCreated,
|
|
1131
|
+
op: result.op.hash,
|
|
1132
|
+
lane: wctx.agentId,
|
|
1133
|
+
tenantId: effectiveTenant
|
|
1134
|
+
};
|
|
1135
|
+
auditWrite(
|
|
1136
|
+
kernel,
|
|
1137
|
+
wctx,
|
|
1138
|
+
"create_collection_record",
|
|
1139
|
+
{
|
|
1140
|
+
collectionSlug,
|
|
1141
|
+
collectionId,
|
|
1142
|
+
title,
|
|
1143
|
+
body,
|
|
1144
|
+
sortOrder,
|
|
1145
|
+
attributes,
|
|
1146
|
+
ensureCollection,
|
|
1147
|
+
collectionTitle,
|
|
1148
|
+
lane,
|
|
1149
|
+
tenantId,
|
|
1150
|
+
room
|
|
1151
|
+
},
|
|
1152
|
+
payload,
|
|
1153
|
+
collectionCreated ? [recordId, resolvedCollectionId] : [recordId]
|
|
1154
|
+
);
|
|
1155
|
+
return jsonText(payload);
|
|
1156
|
+
});
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
return toolError(err);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
);
|
|
1162
|
+
server.registerTool(
|
|
1163
|
+
"link_nodes",
|
|
1164
|
+
{
|
|
1165
|
+
description: "Create a semantic link between two entities (assignedTo, belongsTo, references, dependsOn, \u2026).",
|
|
1166
|
+
inputSchema: {
|
|
1167
|
+
e1: z2.string().describe("Source entity ID"),
|
|
1168
|
+
relation: z2.string().describe("Relation attribute name"),
|
|
1169
|
+
e2: z2.string().describe("Target entity ID"),
|
|
1170
|
+
lane: laneSchema,
|
|
1171
|
+
tenantId: tenantIdSchema,
|
|
1172
|
+
room: roomSchema
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
async ({ e1, relation, e2, lane, tenantId, room }) => {
|
|
1176
|
+
try {
|
|
1177
|
+
const effectiveTenant = resolveToolTenant(ctx, { tenantId, room });
|
|
1178
|
+
const wctx = writeContext(lane, ctx.auth, ctx.headerLane);
|
|
1179
|
+
return await withMcpIo(ctx, effectiveTenant, async () => {
|
|
1180
|
+
const kernel = await ctx.pool.preload(effectiveTenant);
|
|
1181
|
+
const source = kernel.getEntity(e1);
|
|
1182
|
+
const target = kernel.getEntity(e2);
|
|
1183
|
+
if (!source) return text(`Not found: ${e1}`);
|
|
1184
|
+
if (!target) return text(`Not found: ${e2}`);
|
|
1185
|
+
ctx.permissions?.assert(ctx.auth, source.type, "update", source);
|
|
1186
|
+
const result = await kernel.addLink(e1, relation, e2, wctx);
|
|
1187
|
+
await ctx.subs.notify(effectiveTenant);
|
|
1188
|
+
const payload = {
|
|
1189
|
+
e1,
|
|
1190
|
+
relation,
|
|
1191
|
+
e2,
|
|
1192
|
+
op: result.op.hash,
|
|
1193
|
+
lane: wctx.agentId,
|
|
1194
|
+
tenantId: effectiveTenant
|
|
1195
|
+
};
|
|
1196
|
+
auditWrite(
|
|
1197
|
+
kernel,
|
|
1198
|
+
wctx,
|
|
1199
|
+
"link_nodes",
|
|
1200
|
+
{ e1, relation, e2, lane, tenantId, room },
|
|
1201
|
+
payload,
|
|
1202
|
+
[e1, e2]
|
|
1203
|
+
);
|
|
1204
|
+
return jsonText(payload);
|
|
1205
|
+
});
|
|
1206
|
+
} catch (err) {
|
|
1207
|
+
return toolError(err);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
);
|
|
1211
|
+
return server;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// src/server/mcp-gateway.ts
|
|
1215
|
+
var RoomMcpGateway = class {
|
|
1216
|
+
gateway = new StreamableMcpGateway();
|
|
1217
|
+
async handle(req, ctx, auth, tenantId) {
|
|
1218
|
+
const roomCtx = {
|
|
1219
|
+
pool: ctx.pool,
|
|
1220
|
+
permissions: ctx.permissions,
|
|
1221
|
+
subs: ctx.subs,
|
|
1222
|
+
meter: ctx.meter,
|
|
1223
|
+
auth,
|
|
1224
|
+
tenantId,
|
|
1225
|
+
headerLane: req.headers.get("x-trellis-lane"),
|
|
1226
|
+
headerTenant: req.headers.get("x-trellis-tenant")
|
|
1227
|
+
};
|
|
1228
|
+
return this.gateway.handle(req, () => createRoomMcpServer(roomCtx));
|
|
1229
|
+
}
|
|
1230
|
+
async close() {
|
|
1231
|
+
await this.gateway.close();
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
var roomMcpGateway = new RoomMcpGateway();
|
|
1235
|
+
|
|
685
1236
|
// src/server/server.ts
|
|
686
1237
|
var __moduleDir = import.meta.dir ?? dirname(fileURLToPath(import.meta.url));
|
|
687
1238
|
async function startServer(opts) {
|
|
@@ -698,7 +1249,8 @@ function buildServerContext(opts) {
|
|
|
698
1249
|
};
|
|
699
1250
|
const meter = new UsageMeter();
|
|
700
1251
|
const subs = new SubscriptionManager(pool, permissions ?? null, meter);
|
|
701
|
-
const
|
|
1252
|
+
const enableCors = corsEnabledForConfig(config.apiKey);
|
|
1253
|
+
const handleHttpInner = async (req) => {
|
|
702
1254
|
const url = new URL(req.url);
|
|
703
1255
|
const path = url.pathname;
|
|
704
1256
|
const auth = await resolveAuth(
|
|
@@ -721,8 +1273,29 @@ function buildServerContext(opts) {
|
|
|
721
1273
|
return json(err.toResponse(), 403);
|
|
722
1274
|
}
|
|
723
1275
|
const msg = err instanceof Error ? err.message : String(err);
|
|
724
|
-
|
|
1276
|
+
if (process.env.TRELLIS_DEBUG) {
|
|
1277
|
+
console.error(
|
|
1278
|
+
`[trellis] ${req.method} ${path} \u2192 500:`,
|
|
1279
|
+
msg
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
return json(
|
|
1283
|
+
{
|
|
1284
|
+
error: "Internal Server Error",
|
|
1285
|
+
message: msg,
|
|
1286
|
+
method: req.method,
|
|
1287
|
+
path
|
|
1288
|
+
},
|
|
1289
|
+
500
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
const handleHttp = async (req) => {
|
|
1294
|
+
if (enableCors && req.method === "OPTIONS") {
|
|
1295
|
+
return corsPreflightResponse(req);
|
|
725
1296
|
}
|
|
1297
|
+
const res = await handleHttpInner(req);
|
|
1298
|
+
return enableCors ? withCors(req, res) : res;
|
|
726
1299
|
};
|
|
727
1300
|
return { port, authConfig, subs, handleHttp };
|
|
728
1301
|
}
|
|
@@ -732,6 +1305,7 @@ async function startServerNode(opts) {
|
|
|
732
1305
|
return startNodeServer({
|
|
733
1306
|
port,
|
|
734
1307
|
fetch: handleHttp,
|
|
1308
|
+
attachPresenceRelay: opts.presenceRelay,
|
|
735
1309
|
websocket: {
|
|
736
1310
|
open(ws) {
|
|
737
1311
|
const id = crypto.randomUUID();
|
|
@@ -770,13 +1344,13 @@ async function route(req, url, path, auth, tenantId, ctx) {
|
|
|
770
1344
|
const method = req.method.toUpperCase();
|
|
771
1345
|
if (method === "GET" && path === "/__trellis/inspector.js") {
|
|
772
1346
|
const candidates = [
|
|
773
|
-
|
|
1347
|
+
join(__moduleDir, "db", "inspector.js"),
|
|
774
1348
|
// chunk lives in dist/, inspector in dist/db/
|
|
775
|
-
|
|
1349
|
+
join(__moduleDir, "inspector.js")
|
|
776
1350
|
// chunk lives in dist/db/, inspector alongside
|
|
777
1351
|
];
|
|
778
1352
|
for (const p of candidates) {
|
|
779
|
-
if (
|
|
1353
|
+
if (existsSync(p)) {
|
|
780
1354
|
return new Response(readFileSync(p, "utf-8"), {
|
|
781
1355
|
headers: { "Content-Type": "application/javascript; charset=utf-8" }
|
|
782
1356
|
});
|
|
@@ -791,11 +1365,11 @@ async function route(req, url, path, auth, tenantId, ctx) {
|
|
|
791
1365
|
}
|
|
792
1366
|
if (method === "GET" && path === "/__trellis/trellis.css") {
|
|
793
1367
|
const candidates = [
|
|
794
|
-
|
|
795
|
-
|
|
1368
|
+
join(__moduleDir, "db", "trellis.css"),
|
|
1369
|
+
join(__moduleDir, "trellis.css")
|
|
796
1370
|
];
|
|
797
1371
|
for (const p of candidates) {
|
|
798
|
-
if (
|
|
1372
|
+
if (existsSync(p)) {
|
|
799
1373
|
return new Response(readFileSync(p, "utf-8"), {
|
|
800
1374
|
headers: { "Content-Type": "text/css; charset=utf-8" }
|
|
801
1375
|
});
|
|
@@ -822,13 +1396,38 @@ async function route(req, url, path, auth, tenantId, ctx) {
|
|
|
822
1396
|
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
823
1397
|
});
|
|
824
1398
|
}
|
|
1399
|
+
if (path === "/mcp" || path === "/mcp/" || path === "/trellis/mcp" || path === "/trellis/mcp/") {
|
|
1400
|
+
return roomMcpGateway.handle(req, ctx, auth, tenantId);
|
|
1401
|
+
}
|
|
1402
|
+
if (path === "/gateway/mcp" || path === "/gateway/mcp/") {
|
|
1403
|
+
return discoveryMcpGateway.handle(req, {
|
|
1404
|
+
configDir: ".",
|
|
1405
|
+
origin: url.origin
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
if (method === "GET" && path === "/.well-known/oauth-protected-resource") {
|
|
1409
|
+
return json(oauthProtectedResourceMetadata(url.origin));
|
|
1410
|
+
}
|
|
1411
|
+
if (method === "GET" && path === "/.well-known/oauth-authorization-server") {
|
|
1412
|
+
return json(oauthAuthorizationServerMetadata(url.origin));
|
|
1413
|
+
}
|
|
1414
|
+
if (method === "GET" && path === "/.well-known/trellis-mcp") {
|
|
1415
|
+
return json(mcpServiceDocument(url.origin, ctx.authConfig));
|
|
1416
|
+
}
|
|
825
1417
|
if (method === "GET" && path === "/health") {
|
|
826
1418
|
const kernel = await ctx.pool.preload(tenantId);
|
|
827
1419
|
const ops = kernel.readAllOps().length;
|
|
828
1420
|
return json({
|
|
829
1421
|
status: "ok",
|
|
830
1422
|
ops,
|
|
831
|
-
tenants: ctx.pool.activeTenants().length
|
|
1423
|
+
tenants: ctx.pool.activeTenants().length,
|
|
1424
|
+
mcp: "/mcp",
|
|
1425
|
+
mcpSprites: "/trellis/mcp",
|
|
1426
|
+
mcpGateway: "/gateway/mcp",
|
|
1427
|
+
oauth: {
|
|
1428
|
+
protectedResource: "/.well-known/oauth-protected-resource",
|
|
1429
|
+
authorizationServer: "/.well-known/oauth-authorization-server"
|
|
1430
|
+
}
|
|
832
1431
|
});
|
|
833
1432
|
}
|
|
834
1433
|
if (method === "GET" && path === "/admin/usage") {
|
|
@@ -846,12 +1445,20 @@ async function route(req, url, path, auth, tenantId, ctx) {
|
|
|
846
1445
|
return handleUpdate(req, id, auth, tenantId, ctx);
|
|
847
1446
|
if (method === "DELETE") return handleDelete(id, auth, tenantId, ctx);
|
|
848
1447
|
}
|
|
1448
|
+
if (method === "GET" && (path === "/ops" || path === "/ops/")) {
|
|
1449
|
+
return handleListOps(url, tenantId, ctx);
|
|
1450
|
+
}
|
|
849
1451
|
if (method === "POST" && path === "/query") {
|
|
850
1452
|
return handleQuery(req, auth, tenantId, ctx);
|
|
851
1453
|
}
|
|
852
1454
|
if (method === "POST" && (path === "/ontologies" || path === "/ontologies/")) {
|
|
853
1455
|
return handleCreateOntology(req, auth, tenantId, ctx);
|
|
854
1456
|
}
|
|
1457
|
+
const ontologyMatch = path.match(/^\/ontologies\/(.+)$/);
|
|
1458
|
+
if (ontologyMatch && (method === "PATCH" || method === "PUT")) {
|
|
1459
|
+
const id = decodeURIComponent(ontologyMatch[1]);
|
|
1460
|
+
return handleUpdateOntology(req, id, auth, tenantId, ctx);
|
|
1461
|
+
}
|
|
855
1462
|
if (method === "POST" && path === "/upload") {
|
|
856
1463
|
return handleUpload(req, auth, tenantId, ctx);
|
|
857
1464
|
}
|
|
@@ -924,6 +1531,42 @@ async function handleDelete(id, auth, tenantId, ctx) {
|
|
|
924
1531
|
await ctx.subs.notify(tenantId);
|
|
925
1532
|
return json({ id, deleted: true });
|
|
926
1533
|
}
|
|
1534
|
+
async function handleListOps(url, tenantId, ctx) {
|
|
1535
|
+
const kind = url.searchParams.get("kind") ?? void 0;
|
|
1536
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") ?? "100", 10), 500);
|
|
1537
|
+
const offset = parseInt(url.searchParams.get("offset") ?? "0", 10);
|
|
1538
|
+
const kernel = await ctx.pool.preload(tenantId);
|
|
1539
|
+
let ops = kernel.readAllOps();
|
|
1540
|
+
if (kind) {
|
|
1541
|
+
ops = ops.filter((op) => op.kind === kind);
|
|
1542
|
+
}
|
|
1543
|
+
const total = ops.length;
|
|
1544
|
+
const page = ops.slice(offset, offset + limit);
|
|
1545
|
+
return json({
|
|
1546
|
+
data: page.map((op, index) => summarizeKernelOp(op, offset + index)),
|
|
1547
|
+
total,
|
|
1548
|
+
limit,
|
|
1549
|
+
offset
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
function summarizeKernelOp(op, index) {
|
|
1553
|
+
const entityIds = /* @__PURE__ */ new Set();
|
|
1554
|
+
for (const fact of op.facts ?? []) entityIds.add(fact.e);
|
|
1555
|
+
for (const fact of op.deleteFacts ?? []) entityIds.add(fact.e);
|
|
1556
|
+
return {
|
|
1557
|
+
index,
|
|
1558
|
+
hash: op.hash,
|
|
1559
|
+
kind: op.kind,
|
|
1560
|
+
timestamp: op.timestamp,
|
|
1561
|
+
agentId: op.agentId,
|
|
1562
|
+
previousHash: op.previousHash,
|
|
1563
|
+
factCount: op.facts?.length ?? 0,
|
|
1564
|
+
linkCount: op.links?.length ?? 0,
|
|
1565
|
+
deleteFactCount: op.deleteFacts?.length ?? 0,
|
|
1566
|
+
deleteLinkCount: op.deleteLinks?.length ?? 0,
|
|
1567
|
+
entityCount: entityIds.size
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
927
1570
|
async function handleList(url, auth, tenantId, ctx) {
|
|
928
1571
|
const type = url.searchParams.get("type") ?? void 0;
|
|
929
1572
|
const limit = parseInt(url.searchParams.get("limit") ?? "100");
|
|
@@ -964,8 +1607,56 @@ async function handleQuery(req, auth, tenantId, ctx) {
|
|
|
964
1607
|
executionTime: result.executionTime
|
|
965
1608
|
});
|
|
966
1609
|
}
|
|
1610
|
+
async function readJsonObject(req, route2) {
|
|
1611
|
+
const raw = await req.text();
|
|
1612
|
+
if (!raw.trim()) {
|
|
1613
|
+
if (process.env.TRELLIS_DEBUG) {
|
|
1614
|
+
console.warn(`[trellis] ${req.method} ${route2}: empty request body`);
|
|
1615
|
+
}
|
|
1616
|
+
return {
|
|
1617
|
+
ok: false,
|
|
1618
|
+
response: json(
|
|
1619
|
+
{
|
|
1620
|
+
error: "Bad Request",
|
|
1621
|
+
message: "Request body is empty \u2014 client may not have sent JSON (common on iOS HTTP dev)",
|
|
1622
|
+
method: req.method,
|
|
1623
|
+
path: route2,
|
|
1624
|
+
bodyBytes: 0
|
|
1625
|
+
},
|
|
1626
|
+
400
|
|
1627
|
+
)
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
try {
|
|
1631
|
+
const data = JSON.parse(raw);
|
|
1632
|
+
return { ok: true, data };
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1635
|
+
if (process.env.TRELLIS_DEBUG) {
|
|
1636
|
+
console.warn(
|
|
1637
|
+
`[trellis] ${req.method} ${route2}: invalid JSON (${raw.length}B):`,
|
|
1638
|
+
msg
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
return {
|
|
1642
|
+
ok: false,
|
|
1643
|
+
response: json(
|
|
1644
|
+
{
|
|
1645
|
+
error: "Bad Request",
|
|
1646
|
+
message: `Invalid JSON: ${msg}`,
|
|
1647
|
+
method: req.method,
|
|
1648
|
+
path: route2,
|
|
1649
|
+
bodyBytes: raw.length
|
|
1650
|
+
},
|
|
1651
|
+
400
|
|
1652
|
+
)
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
967
1656
|
async function handleCreateOntology(req, auth, tenantId, ctx) {
|
|
968
|
-
const
|
|
1657
|
+
const parsed = await readJsonObject(req, "/ontologies");
|
|
1658
|
+
if (!parsed.ok) return parsed.response;
|
|
1659
|
+
const schema = parsed.data;
|
|
969
1660
|
if (!schema || !schema["@id"] || !Array.isArray(schema.fields)) {
|
|
970
1661
|
return json(
|
|
971
1662
|
{ error: "A SchemaDefinition (with @id and fields) is required" },
|
|
@@ -992,6 +1683,28 @@ async function handleCreateOntology(req, auth, tenantId, ctx) {
|
|
|
992
1683
|
}
|
|
993
1684
|
return json({ id: schema["@id"], registered: true }, 201);
|
|
994
1685
|
}
|
|
1686
|
+
async function handleUpdateOntology(req, id, auth, tenantId, ctx) {
|
|
1687
|
+
const parsed = await readJsonObject(req, "/ontologies");
|
|
1688
|
+
if (!parsed.ok) return parsed.response;
|
|
1689
|
+
const updates = parsed.data;
|
|
1690
|
+
const kernel = await ctx.pool.preload(tenantId);
|
|
1691
|
+
const existing = kernel.getOntology(id);
|
|
1692
|
+
if (!existing) {
|
|
1693
|
+
return json({ error: `Ontology ${id} not found` }, 404);
|
|
1694
|
+
}
|
|
1695
|
+
if ((existing.tier ?? "user") === "core") {
|
|
1696
|
+
return json({ error: "Core ontologies are immutable" }, 403);
|
|
1697
|
+
}
|
|
1698
|
+
try {
|
|
1699
|
+
kernel.updateOntology(id, updates);
|
|
1700
|
+
recordGraphIo(ctx, tenantId);
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1703
|
+
return json({ error: "Could not update schema", message: msg }, 400);
|
|
1704
|
+
}
|
|
1705
|
+
const updated = kernel.getOntology(id);
|
|
1706
|
+
return json(updated ?? { id, updated: true });
|
|
1707
|
+
}
|
|
995
1708
|
async function handleUpload(req, auth, tenantId, ctx) {
|
|
996
1709
|
if (!auth.authenticated && ctx.config.apiKey) {
|
|
997
1710
|
return json({ error: "Unauthorized" }, 401);
|
|
@@ -1229,11 +1942,6 @@ export {
|
|
|
1229
1942
|
FULLY_PUBLIC,
|
|
1230
1943
|
OWNER_ONLY,
|
|
1231
1944
|
ADMIN_ONLY,
|
|
1232
|
-
resolveUsageTenantId,
|
|
1233
|
-
dayKey,
|
|
1234
|
-
sampleTenantStorage,
|
|
1235
|
-
verifyAdminKey,
|
|
1236
|
-
UsageMeter,
|
|
1237
1945
|
SubscriptionManager,
|
|
1238
1946
|
startServer,
|
|
1239
1947
|
startServerCrossRuntime
|