@xnetjs/plugins 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -516,6 +516,7 @@ var ContributionRegistry = class {
516
516
  importers = new TypedRegistry();
517
517
  mentionProviders = new TypedRegistry();
518
518
  agentTools = new TypedRegistry();
519
+ surfaceDock = new TypedRegistry();
519
520
  /**
520
521
  * Clear all registries (for cleanup/testing)
521
522
  */
@@ -540,6 +541,7 @@ var ContributionRegistry = class {
540
541
  this.importers.clear();
541
542
  this.mentionProviders.clear();
542
543
  this.agentTools.clear();
544
+ this.surfaceDock.clear();
543
545
  }
544
546
  };
545
547
 
@@ -4874,6 +4876,86 @@ function hasWriteScope(plan) {
4874
4876
  );
4875
4877
  }
4876
4878
 
4879
+ // src/ai-surface/args.ts
4880
+ function readRequiredString(record, key) {
4881
+ const value = record[key];
4882
+ if (typeof value !== "string" || !value.trim()) {
4883
+ throw new Error(`${key} must be a non-empty string`);
4884
+ }
4885
+ return value;
4886
+ }
4887
+ function readRequiredRecord(record, key) {
4888
+ const value = record[key];
4889
+ if (!isRecord3(value)) {
4890
+ throw new Error(`${key} must be an object`);
4891
+ }
4892
+ return value;
4893
+ }
4894
+ function readRequiredStringArray(value, key) {
4895
+ const result = readStringArray(value);
4896
+ if (result.length === 0) {
4897
+ throw new Error(`${key} must contain at least one string`);
4898
+ }
4899
+ return result;
4900
+ }
4901
+ function readStringArray(value) {
4902
+ if (!Array.isArray(value)) return [];
4903
+ return value.filter((item) => typeof item === "string" && item.trim() !== "");
4904
+ }
4905
+ function readCsvStringArray(value) {
4906
+ if (!value) return [];
4907
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
4908
+ }
4909
+ function readOptionalString(record, key) {
4910
+ const value = record[key];
4911
+ return typeof value === "string" && value.trim() ? value : void 0;
4912
+ }
4913
+ function readOptionalRecord(record, key) {
4914
+ return readRecord(record, key);
4915
+ }
4916
+ function readOptionalNumber(record, key) {
4917
+ const value = record[key];
4918
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4919
+ }
4920
+ function readOptionalBoolean(record, key) {
4921
+ const value = record[key];
4922
+ return typeof value === "boolean" ? value : void 0;
4923
+ }
4924
+ function readUrlNumber(params, key) {
4925
+ const value = params.get(key);
4926
+ if (value === null) return void 0;
4927
+ const parsed = Number(value);
4928
+ return Number.isFinite(parsed) ? parsed : void 0;
4929
+ }
4930
+ function readRecordString(record, key) {
4931
+ const value = record[key];
4932
+ return typeof value === "string" && value.trim() ? value : void 0;
4933
+ }
4934
+ function readRecord(record, key) {
4935
+ const value = record[key];
4936
+ return isRecord3(value) ? value : void 0;
4937
+ }
4938
+ function readRecordNumber(record, key) {
4939
+ const value = record[key];
4940
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4941
+ }
4942
+ function readRecordBoolean(record, key) {
4943
+ const value = record[key];
4944
+ return typeof value === "boolean" ? value : void 0;
4945
+ }
4946
+ function isRecord3(value) {
4947
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4948
+ }
4949
+ function readContextSeeds(value) {
4950
+ if (!Array.isArray(value)) return [];
4951
+ return value.map((seed) => {
4952
+ if (!isRecord3(seed)) return null;
4953
+ const kind = typeof seed.kind === "string" ? seed.kind : null;
4954
+ const id = typeof seed.id === "string" ? seed.id : null;
4955
+ return kind && id ? { kind, id } : null;
4956
+ }).filter((seed) => seed !== null);
4957
+ }
4958
+
4877
4959
  // src/ai-surface/page-markdown.ts
4878
4960
  var XNET_MARKDOWN_DIRECTIVE_SPECS = [
4879
4961
  {
@@ -5109,7 +5191,7 @@ function findJsonPayloadEnd(src, startIndex) {
5109
5191
  function parseJsonObject(rawPayload, label, errors) {
5110
5192
  try {
5111
5193
  const parsed = JSON.parse(rawPayload);
5112
- if (isRecord3(parsed)) return parsed;
5194
+ if (isRecord4(parsed)) return parsed;
5113
5195
  errors.push(`${label} payload must be a JSON object`);
5114
5196
  return null;
5115
5197
  } catch {
@@ -5143,7 +5225,7 @@ function unquoteYamlScalar(value) {
5143
5225
  return trimmed.slice(1, -1);
5144
5226
  }
5145
5227
  }
5146
- function isRecord3(value) {
5228
+ function isRecord4(value) {
5147
5229
  return typeof value === "object" && value !== null && !Array.isArray(value);
5148
5230
  }
5149
5231
  function markdownDiffPrefix(kind) {
@@ -5152,900 +5234,1094 @@ function markdownDiffPrefix(kind) {
5152
5234
  return " ";
5153
5235
  }
5154
5236
 
5155
- // src/ai-surface/service.ts
5156
- var DEFAULT_LIMITS = {
5157
- maxListLimit: 100,
5158
- maxSearchScan: 500,
5159
- maxSearchResults: 20,
5160
- maxContextResources: 12,
5161
- maxCharactersPerResource: 12e3,
5162
- maxJsonCharacters: 24e3,
5163
- maxCanvasObjects: 200,
5164
- maxDatabaseRows: 100
5165
- };
5166
- var AiSurfaceService = class {
5167
- constructor(config) {
5168
- this.config = config;
5169
- this.limits = { ...DEFAULT_LIMITS, ...config.limits };
5170
- this.clock = config.clock ?? (() => /* @__PURE__ */ new Date());
5171
- for (const tool of config.extraTools ?? []) {
5172
- if (!this.extraTools.has(tool.name)) this.extraTools.set(tool.name, tool);
5237
+ // src/ai-surface/resources/router.ts
5238
+ var XNET_URI_PREFIX = "xnet://";
5239
+ var PARAM_SEGMENT_PATTERN = /^\{([a-zA-Z][a-zA-Z0-9]*)\}(.*)$/;
5240
+ function createAiResourceRouter() {
5241
+ const routes = [];
5242
+ const router = {
5243
+ register(template, handler) {
5244
+ routes.push(compileRoute(template, handler));
5245
+ return router;
5246
+ },
5247
+ async resolve(host, uri) {
5248
+ const parsed = parseXNetUri(uri);
5249
+ for (const route of routes) {
5250
+ const params = matchRoute(route, parsed);
5251
+ if (params) {
5252
+ return await route.handler(host, { uri, params, searchParams: parsed.searchParams });
5253
+ }
5254
+ }
5255
+ throw new Error(`Resource not found: ${uri}`);
5173
5256
  }
5257
+ };
5258
+ return router;
5259
+ }
5260
+ function parseXNetUri(uri) {
5261
+ let parsed;
5262
+ try {
5263
+ parsed = new URL(uri);
5264
+ } catch {
5265
+ throw new Error(`Invalid xNet resource URI: ${uri}`);
5174
5266
  }
5175
- limits;
5176
- clock;
5177
- sequence = 0;
5178
- auditEvents = [];
5179
- rollbackSnapshots = /* @__PURE__ */ new Map();
5180
- /** Contributed tools by name (exploration 0196), de-duped at construction. */
5181
- extraTools = /* @__PURE__ */ new Map();
5182
- /** The contributed (non-built-in) tools, with built-in name collisions removed. */
5183
- getExtraTools() {
5184
- const builtIn = new Set(this.builtInTools().map((t) => t.name));
5185
- return [...this.extraTools.values()].filter((t) => !builtIn.has(t.name));
5267
+ if (parsed.protocol !== "xnet:") {
5268
+ throw new Error(`Invalid xNet resource URI: ${uri}`);
5186
5269
  }
5187
- getResources() {
5188
- return [
5189
- createResource(
5190
- "xnet://workspace/summary",
5191
- "Workspace Summary",
5192
- "High-level schema counts, recent nodes, and AI surface capabilities.",
5193
- "application/json",
5194
- "low",
5195
- ["workspace.read"]
5196
- ),
5197
- createResource(
5198
- "xnet://workspace/recent",
5199
- "Recent Workspace Nodes",
5200
- "Recent nodes with ids, schemas, titles, and revisions.",
5201
- "application/json",
5202
- "low",
5203
- ["workspace.read"]
5204
- ),
5205
- createResource(
5206
- "xnet://nodes",
5207
- "All Nodes",
5208
- "Limited list of nodes in the local store.",
5209
- "application/json",
5210
- "low",
5211
- ["workspace.read"]
5212
- ),
5213
- createResource(
5214
- "xnet://schemas",
5215
- "All Schemas",
5216
- "List of available schemas and property metadata.",
5217
- "application/json",
5218
- "low",
5219
- ["workspace.read"]
5220
- ),
5221
- createResource(
5222
- "xnet://page/{pageId}.md",
5223
- "Page Markdown",
5224
- "Markdown projection for a page, including xNet frontmatter identity.",
5225
- "text/markdown",
5226
- "low",
5227
- ["page.read"],
5228
- true
5229
- ),
5230
- createResource(
5231
- "xnet://page/{pageId}/outline",
5232
- "Page Outline",
5233
- "Page heading outline extracted from the Markdown projection.",
5234
- "application/json",
5235
- "low",
5236
- ["page.read"],
5237
- true
5238
- ),
5239
- createResource(
5240
- "xnet://database/{databaseId}/schema",
5241
- "Database Schema",
5242
- "Database node metadata, declared properties, and known column descriptors.",
5243
- "application/json",
5244
- "low",
5245
- ["database.read"],
5246
- true
5247
- ),
5248
- createResource(
5249
- "xnet://database/{databaseId}/views",
5250
- "Database Views",
5251
- "Known database view descriptors from the database node projection.",
5252
- "application/json",
5253
- "low",
5254
- ["database.read"],
5255
- true
5256
- ),
5257
- createResource(
5258
- "xnet://database/{databaseId}/sample?limit=10",
5259
- "Database Sample Rows",
5260
- "Bounded sample of rows for a database using NodeQueryDescriptor semantics.",
5261
- "application/json",
5262
- "low",
5263
- ["database.read", "database.query"],
5264
- true
5265
- ),
5266
- createResource(
5267
- "xnet://canvas/{canvasId}/viewport?x=0&y=0&w=1000&h=800",
5268
- "Canvas Viewport",
5269
- "Viewport-scoped canvas objects and edges.",
5270
- "application/json",
5271
- "low",
5272
- ["canvas.read"],
5273
- true
5274
- ),
5275
- createResource(
5276
- "xnet://canvas/{canvasId}/objects",
5277
- "Canvas Objects",
5278
- "Bounded list of canvas objects and connectors.",
5279
- "application/json",
5280
- "low",
5281
- ["canvas.read"],
5282
- true
5283
- ),
5284
- createResource(
5285
- "xnet://canvas/{canvasId}/selection?ids=object-1,object-2",
5286
- "Canvas Selection",
5287
- "Selection-scoped canvas objects, edges, and source previews.",
5288
- "application/json",
5289
- "low",
5290
- ["canvas.read"],
5291
- true
5292
- ),
5293
- createResource(
5294
- "xnet://canvas/{canvasId}/json-canvas",
5295
- "Canvas JSON Canvas",
5296
- "JSON Canvas projection with xNet source metadata sidecars.",
5297
- "application/json",
5298
- "low",
5299
- ["canvas.read"],
5300
- true
5301
- )
5302
- ];
5270
+ return {
5271
+ host: parsed.hostname,
5272
+ parts: parsed.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part)),
5273
+ searchParams: parsed.searchParams
5274
+ };
5275
+ }
5276
+ function compileRoute(template, handler) {
5277
+ if (!template.startsWith(XNET_URI_PREFIX)) {
5278
+ throw new Error(`Resource route template must start with ${XNET_URI_PREFIX}: ${template}`);
5303
5279
  }
5304
- /** The full tool surface: built-in `xnet_*` tools plus contributed extras. */
5305
- getTools() {
5306
- const extras = this.getExtraTools().map(
5307
- ({ invoke: _invoke, ...def }) => def
5308
- );
5309
- return [...this.builtInTools(), ...extras];
5280
+ const [path] = template.slice(XNET_URI_PREFIX.length).split("?");
5281
+ const [host, ...rawSegments] = path.split("/").filter(Boolean);
5282
+ if (!host) {
5283
+ throw new Error(`Resource route template must include a host: ${template}`);
5310
5284
  }
5311
- builtInTools() {
5312
- return [
5313
- {
5314
- name: "xnet_search",
5315
- title: "Search xNet workspace",
5316
- description: "Search node titles and searchable properties with pagination and limits.",
5317
- risk: "low",
5318
- requiredScopes: ["workspace.search"],
5319
- inputSchema: {
5320
- type: "object",
5321
- properties: {
5322
- query: { type: "string", description: "Search text." },
5323
- schemaId: { type: "string", description: "Optional schema IRI filter." },
5324
- limit: { type: "number", description: "Maximum result count." },
5325
- offset: { type: "number", description: "Result offset for pagination." }
5326
- },
5327
- required: ["query"]
5328
- }
5329
- },
5330
- {
5331
- name: "xnet_graph_expand",
5332
- title: "Expand a node along its relations",
5333
- description: "Walk typed relation edges out from a node to its connected neighbors (bounded by hops and a result limit). Use for just-in-time expansion: fetch a specific node\u2019s connections only when you need them, instead of pulling the whole graph into context.",
5334
- risk: "low",
5335
- requiredScopes: ["workspace.read"],
5336
- inputSchema: {
5337
- type: "object",
5338
- properties: {
5339
- nodeId: { type: "string", description: "The node to expand from." },
5340
- hops: {
5341
- type: "number",
5342
- description: "How many relation hops to walk (1\u20132, default 1)."
5343
- },
5344
- limit: { type: "number", description: "Maximum neighbors to return." }
5345
- },
5346
- required: ["nodeId"]
5347
- }
5285
+ return {
5286
+ host,
5287
+ segments: rawSegments.map((segment) => {
5288
+ const param = PARAM_SEGMENT_PATTERN.exec(segment);
5289
+ return param ? { kind: "param", name: param[1], suffix: param[2] } : { kind: "literal", value: segment };
5290
+ }),
5291
+ handler
5292
+ };
5293
+ }
5294
+ function matchRoute(route, parsed) {
5295
+ if (parsed.host !== route.host) return null;
5296
+ if (parsed.parts.length !== route.segments.length) return null;
5297
+ const params = {};
5298
+ for (const [index, segment] of route.segments.entries()) {
5299
+ const part = parsed.parts[index];
5300
+ if (segment.kind === "literal") {
5301
+ if (part !== segment.value) return null;
5302
+ continue;
5303
+ }
5304
+ if (segment.suffix && !part.endsWith(segment.suffix)) return null;
5305
+ params[segment.name] = segment.suffix ? part.slice(0, -segment.suffix.length) : part;
5306
+ }
5307
+ return params;
5308
+ }
5309
+
5310
+ // src/ai-surface/resources/routes.ts
5311
+ function createBuiltInResourceRouter() {
5312
+ return createAiResourceRouter().register(
5313
+ "xnet://nodes",
5314
+ async (host, { uri }) => host.jsonResource(uri, await host.listNodes())
5315
+ ).register(
5316
+ "xnet://schemas",
5317
+ async (host, { uri }) => host.jsonResource(uri, await host.listSchemas())
5318
+ ).register(
5319
+ "xnet://workspace/summary",
5320
+ async (host, { uri }) => host.jsonResource(uri, await host.getWorkspaceSummary())
5321
+ ).register(
5322
+ "xnet://workspace/recent",
5323
+ async (host, { uri }) => host.jsonResource(uri, await host.getRecentNodes())
5324
+ ).register(
5325
+ "xnet://workspace/search",
5326
+ async (host, { uri, searchParams }) => host.jsonResource(
5327
+ uri,
5328
+ await host.search({
5329
+ query: searchParams.get("q") ?? "",
5330
+ schemaId: searchParams.get("schema") ?? void 0,
5331
+ limit: readUrlNumber(searchParams, "limit"),
5332
+ offset: readUrlNumber(searchParams, "offset")
5333
+ })
5334
+ )
5335
+ ).register(
5336
+ "xnet://node/{nodeId}",
5337
+ async (host, { uri, params }) => host.jsonResource(uri, await host.getNodeProjection(params.nodeId))
5338
+ ).register(
5339
+ "xnet://page/{pageId}.md",
5340
+ async (host, { uri, params }) => await host.readPageMarkdown(params.pageId, true, uri)
5341
+ ).register(
5342
+ "xnet://page/{pageId}",
5343
+ async (host, { uri, params }) => await host.readPageMarkdown(params.pageId, true, uri)
5344
+ ).register(
5345
+ "xnet://page/{pageId}/outline",
5346
+ async (host, { uri, params }) => host.jsonResource(uri, await host.readPageOutline(params.pageId))
5347
+ ).register(
5348
+ "xnet://page/{pageId}/context-pack",
5349
+ async (host, { uri, params }) => host.jsonResource(
5350
+ uri,
5351
+ await host.createContextPack({ seeds: [{ kind: "page", id: params.pageId }] })
5352
+ )
5353
+ ).register(
5354
+ "xnet://database/{databaseId}/schema",
5355
+ async (host, { uri, params }) => host.jsonResource(uri, await host.describeDatabase(params.databaseId))
5356
+ ).register(
5357
+ "xnet://database/{databaseId}/views",
5358
+ async (host, { uri, params }) => host.jsonResource(uri, await host.readDatabaseViews(params.databaseId))
5359
+ ).register(
5360
+ "xnet://database/{databaseId}/sample",
5361
+ async (host, { uri, params, searchParams }) => host.jsonResource(
5362
+ uri,
5363
+ await host.sampleDatabase({
5364
+ databaseId: params.databaseId,
5365
+ sampleSize: readUrlNumber(searchParams, "limit")
5366
+ })
5367
+ )
5368
+ ).register(
5369
+ "xnet://database/{databaseId}/query",
5370
+ async (host, { uri, params, searchParams }) => host.jsonResource(
5371
+ uri,
5372
+ await host.queryDatabase({
5373
+ databaseId: params.databaseId,
5374
+ schemaId: searchParams.get("schema") ?? void 0,
5375
+ search: searchParams.get("q") ?? void 0,
5376
+ materializedView: searchParams.get("view") ? { viewId: searchParams.get("view") ?? "" } : void 0,
5377
+ limit: readUrlNumber(searchParams, "limit"),
5378
+ offset: readUrlNumber(searchParams, "offset")
5379
+ })
5380
+ )
5381
+ ).register(
5382
+ "xnet://canvas/{canvasId}/viewport",
5383
+ async (host, { uri, params, searchParams }) => host.jsonResource(
5384
+ uri,
5385
+ await host.readCanvasViewport({
5386
+ canvasId: params.canvasId,
5387
+ x: readUrlNumber(searchParams, "x"),
5388
+ y: readUrlNumber(searchParams, "y"),
5389
+ w: readUrlNumber(searchParams, "w"),
5390
+ h: readUrlNumber(searchParams, "h"),
5391
+ tileSize: readUrlNumber(searchParams, "tileSize"),
5392
+ tileIds: readCsvStringArray(searchParams.get("tileIds")),
5393
+ includeSourcePreviews: searchParams.get("includeSourcePreviews") === "true"
5394
+ })
5395
+ )
5396
+ ).register(
5397
+ "xnet://canvas/{canvasId}/objects",
5398
+ async (host, { uri, params }) => host.jsonResource(uri, await host.readCanvasObjects(params.canvasId))
5399
+ ).register(
5400
+ "xnet://canvas/{canvasId}/selection",
5401
+ async (host, { uri, params, searchParams }) => host.jsonResource(
5402
+ uri,
5403
+ await host.readCanvasSelection({
5404
+ canvasId: params.canvasId,
5405
+ objectIds: readCsvStringArray(searchParams.get("ids")),
5406
+ includeSourcePreviews: searchParams.get("includeSourcePreviews") !== "false"
5407
+ })
5408
+ )
5409
+ ).register(
5410
+ "xnet://canvas/{canvasId}/json-canvas",
5411
+ async (host, { uri, params, searchParams }) => host.jsonResource(
5412
+ uri,
5413
+ await host.exportCanvasJsonCanvas({
5414
+ canvasId: params.canvasId,
5415
+ includeXNetMetadata: searchParams.get("includeXNetMetadata") !== "false"
5416
+ })
5417
+ )
5418
+ ).register(
5419
+ "xnet://canvas/{canvasId}/object/{objectId}",
5420
+ async (host, { uri, params }) => host.jsonResource(uri, await host.readCanvasObject(params.canvasId, params.objectId))
5421
+ );
5422
+ }
5423
+
5424
+ // src/ai-surface/tools/audit.ts
5425
+ var getAuditLogTool = {
5426
+ definition: {
5427
+ name: "xnet_get_audit_log",
5428
+ title: "Read AI audit log",
5429
+ description: "Read recent AI mutation audit events with optional plan filtering.",
5430
+ risk: "low",
5431
+ requiredScopes: ["workspace.read"],
5432
+ inputSchema: {
5433
+ type: "object",
5434
+ properties: {
5435
+ planId: { type: "string", description: "Optional mutation plan id filter." },
5436
+ limit: { type: "number", description: "Maximum audit events to return." }
5437
+ }
5438
+ }
5439
+ },
5440
+ execute: (host, args) => host.getAuditLog({
5441
+ planId: readOptionalString(args, "planId"),
5442
+ limit: readOptionalNumber(args, "limit")
5443
+ })
5444
+ };
5445
+ var validateMutationPlanTool = {
5446
+ definition: {
5447
+ name: "xnet_validate_mutation_plan",
5448
+ title: "Validate mutation plan",
5449
+ description: "Validate a serialized mutation plan and return errors or warnings.",
5450
+ risk: "medium",
5451
+ requiredScopes: ["workspace.read"],
5452
+ inputSchema: {
5453
+ type: "object",
5454
+ properties: {
5455
+ plan: { type: "object", description: "Mutation plan object to validate." }
5348
5456
  },
5349
- {
5350
- name: "xnet_create_context_pack",
5351
- title: "Create context pack",
5352
- description: "Create a bounded context pack from seeds and optional search results.",
5353
- risk: "low",
5354
- requiredScopes: ["workspace.read", "workspace.search"],
5355
- inputSchema: {
5356
- type: "object",
5357
- properties: {
5358
- query: { type: "string", description: "Optional search query." },
5359
- seeds: {
5360
- type: "array",
5361
- description: "Seed resources such as pages, databases, canvases, or nodes.",
5362
- items: {
5363
- type: "object",
5364
- properties: {
5365
- kind: { type: "string", description: "Seed kind." },
5366
- id: { type: "string", description: "Seed id." }
5367
- }
5368
- }
5369
- },
5370
- limit: { type: "number", description: "Maximum resources to include." }
5371
- }
5457
+ required: ["plan"]
5458
+ }
5459
+ },
5460
+ execute: (_host, args) => {
5461
+ const validation = validateAiMutationPlan(args.plan);
5462
+ return { validation };
5463
+ }
5464
+ };
5465
+
5466
+ // src/ai-surface/tools/canvas.ts
5467
+ var canvasListTool = {
5468
+ definition: {
5469
+ name: "xnet_canvas_list",
5470
+ title: "List canvases",
5471
+ description: "List canvas nodes visible to the AI surface.",
5472
+ risk: "low",
5473
+ requiredScopes: ["canvas.read"],
5474
+ inputSchema: {
5475
+ type: "object",
5476
+ properties: {
5477
+ limit: { type: "number", description: "Maximum canvas count." },
5478
+ offset: { type: "number", description: "Canvas offset." }
5479
+ }
5480
+ }
5481
+ },
5482
+ execute: async (host, args) => await host.listCanvases({
5483
+ limit: readOptionalNumber(args, "limit"),
5484
+ offset: readOptionalNumber(args, "offset")
5485
+ })
5486
+ };
5487
+ var canvasReadViewportTool = {
5488
+ definition: {
5489
+ name: "xnet_canvas_read_viewport",
5490
+ title: "Read canvas viewport",
5491
+ description: "Read canvas objects and edges intersecting a viewport.",
5492
+ risk: "low",
5493
+ requiredScopes: ["canvas.read"],
5494
+ inputSchema: {
5495
+ type: "object",
5496
+ properties: {
5497
+ canvasId: { type: "string", description: "Canvas node id." },
5498
+ x: { type: "number", description: "Viewport x." },
5499
+ y: { type: "number", description: "Viewport y." },
5500
+ w: { type: "number", description: "Viewport width." },
5501
+ h: { type: "number", description: "Viewport height." },
5502
+ includeSourcePreviews: {
5503
+ type: "boolean",
5504
+ description: "Include previews for source-backed objects."
5505
+ },
5506
+ tileSize: { type: "number", description: "Optional tile size for tile scoping." },
5507
+ tileIds: {
5508
+ type: "array",
5509
+ description: "Optional tile ids such as 0/1/-2 to constrain the read.",
5510
+ items: { type: "string" }
5372
5511
  }
5373
5512
  },
5374
- {
5375
- name: "xnet_create_external_context_resource",
5376
- title: "Create untrusted external context resource",
5377
- description: "Wrap externally fetched content as an untrusted context-pack resource with an explicit instruction boundary.",
5378
- risk: "medium",
5379
- requiredScopes: ["network.fetch"],
5380
- inputSchema: {
5381
- type: "object",
5382
- properties: {
5383
- url: { type: "string", description: "External source URL." },
5384
- text: { type: "string", description: "Fetched external text content." },
5385
- mimeType: { type: "string", description: "Source MIME type. Defaults to text/plain." }
5386
- },
5387
- required: ["url", "text"]
5513
+ required: ["canvasId"]
5514
+ }
5515
+ },
5516
+ execute: async (host, args) => await host.readCanvasViewport({
5517
+ canvasId: readRequiredString(args, "canvasId"),
5518
+ x: readOptionalNumber(args, "x"),
5519
+ y: readOptionalNumber(args, "y"),
5520
+ w: readOptionalNumber(args, "w"),
5521
+ h: readOptionalNumber(args, "h"),
5522
+ tileSize: readOptionalNumber(args, "tileSize"),
5523
+ tileIds: readStringArray(args.tileIds),
5524
+ includeSourcePreviews: readOptionalBoolean(args, "includeSourcePreviews") ?? false
5525
+ })
5526
+ };
5527
+ var canvasReadSelectionTool = {
5528
+ definition: {
5529
+ name: "xnet_canvas_read_selection",
5530
+ title: "Read canvas selection",
5531
+ description: "Read selected canvas objects, connected edges, and optional source previews.",
5532
+ risk: "low",
5533
+ requiredScopes: ["canvas.read"],
5534
+ inputSchema: {
5535
+ type: "object",
5536
+ properties: {
5537
+ canvasId: { type: "string", description: "Canvas node id." },
5538
+ objectIds: {
5539
+ type: "array",
5540
+ description: "Selected object ids.",
5541
+ items: { type: "string" }
5542
+ },
5543
+ includeSourcePreviews: {
5544
+ type: "boolean",
5545
+ description: "Include previews for source-backed objects."
5388
5546
  }
5389
5547
  },
5390
- {
5391
- name: "xnet_read_page_markdown",
5392
- title: "Read page Markdown",
5393
- description: "Read a page as Markdown with optional xNet frontmatter.",
5394
- risk: "low",
5395
- requiredScopes: ["page.read"],
5396
- inputSchema: {
5397
- type: "object",
5398
- properties: {
5399
- pageId: { type: "string", description: "Page node id." },
5400
- includeFrontmatter: {
5401
- type: "boolean",
5402
- description: "Include xNet identity frontmatter. Defaults to true."
5403
- }
5404
- },
5405
- required: ["pageId"]
5406
- }
5548
+ required: ["canvasId", "objectIds"]
5549
+ }
5550
+ },
5551
+ execute: async (host, args) => await host.readCanvasSelection({
5552
+ canvasId: readRequiredString(args, "canvasId"),
5553
+ objectIds: readRequiredStringArray(args.objectIds, "objectIds"),
5554
+ includeSourcePreviews: readOptionalBoolean(args, "includeSourcePreviews") ?? false
5555
+ })
5556
+ };
5557
+ var canvasSearchTool = {
5558
+ definition: {
5559
+ name: "xnet_canvas_search",
5560
+ title: "Search canvas",
5561
+ description: "Search canvas object text, labels, ids, and source metadata.",
5562
+ risk: "low",
5563
+ requiredScopes: ["canvas.read"],
5564
+ inputSchema: {
5565
+ type: "object",
5566
+ properties: {
5567
+ canvasId: { type: "string", description: "Canvas node id." },
5568
+ query: { type: "string", description: "Search text." },
5569
+ limit: { type: "number", description: "Maximum result count." }
5407
5570
  },
5408
- {
5409
- name: "xnet_validate_page_markdown",
5410
- title: "Validate page Markdown",
5411
- description: "Validate xNet page frontmatter and supported xNet Markdown directives.",
5412
- risk: "low",
5413
- requiredScopes: ["page.read"],
5414
- inputSchema: {
5415
- type: "object",
5416
- properties: {
5417
- pageId: { type: "string", description: "Optional target page node id." },
5418
- baseRevision: { type: "string", description: "Optional expected base revision." },
5419
- markdown: { type: "string", description: "Markdown to validate." }
5420
- },
5421
- required: ["markdown"]
5422
- }
5571
+ required: ["canvasId", "query"]
5572
+ }
5573
+ },
5574
+ execute: async (host, args) => await host.searchCanvas({
5575
+ canvasId: readRequiredString(args, "canvasId"),
5576
+ query: readRequiredString(args, "query"),
5577
+ limit: readOptionalNumber(args, "limit")
5578
+ })
5579
+ };
5580
+ var canvasExportJsonCanvasTool = {
5581
+ definition: {
5582
+ name: "xnet_canvas_export_json_canvas",
5583
+ title: "Export canvas as JSON Canvas",
5584
+ description: "Export a canvas or viewport as JSON Canvas with xNet source metadata.",
5585
+ risk: "low",
5586
+ requiredScopes: ["canvas.read"],
5587
+ inputSchema: {
5588
+ type: "object",
5589
+ properties: {
5590
+ canvasId: { type: "string", description: "Canvas node id." },
5591
+ includeXNetMetadata: {
5592
+ type: "boolean",
5593
+ description: "Include xNet source metadata. Defaults to true."
5594
+ },
5595
+ x: { type: "number", description: "Optional viewport x." },
5596
+ y: { type: "number", description: "Optional viewport y." },
5597
+ w: { type: "number", description: "Optional viewport width." },
5598
+ h: { type: "number", description: "Optional viewport height." }
5423
5599
  },
5424
- {
5425
- name: "xnet_plan_page_patch",
5426
- title: "Plan page Markdown patch",
5427
- description: "Validate an edited Markdown page and return a mutation plan without applying it.",
5428
- risk: "medium",
5429
- requiredScopes: ["page.read", "page.propose"],
5430
- inputSchema: {
5431
- type: "object",
5432
- properties: {
5433
- pageId: { type: "string", description: "Page node id." },
5434
- baseRevision: { type: "string", description: "Revision the patch was based on." },
5435
- markdown: { type: "string", description: "Proposed full Markdown replacement." },
5436
- intent: { type: "string", description: "User or agent intent for the patch." },
5437
- actor: { type: "string", description: "Agent or user creating the plan." }
5438
- },
5439
- required: ["pageId", "markdown"]
5440
- }
5600
+ required: ["canvasId"]
5601
+ }
5602
+ },
5603
+ execute: async (host, args) => await host.exportCanvasJsonCanvas({
5604
+ canvasId: readRequiredString(args, "canvasId"),
5605
+ includeXNetMetadata: readOptionalBoolean(args, "includeXNetMetadata") ?? true,
5606
+ x: readOptionalNumber(args, "x"),
5607
+ y: readOptionalNumber(args, "y"),
5608
+ w: readOptionalNumber(args, "w"),
5609
+ h: readOptionalNumber(args, "h")
5610
+ })
5611
+ };
5612
+ var canvasPlanJsonCanvasImportTool = {
5613
+ definition: {
5614
+ name: "xnet_canvas_plan_json_canvas_import",
5615
+ title: "Plan JSON Canvas import",
5616
+ description: "Convert a JSON Canvas document into a plan-only canvas mutation.",
5617
+ risk: "medium",
5618
+ requiredScopes: ["canvas.read", "canvas.propose"],
5619
+ inputSchema: {
5620
+ type: "object",
5621
+ properties: {
5622
+ canvasId: { type: "string", description: "Canvas node id." },
5623
+ document: { type: "object", description: "JSON Canvas document." },
5624
+ baseRevision: { type: "string", description: "Revision the import was based on." },
5625
+ actor: { type: "string", description: "Agent or user creating the plan." },
5626
+ intent: { type: "string", description: "User or agent intent for the import." }
5441
5627
  },
5442
- {
5443
- name: "xnet_apply_page_markdown",
5444
- title: "Apply page Markdown plan",
5445
- description: "Apply a validated page Markdown mutation plan through the configured TipTap/Yjs document adapter, with a node-property fallback.",
5446
- risk: "high",
5447
- requiredScopes: ["page.read", "page.write"],
5448
- inputSchema: {
5449
- type: "object",
5450
- properties: {
5451
- plan: { type: "object", description: "Validated page Markdown mutation plan." },
5452
- confirmApply: {
5453
- type: "boolean",
5454
- description: "Must be true to apply the page Markdown plan."
5455
- },
5456
- allowStale: {
5457
- type: "boolean",
5458
- description: "Allow applying when the plan base revision differs from the live node."
5459
- }
5460
- },
5461
- required: ["plan", "confirmApply"]
5462
- }
5628
+ required: ["canvasId", "document"]
5629
+ }
5630
+ },
5631
+ execute: async (host, args) => await host.planCanvasJsonCanvasImport(args)
5632
+ };
5633
+ var planCanvasMutationTool = {
5634
+ definition: {
5635
+ name: "xnet_plan_canvas_mutation",
5636
+ title: "Plan canvas mutation",
5637
+ description: "Create a canvas mutation plan for later review without applying it.",
5638
+ risk: "medium",
5639
+ requiredScopes: ["canvas.read", "canvas.propose"],
5640
+ inputSchema: {
5641
+ type: "object",
5642
+ properties: {
5643
+ canvasId: { type: "string", description: "Canvas node id." },
5644
+ baseRevision: { type: "string", description: "Revision the mutation was based on." },
5645
+ operations: { type: "array", description: "Canvas operations to validate." },
5646
+ intent: { type: "string", description: "User or agent intent for the mutation." },
5647
+ actor: { type: "string", description: "Agent or user creating the plan." }
5463
5648
  },
5464
- {
5465
- name: "xnet_get_audit_log",
5466
- title: "Read AI audit log",
5467
- description: "Read recent AI mutation audit events with optional plan filtering.",
5468
- risk: "low",
5469
- requiredScopes: ["workspace.read"],
5470
- inputSchema: {
5471
- type: "object",
5472
- properties: {
5473
- planId: { type: "string", description: "Optional mutation plan id filter." },
5474
- limit: { type: "number", description: "Maximum audit events to return." }
5475
- }
5649
+ required: ["canvasId", "operations"]
5650
+ }
5651
+ },
5652
+ execute: async (host, args) => await host.planCanvasMutation(args)
5653
+ };
5654
+ var canvasToolEntries = [
5655
+ canvasListTool,
5656
+ canvasReadViewportTool,
5657
+ canvasReadSelectionTool,
5658
+ canvasSearchTool,
5659
+ canvasExportJsonCanvasTool,
5660
+ canvasPlanJsonCanvasImportTool,
5661
+ planCanvasMutationTool
5662
+ ];
5663
+
5664
+ // src/ai-surface/tools/database.ts
5665
+ var databaseDescribeTool = {
5666
+ definition: {
5667
+ name: "xnet_database_describe",
5668
+ title: "Describe database",
5669
+ description: "Describe database schema, columns, views, row schema, and row counts.",
5670
+ risk: "low",
5671
+ requiredScopes: ["database.read"],
5672
+ inputSchema: {
5673
+ type: "object",
5674
+ properties: {
5675
+ databaseId: { type: "string", description: "Database node id." },
5676
+ includeSample: {
5677
+ type: "boolean",
5678
+ description: "Include a small descriptor-backed row sample."
5476
5679
  }
5477
5680
  },
5478
- {
5479
- name: "xnet_rollback_page_markdown",
5480
- title: "Rollback page Markdown apply",
5481
- description: "Rollback a previously applied page Markdown plan by rollback handle.",
5482
- risk: "high",
5483
- requiredScopes: ["page.write"],
5484
- inputSchema: {
5681
+ required: ["databaseId"]
5682
+ }
5683
+ },
5684
+ execute: async (host, args) => await host.describeDatabase(readRequiredString(args, "databaseId"), {
5685
+ includeSample: readOptionalBoolean(args, "includeSample") ?? false
5686
+ })
5687
+ };
5688
+ var databaseQueryTool = {
5689
+ definition: {
5690
+ name: "xnet_database_query",
5691
+ title: "Query database rows",
5692
+ description: "Read a bounded page of database rows using NodeQueryDescriptor-compatible options.",
5693
+ risk: "low",
5694
+ requiredScopes: ["database.read", "database.query"],
5695
+ inputSchema: {
5696
+ type: "object",
5697
+ properties: {
5698
+ databaseId: { type: "string", description: "Database node id." },
5699
+ schemaId: { type: "string", description: "Optional row schema IRI." },
5700
+ descriptor: {
5485
5701
  type: "object",
5486
- properties: {
5487
- rollbackHandle: { type: "string", description: "Rollback handle from apply result." },
5488
- confirmRollback: {
5489
- type: "boolean",
5490
- description: "Must be true to perform the rollback."
5491
- }
5492
- },
5493
- required: ["rollbackHandle", "confirmRollback"]
5494
- }
5495
- },
5496
- {
5497
- name: "xnet_database_describe",
5498
- title: "Describe database",
5499
- description: "Describe database schema, columns, views, row schema, and row counts.",
5500
- risk: "low",
5501
- requiredScopes: ["database.read"],
5502
- inputSchema: {
5702
+ description: "Optional NodeQueryDescriptor-compatible query shape."
5703
+ },
5704
+ where: {
5503
5705
  type: "object",
5504
- properties: {
5505
- databaseId: { type: "string", description: "Database node id." },
5506
- includeSample: {
5507
- type: "boolean",
5508
- description: "Include a small descriptor-backed row sample."
5509
- }
5510
- },
5511
- required: ["databaseId"]
5512
- }
5513
- },
5514
- {
5515
- name: "xnet_database_query",
5516
- title: "Query database rows",
5517
- description: "Read a bounded page of database rows using NodeQueryDescriptor-compatible options.",
5518
- risk: "low",
5519
- requiredScopes: ["database.read", "database.query"],
5520
- inputSchema: {
5706
+ description: "Optional exact property filters for row nodes."
5707
+ },
5708
+ search: {
5521
5709
  type: "object",
5522
- properties: {
5523
- databaseId: { type: "string", description: "Database node id." },
5524
- schemaId: { type: "string", description: "Optional row schema IRI." },
5525
- descriptor: {
5526
- type: "object",
5527
- description: "Optional NodeQueryDescriptor-compatible query shape."
5528
- },
5529
- where: {
5530
- type: "object",
5531
- description: "Optional exact property filters for row nodes."
5532
- },
5533
- search: {
5534
- type: "object",
5535
- description: "Optional NodeQueryDescriptor search filter."
5536
- },
5537
- orderBy: {
5538
- type: "object",
5539
- description: "Optional NodeQueryDescriptor order map."
5540
- },
5541
- materializedView: {
5542
- type: "object",
5543
- description: "Optional materialized view query options."
5544
- },
5545
- count: { type: "string", description: "Page count mode: exact, estimate, or none." },
5546
- limit: { type: "number", description: "Maximum row count." },
5547
- offset: { type: "number", description: "Row offset." }
5548
- },
5549
- required: ["databaseId"]
5550
- }
5551
- },
5552
- {
5553
- name: "xnet_database_sample",
5554
- title: "Sample database rows",
5555
- description: "Return a small deterministic sample for schema and content inspection.",
5556
- risk: "low",
5557
- requiredScopes: ["database.read", "database.query"],
5558
- inputSchema: {
5710
+ description: "Optional NodeQueryDescriptor search filter."
5711
+ },
5712
+ orderBy: {
5559
5713
  type: "object",
5560
- properties: {
5561
- databaseId: { type: "string", description: "Database node id." },
5562
- schemaId: { type: "string", description: "Optional row schema IRI." },
5563
- sampleSize: { type: "number", description: "Sample row count." },
5564
- descriptor: {
5565
- type: "object",
5566
- description: "Optional NodeQueryDescriptor-compatible query shape."
5567
- }
5568
- },
5569
- required: ["databaseId"]
5570
- }
5571
- },
5572
- {
5573
- name: "xnet_database_explain_query",
5574
- title: "Explain database query",
5575
- description: "Explain descriptor, pagination, materialized view, and storage plan metadata.",
5576
- risk: "low",
5577
- requiredScopes: ["database.read", "database.query", "storage.diagnostics"],
5578
- inputSchema: {
5714
+ description: "Optional NodeQueryDescriptor order map."
5715
+ },
5716
+ materializedView: {
5579
5717
  type: "object",
5580
- properties: {
5581
- databaseId: { type: "string", description: "Database node id." },
5582
- schemaId: { type: "string", description: "Optional row schema IRI." },
5583
- descriptor: {
5584
- type: "object",
5585
- description: "Optional NodeQueryDescriptor-compatible query shape."
5586
- },
5587
- limit: { type: "number", description: "Maximum row count for the dry-run query." },
5588
- offset: { type: "number", description: "Row offset." }
5589
- },
5590
- required: ["databaseId"]
5591
- }
5718
+ description: "Optional materialized view query options."
5719
+ },
5720
+ count: { type: "string", description: "Page count mode: exact, estimate, or none." },
5721
+ limit: { type: "number", description: "Maximum row count." },
5722
+ offset: { type: "number", description: "Row offset." }
5592
5723
  },
5593
- {
5594
- name: "xnet_plan_database_mutation",
5595
- title: "Plan database mutation",
5596
- description: "Create a database mutation plan for later review without applying it.",
5597
- risk: "medium",
5598
- requiredScopes: ["database.read", "database.propose"],
5599
- inputSchema: {
5724
+ required: ["databaseId"]
5725
+ }
5726
+ },
5727
+ execute: async (host, args) => await host.queryDatabase({
5728
+ databaseId: readRequiredString(args, "databaseId"),
5729
+ schemaId: readOptionalString(args, "schemaId"),
5730
+ descriptor: readOptionalRecord(args, "descriptor"),
5731
+ where: readOptionalRecord(args, "where"),
5732
+ search: args.search,
5733
+ orderBy: readOptionalRecord(args, "orderBy"),
5734
+ materializedView: args.materializedView,
5735
+ count: readOptionalString(args, "count"),
5736
+ limit: readOptionalNumber(args, "limit"),
5737
+ offset: readOptionalNumber(args, "offset")
5738
+ })
5739
+ };
5740
+ var databaseSampleTool = {
5741
+ definition: {
5742
+ name: "xnet_database_sample",
5743
+ title: "Sample database rows",
5744
+ description: "Return a small deterministic sample for schema and content inspection.",
5745
+ risk: "low",
5746
+ requiredScopes: ["database.read", "database.query"],
5747
+ inputSchema: {
5748
+ type: "object",
5749
+ properties: {
5750
+ databaseId: { type: "string", description: "Database node id." },
5751
+ schemaId: { type: "string", description: "Optional row schema IRI." },
5752
+ sampleSize: { type: "number", description: "Sample row count." },
5753
+ descriptor: {
5600
5754
  type: "object",
5601
- properties: {
5602
- databaseId: { type: "string", description: "Database node id." },
5603
- baseRevision: { type: "string", description: "Revision the mutation was based on." },
5604
- operations: { type: "array", description: "Database operations to validate." },
5605
- intent: { type: "string", description: "User or agent intent for the mutation." },
5606
- actor: { type: "string", description: "Agent or user creating the plan." }
5607
- },
5608
- required: ["databaseId", "operations"]
5755
+ description: "Optional NodeQueryDescriptor-compatible query shape."
5609
5756
  }
5610
5757
  },
5611
- {
5612
- name: "xnet_apply_database_mutation",
5613
- title: "Apply database mutation plan",
5614
- description: "Apply a validated database row/schema mutation plan with transactional row rollback and audit logging.",
5615
- risk: "high",
5616
- requiredScopes: ["database.read", "database.write.rows", "database.write.schema"],
5617
- inputSchema: {
5758
+ required: ["databaseId"]
5759
+ }
5760
+ },
5761
+ execute: async (host, args) => await host.sampleDatabase({
5762
+ databaseId: readRequiredString(args, "databaseId"),
5763
+ schemaId: readOptionalString(args, "schemaId"),
5764
+ descriptor: readOptionalRecord(args, "descriptor"),
5765
+ sampleSize: readOptionalNumber(args, "sampleSize")
5766
+ })
5767
+ };
5768
+ var databaseExplainQueryTool = {
5769
+ definition: {
5770
+ name: "xnet_database_explain_query",
5771
+ title: "Explain database query",
5772
+ description: "Explain descriptor, pagination, materialized view, and storage plan metadata.",
5773
+ risk: "low",
5774
+ requiredScopes: ["database.read", "database.query", "storage.diagnostics"],
5775
+ inputSchema: {
5776
+ type: "object",
5777
+ properties: {
5778
+ databaseId: { type: "string", description: "Database node id." },
5779
+ schemaId: { type: "string", description: "Optional row schema IRI." },
5780
+ descriptor: {
5618
5781
  type: "object",
5619
- properties: {
5620
- plan: { type: "object", description: "Validated database mutation plan." },
5621
- confirmApply: {
5622
- type: "boolean",
5623
- description: "Must be true to apply the database mutation plan."
5624
- },
5625
- allowStale: {
5626
- type: "boolean",
5627
- description: "Allow applying when the plan base revision differs from the live database node."
5628
- }
5629
- },
5630
- required: ["plan", "confirmApply"]
5631
- }
5782
+ description: "Optional NodeQueryDescriptor-compatible query shape."
5783
+ },
5784
+ limit: { type: "number", description: "Maximum row count for the dry-run query." },
5785
+ offset: { type: "number", description: "Row offset." }
5632
5786
  },
5633
- {
5634
- name: "xnet_canvas_list",
5635
- title: "List canvases",
5636
- description: "List canvas nodes visible to the AI surface.",
5637
- risk: "low",
5638
- requiredScopes: ["canvas.read"],
5639
- inputSchema: {
5640
- type: "object",
5641
- properties: {
5642
- limit: { type: "number", description: "Maximum canvas count." },
5643
- offset: { type: "number", description: "Canvas offset." }
5644
- }
5645
- }
5787
+ required: ["databaseId"]
5788
+ }
5789
+ },
5790
+ execute: async (host, args) => await host.explainDatabaseQuery({
5791
+ databaseId: readRequiredString(args, "databaseId"),
5792
+ schemaId: readOptionalString(args, "schemaId"),
5793
+ descriptor: readOptionalRecord(args, "descriptor"),
5794
+ limit: readOptionalNumber(args, "limit"),
5795
+ offset: readOptionalNumber(args, "offset")
5796
+ })
5797
+ };
5798
+ var planDatabaseMutationTool = {
5799
+ definition: {
5800
+ name: "xnet_plan_database_mutation",
5801
+ title: "Plan database mutation",
5802
+ description: "Create a database mutation plan for later review without applying it.",
5803
+ risk: "medium",
5804
+ requiredScopes: ["database.read", "database.propose"],
5805
+ inputSchema: {
5806
+ type: "object",
5807
+ properties: {
5808
+ databaseId: { type: "string", description: "Database node id." },
5809
+ baseRevision: { type: "string", description: "Revision the mutation was based on." },
5810
+ operations: { type: "array", description: "Database operations to validate." },
5811
+ intent: { type: "string", description: "User or agent intent for the mutation." },
5812
+ actor: { type: "string", description: "Agent or user creating the plan." }
5646
5813
  },
5647
- {
5648
- name: "xnet_canvas_read_viewport",
5649
- title: "Read canvas viewport",
5650
- description: "Read canvas objects and edges intersecting a viewport.",
5651
- risk: "low",
5652
- requiredScopes: ["canvas.read"],
5653
- inputSchema: {
5654
- type: "object",
5655
- properties: {
5656
- canvasId: { type: "string", description: "Canvas node id." },
5657
- x: { type: "number", description: "Viewport x." },
5658
- y: { type: "number", description: "Viewport y." },
5659
- w: { type: "number", description: "Viewport width." },
5660
- h: { type: "number", description: "Viewport height." },
5661
- includeSourcePreviews: {
5662
- type: "boolean",
5663
- description: "Include previews for source-backed objects."
5664
- },
5665
- tileSize: { type: "number", description: "Optional tile size for tile scoping." },
5666
- tileIds: {
5667
- type: "array",
5668
- description: "Optional tile ids such as 0/1/-2 to constrain the read.",
5669
- items: { type: "string" }
5670
- }
5671
- },
5672
- required: ["canvasId"]
5814
+ required: ["databaseId", "operations"]
5815
+ }
5816
+ },
5817
+ execute: async (host, args) => await host.planDatabaseMutation(args)
5818
+ };
5819
+ var applyDatabaseMutationTool = {
5820
+ definition: {
5821
+ name: "xnet_apply_database_mutation",
5822
+ title: "Apply database mutation plan",
5823
+ description: "Apply a validated database row/schema mutation plan with transactional row rollback and audit logging.",
5824
+ risk: "high",
5825
+ requiredScopes: ["database.read", "database.write.rows", "database.write.schema"],
5826
+ inputSchema: {
5827
+ type: "object",
5828
+ properties: {
5829
+ plan: { type: "object", description: "Validated database mutation plan." },
5830
+ confirmApply: {
5831
+ type: "boolean",
5832
+ description: "Must be true to apply the database mutation plan."
5833
+ },
5834
+ allowStale: {
5835
+ type: "boolean",
5836
+ description: "Allow applying when the plan base revision differs from the live database node."
5673
5837
  }
5674
5838
  },
5675
- {
5676
- name: "xnet_canvas_read_selection",
5677
- title: "Read canvas selection",
5678
- description: "Read selected canvas objects, connected edges, and optional source previews.",
5679
- risk: "low",
5680
- requiredScopes: ["canvas.read"],
5681
- inputSchema: {
5682
- type: "object",
5683
- properties: {
5684
- canvasId: { type: "string", description: "Canvas node id." },
5685
- objectIds: {
5686
- type: "array",
5687
- description: "Selected object ids.",
5688
- items: { type: "string" }
5689
- },
5690
- includeSourcePreviews: {
5691
- type: "boolean",
5692
- description: "Include previews for source-backed objects."
5693
- }
5694
- },
5695
- required: ["canvasId", "objectIds"]
5839
+ required: ["plan", "confirmApply"]
5840
+ }
5841
+ },
5842
+ execute: async (host, args) => await host.applyDatabaseMutation(args)
5843
+ };
5844
+ var databaseToolEntries = [
5845
+ databaseDescribeTool,
5846
+ databaseQueryTool,
5847
+ databaseSampleTool,
5848
+ databaseExplainQueryTool,
5849
+ planDatabaseMutationTool,
5850
+ applyDatabaseMutationTool
5851
+ ];
5852
+
5853
+ // src/ai-surface/tools/page-mutation.ts
5854
+ var readPageMarkdownTool = {
5855
+ definition: {
5856
+ name: "xnet_read_page_markdown",
5857
+ title: "Read page Markdown",
5858
+ description: "Read a page as Markdown with optional xNet frontmatter.",
5859
+ risk: "low",
5860
+ requiredScopes: ["page.read"],
5861
+ inputSchema: {
5862
+ type: "object",
5863
+ properties: {
5864
+ pageId: { type: "string", description: "Page node id." },
5865
+ includeFrontmatter: {
5866
+ type: "boolean",
5867
+ description: "Include xNet identity frontmatter. Defaults to true."
5696
5868
  }
5697
5869
  },
5698
- {
5699
- name: "xnet_canvas_search",
5700
- title: "Search canvas",
5701
- description: "Search canvas object text, labels, ids, and source metadata.",
5702
- risk: "low",
5703
- requiredScopes: ["canvas.read"],
5704
- inputSchema: {
5705
- type: "object",
5706
- properties: {
5707
- canvasId: { type: "string", description: "Canvas node id." },
5708
- query: { type: "string", description: "Search text." },
5709
- limit: { type: "number", description: "Maximum result count." }
5710
- },
5711
- required: ["canvasId", "query"]
5712
- }
5870
+ required: ["pageId"]
5871
+ }
5872
+ },
5873
+ execute: async (host, args) => {
5874
+ const content = await host.readPageMarkdown(
5875
+ readRequiredString(args, "pageId"),
5876
+ readOptionalBoolean(args, "includeFrontmatter") ?? true
5877
+ );
5878
+ return { markdown: content.text, mimeType: content.mimeType, uri: content.uri };
5879
+ }
5880
+ };
5881
+ var validatePageMarkdownTool = {
5882
+ definition: {
5883
+ name: "xnet_validate_page_markdown",
5884
+ title: "Validate page Markdown",
5885
+ description: "Validate xNet page frontmatter and supported xNet Markdown directives.",
5886
+ risk: "low",
5887
+ requiredScopes: ["page.read"],
5888
+ inputSchema: {
5889
+ type: "object",
5890
+ properties: {
5891
+ pageId: { type: "string", description: "Optional target page node id." },
5892
+ baseRevision: { type: "string", description: "Optional expected base revision." },
5893
+ markdown: { type: "string", description: "Markdown to validate." }
5713
5894
  },
5714
- {
5715
- name: "xnet_canvas_export_json_canvas",
5716
- title: "Export canvas as JSON Canvas",
5717
- description: "Export a canvas or viewport as JSON Canvas with xNet source metadata.",
5718
- risk: "low",
5719
- requiredScopes: ["canvas.read"],
5720
- inputSchema: {
5721
- type: "object",
5722
- properties: {
5723
- canvasId: { type: "string", description: "Canvas node id." },
5724
- includeXNetMetadata: {
5725
- type: "boolean",
5726
- description: "Include xNet source metadata. Defaults to true."
5727
- },
5728
- x: { type: "number", description: "Optional viewport x." },
5729
- y: { type: "number", description: "Optional viewport y." },
5730
- w: { type: "number", description: "Optional viewport width." },
5731
- h: { type: "number", description: "Optional viewport height." }
5732
- },
5733
- required: ["canvasId"]
5734
- }
5895
+ required: ["markdown"]
5896
+ }
5897
+ },
5898
+ execute: async (host, args) => {
5899
+ const pageId = readOptionalString(args, "pageId");
5900
+ const node = pageId ? await host.getNodeOrThrow(pageId) : null;
5901
+ return validateXNetPageMarkdown(readRequiredString(args, "markdown"), {
5902
+ pageId,
5903
+ schemaId: node?.schemaId,
5904
+ baseRevision: readOptionalString(args, "baseRevision")
5905
+ });
5906
+ }
5907
+ };
5908
+ var planPagePatchTool = {
5909
+ definition: {
5910
+ name: "xnet_plan_page_patch",
5911
+ title: "Plan page Markdown patch",
5912
+ description: "Validate an edited Markdown page and return a mutation plan without applying it.",
5913
+ risk: "medium",
5914
+ requiredScopes: ["page.read", "page.propose"],
5915
+ inputSchema: {
5916
+ type: "object",
5917
+ properties: {
5918
+ pageId: { type: "string", description: "Page node id." },
5919
+ baseRevision: { type: "string", description: "Revision the patch was based on." },
5920
+ markdown: { type: "string", description: "Proposed full Markdown replacement." },
5921
+ intent: { type: "string", description: "User or agent intent for the patch." },
5922
+ actor: { type: "string", description: "Agent or user creating the plan." }
5735
5923
  },
5736
- {
5737
- name: "xnet_canvas_plan_json_canvas_import",
5738
- title: "Plan JSON Canvas import",
5739
- description: "Convert a JSON Canvas document into a plan-only canvas mutation.",
5740
- risk: "medium",
5741
- requiredScopes: ["canvas.read", "canvas.propose"],
5742
- inputSchema: {
5743
- type: "object",
5744
- properties: {
5745
- canvasId: { type: "string", description: "Canvas node id." },
5746
- document: { type: "object", description: "JSON Canvas document." },
5747
- baseRevision: { type: "string", description: "Revision the import was based on." },
5748
- actor: { type: "string", description: "Agent or user creating the plan." },
5749
- intent: { type: "string", description: "User or agent intent for the import." }
5750
- },
5751
- required: ["canvasId", "document"]
5924
+ required: ["pageId", "markdown"]
5925
+ }
5926
+ },
5927
+ execute: async (host, args) => await host.planPagePatch(args)
5928
+ };
5929
+ var applyPageMarkdownTool = {
5930
+ definition: {
5931
+ name: "xnet_apply_page_markdown",
5932
+ title: "Apply page Markdown plan",
5933
+ description: "Apply a validated page Markdown mutation plan through the configured TipTap/Yjs document adapter, with a node-property fallback.",
5934
+ risk: "high",
5935
+ requiredScopes: ["page.read", "page.write"],
5936
+ inputSchema: {
5937
+ type: "object",
5938
+ properties: {
5939
+ plan: { type: "object", description: "Validated page Markdown mutation plan." },
5940
+ confirmApply: {
5941
+ type: "boolean",
5942
+ description: "Must be true to apply the page Markdown plan."
5943
+ },
5944
+ allowStale: {
5945
+ type: "boolean",
5946
+ description: "Allow applying when the plan base revision differs from the live node."
5752
5947
  }
5753
5948
  },
5754
- {
5755
- name: "xnet_plan_canvas_mutation",
5756
- title: "Plan canvas mutation",
5757
- description: "Create a canvas mutation plan for later review without applying it.",
5758
- risk: "medium",
5759
- requiredScopes: ["canvas.read", "canvas.propose"],
5760
- inputSchema: {
5761
- type: "object",
5762
- properties: {
5763
- canvasId: { type: "string", description: "Canvas node id." },
5764
- baseRevision: { type: "string", description: "Revision the mutation was based on." },
5765
- operations: { type: "array", description: "Canvas operations to validate." },
5766
- intent: { type: "string", description: "User or agent intent for the mutation." },
5767
- actor: { type: "string", description: "Agent or user creating the plan." }
5768
- },
5769
- required: ["canvasId", "operations"]
5949
+ required: ["plan", "confirmApply"]
5950
+ }
5951
+ },
5952
+ execute: async (host, args) => await host.applyPageMarkdown(args)
5953
+ };
5954
+ var rollbackPageMarkdownTool = {
5955
+ definition: {
5956
+ name: "xnet_rollback_page_markdown",
5957
+ title: "Rollback page Markdown apply",
5958
+ description: "Rollback a previously applied page Markdown plan by rollback handle.",
5959
+ risk: "high",
5960
+ requiredScopes: ["page.write"],
5961
+ inputSchema: {
5962
+ type: "object",
5963
+ properties: {
5964
+ rollbackHandle: { type: "string", description: "Rollback handle from apply result." },
5965
+ confirmRollback: {
5966
+ type: "boolean",
5967
+ description: "Must be true to perform the rollback."
5770
5968
  }
5771
5969
  },
5772
- {
5773
- name: "xnet_validate_mutation_plan",
5774
- title: "Validate mutation plan",
5775
- description: "Validate a serialized mutation plan and return errors or warnings.",
5776
- risk: "medium",
5777
- requiredScopes: ["workspace.read"],
5778
- inputSchema: {
5779
- type: "object",
5780
- properties: {
5781
- plan: { type: "object", description: "Mutation plan object to validate." }
5782
- },
5783
- required: ["plan"]
5784
- }
5970
+ required: ["rollbackHandle", "confirmRollback"]
5971
+ }
5972
+ },
5973
+ execute: async (host, args) => await host.rollbackPageMarkdown(args)
5974
+ };
5975
+
5976
+ // src/ai-surface/tools/search.ts
5977
+ var searchTool4 = {
5978
+ definition: {
5979
+ name: "xnet_search",
5980
+ title: "Search xNet workspace",
5981
+ description: "Search node titles and searchable properties with pagination and limits.",
5982
+ risk: "low",
5983
+ requiredScopes: ["workspace.search"],
5984
+ inputSchema: {
5985
+ type: "object",
5986
+ properties: {
5987
+ query: { type: "string", description: "Search text." },
5988
+ schemaId: { type: "string", description: "Optional schema IRI filter." },
5989
+ limit: { type: "number", description: "Maximum result count." },
5990
+ offset: { type: "number", description: "Result offset for pagination." }
5991
+ },
5992
+ required: ["query"]
5993
+ }
5994
+ },
5995
+ execute: async (host, args) => await host.search({
5996
+ query: readRequiredString(args, "query"),
5997
+ schemaId: readOptionalString(args, "schemaId") ?? readOptionalString(args, "schema"),
5998
+ limit: readOptionalNumber(args, "limit"),
5999
+ offset: readOptionalNumber(args, "offset")
6000
+ })
6001
+ };
6002
+ var graphExpandTool = {
6003
+ definition: {
6004
+ name: "xnet_graph_expand",
6005
+ title: "Expand a node along its relations",
6006
+ description: "Walk typed relation edges out from a node to its connected neighbors (bounded by hops and a result limit). Use for just-in-time expansion: fetch a specific node\u2019s connections only when you need them, instead of pulling the whole graph into context.",
6007
+ risk: "low",
6008
+ requiredScopes: ["workspace.read"],
6009
+ inputSchema: {
6010
+ type: "object",
6011
+ properties: {
6012
+ nodeId: { type: "string", description: "The node to expand from." },
6013
+ hops: {
6014
+ type: "number",
6015
+ description: "How many relation hops to walk (1\u20132, default 1)."
6016
+ },
6017
+ limit: { type: "number", description: "Maximum neighbors to return." }
6018
+ },
6019
+ required: ["nodeId"]
6020
+ }
6021
+ },
6022
+ execute: async (host, args) => await host.expandGraph({
6023
+ nodeId: readRequiredString(args, "nodeId"),
6024
+ hops: readOptionalNumber(args, "hops"),
6025
+ limit: readOptionalNumber(args, "limit")
6026
+ })
6027
+ };
6028
+ var createContextPackTool = {
6029
+ definition: {
6030
+ name: "xnet_create_context_pack",
6031
+ title: "Create context pack",
6032
+ description: "Create a bounded context pack from seeds and optional search results.",
6033
+ risk: "low",
6034
+ requiredScopes: ["workspace.read", "workspace.search"],
6035
+ inputSchema: {
6036
+ type: "object",
6037
+ properties: {
6038
+ query: { type: "string", description: "Optional search query." },
6039
+ seeds: {
6040
+ type: "array",
6041
+ description: "Seed resources such as pages, databases, canvases, or nodes.",
6042
+ items: {
6043
+ type: "object",
6044
+ properties: {
6045
+ kind: { type: "string", description: "Seed kind." },
6046
+ id: { type: "string", description: "Seed id." }
6047
+ }
6048
+ }
6049
+ },
6050
+ limit: { type: "number", description: "Maximum resources to include." }
5785
6051
  }
6052
+ }
6053
+ },
6054
+ execute: async (host, args) => await host.createContextPack({
6055
+ query: readOptionalString(args, "query"),
6056
+ seeds: readContextSeeds(args.seeds),
6057
+ limit: readOptionalNumber(args, "limit")
6058
+ })
6059
+ };
6060
+ var createExternalContextResourceTool = {
6061
+ definition: {
6062
+ name: "xnet_create_external_context_resource",
6063
+ title: "Create untrusted external context resource",
6064
+ description: "Wrap externally fetched content as an untrusted context-pack resource with an explicit instruction boundary.",
6065
+ risk: "medium",
6066
+ requiredScopes: ["network.fetch"],
6067
+ inputSchema: {
6068
+ type: "object",
6069
+ properties: {
6070
+ url: { type: "string", description: "External source URL." },
6071
+ text: { type: "string", description: "Fetched external text content." },
6072
+ mimeType: { type: "string", description: "Source MIME type. Defaults to text/plain." }
6073
+ },
6074
+ required: ["url", "text"]
6075
+ }
6076
+ },
6077
+ execute: (host, args) => host.createExternalContextResource({
6078
+ url: readRequiredString(args, "url"),
6079
+ text: readRequiredString(args, "text"),
6080
+ mimeType: readOptionalString(args, "mimeType")
6081
+ })
6082
+ };
6083
+ var searchToolEntries = [
6084
+ searchTool4,
6085
+ graphExpandTool,
6086
+ createContextPackTool,
6087
+ createExternalContextResourceTool
6088
+ ];
6089
+
6090
+ // src/ai-surface/tools/index.ts
6091
+ var BUILT_IN_TOOL_ENTRIES = [
6092
+ ...searchToolEntries,
6093
+ readPageMarkdownTool,
6094
+ validatePageMarkdownTool,
6095
+ planPagePatchTool,
6096
+ applyPageMarkdownTool,
6097
+ getAuditLogTool,
6098
+ rollbackPageMarkdownTool,
6099
+ ...databaseToolEntries,
6100
+ ...canvasToolEntries,
6101
+ validateMutationPlanTool
6102
+ ];
6103
+ var BUILT_IN_TOOLS_BY_NAME = new Map(
6104
+ BUILT_IN_TOOL_ENTRIES.map((entry) => [entry.definition.name, entry])
6105
+ );
6106
+
6107
+ // src/ai-surface/service.ts
6108
+ var DEFAULT_LIMITS = {
6109
+ maxListLimit: 100,
6110
+ maxSearchScan: 500,
6111
+ maxSearchResults: 20,
6112
+ maxContextResources: 12,
6113
+ maxCharactersPerResource: 12e3,
6114
+ maxJsonCharacters: 24e3,
6115
+ maxCanvasObjects: 200,
6116
+ maxDatabaseRows: 100
6117
+ };
6118
+ var BUILT_IN_RESOURCE_ROUTES = createBuiltInResourceRouter();
6119
+ var AiSurfaceService = class {
6120
+ constructor(config) {
6121
+ this.config = config;
6122
+ this.limits = { ...DEFAULT_LIMITS, ...config.limits };
6123
+ this.clock = config.clock ?? (() => /* @__PURE__ */ new Date());
6124
+ for (const tool of config.extraTools ?? []) {
6125
+ if (!this.extraTools.has(tool.name)) this.extraTools.set(tool.name, tool);
6126
+ }
6127
+ }
6128
+ limits;
6129
+ clock;
6130
+ sequence = 0;
6131
+ auditEvents = [];
6132
+ rollbackSnapshots = /* @__PURE__ */ new Map();
6133
+ /** Contributed tools by name (exploration 0196), de-duped at construction. */
6134
+ extraTools = /* @__PURE__ */ new Map();
6135
+ /**
6136
+ * The narrow surface handed to built-in tool handlers and resource routes
6137
+ * (`tools/`, `resources/`): closures over the private methods, so the class
6138
+ * stays the facade and its public type is unchanged. The closures are lazy —
6139
+ * nothing here runs until a tool call or resource read arrives.
6140
+ */
6141
+ host = {
6142
+ search: (options) => this.search(options),
6143
+ expandGraph: (options) => this.expandGraph(options),
6144
+ createContextPack: (options) => this.createContextPack(options),
6145
+ createExternalContextResource: (options) => this.createExternalContextResource(options),
6146
+ getWorkspaceSummary: () => this.getWorkspaceSummary(),
6147
+ getRecentNodes: () => this.getRecentNodes(),
6148
+ listNodes: async () => {
6149
+ const nodes = await this.config.store.list({ limit: this.limits.maxListLimit });
6150
+ return { nodes, count: nodes.length, limit: this.limits.maxListLimit };
6151
+ },
6152
+ listSchemas: async () => ({ schemas: await this.getSchemaSummaries(true) }),
6153
+ getNodeOrThrow: (id) => this.getNodeOrThrow(id),
6154
+ getNodeProjection: (id) => this.getNodeProjection(id),
6155
+ readPageMarkdown: (pageId, includeFrontmatter, uri) => this.readPageMarkdown(pageId, includeFrontmatter, uri),
6156
+ readPageOutline: (pageId) => this.readPageOutline(pageId),
6157
+ planPagePatch: (args) => this.planPagePatch(args),
6158
+ applyPageMarkdown: (args) => this.applyPageMarkdown(args),
6159
+ rollbackPageMarkdown: (args) => this.rollbackPageMarkdown(args),
6160
+ getAuditLog: (options) => this.getAuditLog(options),
6161
+ describeDatabase: (databaseId, options) => this.describeDatabase(databaseId, options),
6162
+ readDatabaseViews: (databaseId) => this.readDatabaseViews(databaseId),
6163
+ queryDatabase: (options) => this.queryDatabase(options),
6164
+ sampleDatabase: (options) => this.sampleDatabase(options),
6165
+ explainDatabaseQuery: (options) => this.explainDatabaseQuery(options),
6166
+ planDatabaseMutation: (args) => this.planDatabaseMutation(args),
6167
+ applyDatabaseMutation: (args) => this.applyDatabaseMutation(args),
6168
+ listCanvases: (options) => this.listCanvases(options),
6169
+ readCanvasViewport: (options) => this.readCanvasViewport(options),
6170
+ readCanvasObjects: (canvasId) => this.readCanvasObjects(canvasId),
6171
+ readCanvasSelection: (options) => this.readCanvasSelection(options),
6172
+ searchCanvas: (options) => this.searchCanvas(options),
6173
+ exportCanvasJsonCanvas: (options) => this.exportCanvasJsonCanvas(options),
6174
+ readCanvasObject: (canvasId, objectId) => this.readCanvasObject(canvasId, objectId),
6175
+ planCanvasJsonCanvasImport: (args) => this.planCanvasJsonCanvasImport(args),
6176
+ planCanvasMutation: (args) => this.planCanvasMutation(args),
6177
+ jsonResource: (uri, value) => this.jsonResource(uri, value)
6178
+ };
6179
+ /** The contributed (non-built-in) tools, with built-in name collisions removed. */
6180
+ getExtraTools() {
6181
+ const builtIn = new Set(this.builtInTools().map((t) => t.name));
6182
+ return [...this.extraTools.values()].filter((t) => !builtIn.has(t.name));
6183
+ }
6184
+ getResources() {
6185
+ return [
6186
+ createResource(
6187
+ "xnet://workspace/summary",
6188
+ "Workspace Summary",
6189
+ "High-level schema counts, recent nodes, and AI surface capabilities.",
6190
+ "application/json",
6191
+ "low",
6192
+ ["workspace.read"]
6193
+ ),
6194
+ createResource(
6195
+ "xnet://workspace/recent",
6196
+ "Recent Workspace Nodes",
6197
+ "Recent nodes with ids, schemas, titles, and revisions.",
6198
+ "application/json",
6199
+ "low",
6200
+ ["workspace.read"]
6201
+ ),
6202
+ createResource(
6203
+ "xnet://nodes",
6204
+ "All Nodes",
6205
+ "Limited list of nodes in the local store.",
6206
+ "application/json",
6207
+ "low",
6208
+ ["workspace.read"]
6209
+ ),
6210
+ createResource(
6211
+ "xnet://schemas",
6212
+ "All Schemas",
6213
+ "List of available schemas and property metadata.",
6214
+ "application/json",
6215
+ "low",
6216
+ ["workspace.read"]
6217
+ ),
6218
+ createResource(
6219
+ "xnet://page/{pageId}.md",
6220
+ "Page Markdown",
6221
+ "Markdown projection for a page, including xNet frontmatter identity.",
6222
+ "text/markdown",
6223
+ "low",
6224
+ ["page.read"],
6225
+ true
6226
+ ),
6227
+ createResource(
6228
+ "xnet://page/{pageId}/outline",
6229
+ "Page Outline",
6230
+ "Page heading outline extracted from the Markdown projection.",
6231
+ "application/json",
6232
+ "low",
6233
+ ["page.read"],
6234
+ true
6235
+ ),
6236
+ createResource(
6237
+ "xnet://database/{databaseId}/schema",
6238
+ "Database Schema",
6239
+ "Database node metadata, declared properties, and known column descriptors.",
6240
+ "application/json",
6241
+ "low",
6242
+ ["database.read"],
6243
+ true
6244
+ ),
6245
+ createResource(
6246
+ "xnet://database/{databaseId}/views",
6247
+ "Database Views",
6248
+ "Known database view descriptors from the database node projection.",
6249
+ "application/json",
6250
+ "low",
6251
+ ["database.read"],
6252
+ true
6253
+ ),
6254
+ createResource(
6255
+ "xnet://database/{databaseId}/sample?limit=10",
6256
+ "Database Sample Rows",
6257
+ "Bounded sample of rows for a database using NodeQueryDescriptor semantics.",
6258
+ "application/json",
6259
+ "low",
6260
+ ["database.read", "database.query"],
6261
+ true
6262
+ ),
6263
+ createResource(
6264
+ "xnet://canvas/{canvasId}/viewport?x=0&y=0&w=1000&h=800",
6265
+ "Canvas Viewport",
6266
+ "Viewport-scoped canvas objects and edges.",
6267
+ "application/json",
6268
+ "low",
6269
+ ["canvas.read"],
6270
+ true
6271
+ ),
6272
+ createResource(
6273
+ "xnet://canvas/{canvasId}/objects",
6274
+ "Canvas Objects",
6275
+ "Bounded list of canvas objects and connectors.",
6276
+ "application/json",
6277
+ "low",
6278
+ ["canvas.read"],
6279
+ true
6280
+ ),
6281
+ createResource(
6282
+ "xnet://canvas/{canvasId}/selection?ids=object-1,object-2",
6283
+ "Canvas Selection",
6284
+ "Selection-scoped canvas objects, edges, and source previews.",
6285
+ "application/json",
6286
+ "low",
6287
+ ["canvas.read"],
6288
+ true
6289
+ ),
6290
+ createResource(
6291
+ "xnet://canvas/{canvasId}/json-canvas",
6292
+ "Canvas JSON Canvas",
6293
+ "JSON Canvas projection with xNet source metadata sidecars.",
6294
+ "application/json",
6295
+ "low",
6296
+ ["canvas.read"],
6297
+ true
6298
+ )
5786
6299
  ];
5787
6300
  }
6301
+ /** The full tool surface: built-in `xnet_*` tools plus contributed extras. */
6302
+ getTools() {
6303
+ const extras = this.getExtraTools().map(
6304
+ ({ invoke: _invoke, ...def }) => def
6305
+ );
6306
+ return [...this.builtInTools(), ...extras];
6307
+ }
6308
+ builtInTools() {
6309
+ return BUILT_IN_TOOL_ENTRIES.map((entry) => entry.definition);
6310
+ }
6311
+ /**
6312
+ * Dispatch a tool call: the built-in registry first (a built-in `xnet_*`
6313
+ * name always wins), then contributed tools (plugin/connector `agentTools`,
6314
+ * exploration 0196).
6315
+ */
5788
6316
  async callTool(name, args = {}) {
5789
- switch (name) {
5790
- case "xnet_search":
5791
- return await this.search({
5792
- query: readRequiredString(args, "query"),
5793
- schemaId: readOptionalString(args, "schemaId") ?? readOptionalString(args, "schema"),
5794
- limit: readOptionalNumber(args, "limit"),
5795
- offset: readOptionalNumber(args, "offset")
5796
- });
5797
- case "xnet_graph_expand":
5798
- return await this.expandGraph({
5799
- nodeId: readRequiredString(args, "nodeId"),
5800
- hops: readOptionalNumber(args, "hops"),
5801
- limit: readOptionalNumber(args, "limit")
5802
- });
5803
- case "xnet_create_context_pack":
5804
- return await this.createContextPack({
5805
- query: readOptionalString(args, "query"),
5806
- seeds: readContextSeeds(args.seeds),
5807
- limit: readOptionalNumber(args, "limit")
5808
- });
5809
- case "xnet_create_external_context_resource":
5810
- return this.createExternalContextResource({
5811
- url: readRequiredString(args, "url"),
5812
- text: readRequiredString(args, "text"),
5813
- mimeType: readOptionalString(args, "mimeType")
5814
- });
5815
- case "xnet_read_page_markdown": {
5816
- const content = await this.readPageMarkdown(
5817
- readRequiredString(args, "pageId"),
5818
- readOptionalBoolean(args, "includeFrontmatter") ?? true
5819
- );
5820
- return { markdown: content.text, mimeType: content.mimeType, uri: content.uri };
5821
- }
5822
- case "xnet_validate_page_markdown": {
5823
- const pageId = readOptionalString(args, "pageId");
5824
- const node = pageId ? await this.getNodeOrThrow(pageId) : null;
5825
- return validateXNetPageMarkdown(readRequiredString(args, "markdown"), {
5826
- pageId,
5827
- schemaId: node?.schemaId,
5828
- baseRevision: readOptionalString(args, "baseRevision")
5829
- });
5830
- }
5831
- case "xnet_plan_page_patch":
5832
- return await this.planPagePatch(args);
5833
- case "xnet_apply_page_markdown":
5834
- return await this.applyPageMarkdown(args);
5835
- case "xnet_get_audit_log":
5836
- return this.getAuditLog({
5837
- planId: readOptionalString(args, "planId"),
5838
- limit: readOptionalNumber(args, "limit")
5839
- });
5840
- case "xnet_rollback_page_markdown":
5841
- return await this.rollbackPageMarkdown(args);
5842
- case "xnet_database_describe":
5843
- return await this.describeDatabase(readRequiredString(args, "databaseId"), {
5844
- includeSample: readOptionalBoolean(args, "includeSample") ?? false
5845
- });
5846
- case "xnet_database_query":
5847
- return await this.queryDatabase({
5848
- databaseId: readRequiredString(args, "databaseId"),
5849
- schemaId: readOptionalString(args, "schemaId"),
5850
- descriptor: readOptionalRecord(args, "descriptor"),
5851
- where: readOptionalRecord(args, "where"),
5852
- search: args.search,
5853
- orderBy: readOptionalRecord(args, "orderBy"),
5854
- materializedView: args.materializedView,
5855
- count: readOptionalString(args, "count"),
5856
- limit: readOptionalNumber(args, "limit"),
5857
- offset: readOptionalNumber(args, "offset")
5858
- });
5859
- case "xnet_database_sample":
5860
- return await this.sampleDatabase({
5861
- databaseId: readRequiredString(args, "databaseId"),
5862
- schemaId: readOptionalString(args, "schemaId"),
5863
- descriptor: readOptionalRecord(args, "descriptor"),
5864
- sampleSize: readOptionalNumber(args, "sampleSize")
5865
- });
5866
- case "xnet_database_explain_query":
5867
- return await this.explainDatabaseQuery({
5868
- databaseId: readRequiredString(args, "databaseId"),
5869
- schemaId: readOptionalString(args, "schemaId"),
5870
- descriptor: readOptionalRecord(args, "descriptor"),
5871
- limit: readOptionalNumber(args, "limit"),
5872
- offset: readOptionalNumber(args, "offset")
5873
- });
5874
- case "xnet_plan_database_mutation":
5875
- return await this.planDatabaseMutation(args);
5876
- case "xnet_apply_database_mutation":
5877
- return await this.applyDatabaseMutation(args);
5878
- case "xnet_canvas_list":
5879
- return await this.listCanvases({
5880
- limit: readOptionalNumber(args, "limit"),
5881
- offset: readOptionalNumber(args, "offset")
5882
- });
5883
- case "xnet_canvas_read_viewport":
5884
- return await this.readCanvasViewport({
5885
- canvasId: readRequiredString(args, "canvasId"),
5886
- x: readOptionalNumber(args, "x"),
5887
- y: readOptionalNumber(args, "y"),
5888
- w: readOptionalNumber(args, "w"),
5889
- h: readOptionalNumber(args, "h"),
5890
- tileSize: readOptionalNumber(args, "tileSize"),
5891
- tileIds: readStringArray(args.tileIds),
5892
- includeSourcePreviews: readOptionalBoolean(args, "includeSourcePreviews") ?? false
5893
- });
5894
- case "xnet_canvas_read_selection":
5895
- return await this.readCanvasSelection({
5896
- canvasId: readRequiredString(args, "canvasId"),
5897
- objectIds: readRequiredStringArray(args.objectIds, "objectIds"),
5898
- includeSourcePreviews: readOptionalBoolean(args, "includeSourcePreviews") ?? false
5899
- });
5900
- case "xnet_canvas_search":
5901
- return await this.searchCanvas({
5902
- canvasId: readRequiredString(args, "canvasId"),
5903
- query: readRequiredString(args, "query"),
5904
- limit: readOptionalNumber(args, "limit")
5905
- });
5906
- case "xnet_canvas_export_json_canvas":
5907
- return await this.exportCanvasJsonCanvas({
5908
- canvasId: readRequiredString(args, "canvasId"),
5909
- includeXNetMetadata: readOptionalBoolean(args, "includeXNetMetadata") ?? true,
5910
- x: readOptionalNumber(args, "x"),
5911
- y: readOptionalNumber(args, "y"),
5912
- w: readOptionalNumber(args, "w"),
5913
- h: readOptionalNumber(args, "h")
5914
- });
5915
- case "xnet_canvas_plan_json_canvas_import":
5916
- return await this.planCanvasJsonCanvasImport(args);
5917
- case "xnet_plan_canvas_mutation":
5918
- return await this.planCanvasMutation(args);
5919
- case "xnet_validate_mutation_plan": {
5920
- const validation = validateAiMutationPlan(args.plan);
5921
- return { validation };
5922
- }
5923
- default: {
5924
- const extra = this.extraTools.get(name);
5925
- if (extra) return await extra.invoke(args);
5926
- throw new Error(`Unknown AI surface tool: ${name}`);
5927
- }
5928
- }
6317
+ const builtIn = BUILT_IN_TOOLS_BY_NAME.get(name);
6318
+ if (builtIn) return await builtIn.execute(this.host, args);
6319
+ const extra = this.extraTools.get(name);
6320
+ if (extra) return await extra.invoke(args);
6321
+ throw new Error(`Unknown AI surface tool: ${name}`);
5929
6322
  }
5930
6323
  async readResource(uri) {
5931
- const parsed = parseXNetUri(uri);
5932
- if (uri === "xnet://nodes") {
5933
- const nodes = await this.config.store.list({ limit: this.limits.maxListLimit });
5934
- return this.jsonResource(uri, { nodes, count: nodes.length, limit: this.limits.maxListLimit });
5935
- }
5936
- if (uri === "xnet://schemas") {
5937
- return this.jsonResource(uri, { schemas: await this.getSchemaSummaries(true) });
5938
- }
5939
- if (parsed.host === "workspace" && parsed.parts[0] === "summary") {
5940
- return this.jsonResource(uri, await this.getWorkspaceSummary());
5941
- }
5942
- if (parsed.host === "workspace" && parsed.parts[0] === "recent") {
5943
- return this.jsonResource(uri, await this.getRecentNodes());
5944
- }
5945
- if (parsed.host === "workspace" && parsed.parts[0] === "search") {
5946
- return this.jsonResource(
5947
- uri,
5948
- await this.search({
5949
- query: parsed.searchParams.get("q") ?? "",
5950
- schemaId: parsed.searchParams.get("schema") ?? void 0,
5951
- limit: readUrlNumber(parsed.searchParams, "limit"),
5952
- offset: readUrlNumber(parsed.searchParams, "offset")
5953
- })
5954
- );
5955
- }
5956
- if (parsed.host === "node" && parsed.parts[0]) {
5957
- return this.jsonResource(uri, await this.getNodeProjection(parsed.parts[0]));
5958
- }
5959
- if (parsed.host === "page" && parsed.parts[0]) {
5960
- const pageId = parsed.parts[0].endsWith(".md") ? parsed.parts[0].slice(0, -".md".length) : parsed.parts[0];
5961
- if (parsed.parts.length === 1 || parsed.parts[0].endsWith(".md")) {
5962
- return await this.readPageMarkdown(pageId, true, uri);
5963
- }
5964
- if (parsed.parts[1] === "outline") {
5965
- return this.jsonResource(uri, await this.readPageOutline(pageId));
5966
- }
5967
- if (parsed.parts[1] === "context-pack") {
5968
- return this.jsonResource(
5969
- uri,
5970
- await this.createContextPack({ seeds: [{ kind: "page", id: pageId }] })
5971
- );
5972
- }
5973
- }
5974
- if (parsed.host === "database" && parsed.parts[0]) {
5975
- const databaseId = parsed.parts[0];
5976
- if (parsed.parts[1] === "schema") {
5977
- return this.jsonResource(uri, await this.describeDatabase(databaseId));
5978
- }
5979
- if (parsed.parts[1] === "views") {
5980
- return this.jsonResource(uri, await this.readDatabaseViews(databaseId));
5981
- }
5982
- if (parsed.parts[1] === "sample") {
5983
- return this.jsonResource(
5984
- uri,
5985
- await this.sampleDatabase({
5986
- databaseId,
5987
- sampleSize: readUrlNumber(parsed.searchParams, "limit")
5988
- })
5989
- );
5990
- }
5991
- if (parsed.parts[1] === "query") {
5992
- return this.jsonResource(
5993
- uri,
5994
- await this.queryDatabase({
5995
- databaseId,
5996
- schemaId: parsed.searchParams.get("schema") ?? void 0,
5997
- search: parsed.searchParams.get("q") ?? void 0,
5998
- materializedView: parsed.searchParams.get("view") ? { viewId: parsed.searchParams.get("view") ?? "" } : void 0,
5999
- limit: readUrlNumber(parsed.searchParams, "limit"),
6000
- offset: readUrlNumber(parsed.searchParams, "offset")
6001
- })
6002
- );
6003
- }
6004
- }
6005
- if (parsed.host === "canvas" && parsed.parts[0]) {
6006
- const canvasId = parsed.parts[0];
6007
- if (parsed.parts[1] === "viewport") {
6008
- return this.jsonResource(
6009
- uri,
6010
- await this.readCanvasViewport({
6011
- canvasId,
6012
- x: readUrlNumber(parsed.searchParams, "x"),
6013
- y: readUrlNumber(parsed.searchParams, "y"),
6014
- w: readUrlNumber(parsed.searchParams, "w"),
6015
- h: readUrlNumber(parsed.searchParams, "h"),
6016
- tileSize: readUrlNumber(parsed.searchParams, "tileSize"),
6017
- tileIds: readCsvStringArray(parsed.searchParams.get("tileIds")),
6018
- includeSourcePreviews: parsed.searchParams.get("includeSourcePreviews") === "true"
6019
- })
6020
- );
6021
- }
6022
- if (parsed.parts[1] === "objects") {
6023
- return this.jsonResource(uri, await this.readCanvasObjects(canvasId));
6024
- }
6025
- if (parsed.parts[1] === "selection") {
6026
- return this.jsonResource(
6027
- uri,
6028
- await this.readCanvasSelection({
6029
- canvasId,
6030
- objectIds: readCsvStringArray(parsed.searchParams.get("ids")),
6031
- includeSourcePreviews: parsed.searchParams.get("includeSourcePreviews") !== "false"
6032
- })
6033
- );
6034
- }
6035
- if (parsed.parts[1] === "json-canvas") {
6036
- return this.jsonResource(
6037
- uri,
6038
- await this.exportCanvasJsonCanvas({
6039
- canvasId,
6040
- includeXNetMetadata: parsed.searchParams.get("includeXNetMetadata") !== "false"
6041
- })
6042
- );
6043
- }
6044
- if (parsed.parts[1] === "object" && parsed.parts[2]) {
6045
- return this.jsonResource(uri, await this.readCanvasObject(canvasId, parsed.parts[2]));
6046
- }
6047
- }
6048
- throw new Error(`Resource not found: ${uri}`);
6324
+ return await BUILT_IN_RESOURCE_ROUTES.resolve(this.host, uri);
6049
6325
  }
6050
6326
  toJsonText(value, format = "concise") {
6051
6327
  return this.stringifyJson(value, format);
@@ -6142,7 +6418,7 @@ var AiSurfaceService = class {
6142
6418
  const results = Array.isArray(search.results) ? search.results : [];
6143
6419
  const ids = [];
6144
6420
  for (const result of results) {
6145
- if (isRecord4(result) && typeof result.id === "string") ids.push(result.id);
6421
+ if (isRecord3(result) && typeof result.id === "string") ids.push(result.id);
6146
6422
  }
6147
6423
  return ids;
6148
6424
  }
@@ -6784,8 +7060,8 @@ var AiSurfaceService = class {
6784
7060
  h: options.h,
6785
7061
  includeSourcePreviews: false
6786
7062
  });
6787
- const objects = Array.isArray(viewport.objects) ? viewport.objects.filter(isRecord4) : [];
6788
- const edges = Array.isArray(viewport.edges) ? viewport.edges.filter(isRecord4) : [];
7063
+ const objects = Array.isArray(viewport.objects) ? viewport.objects.filter(isRecord3) : [];
7064
+ const edges = Array.isArray(viewport.edges) ? viewport.edges.filter(isRecord3) : [];
6789
7065
  return {
6790
7066
  canvasId: options.canvasId,
6791
7067
  revision: viewport.revision,
@@ -6827,7 +7103,7 @@ var AiSurfaceService = class {
6827
7103
  });
6828
7104
  const objects = Array.isArray(viewport.objects) ? viewport.objects : [];
6829
7105
  const object = objects.find(
6830
- (candidate) => isRecord4(candidate) && readRecordString(candidate, "id") === objectId
7106
+ (candidate) => isRecord3(candidate) && readRecordString(candidate, "id") === objectId
6831
7107
  );
6832
7108
  if (!object) throw new Error(`Canvas object not found: ${objectId}`);
6833
7109
  return {
@@ -7303,7 +7579,7 @@ function invalidPageApplyResult(plan, validation) {
7303
7579
  return {
7304
7580
  applied: false,
7305
7581
  pageId: "unknown",
7306
- planId: isRecord4(plan) && typeof plan.id === "string" ? plan.id : "unknown",
7582
+ planId: isRecord3(plan) && typeof plan.id === "string" ? plan.id : "unknown",
7307
7583
  mode: "node-property",
7308
7584
  baseRevision: "unknown",
7309
7585
  liveRevision: "unknown",
@@ -7326,7 +7602,7 @@ function rejectedPageApplyResult(input) {
7326
7602
  };
7327
7603
  }
7328
7604
  function isMutationPlan(value) {
7329
- return isRecord4(value) && typeof value.id === "string" && typeof value.actor === "string" && typeof value.intent === "string" && Array.isArray(value.changes) && isRecord4(value.validation);
7605
+ return isRecord3(value) && typeof value.id === "string" && typeof value.actor === "string" && typeof value.intent === "string" && Array.isArray(value.changes) && isRecord3(value.validation);
7330
7606
  }
7331
7607
  function createResource(uri, name, description, mimeType, risk, requiredScopes, dynamic = false) {
7332
7608
  return {
@@ -7339,22 +7615,6 @@ function createResource(uri, name, description, mimeType, risk, requiredScopes,
7339
7615
  dynamic
7340
7616
  };
7341
7617
  }
7342
- function parseXNetUri(uri) {
7343
- let parsed;
7344
- try {
7345
- parsed = new URL(uri);
7346
- } catch {
7347
- throw new Error(`Invalid xNet resource URI: ${uri}`);
7348
- }
7349
- if (parsed.protocol !== "xnet:") {
7350
- throw new Error(`Invalid xNet resource URI: ${uri}`);
7351
- }
7352
- return {
7353
- host: parsed.hostname,
7354
- parts: parsed.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part)),
7355
- searchParams: parsed.searchParams
7356
- };
7357
- }
7358
7618
  function renderPageMarkdown(node, includeFrontmatter, exportedAt) {
7359
7619
  const title = readStringProperty(node, "title") ?? "Untitled Page";
7360
7620
  const body = readMarkdownBody(node);
@@ -7381,7 +7641,7 @@ function readMarkdownBody(node) {
7381
7641
  const value = readStringProperty(node, "markdown") ?? readStringProperty(node, "content") ?? readStringProperty(node, "body") ?? readStringProperty(node, "text") ?? readStringProperty(node, "description");
7382
7642
  if (value) return value;
7383
7643
  const richContent = node.properties.content;
7384
- if (isRecord4(richContent)) {
7644
+ if (isRecord3(richContent)) {
7385
7645
  return `\`\`\`json
7386
7646
  ${JSON.stringify(richContent, null, 2)}
7387
7647
  \`\`\``;
@@ -7442,7 +7702,7 @@ function relationFieldNames(schema) {
7442
7702
  if (!schema) return [];
7443
7703
  const fields = [];
7444
7704
  for (const [name, definition] of Object.entries(schema.properties)) {
7445
- if (isRecord4(definition) && definition.type === "relation") fields.push(name);
7705
+ if (isRecord3(definition) && definition.type === "relation") fields.push(name);
7446
7706
  }
7447
7707
  return fields;
7448
7708
  }
@@ -7472,7 +7732,7 @@ function readArrayProperty(node, property) {
7472
7732
  }
7473
7733
  function readNestedArrayProperty(node, objectProperty, arrayProperty) {
7474
7734
  const value = node.properties[objectProperty];
7475
- return isRecord4(value) && Array.isArray(value[arrayProperty]) ? value[arrayProperty] : void 0;
7735
+ return isRecord3(value) && Array.isArray(value[arrayProperty]) ? value[arrayProperty] : void 0;
7476
7736
  }
7477
7737
  function belongsToDatabase(node, databaseId) {
7478
7738
  return readStringProperty(node, "databaseId") === databaseId || readStringProperty(node, "database") === databaseId || readStringProperty(node, "parentDatabaseId") === databaseId;
@@ -7531,7 +7791,7 @@ function normalizeQuerySearch(value) {
7531
7791
  const text3 = value.trim();
7532
7792
  return text3 ? { text: text3 } : void 0;
7533
7793
  }
7534
- if (!isRecord4(value) || typeof value.text !== "string" || !value.text.trim()) {
7794
+ if (!isRecord3(value) || typeof value.text !== "string" || !value.text.trim()) {
7535
7795
  return void 0;
7536
7796
  }
7537
7797
  const fields = Array.isArray(value.fields) ? value.fields.filter(
@@ -7547,7 +7807,7 @@ function normalizeQueryMaterializedView(value) {
7547
7807
  const viewId2 = value.trim();
7548
7808
  return viewId2 ? { viewId: viewId2 } : void 0;
7549
7809
  }
7550
- if (!isRecord4(value)) return void 0;
7810
+ if (!isRecord3(value)) return void 0;
7551
7811
  const viewId = readRecordString(value, "viewId");
7552
7812
  if (!viewId) return void 0;
7553
7813
  const maxAgeMs = readRecordNumber(value, "maxAgeMs");
@@ -7820,7 +8080,7 @@ function invalidDatabaseApplyResult(plan, validation) {
7820
8080
  return {
7821
8081
  applied: false,
7822
8082
  databaseId: "unknown",
7823
- planId: isRecord4(plan) && typeof plan.id === "string" ? plan.id : "unknown",
8083
+ planId: isRecord3(plan) && typeof plan.id === "string" ? plan.id : "unknown",
7824
8084
  baseRevision: "unknown",
7825
8085
  liveRevision: "unknown",
7826
8086
  appliedChangeIds: [],
@@ -7838,7 +8098,7 @@ function readTransactionOperations(operation) {
7838
8098
  );
7839
8099
  }
7840
8100
  function readTransactionOperation(value, path) {
7841
- if (!isRecord4(value)) throw new Error(`${path} must be an object`);
8101
+ if (!isRecord3(value)) throw new Error(`${path} must be an object`);
7842
8102
  if (value.type === "create") {
7843
8103
  const options = readRecord(value, "options");
7844
8104
  const schemaId = options ? readRecordString(options, "schemaId") : void 0;
@@ -7931,15 +8191,15 @@ function databaseEntityMatchesId(entity, id) {
7931
8191
  }
7932
8192
  function readRecordArrayProperty(record, key) {
7933
8193
  const value = record[key];
7934
- return Array.isArray(value) ? value.filter(isRecord4) : [];
8194
+ return Array.isArray(value) ? value.filter(isRecord3) : [];
7935
8195
  }
7936
8196
  function isCanvasNode(node) {
7937
8197
  return node.schemaId.includes("/Canvas") || node.schemaId.includes("Canvas@") || Array.isArray(node.properties.objects) || Array.isArray(node.properties.nodes);
7938
8198
  }
7939
8199
  function readCanvasScene(canvas) {
7940
8200
  return {
7941
- objects: (readArrayProperty(canvas, "objects") ?? readArrayProperty(canvas, "nodes") ?? []).filter(isRecord4).map(normalizeCanvasObject),
7942
- edges: (readArrayProperty(canvas, "edges") ?? readArrayProperty(canvas, "connectors") ?? []).filter(isRecord4).map(normalizeCanvasEdge)
8201
+ objects: (readArrayProperty(canvas, "objects") ?? readArrayProperty(canvas, "nodes") ?? []).filter(isRecord3).map(normalizeCanvasObject),
8202
+ edges: (readArrayProperty(canvas, "edges") ?? readArrayProperty(canvas, "connectors") ?? []).filter(isRecord3).map(normalizeCanvasEdge)
7943
8203
  };
7944
8204
  }
7945
8205
  function normalizeCanvasObject(object) {
@@ -8082,8 +8342,8 @@ function canvasObjectFile(object) {
8082
8342
  return readRecordString(object, "file") ?? readRecordString(object, "filePath") ?? readRecordString(properties ?? {}, "file") ?? readRecordString(properties ?? {}, "filePath");
8083
8343
  }
8084
8344
  function jsonCanvasDocumentToCanvasOperations(document) {
8085
- const nodes = Array.isArray(document.nodes) ? document.nodes.filter(isRecord4) : [];
8086
- const edges = Array.isArray(document.edges) ? document.edges.filter(isRecord4) : [];
8345
+ const nodes = Array.isArray(document.nodes) ? document.nodes.filter(isRecord3) : [];
8346
+ const edges = Array.isArray(document.edges) ? document.edges.filter(isRecord3) : [];
8087
8347
  const nodeIds = new Set(nodes.map((node) => readRecordString(node, "id")).filter(Boolean));
8088
8348
  const warnings = edges.flatMap((edge) => {
8089
8349
  const fromNode = readRecordString(edge, "fromNode");
@@ -8315,10 +8575,10 @@ function readOperations(value) {
8315
8575
  throw new Error("operations must contain at least one operation");
8316
8576
  }
8317
8577
  return value.map((operation, index) => {
8318
- if (!isRecord4(operation) || typeof operation.op !== "string") {
8578
+ if (!isRecord3(operation) || typeof operation.op !== "string") {
8319
8579
  throw new Error(`operations[${index}].op must be a string`);
8320
8580
  }
8321
- const args = isRecord4(operation.args) ? operation.args : {};
8581
+ const args = isRecord3(operation.args) ? operation.args : {};
8322
8582
  return createAiOperation(
8323
8583
  operation.op,
8324
8584
  args,
@@ -8326,15 +8586,6 @@ function readOperations(value) {
8326
8586
  );
8327
8587
  });
8328
8588
  }
8329
- function readContextSeeds(value) {
8330
- if (!Array.isArray(value)) return [];
8331
- return value.map((seed) => {
8332
- if (!isRecord4(seed)) return null;
8333
- const kind = typeof seed.kind === "string" ? seed.kind : null;
8334
- const id = typeof seed.id === "string" ? seed.id : null;
8335
- return kind && id ? { kind, id } : null;
8336
- }).filter((seed) => seed !== null);
8337
- }
8338
8589
  function uriForSeed(seed) {
8339
8590
  switch (seed.kind) {
8340
8591
  case "node":
@@ -8394,75 +8645,6 @@ function stringifyTruncatedJson(text3, maxCharacters) {
8394
8645
  function quoteYaml(value) {
8395
8646
  return JSON.stringify(value);
8396
8647
  }
8397
- function readRequiredString(record, key) {
8398
- const value = record[key];
8399
- if (typeof value !== "string" || !value.trim()) {
8400
- throw new Error(`${key} must be a non-empty string`);
8401
- }
8402
- return value;
8403
- }
8404
- function readRequiredRecord(record, key) {
8405
- const value = record[key];
8406
- if (!isRecord4(value)) {
8407
- throw new Error(`${key} must be an object`);
8408
- }
8409
- return value;
8410
- }
8411
- function readRequiredStringArray(value, key) {
8412
- const result = readStringArray(value);
8413
- if (result.length === 0) {
8414
- throw new Error(`${key} must contain at least one string`);
8415
- }
8416
- return result;
8417
- }
8418
- function readStringArray(value) {
8419
- if (!Array.isArray(value)) return [];
8420
- return value.filter((item) => typeof item === "string" && item.trim() !== "");
8421
- }
8422
- function readCsvStringArray(value) {
8423
- if (!value) return [];
8424
- return value.split(",").map((item) => item.trim()).filter(Boolean);
8425
- }
8426
- function readOptionalString(record, key) {
8427
- const value = record[key];
8428
- return typeof value === "string" && value.trim() ? value : void 0;
8429
- }
8430
- function readOptionalRecord(record, key) {
8431
- return readRecord(record, key);
8432
- }
8433
- function readOptionalNumber(record, key) {
8434
- const value = record[key];
8435
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
8436
- }
8437
- function readOptionalBoolean(record, key) {
8438
- const value = record[key];
8439
- return typeof value === "boolean" ? value : void 0;
8440
- }
8441
- function readUrlNumber(params, key) {
8442
- const value = params.get(key);
8443
- if (value === null) return void 0;
8444
- const parsed = Number(value);
8445
- return Number.isFinite(parsed) ? parsed : void 0;
8446
- }
8447
- function readRecordString(record, key) {
8448
- const value = record[key];
8449
- return typeof value === "string" && value.trim() ? value : void 0;
8450
- }
8451
- function readRecord(record, key) {
8452
- const value = record[key];
8453
- return isRecord4(value) ? value : void 0;
8454
- }
8455
- function readRecordNumber(record, key) {
8456
- const value = record[key];
8457
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
8458
- }
8459
- function readRecordBoolean(record, key) {
8460
- const value = record[key];
8461
- return typeof value === "boolean" ? value : void 0;
8462
- }
8463
- function isRecord4(value) {
8464
- return typeof value === "object" && value !== null && !Array.isArray(value);
8465
- }
8466
8648
 
8467
8649
  // src/sandbox/ast-validator.ts
8468
8650
  import * as acorn from "acorn";