@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.
@@ -180,6 +180,86 @@ function hasWriteScope(plan) {
180
180
  );
181
181
  }
182
182
 
183
+ // src/ai-surface/args.ts
184
+ function readRequiredString(record, key) {
185
+ const value = record[key];
186
+ if (typeof value !== "string" || !value.trim()) {
187
+ throw new Error(`${key} must be a non-empty string`);
188
+ }
189
+ return value;
190
+ }
191
+ function readRequiredRecord(record, key) {
192
+ const value = record[key];
193
+ if (!isRecord2(value)) {
194
+ throw new Error(`${key} must be an object`);
195
+ }
196
+ return value;
197
+ }
198
+ function readRequiredStringArray(value, key) {
199
+ const result = readStringArray(value);
200
+ if (result.length === 0) {
201
+ throw new Error(`${key} must contain at least one string`);
202
+ }
203
+ return result;
204
+ }
205
+ function readStringArray(value) {
206
+ if (!Array.isArray(value)) return [];
207
+ return value.filter((item) => typeof item === "string" && item.trim() !== "");
208
+ }
209
+ function readCsvStringArray(value) {
210
+ if (!value) return [];
211
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
212
+ }
213
+ function readOptionalString(record, key) {
214
+ const value = record[key];
215
+ return typeof value === "string" && value.trim() ? value : void 0;
216
+ }
217
+ function readOptionalRecord(record, key) {
218
+ return readRecord(record, key);
219
+ }
220
+ function readOptionalNumber(record, key) {
221
+ const value = record[key];
222
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
223
+ }
224
+ function readOptionalBoolean(record, key) {
225
+ const value = record[key];
226
+ return typeof value === "boolean" ? value : void 0;
227
+ }
228
+ function readUrlNumber(params, key) {
229
+ const value = params.get(key);
230
+ if (value === null) return void 0;
231
+ const parsed = Number(value);
232
+ return Number.isFinite(parsed) ? parsed : void 0;
233
+ }
234
+ function readRecordString(record, key) {
235
+ const value = record[key];
236
+ return typeof value === "string" && value.trim() ? value : void 0;
237
+ }
238
+ function readRecord(record, key) {
239
+ const value = record[key];
240
+ return isRecord2(value) ? value : void 0;
241
+ }
242
+ function readRecordNumber(record, key) {
243
+ const value = record[key];
244
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
245
+ }
246
+ function readRecordBoolean(record, key) {
247
+ const value = record[key];
248
+ return typeof value === "boolean" ? value : void 0;
249
+ }
250
+ function isRecord2(value) {
251
+ return typeof value === "object" && value !== null && !Array.isArray(value);
252
+ }
253
+ function readContextSeeds(value) {
254
+ if (!Array.isArray(value)) return [];
255
+ return value.map((seed) => {
256
+ if (!isRecord2(seed)) return null;
257
+ const kind = typeof seed.kind === "string" ? seed.kind : null;
258
+ const id = typeof seed.id === "string" ? seed.id : null;
259
+ return kind && id ? { kind, id } : null;
260
+ }).filter((seed) => seed !== null);
261
+ }
262
+
183
263
  // src/ai-surface/page-markdown.ts
184
264
  var XNET_MARKDOWN_DIRECTIVE_SPECS = [
185
265
  {
@@ -415,7 +495,7 @@ function findJsonPayloadEnd(src, startIndex) {
415
495
  function parseJsonObject(rawPayload, label, errors) {
416
496
  try {
417
497
  const parsed = JSON.parse(rawPayload);
418
- if (isRecord2(parsed)) return parsed;
498
+ if (isRecord3(parsed)) return parsed;
419
499
  errors.push(`${label} payload must be a JSON object`);
420
500
  return null;
421
501
  } catch {
@@ -449,7 +529,7 @@ function unquoteYamlScalar(value) {
449
529
  return trimmed.slice(1, -1);
450
530
  }
451
531
  }
452
- function isRecord2(value) {
532
+ function isRecord3(value) {
453
533
  return typeof value === "object" && value !== null && !Array.isArray(value);
454
534
  }
455
535
  function markdownDiffPrefix(kind) {
@@ -458,900 +538,1094 @@ function markdownDiffPrefix(kind) {
458
538
  return " ";
459
539
  }
460
540
 
461
- // src/ai-surface/service.ts
462
- var DEFAULT_LIMITS = {
463
- maxListLimit: 100,
464
- maxSearchScan: 500,
465
- maxSearchResults: 20,
466
- maxContextResources: 12,
467
- maxCharactersPerResource: 12e3,
468
- maxJsonCharacters: 24e3,
469
- maxCanvasObjects: 200,
470
- maxDatabaseRows: 100
471
- };
472
- var AiSurfaceService = class {
473
- constructor(config) {
474
- this.config = config;
475
- this.limits = { ...DEFAULT_LIMITS, ...config.limits };
476
- this.clock = config.clock ?? (() => /* @__PURE__ */ new Date());
477
- for (const tool of config.extraTools ?? []) {
478
- if (!this.extraTools.has(tool.name)) this.extraTools.set(tool.name, tool);
541
+ // src/ai-surface/resources/router.ts
542
+ var XNET_URI_PREFIX = "xnet://";
543
+ var PARAM_SEGMENT_PATTERN = /^\{([a-zA-Z][a-zA-Z0-9]*)\}(.*)$/;
544
+ function createAiResourceRouter() {
545
+ const routes = [];
546
+ const router = {
547
+ register(template, handler) {
548
+ routes.push(compileRoute(template, handler));
549
+ return router;
550
+ },
551
+ async resolve(host, uri) {
552
+ const parsed = parseXNetUri(uri);
553
+ for (const route of routes) {
554
+ const params = matchRoute(route, parsed);
555
+ if (params) {
556
+ return await route.handler(host, { uri, params, searchParams: parsed.searchParams });
557
+ }
558
+ }
559
+ throw new Error(`Resource not found: ${uri}`);
479
560
  }
561
+ };
562
+ return router;
563
+ }
564
+ function parseXNetUri(uri) {
565
+ let parsed;
566
+ try {
567
+ parsed = new URL(uri);
568
+ } catch {
569
+ throw new Error(`Invalid xNet resource URI: ${uri}`);
480
570
  }
481
- limits;
482
- clock;
483
- sequence = 0;
484
- auditEvents = [];
485
- rollbackSnapshots = /* @__PURE__ */ new Map();
486
- /** Contributed tools by name (exploration 0196), de-duped at construction. */
487
- extraTools = /* @__PURE__ */ new Map();
488
- /** The contributed (non-built-in) tools, with built-in name collisions removed. */
489
- getExtraTools() {
490
- const builtIn = new Set(this.builtInTools().map((t) => t.name));
491
- return [...this.extraTools.values()].filter((t) => !builtIn.has(t.name));
571
+ if (parsed.protocol !== "xnet:") {
572
+ throw new Error(`Invalid xNet resource URI: ${uri}`);
492
573
  }
493
- getResources() {
494
- return [
495
- createResource(
496
- "xnet://workspace/summary",
497
- "Workspace Summary",
498
- "High-level schema counts, recent nodes, and AI surface capabilities.",
499
- "application/json",
500
- "low",
501
- ["workspace.read"]
502
- ),
503
- createResource(
504
- "xnet://workspace/recent",
505
- "Recent Workspace Nodes",
506
- "Recent nodes with ids, schemas, titles, and revisions.",
507
- "application/json",
508
- "low",
509
- ["workspace.read"]
510
- ),
511
- createResource(
512
- "xnet://nodes",
513
- "All Nodes",
514
- "Limited list of nodes in the local store.",
515
- "application/json",
516
- "low",
517
- ["workspace.read"]
518
- ),
519
- createResource(
520
- "xnet://schemas",
521
- "All Schemas",
522
- "List of available schemas and property metadata.",
523
- "application/json",
524
- "low",
525
- ["workspace.read"]
526
- ),
527
- createResource(
528
- "xnet://page/{pageId}.md",
529
- "Page Markdown",
530
- "Markdown projection for a page, including xNet frontmatter identity.",
531
- "text/markdown",
532
- "low",
533
- ["page.read"],
534
- true
535
- ),
536
- createResource(
537
- "xnet://page/{pageId}/outline",
538
- "Page Outline",
539
- "Page heading outline extracted from the Markdown projection.",
540
- "application/json",
541
- "low",
542
- ["page.read"],
543
- true
544
- ),
545
- createResource(
546
- "xnet://database/{databaseId}/schema",
547
- "Database Schema",
548
- "Database node metadata, declared properties, and known column descriptors.",
549
- "application/json",
550
- "low",
551
- ["database.read"],
552
- true
553
- ),
554
- createResource(
555
- "xnet://database/{databaseId}/views",
556
- "Database Views",
557
- "Known database view descriptors from the database node projection.",
558
- "application/json",
559
- "low",
560
- ["database.read"],
561
- true
562
- ),
563
- createResource(
564
- "xnet://database/{databaseId}/sample?limit=10",
565
- "Database Sample Rows",
566
- "Bounded sample of rows for a database using NodeQueryDescriptor semantics.",
567
- "application/json",
568
- "low",
569
- ["database.read", "database.query"],
570
- true
571
- ),
572
- createResource(
573
- "xnet://canvas/{canvasId}/viewport?x=0&y=0&w=1000&h=800",
574
- "Canvas Viewport",
575
- "Viewport-scoped canvas objects and edges.",
576
- "application/json",
577
- "low",
578
- ["canvas.read"],
579
- true
580
- ),
581
- createResource(
582
- "xnet://canvas/{canvasId}/objects",
583
- "Canvas Objects",
584
- "Bounded list of canvas objects and connectors.",
585
- "application/json",
586
- "low",
587
- ["canvas.read"],
588
- true
589
- ),
590
- createResource(
591
- "xnet://canvas/{canvasId}/selection?ids=object-1,object-2",
592
- "Canvas Selection",
593
- "Selection-scoped canvas objects, edges, and source previews.",
594
- "application/json",
595
- "low",
596
- ["canvas.read"],
597
- true
598
- ),
599
- createResource(
600
- "xnet://canvas/{canvasId}/json-canvas",
601
- "Canvas JSON Canvas",
602
- "JSON Canvas projection with xNet source metadata sidecars.",
603
- "application/json",
604
- "low",
605
- ["canvas.read"],
606
- true
607
- )
608
- ];
574
+ return {
575
+ host: parsed.hostname,
576
+ parts: parsed.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part)),
577
+ searchParams: parsed.searchParams
578
+ };
579
+ }
580
+ function compileRoute(template, handler) {
581
+ if (!template.startsWith(XNET_URI_PREFIX)) {
582
+ throw new Error(`Resource route template must start with ${XNET_URI_PREFIX}: ${template}`);
609
583
  }
610
- /** The full tool surface: built-in `xnet_*` tools plus contributed extras. */
611
- getTools() {
612
- const extras = this.getExtraTools().map(
613
- ({ invoke: _invoke, ...def }) => def
614
- );
615
- return [...this.builtInTools(), ...extras];
584
+ const [path] = template.slice(XNET_URI_PREFIX.length).split("?");
585
+ const [host, ...rawSegments] = path.split("/").filter(Boolean);
586
+ if (!host) {
587
+ throw new Error(`Resource route template must include a host: ${template}`);
616
588
  }
617
- builtInTools() {
618
- return [
619
- {
620
- name: "xnet_search",
621
- title: "Search xNet workspace",
622
- description: "Search node titles and searchable properties with pagination and limits.",
623
- risk: "low",
624
- requiredScopes: ["workspace.search"],
625
- inputSchema: {
626
- type: "object",
627
- properties: {
628
- query: { type: "string", description: "Search text." },
629
- schemaId: { type: "string", description: "Optional schema IRI filter." },
630
- limit: { type: "number", description: "Maximum result count." },
631
- offset: { type: "number", description: "Result offset for pagination." }
632
- },
633
- required: ["query"]
634
- }
635
- },
636
- {
637
- name: "xnet_graph_expand",
638
- title: "Expand a node along its relations",
639
- 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.",
640
- risk: "low",
641
- requiredScopes: ["workspace.read"],
642
- inputSchema: {
643
- type: "object",
644
- properties: {
645
- nodeId: { type: "string", description: "The node to expand from." },
646
- hops: {
647
- type: "number",
648
- description: "How many relation hops to walk (1\u20132, default 1)."
649
- },
650
- limit: { type: "number", description: "Maximum neighbors to return." }
651
- },
652
- required: ["nodeId"]
653
- }
589
+ return {
590
+ host,
591
+ segments: rawSegments.map((segment) => {
592
+ const param = PARAM_SEGMENT_PATTERN.exec(segment);
593
+ return param ? { kind: "param", name: param[1], suffix: param[2] } : { kind: "literal", value: segment };
594
+ }),
595
+ handler
596
+ };
597
+ }
598
+ function matchRoute(route, parsed) {
599
+ if (parsed.host !== route.host) return null;
600
+ if (parsed.parts.length !== route.segments.length) return null;
601
+ const params = {};
602
+ for (const [index, segment] of route.segments.entries()) {
603
+ const part = parsed.parts[index];
604
+ if (segment.kind === "literal") {
605
+ if (part !== segment.value) return null;
606
+ continue;
607
+ }
608
+ if (segment.suffix && !part.endsWith(segment.suffix)) return null;
609
+ params[segment.name] = segment.suffix ? part.slice(0, -segment.suffix.length) : part;
610
+ }
611
+ return params;
612
+ }
613
+
614
+ // src/ai-surface/resources/routes.ts
615
+ function createBuiltInResourceRouter() {
616
+ return createAiResourceRouter().register(
617
+ "xnet://nodes",
618
+ async (host, { uri }) => host.jsonResource(uri, await host.listNodes())
619
+ ).register(
620
+ "xnet://schemas",
621
+ async (host, { uri }) => host.jsonResource(uri, await host.listSchemas())
622
+ ).register(
623
+ "xnet://workspace/summary",
624
+ async (host, { uri }) => host.jsonResource(uri, await host.getWorkspaceSummary())
625
+ ).register(
626
+ "xnet://workspace/recent",
627
+ async (host, { uri }) => host.jsonResource(uri, await host.getRecentNodes())
628
+ ).register(
629
+ "xnet://workspace/search",
630
+ async (host, { uri, searchParams }) => host.jsonResource(
631
+ uri,
632
+ await host.search({
633
+ query: searchParams.get("q") ?? "",
634
+ schemaId: searchParams.get("schema") ?? void 0,
635
+ limit: readUrlNumber(searchParams, "limit"),
636
+ offset: readUrlNumber(searchParams, "offset")
637
+ })
638
+ )
639
+ ).register(
640
+ "xnet://node/{nodeId}",
641
+ async (host, { uri, params }) => host.jsonResource(uri, await host.getNodeProjection(params.nodeId))
642
+ ).register(
643
+ "xnet://page/{pageId}.md",
644
+ async (host, { uri, params }) => await host.readPageMarkdown(params.pageId, true, uri)
645
+ ).register(
646
+ "xnet://page/{pageId}",
647
+ async (host, { uri, params }) => await host.readPageMarkdown(params.pageId, true, uri)
648
+ ).register(
649
+ "xnet://page/{pageId}/outline",
650
+ async (host, { uri, params }) => host.jsonResource(uri, await host.readPageOutline(params.pageId))
651
+ ).register(
652
+ "xnet://page/{pageId}/context-pack",
653
+ async (host, { uri, params }) => host.jsonResource(
654
+ uri,
655
+ await host.createContextPack({ seeds: [{ kind: "page", id: params.pageId }] })
656
+ )
657
+ ).register(
658
+ "xnet://database/{databaseId}/schema",
659
+ async (host, { uri, params }) => host.jsonResource(uri, await host.describeDatabase(params.databaseId))
660
+ ).register(
661
+ "xnet://database/{databaseId}/views",
662
+ async (host, { uri, params }) => host.jsonResource(uri, await host.readDatabaseViews(params.databaseId))
663
+ ).register(
664
+ "xnet://database/{databaseId}/sample",
665
+ async (host, { uri, params, searchParams }) => host.jsonResource(
666
+ uri,
667
+ await host.sampleDatabase({
668
+ databaseId: params.databaseId,
669
+ sampleSize: readUrlNumber(searchParams, "limit")
670
+ })
671
+ )
672
+ ).register(
673
+ "xnet://database/{databaseId}/query",
674
+ async (host, { uri, params, searchParams }) => host.jsonResource(
675
+ uri,
676
+ await host.queryDatabase({
677
+ databaseId: params.databaseId,
678
+ schemaId: searchParams.get("schema") ?? void 0,
679
+ search: searchParams.get("q") ?? void 0,
680
+ materializedView: searchParams.get("view") ? { viewId: searchParams.get("view") ?? "" } : void 0,
681
+ limit: readUrlNumber(searchParams, "limit"),
682
+ offset: readUrlNumber(searchParams, "offset")
683
+ })
684
+ )
685
+ ).register(
686
+ "xnet://canvas/{canvasId}/viewport",
687
+ async (host, { uri, params, searchParams }) => host.jsonResource(
688
+ uri,
689
+ await host.readCanvasViewport({
690
+ canvasId: params.canvasId,
691
+ x: readUrlNumber(searchParams, "x"),
692
+ y: readUrlNumber(searchParams, "y"),
693
+ w: readUrlNumber(searchParams, "w"),
694
+ h: readUrlNumber(searchParams, "h"),
695
+ tileSize: readUrlNumber(searchParams, "tileSize"),
696
+ tileIds: readCsvStringArray(searchParams.get("tileIds")),
697
+ includeSourcePreviews: searchParams.get("includeSourcePreviews") === "true"
698
+ })
699
+ )
700
+ ).register(
701
+ "xnet://canvas/{canvasId}/objects",
702
+ async (host, { uri, params }) => host.jsonResource(uri, await host.readCanvasObjects(params.canvasId))
703
+ ).register(
704
+ "xnet://canvas/{canvasId}/selection",
705
+ async (host, { uri, params, searchParams }) => host.jsonResource(
706
+ uri,
707
+ await host.readCanvasSelection({
708
+ canvasId: params.canvasId,
709
+ objectIds: readCsvStringArray(searchParams.get("ids")),
710
+ includeSourcePreviews: searchParams.get("includeSourcePreviews") !== "false"
711
+ })
712
+ )
713
+ ).register(
714
+ "xnet://canvas/{canvasId}/json-canvas",
715
+ async (host, { uri, params, searchParams }) => host.jsonResource(
716
+ uri,
717
+ await host.exportCanvasJsonCanvas({
718
+ canvasId: params.canvasId,
719
+ includeXNetMetadata: searchParams.get("includeXNetMetadata") !== "false"
720
+ })
721
+ )
722
+ ).register(
723
+ "xnet://canvas/{canvasId}/object/{objectId}",
724
+ async (host, { uri, params }) => host.jsonResource(uri, await host.readCanvasObject(params.canvasId, params.objectId))
725
+ );
726
+ }
727
+
728
+ // src/ai-surface/tools/audit.ts
729
+ var getAuditLogTool = {
730
+ definition: {
731
+ name: "xnet_get_audit_log",
732
+ title: "Read AI audit log",
733
+ description: "Read recent AI mutation audit events with optional plan filtering.",
734
+ risk: "low",
735
+ requiredScopes: ["workspace.read"],
736
+ inputSchema: {
737
+ type: "object",
738
+ properties: {
739
+ planId: { type: "string", description: "Optional mutation plan id filter." },
740
+ limit: { type: "number", description: "Maximum audit events to return." }
741
+ }
742
+ }
743
+ },
744
+ execute: (host, args) => host.getAuditLog({
745
+ planId: readOptionalString(args, "planId"),
746
+ limit: readOptionalNumber(args, "limit")
747
+ })
748
+ };
749
+ var validateMutationPlanTool = {
750
+ definition: {
751
+ name: "xnet_validate_mutation_plan",
752
+ title: "Validate mutation plan",
753
+ description: "Validate a serialized mutation plan and return errors or warnings.",
754
+ risk: "medium",
755
+ requiredScopes: ["workspace.read"],
756
+ inputSchema: {
757
+ type: "object",
758
+ properties: {
759
+ plan: { type: "object", description: "Mutation plan object to validate." }
654
760
  },
655
- {
656
- name: "xnet_create_context_pack",
657
- title: "Create context pack",
658
- description: "Create a bounded context pack from seeds and optional search results.",
659
- risk: "low",
660
- requiredScopes: ["workspace.read", "workspace.search"],
661
- inputSchema: {
662
- type: "object",
663
- properties: {
664
- query: { type: "string", description: "Optional search query." },
665
- seeds: {
666
- type: "array",
667
- description: "Seed resources such as pages, databases, canvases, or nodes.",
668
- items: {
669
- type: "object",
670
- properties: {
671
- kind: { type: "string", description: "Seed kind." },
672
- id: { type: "string", description: "Seed id." }
673
- }
674
- }
675
- },
676
- limit: { type: "number", description: "Maximum resources to include." }
677
- }
761
+ required: ["plan"]
762
+ }
763
+ },
764
+ execute: (_host, args) => {
765
+ const validation = validateAiMutationPlan(args.plan);
766
+ return { validation };
767
+ }
768
+ };
769
+
770
+ // src/ai-surface/tools/canvas.ts
771
+ var canvasListTool = {
772
+ definition: {
773
+ name: "xnet_canvas_list",
774
+ title: "List canvases",
775
+ description: "List canvas nodes visible to the AI surface.",
776
+ risk: "low",
777
+ requiredScopes: ["canvas.read"],
778
+ inputSchema: {
779
+ type: "object",
780
+ properties: {
781
+ limit: { type: "number", description: "Maximum canvas count." },
782
+ offset: { type: "number", description: "Canvas offset." }
783
+ }
784
+ }
785
+ },
786
+ execute: async (host, args) => await host.listCanvases({
787
+ limit: readOptionalNumber(args, "limit"),
788
+ offset: readOptionalNumber(args, "offset")
789
+ })
790
+ };
791
+ var canvasReadViewportTool = {
792
+ definition: {
793
+ name: "xnet_canvas_read_viewport",
794
+ title: "Read canvas viewport",
795
+ description: "Read canvas objects and edges intersecting a viewport.",
796
+ risk: "low",
797
+ requiredScopes: ["canvas.read"],
798
+ inputSchema: {
799
+ type: "object",
800
+ properties: {
801
+ canvasId: { type: "string", description: "Canvas node id." },
802
+ x: { type: "number", description: "Viewport x." },
803
+ y: { type: "number", description: "Viewport y." },
804
+ w: { type: "number", description: "Viewport width." },
805
+ h: { type: "number", description: "Viewport height." },
806
+ includeSourcePreviews: {
807
+ type: "boolean",
808
+ description: "Include previews for source-backed objects."
809
+ },
810
+ tileSize: { type: "number", description: "Optional tile size for tile scoping." },
811
+ tileIds: {
812
+ type: "array",
813
+ description: "Optional tile ids such as 0/1/-2 to constrain the read.",
814
+ items: { type: "string" }
678
815
  }
679
816
  },
680
- {
681
- name: "xnet_create_external_context_resource",
682
- title: "Create untrusted external context resource",
683
- description: "Wrap externally fetched content as an untrusted context-pack resource with an explicit instruction boundary.",
684
- risk: "medium",
685
- requiredScopes: ["network.fetch"],
686
- inputSchema: {
687
- type: "object",
688
- properties: {
689
- url: { type: "string", description: "External source URL." },
690
- text: { type: "string", description: "Fetched external text content." },
691
- mimeType: { type: "string", description: "Source MIME type. Defaults to text/plain." }
692
- },
693
- required: ["url", "text"]
817
+ required: ["canvasId"]
818
+ }
819
+ },
820
+ execute: async (host, args) => await host.readCanvasViewport({
821
+ canvasId: readRequiredString(args, "canvasId"),
822
+ x: readOptionalNumber(args, "x"),
823
+ y: readOptionalNumber(args, "y"),
824
+ w: readOptionalNumber(args, "w"),
825
+ h: readOptionalNumber(args, "h"),
826
+ tileSize: readOptionalNumber(args, "tileSize"),
827
+ tileIds: readStringArray(args.tileIds),
828
+ includeSourcePreviews: readOptionalBoolean(args, "includeSourcePreviews") ?? false
829
+ })
830
+ };
831
+ var canvasReadSelectionTool = {
832
+ definition: {
833
+ name: "xnet_canvas_read_selection",
834
+ title: "Read canvas selection",
835
+ description: "Read selected canvas objects, connected edges, and optional source previews.",
836
+ risk: "low",
837
+ requiredScopes: ["canvas.read"],
838
+ inputSchema: {
839
+ type: "object",
840
+ properties: {
841
+ canvasId: { type: "string", description: "Canvas node id." },
842
+ objectIds: {
843
+ type: "array",
844
+ description: "Selected object ids.",
845
+ items: { type: "string" }
846
+ },
847
+ includeSourcePreviews: {
848
+ type: "boolean",
849
+ description: "Include previews for source-backed objects."
694
850
  }
695
851
  },
696
- {
697
- name: "xnet_read_page_markdown",
698
- title: "Read page Markdown",
699
- description: "Read a page as Markdown with optional xNet frontmatter.",
700
- risk: "low",
701
- requiredScopes: ["page.read"],
702
- inputSchema: {
703
- type: "object",
704
- properties: {
705
- pageId: { type: "string", description: "Page node id." },
706
- includeFrontmatter: {
707
- type: "boolean",
708
- description: "Include xNet identity frontmatter. Defaults to true."
709
- }
710
- },
711
- required: ["pageId"]
712
- }
852
+ required: ["canvasId", "objectIds"]
853
+ }
854
+ },
855
+ execute: async (host, args) => await host.readCanvasSelection({
856
+ canvasId: readRequiredString(args, "canvasId"),
857
+ objectIds: readRequiredStringArray(args.objectIds, "objectIds"),
858
+ includeSourcePreviews: readOptionalBoolean(args, "includeSourcePreviews") ?? false
859
+ })
860
+ };
861
+ var canvasSearchTool = {
862
+ definition: {
863
+ name: "xnet_canvas_search",
864
+ title: "Search canvas",
865
+ description: "Search canvas object text, labels, ids, and source metadata.",
866
+ risk: "low",
867
+ requiredScopes: ["canvas.read"],
868
+ inputSchema: {
869
+ type: "object",
870
+ properties: {
871
+ canvasId: { type: "string", description: "Canvas node id." },
872
+ query: { type: "string", description: "Search text." },
873
+ limit: { type: "number", description: "Maximum result count." }
713
874
  },
714
- {
715
- name: "xnet_validate_page_markdown",
716
- title: "Validate page Markdown",
717
- description: "Validate xNet page frontmatter and supported xNet Markdown directives.",
718
- risk: "low",
719
- requiredScopes: ["page.read"],
720
- inputSchema: {
721
- type: "object",
722
- properties: {
723
- pageId: { type: "string", description: "Optional target page node id." },
724
- baseRevision: { type: "string", description: "Optional expected base revision." },
725
- markdown: { type: "string", description: "Markdown to validate." }
726
- },
727
- required: ["markdown"]
728
- }
875
+ required: ["canvasId", "query"]
876
+ }
877
+ },
878
+ execute: async (host, args) => await host.searchCanvas({
879
+ canvasId: readRequiredString(args, "canvasId"),
880
+ query: readRequiredString(args, "query"),
881
+ limit: readOptionalNumber(args, "limit")
882
+ })
883
+ };
884
+ var canvasExportJsonCanvasTool = {
885
+ definition: {
886
+ name: "xnet_canvas_export_json_canvas",
887
+ title: "Export canvas as JSON Canvas",
888
+ description: "Export a canvas or viewport as JSON Canvas with xNet source metadata.",
889
+ risk: "low",
890
+ requiredScopes: ["canvas.read"],
891
+ inputSchema: {
892
+ type: "object",
893
+ properties: {
894
+ canvasId: { type: "string", description: "Canvas node id." },
895
+ includeXNetMetadata: {
896
+ type: "boolean",
897
+ description: "Include xNet source metadata. Defaults to true."
898
+ },
899
+ x: { type: "number", description: "Optional viewport x." },
900
+ y: { type: "number", description: "Optional viewport y." },
901
+ w: { type: "number", description: "Optional viewport width." },
902
+ h: { type: "number", description: "Optional viewport height." }
729
903
  },
730
- {
731
- name: "xnet_plan_page_patch",
732
- title: "Plan page Markdown patch",
733
- description: "Validate an edited Markdown page and return a mutation plan without applying it.",
734
- risk: "medium",
735
- requiredScopes: ["page.read", "page.propose"],
736
- inputSchema: {
737
- type: "object",
738
- properties: {
739
- pageId: { type: "string", description: "Page node id." },
740
- baseRevision: { type: "string", description: "Revision the patch was based on." },
741
- markdown: { type: "string", description: "Proposed full Markdown replacement." },
742
- intent: { type: "string", description: "User or agent intent for the patch." },
743
- actor: { type: "string", description: "Agent or user creating the plan." }
744
- },
745
- required: ["pageId", "markdown"]
746
- }
904
+ required: ["canvasId"]
905
+ }
906
+ },
907
+ execute: async (host, args) => await host.exportCanvasJsonCanvas({
908
+ canvasId: readRequiredString(args, "canvasId"),
909
+ includeXNetMetadata: readOptionalBoolean(args, "includeXNetMetadata") ?? true,
910
+ x: readOptionalNumber(args, "x"),
911
+ y: readOptionalNumber(args, "y"),
912
+ w: readOptionalNumber(args, "w"),
913
+ h: readOptionalNumber(args, "h")
914
+ })
915
+ };
916
+ var canvasPlanJsonCanvasImportTool = {
917
+ definition: {
918
+ name: "xnet_canvas_plan_json_canvas_import",
919
+ title: "Plan JSON Canvas import",
920
+ description: "Convert a JSON Canvas document into a plan-only canvas mutation.",
921
+ risk: "medium",
922
+ requiredScopes: ["canvas.read", "canvas.propose"],
923
+ inputSchema: {
924
+ type: "object",
925
+ properties: {
926
+ canvasId: { type: "string", description: "Canvas node id." },
927
+ document: { type: "object", description: "JSON Canvas document." },
928
+ baseRevision: { type: "string", description: "Revision the import was based on." },
929
+ actor: { type: "string", description: "Agent or user creating the plan." },
930
+ intent: { type: "string", description: "User or agent intent for the import." }
747
931
  },
748
- {
749
- name: "xnet_apply_page_markdown",
750
- title: "Apply page Markdown plan",
751
- description: "Apply a validated page Markdown mutation plan through the configured TipTap/Yjs document adapter, with a node-property fallback.",
752
- risk: "high",
753
- requiredScopes: ["page.read", "page.write"],
754
- inputSchema: {
755
- type: "object",
756
- properties: {
757
- plan: { type: "object", description: "Validated page Markdown mutation plan." },
758
- confirmApply: {
759
- type: "boolean",
760
- description: "Must be true to apply the page Markdown plan."
761
- },
762
- allowStale: {
763
- type: "boolean",
764
- description: "Allow applying when the plan base revision differs from the live node."
765
- }
766
- },
767
- required: ["plan", "confirmApply"]
768
- }
932
+ required: ["canvasId", "document"]
933
+ }
934
+ },
935
+ execute: async (host, args) => await host.planCanvasJsonCanvasImport(args)
936
+ };
937
+ var planCanvasMutationTool = {
938
+ definition: {
939
+ name: "xnet_plan_canvas_mutation",
940
+ title: "Plan canvas mutation",
941
+ description: "Create a canvas mutation plan for later review without applying it.",
942
+ risk: "medium",
943
+ requiredScopes: ["canvas.read", "canvas.propose"],
944
+ inputSchema: {
945
+ type: "object",
946
+ properties: {
947
+ canvasId: { type: "string", description: "Canvas node id." },
948
+ baseRevision: { type: "string", description: "Revision the mutation was based on." },
949
+ operations: { type: "array", description: "Canvas operations to validate." },
950
+ intent: { type: "string", description: "User or agent intent for the mutation." },
951
+ actor: { type: "string", description: "Agent or user creating the plan." }
769
952
  },
770
- {
771
- name: "xnet_get_audit_log",
772
- title: "Read AI audit log",
773
- description: "Read recent AI mutation audit events with optional plan filtering.",
774
- risk: "low",
775
- requiredScopes: ["workspace.read"],
776
- inputSchema: {
777
- type: "object",
778
- properties: {
779
- planId: { type: "string", description: "Optional mutation plan id filter." },
780
- limit: { type: "number", description: "Maximum audit events to return." }
781
- }
953
+ required: ["canvasId", "operations"]
954
+ }
955
+ },
956
+ execute: async (host, args) => await host.planCanvasMutation(args)
957
+ };
958
+ var canvasToolEntries = [
959
+ canvasListTool,
960
+ canvasReadViewportTool,
961
+ canvasReadSelectionTool,
962
+ canvasSearchTool,
963
+ canvasExportJsonCanvasTool,
964
+ canvasPlanJsonCanvasImportTool,
965
+ planCanvasMutationTool
966
+ ];
967
+
968
+ // src/ai-surface/tools/database.ts
969
+ var databaseDescribeTool = {
970
+ definition: {
971
+ name: "xnet_database_describe",
972
+ title: "Describe database",
973
+ description: "Describe database schema, columns, views, row schema, and row counts.",
974
+ risk: "low",
975
+ requiredScopes: ["database.read"],
976
+ inputSchema: {
977
+ type: "object",
978
+ properties: {
979
+ databaseId: { type: "string", description: "Database node id." },
980
+ includeSample: {
981
+ type: "boolean",
982
+ description: "Include a small descriptor-backed row sample."
782
983
  }
783
984
  },
784
- {
785
- name: "xnet_rollback_page_markdown",
786
- title: "Rollback page Markdown apply",
787
- description: "Rollback a previously applied page Markdown plan by rollback handle.",
788
- risk: "high",
789
- requiredScopes: ["page.write"],
790
- inputSchema: {
985
+ required: ["databaseId"]
986
+ }
987
+ },
988
+ execute: async (host, args) => await host.describeDatabase(readRequiredString(args, "databaseId"), {
989
+ includeSample: readOptionalBoolean(args, "includeSample") ?? false
990
+ })
991
+ };
992
+ var databaseQueryTool = {
993
+ definition: {
994
+ name: "xnet_database_query",
995
+ title: "Query database rows",
996
+ description: "Read a bounded page of database rows using NodeQueryDescriptor-compatible options.",
997
+ risk: "low",
998
+ requiredScopes: ["database.read", "database.query"],
999
+ inputSchema: {
1000
+ type: "object",
1001
+ properties: {
1002
+ databaseId: { type: "string", description: "Database node id." },
1003
+ schemaId: { type: "string", description: "Optional row schema IRI." },
1004
+ descriptor: {
791
1005
  type: "object",
792
- properties: {
793
- rollbackHandle: { type: "string", description: "Rollback handle from apply result." },
794
- confirmRollback: {
795
- type: "boolean",
796
- description: "Must be true to perform the rollback."
797
- }
798
- },
799
- required: ["rollbackHandle", "confirmRollback"]
800
- }
801
- },
802
- {
803
- name: "xnet_database_describe",
804
- title: "Describe database",
805
- description: "Describe database schema, columns, views, row schema, and row counts.",
806
- risk: "low",
807
- requiredScopes: ["database.read"],
808
- inputSchema: {
1006
+ description: "Optional NodeQueryDescriptor-compatible query shape."
1007
+ },
1008
+ where: {
809
1009
  type: "object",
810
- properties: {
811
- databaseId: { type: "string", description: "Database node id." },
812
- includeSample: {
813
- type: "boolean",
814
- description: "Include a small descriptor-backed row sample."
815
- }
816
- },
817
- required: ["databaseId"]
818
- }
819
- },
820
- {
821
- name: "xnet_database_query",
822
- title: "Query database rows",
823
- description: "Read a bounded page of database rows using NodeQueryDescriptor-compatible options.",
824
- risk: "low",
825
- requiredScopes: ["database.read", "database.query"],
826
- inputSchema: {
1010
+ description: "Optional exact property filters for row nodes."
1011
+ },
1012
+ search: {
827
1013
  type: "object",
828
- properties: {
829
- databaseId: { type: "string", description: "Database node id." },
830
- schemaId: { type: "string", description: "Optional row schema IRI." },
831
- descriptor: {
832
- type: "object",
833
- description: "Optional NodeQueryDescriptor-compatible query shape."
834
- },
835
- where: {
836
- type: "object",
837
- description: "Optional exact property filters for row nodes."
838
- },
839
- search: {
840
- type: "object",
841
- description: "Optional NodeQueryDescriptor search filter."
842
- },
843
- orderBy: {
844
- type: "object",
845
- description: "Optional NodeQueryDescriptor order map."
846
- },
847
- materializedView: {
848
- type: "object",
849
- description: "Optional materialized view query options."
850
- },
851
- count: { type: "string", description: "Page count mode: exact, estimate, or none." },
852
- limit: { type: "number", description: "Maximum row count." },
853
- offset: { type: "number", description: "Row offset." }
854
- },
855
- required: ["databaseId"]
856
- }
857
- },
858
- {
859
- name: "xnet_database_sample",
860
- title: "Sample database rows",
861
- description: "Return a small deterministic sample for schema and content inspection.",
862
- risk: "low",
863
- requiredScopes: ["database.read", "database.query"],
864
- inputSchema: {
1014
+ description: "Optional NodeQueryDescriptor search filter."
1015
+ },
1016
+ orderBy: {
865
1017
  type: "object",
866
- properties: {
867
- databaseId: { type: "string", description: "Database node id." },
868
- schemaId: { type: "string", description: "Optional row schema IRI." },
869
- sampleSize: { type: "number", description: "Sample row count." },
870
- descriptor: {
871
- type: "object",
872
- description: "Optional NodeQueryDescriptor-compatible query shape."
873
- }
874
- },
875
- required: ["databaseId"]
876
- }
877
- },
878
- {
879
- name: "xnet_database_explain_query",
880
- title: "Explain database query",
881
- description: "Explain descriptor, pagination, materialized view, and storage plan metadata.",
882
- risk: "low",
883
- requiredScopes: ["database.read", "database.query", "storage.diagnostics"],
884
- inputSchema: {
1018
+ description: "Optional NodeQueryDescriptor order map."
1019
+ },
1020
+ materializedView: {
885
1021
  type: "object",
886
- properties: {
887
- databaseId: { type: "string", description: "Database node id." },
888
- schemaId: { type: "string", description: "Optional row schema IRI." },
889
- descriptor: {
890
- type: "object",
891
- description: "Optional NodeQueryDescriptor-compatible query shape."
892
- },
893
- limit: { type: "number", description: "Maximum row count for the dry-run query." },
894
- offset: { type: "number", description: "Row offset." }
895
- },
896
- required: ["databaseId"]
897
- }
1022
+ description: "Optional materialized view query options."
1023
+ },
1024
+ count: { type: "string", description: "Page count mode: exact, estimate, or none." },
1025
+ limit: { type: "number", description: "Maximum row count." },
1026
+ offset: { type: "number", description: "Row offset." }
898
1027
  },
899
- {
900
- name: "xnet_plan_database_mutation",
901
- title: "Plan database mutation",
902
- description: "Create a database mutation plan for later review without applying it.",
903
- risk: "medium",
904
- requiredScopes: ["database.read", "database.propose"],
905
- inputSchema: {
1028
+ required: ["databaseId"]
1029
+ }
1030
+ },
1031
+ execute: async (host, args) => await host.queryDatabase({
1032
+ databaseId: readRequiredString(args, "databaseId"),
1033
+ schemaId: readOptionalString(args, "schemaId"),
1034
+ descriptor: readOptionalRecord(args, "descriptor"),
1035
+ where: readOptionalRecord(args, "where"),
1036
+ search: args.search,
1037
+ orderBy: readOptionalRecord(args, "orderBy"),
1038
+ materializedView: args.materializedView,
1039
+ count: readOptionalString(args, "count"),
1040
+ limit: readOptionalNumber(args, "limit"),
1041
+ offset: readOptionalNumber(args, "offset")
1042
+ })
1043
+ };
1044
+ var databaseSampleTool = {
1045
+ definition: {
1046
+ name: "xnet_database_sample",
1047
+ title: "Sample database rows",
1048
+ description: "Return a small deterministic sample for schema and content inspection.",
1049
+ risk: "low",
1050
+ requiredScopes: ["database.read", "database.query"],
1051
+ inputSchema: {
1052
+ type: "object",
1053
+ properties: {
1054
+ databaseId: { type: "string", description: "Database node id." },
1055
+ schemaId: { type: "string", description: "Optional row schema IRI." },
1056
+ sampleSize: { type: "number", description: "Sample row count." },
1057
+ descriptor: {
906
1058
  type: "object",
907
- properties: {
908
- databaseId: { type: "string", description: "Database node id." },
909
- baseRevision: { type: "string", description: "Revision the mutation was based on." },
910
- operations: { type: "array", description: "Database operations to validate." },
911
- intent: { type: "string", description: "User or agent intent for the mutation." },
912
- actor: { type: "string", description: "Agent or user creating the plan." }
913
- },
914
- required: ["databaseId", "operations"]
1059
+ description: "Optional NodeQueryDescriptor-compatible query shape."
915
1060
  }
916
1061
  },
917
- {
918
- name: "xnet_apply_database_mutation",
919
- title: "Apply database mutation plan",
920
- description: "Apply a validated database row/schema mutation plan with transactional row rollback and audit logging.",
921
- risk: "high",
922
- requiredScopes: ["database.read", "database.write.rows", "database.write.schema"],
923
- inputSchema: {
1062
+ required: ["databaseId"]
1063
+ }
1064
+ },
1065
+ execute: async (host, args) => await host.sampleDatabase({
1066
+ databaseId: readRequiredString(args, "databaseId"),
1067
+ schemaId: readOptionalString(args, "schemaId"),
1068
+ descriptor: readOptionalRecord(args, "descriptor"),
1069
+ sampleSize: readOptionalNumber(args, "sampleSize")
1070
+ })
1071
+ };
1072
+ var databaseExplainQueryTool = {
1073
+ definition: {
1074
+ name: "xnet_database_explain_query",
1075
+ title: "Explain database query",
1076
+ description: "Explain descriptor, pagination, materialized view, and storage plan metadata.",
1077
+ risk: "low",
1078
+ requiredScopes: ["database.read", "database.query", "storage.diagnostics"],
1079
+ inputSchema: {
1080
+ type: "object",
1081
+ properties: {
1082
+ databaseId: { type: "string", description: "Database node id." },
1083
+ schemaId: { type: "string", description: "Optional row schema IRI." },
1084
+ descriptor: {
924
1085
  type: "object",
925
- properties: {
926
- plan: { type: "object", description: "Validated database mutation plan." },
927
- confirmApply: {
928
- type: "boolean",
929
- description: "Must be true to apply the database mutation plan."
930
- },
931
- allowStale: {
932
- type: "boolean",
933
- description: "Allow applying when the plan base revision differs from the live database node."
934
- }
935
- },
936
- required: ["plan", "confirmApply"]
937
- }
1086
+ description: "Optional NodeQueryDescriptor-compatible query shape."
1087
+ },
1088
+ limit: { type: "number", description: "Maximum row count for the dry-run query." },
1089
+ offset: { type: "number", description: "Row offset." }
938
1090
  },
939
- {
940
- name: "xnet_canvas_list",
941
- title: "List canvases",
942
- description: "List canvas nodes visible to the AI surface.",
943
- risk: "low",
944
- requiredScopes: ["canvas.read"],
945
- inputSchema: {
946
- type: "object",
947
- properties: {
948
- limit: { type: "number", description: "Maximum canvas count." },
949
- offset: { type: "number", description: "Canvas offset." }
950
- }
951
- }
1091
+ required: ["databaseId"]
1092
+ }
1093
+ },
1094
+ execute: async (host, args) => await host.explainDatabaseQuery({
1095
+ databaseId: readRequiredString(args, "databaseId"),
1096
+ schemaId: readOptionalString(args, "schemaId"),
1097
+ descriptor: readOptionalRecord(args, "descriptor"),
1098
+ limit: readOptionalNumber(args, "limit"),
1099
+ offset: readOptionalNumber(args, "offset")
1100
+ })
1101
+ };
1102
+ var planDatabaseMutationTool = {
1103
+ definition: {
1104
+ name: "xnet_plan_database_mutation",
1105
+ title: "Plan database mutation",
1106
+ description: "Create a database mutation plan for later review without applying it.",
1107
+ risk: "medium",
1108
+ requiredScopes: ["database.read", "database.propose"],
1109
+ inputSchema: {
1110
+ type: "object",
1111
+ properties: {
1112
+ databaseId: { type: "string", description: "Database node id." },
1113
+ baseRevision: { type: "string", description: "Revision the mutation was based on." },
1114
+ operations: { type: "array", description: "Database operations to validate." },
1115
+ intent: { type: "string", description: "User or agent intent for the mutation." },
1116
+ actor: { type: "string", description: "Agent or user creating the plan." }
952
1117
  },
953
- {
954
- name: "xnet_canvas_read_viewport",
955
- title: "Read canvas viewport",
956
- description: "Read canvas objects and edges intersecting a viewport.",
957
- risk: "low",
958
- requiredScopes: ["canvas.read"],
959
- inputSchema: {
960
- type: "object",
961
- properties: {
962
- canvasId: { type: "string", description: "Canvas node id." },
963
- x: { type: "number", description: "Viewport x." },
964
- y: { type: "number", description: "Viewport y." },
965
- w: { type: "number", description: "Viewport width." },
966
- h: { type: "number", description: "Viewport height." },
967
- includeSourcePreviews: {
968
- type: "boolean",
969
- description: "Include previews for source-backed objects."
970
- },
971
- tileSize: { type: "number", description: "Optional tile size for tile scoping." },
972
- tileIds: {
973
- type: "array",
974
- description: "Optional tile ids such as 0/1/-2 to constrain the read.",
975
- items: { type: "string" }
976
- }
977
- },
978
- required: ["canvasId"]
1118
+ required: ["databaseId", "operations"]
1119
+ }
1120
+ },
1121
+ execute: async (host, args) => await host.planDatabaseMutation(args)
1122
+ };
1123
+ var applyDatabaseMutationTool = {
1124
+ definition: {
1125
+ name: "xnet_apply_database_mutation",
1126
+ title: "Apply database mutation plan",
1127
+ description: "Apply a validated database row/schema mutation plan with transactional row rollback and audit logging.",
1128
+ risk: "high",
1129
+ requiredScopes: ["database.read", "database.write.rows", "database.write.schema"],
1130
+ inputSchema: {
1131
+ type: "object",
1132
+ properties: {
1133
+ plan: { type: "object", description: "Validated database mutation plan." },
1134
+ confirmApply: {
1135
+ type: "boolean",
1136
+ description: "Must be true to apply the database mutation plan."
1137
+ },
1138
+ allowStale: {
1139
+ type: "boolean",
1140
+ description: "Allow applying when the plan base revision differs from the live database node."
979
1141
  }
980
1142
  },
981
- {
982
- name: "xnet_canvas_read_selection",
983
- title: "Read canvas selection",
984
- description: "Read selected canvas objects, connected edges, and optional source previews.",
985
- risk: "low",
986
- requiredScopes: ["canvas.read"],
987
- inputSchema: {
988
- type: "object",
989
- properties: {
990
- canvasId: { type: "string", description: "Canvas node id." },
991
- objectIds: {
992
- type: "array",
993
- description: "Selected object ids.",
994
- items: { type: "string" }
995
- },
996
- includeSourcePreviews: {
997
- type: "boolean",
998
- description: "Include previews for source-backed objects."
999
- }
1000
- },
1001
- required: ["canvasId", "objectIds"]
1143
+ required: ["plan", "confirmApply"]
1144
+ }
1145
+ },
1146
+ execute: async (host, args) => await host.applyDatabaseMutation(args)
1147
+ };
1148
+ var databaseToolEntries = [
1149
+ databaseDescribeTool,
1150
+ databaseQueryTool,
1151
+ databaseSampleTool,
1152
+ databaseExplainQueryTool,
1153
+ planDatabaseMutationTool,
1154
+ applyDatabaseMutationTool
1155
+ ];
1156
+
1157
+ // src/ai-surface/tools/page-mutation.ts
1158
+ var readPageMarkdownTool = {
1159
+ definition: {
1160
+ name: "xnet_read_page_markdown",
1161
+ title: "Read page Markdown",
1162
+ description: "Read a page as Markdown with optional xNet frontmatter.",
1163
+ risk: "low",
1164
+ requiredScopes: ["page.read"],
1165
+ inputSchema: {
1166
+ type: "object",
1167
+ properties: {
1168
+ pageId: { type: "string", description: "Page node id." },
1169
+ includeFrontmatter: {
1170
+ type: "boolean",
1171
+ description: "Include xNet identity frontmatter. Defaults to true."
1002
1172
  }
1003
1173
  },
1004
- {
1005
- name: "xnet_canvas_search",
1006
- title: "Search canvas",
1007
- description: "Search canvas object text, labels, ids, and source metadata.",
1008
- risk: "low",
1009
- requiredScopes: ["canvas.read"],
1010
- inputSchema: {
1011
- type: "object",
1012
- properties: {
1013
- canvasId: { type: "string", description: "Canvas node id." },
1014
- query: { type: "string", description: "Search text." },
1015
- limit: { type: "number", description: "Maximum result count." }
1016
- },
1017
- required: ["canvasId", "query"]
1018
- }
1174
+ required: ["pageId"]
1175
+ }
1176
+ },
1177
+ execute: async (host, args) => {
1178
+ const content = await host.readPageMarkdown(
1179
+ readRequiredString(args, "pageId"),
1180
+ readOptionalBoolean(args, "includeFrontmatter") ?? true
1181
+ );
1182
+ return { markdown: content.text, mimeType: content.mimeType, uri: content.uri };
1183
+ }
1184
+ };
1185
+ var validatePageMarkdownTool = {
1186
+ definition: {
1187
+ name: "xnet_validate_page_markdown",
1188
+ title: "Validate page Markdown",
1189
+ description: "Validate xNet page frontmatter and supported xNet Markdown directives.",
1190
+ risk: "low",
1191
+ requiredScopes: ["page.read"],
1192
+ inputSchema: {
1193
+ type: "object",
1194
+ properties: {
1195
+ pageId: { type: "string", description: "Optional target page node id." },
1196
+ baseRevision: { type: "string", description: "Optional expected base revision." },
1197
+ markdown: { type: "string", description: "Markdown to validate." }
1019
1198
  },
1020
- {
1021
- name: "xnet_canvas_export_json_canvas",
1022
- title: "Export canvas as JSON Canvas",
1023
- description: "Export a canvas or viewport as JSON Canvas with xNet source metadata.",
1024
- risk: "low",
1025
- requiredScopes: ["canvas.read"],
1026
- inputSchema: {
1027
- type: "object",
1028
- properties: {
1029
- canvasId: { type: "string", description: "Canvas node id." },
1030
- includeXNetMetadata: {
1031
- type: "boolean",
1032
- description: "Include xNet source metadata. Defaults to true."
1033
- },
1034
- x: { type: "number", description: "Optional viewport x." },
1035
- y: { type: "number", description: "Optional viewport y." },
1036
- w: { type: "number", description: "Optional viewport width." },
1037
- h: { type: "number", description: "Optional viewport height." }
1038
- },
1039
- required: ["canvasId"]
1040
- }
1199
+ required: ["markdown"]
1200
+ }
1201
+ },
1202
+ execute: async (host, args) => {
1203
+ const pageId = readOptionalString(args, "pageId");
1204
+ const node = pageId ? await host.getNodeOrThrow(pageId) : null;
1205
+ return validateXNetPageMarkdown(readRequiredString(args, "markdown"), {
1206
+ pageId,
1207
+ schemaId: node?.schemaId,
1208
+ baseRevision: readOptionalString(args, "baseRevision")
1209
+ });
1210
+ }
1211
+ };
1212
+ var planPagePatchTool = {
1213
+ definition: {
1214
+ name: "xnet_plan_page_patch",
1215
+ title: "Plan page Markdown patch",
1216
+ description: "Validate an edited Markdown page and return a mutation plan without applying it.",
1217
+ risk: "medium",
1218
+ requiredScopes: ["page.read", "page.propose"],
1219
+ inputSchema: {
1220
+ type: "object",
1221
+ properties: {
1222
+ pageId: { type: "string", description: "Page node id." },
1223
+ baseRevision: { type: "string", description: "Revision the patch was based on." },
1224
+ markdown: { type: "string", description: "Proposed full Markdown replacement." },
1225
+ intent: { type: "string", description: "User or agent intent for the patch." },
1226
+ actor: { type: "string", description: "Agent or user creating the plan." }
1041
1227
  },
1042
- {
1043
- name: "xnet_canvas_plan_json_canvas_import",
1044
- title: "Plan JSON Canvas import",
1045
- description: "Convert a JSON Canvas document into a plan-only canvas mutation.",
1046
- risk: "medium",
1047
- requiredScopes: ["canvas.read", "canvas.propose"],
1048
- inputSchema: {
1049
- type: "object",
1050
- properties: {
1051
- canvasId: { type: "string", description: "Canvas node id." },
1052
- document: { type: "object", description: "JSON Canvas document." },
1053
- baseRevision: { type: "string", description: "Revision the import was based on." },
1054
- actor: { type: "string", description: "Agent or user creating the plan." },
1055
- intent: { type: "string", description: "User or agent intent for the import." }
1056
- },
1057
- required: ["canvasId", "document"]
1228
+ required: ["pageId", "markdown"]
1229
+ }
1230
+ },
1231
+ execute: async (host, args) => await host.planPagePatch(args)
1232
+ };
1233
+ var applyPageMarkdownTool = {
1234
+ definition: {
1235
+ name: "xnet_apply_page_markdown",
1236
+ title: "Apply page Markdown plan",
1237
+ description: "Apply a validated page Markdown mutation plan through the configured TipTap/Yjs document adapter, with a node-property fallback.",
1238
+ risk: "high",
1239
+ requiredScopes: ["page.read", "page.write"],
1240
+ inputSchema: {
1241
+ type: "object",
1242
+ properties: {
1243
+ plan: { type: "object", description: "Validated page Markdown mutation plan." },
1244
+ confirmApply: {
1245
+ type: "boolean",
1246
+ description: "Must be true to apply the page Markdown plan."
1247
+ },
1248
+ allowStale: {
1249
+ type: "boolean",
1250
+ description: "Allow applying when the plan base revision differs from the live node."
1058
1251
  }
1059
1252
  },
1060
- {
1061
- name: "xnet_plan_canvas_mutation",
1062
- title: "Plan canvas mutation",
1063
- description: "Create a canvas mutation plan for later review without applying it.",
1064
- risk: "medium",
1065
- requiredScopes: ["canvas.read", "canvas.propose"],
1066
- inputSchema: {
1067
- type: "object",
1068
- properties: {
1069
- canvasId: { type: "string", description: "Canvas node id." },
1070
- baseRevision: { type: "string", description: "Revision the mutation was based on." },
1071
- operations: { type: "array", description: "Canvas operations to validate." },
1072
- intent: { type: "string", description: "User or agent intent for the mutation." },
1073
- actor: { type: "string", description: "Agent or user creating the plan." }
1074
- },
1075
- required: ["canvasId", "operations"]
1253
+ required: ["plan", "confirmApply"]
1254
+ }
1255
+ },
1256
+ execute: async (host, args) => await host.applyPageMarkdown(args)
1257
+ };
1258
+ var rollbackPageMarkdownTool = {
1259
+ definition: {
1260
+ name: "xnet_rollback_page_markdown",
1261
+ title: "Rollback page Markdown apply",
1262
+ description: "Rollback a previously applied page Markdown plan by rollback handle.",
1263
+ risk: "high",
1264
+ requiredScopes: ["page.write"],
1265
+ inputSchema: {
1266
+ type: "object",
1267
+ properties: {
1268
+ rollbackHandle: { type: "string", description: "Rollback handle from apply result." },
1269
+ confirmRollback: {
1270
+ type: "boolean",
1271
+ description: "Must be true to perform the rollback."
1076
1272
  }
1077
1273
  },
1078
- {
1079
- name: "xnet_validate_mutation_plan",
1080
- title: "Validate mutation plan",
1081
- description: "Validate a serialized mutation plan and return errors or warnings.",
1082
- risk: "medium",
1083
- requiredScopes: ["workspace.read"],
1084
- inputSchema: {
1085
- type: "object",
1086
- properties: {
1087
- plan: { type: "object", description: "Mutation plan object to validate." }
1088
- },
1089
- required: ["plan"]
1090
- }
1274
+ required: ["rollbackHandle", "confirmRollback"]
1275
+ }
1276
+ },
1277
+ execute: async (host, args) => await host.rollbackPageMarkdown(args)
1278
+ };
1279
+
1280
+ // src/ai-surface/tools/search.ts
1281
+ var searchTool = {
1282
+ definition: {
1283
+ name: "xnet_search",
1284
+ title: "Search xNet workspace",
1285
+ description: "Search node titles and searchable properties with pagination and limits.",
1286
+ risk: "low",
1287
+ requiredScopes: ["workspace.search"],
1288
+ inputSchema: {
1289
+ type: "object",
1290
+ properties: {
1291
+ query: { type: "string", description: "Search text." },
1292
+ schemaId: { type: "string", description: "Optional schema IRI filter." },
1293
+ limit: { type: "number", description: "Maximum result count." },
1294
+ offset: { type: "number", description: "Result offset for pagination." }
1295
+ },
1296
+ required: ["query"]
1297
+ }
1298
+ },
1299
+ execute: async (host, args) => await host.search({
1300
+ query: readRequiredString(args, "query"),
1301
+ schemaId: readOptionalString(args, "schemaId") ?? readOptionalString(args, "schema"),
1302
+ limit: readOptionalNumber(args, "limit"),
1303
+ offset: readOptionalNumber(args, "offset")
1304
+ })
1305
+ };
1306
+ var graphExpandTool = {
1307
+ definition: {
1308
+ name: "xnet_graph_expand",
1309
+ title: "Expand a node along its relations",
1310
+ 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.",
1311
+ risk: "low",
1312
+ requiredScopes: ["workspace.read"],
1313
+ inputSchema: {
1314
+ type: "object",
1315
+ properties: {
1316
+ nodeId: { type: "string", description: "The node to expand from." },
1317
+ hops: {
1318
+ type: "number",
1319
+ description: "How many relation hops to walk (1\u20132, default 1)."
1320
+ },
1321
+ limit: { type: "number", description: "Maximum neighbors to return." }
1322
+ },
1323
+ required: ["nodeId"]
1324
+ }
1325
+ },
1326
+ execute: async (host, args) => await host.expandGraph({
1327
+ nodeId: readRequiredString(args, "nodeId"),
1328
+ hops: readOptionalNumber(args, "hops"),
1329
+ limit: readOptionalNumber(args, "limit")
1330
+ })
1331
+ };
1332
+ var createContextPackTool = {
1333
+ definition: {
1334
+ name: "xnet_create_context_pack",
1335
+ title: "Create context pack",
1336
+ description: "Create a bounded context pack from seeds and optional search results.",
1337
+ risk: "low",
1338
+ requiredScopes: ["workspace.read", "workspace.search"],
1339
+ inputSchema: {
1340
+ type: "object",
1341
+ properties: {
1342
+ query: { type: "string", description: "Optional search query." },
1343
+ seeds: {
1344
+ type: "array",
1345
+ description: "Seed resources such as pages, databases, canvases, or nodes.",
1346
+ items: {
1347
+ type: "object",
1348
+ properties: {
1349
+ kind: { type: "string", description: "Seed kind." },
1350
+ id: { type: "string", description: "Seed id." }
1351
+ }
1352
+ }
1353
+ },
1354
+ limit: { type: "number", description: "Maximum resources to include." }
1091
1355
  }
1356
+ }
1357
+ },
1358
+ execute: async (host, args) => await host.createContextPack({
1359
+ query: readOptionalString(args, "query"),
1360
+ seeds: readContextSeeds(args.seeds),
1361
+ limit: readOptionalNumber(args, "limit")
1362
+ })
1363
+ };
1364
+ var createExternalContextResourceTool = {
1365
+ definition: {
1366
+ name: "xnet_create_external_context_resource",
1367
+ title: "Create untrusted external context resource",
1368
+ description: "Wrap externally fetched content as an untrusted context-pack resource with an explicit instruction boundary.",
1369
+ risk: "medium",
1370
+ requiredScopes: ["network.fetch"],
1371
+ inputSchema: {
1372
+ type: "object",
1373
+ properties: {
1374
+ url: { type: "string", description: "External source URL." },
1375
+ text: { type: "string", description: "Fetched external text content." },
1376
+ mimeType: { type: "string", description: "Source MIME type. Defaults to text/plain." }
1377
+ },
1378
+ required: ["url", "text"]
1379
+ }
1380
+ },
1381
+ execute: (host, args) => host.createExternalContextResource({
1382
+ url: readRequiredString(args, "url"),
1383
+ text: readRequiredString(args, "text"),
1384
+ mimeType: readOptionalString(args, "mimeType")
1385
+ })
1386
+ };
1387
+ var searchToolEntries = [
1388
+ searchTool,
1389
+ graphExpandTool,
1390
+ createContextPackTool,
1391
+ createExternalContextResourceTool
1392
+ ];
1393
+
1394
+ // src/ai-surface/tools/index.ts
1395
+ var BUILT_IN_TOOL_ENTRIES = [
1396
+ ...searchToolEntries,
1397
+ readPageMarkdownTool,
1398
+ validatePageMarkdownTool,
1399
+ planPagePatchTool,
1400
+ applyPageMarkdownTool,
1401
+ getAuditLogTool,
1402
+ rollbackPageMarkdownTool,
1403
+ ...databaseToolEntries,
1404
+ ...canvasToolEntries,
1405
+ validateMutationPlanTool
1406
+ ];
1407
+ var BUILT_IN_TOOLS_BY_NAME = new Map(
1408
+ BUILT_IN_TOOL_ENTRIES.map((entry) => [entry.definition.name, entry])
1409
+ );
1410
+
1411
+ // src/ai-surface/service.ts
1412
+ var DEFAULT_LIMITS = {
1413
+ maxListLimit: 100,
1414
+ maxSearchScan: 500,
1415
+ maxSearchResults: 20,
1416
+ maxContextResources: 12,
1417
+ maxCharactersPerResource: 12e3,
1418
+ maxJsonCharacters: 24e3,
1419
+ maxCanvasObjects: 200,
1420
+ maxDatabaseRows: 100
1421
+ };
1422
+ var BUILT_IN_RESOURCE_ROUTES = createBuiltInResourceRouter();
1423
+ var AiSurfaceService = class {
1424
+ constructor(config) {
1425
+ this.config = config;
1426
+ this.limits = { ...DEFAULT_LIMITS, ...config.limits };
1427
+ this.clock = config.clock ?? (() => /* @__PURE__ */ new Date());
1428
+ for (const tool of config.extraTools ?? []) {
1429
+ if (!this.extraTools.has(tool.name)) this.extraTools.set(tool.name, tool);
1430
+ }
1431
+ }
1432
+ limits;
1433
+ clock;
1434
+ sequence = 0;
1435
+ auditEvents = [];
1436
+ rollbackSnapshots = /* @__PURE__ */ new Map();
1437
+ /** Contributed tools by name (exploration 0196), de-duped at construction. */
1438
+ extraTools = /* @__PURE__ */ new Map();
1439
+ /**
1440
+ * The narrow surface handed to built-in tool handlers and resource routes
1441
+ * (`tools/`, `resources/`): closures over the private methods, so the class
1442
+ * stays the facade and its public type is unchanged. The closures are lazy —
1443
+ * nothing here runs until a tool call or resource read arrives.
1444
+ */
1445
+ host = {
1446
+ search: (options) => this.search(options),
1447
+ expandGraph: (options) => this.expandGraph(options),
1448
+ createContextPack: (options) => this.createContextPack(options),
1449
+ createExternalContextResource: (options) => this.createExternalContextResource(options),
1450
+ getWorkspaceSummary: () => this.getWorkspaceSummary(),
1451
+ getRecentNodes: () => this.getRecentNodes(),
1452
+ listNodes: async () => {
1453
+ const nodes = await this.config.store.list({ limit: this.limits.maxListLimit });
1454
+ return { nodes, count: nodes.length, limit: this.limits.maxListLimit };
1455
+ },
1456
+ listSchemas: async () => ({ schemas: await this.getSchemaSummaries(true) }),
1457
+ getNodeOrThrow: (id) => this.getNodeOrThrow(id),
1458
+ getNodeProjection: (id) => this.getNodeProjection(id),
1459
+ readPageMarkdown: (pageId, includeFrontmatter, uri) => this.readPageMarkdown(pageId, includeFrontmatter, uri),
1460
+ readPageOutline: (pageId) => this.readPageOutline(pageId),
1461
+ planPagePatch: (args) => this.planPagePatch(args),
1462
+ applyPageMarkdown: (args) => this.applyPageMarkdown(args),
1463
+ rollbackPageMarkdown: (args) => this.rollbackPageMarkdown(args),
1464
+ getAuditLog: (options) => this.getAuditLog(options),
1465
+ describeDatabase: (databaseId, options) => this.describeDatabase(databaseId, options),
1466
+ readDatabaseViews: (databaseId) => this.readDatabaseViews(databaseId),
1467
+ queryDatabase: (options) => this.queryDatabase(options),
1468
+ sampleDatabase: (options) => this.sampleDatabase(options),
1469
+ explainDatabaseQuery: (options) => this.explainDatabaseQuery(options),
1470
+ planDatabaseMutation: (args) => this.planDatabaseMutation(args),
1471
+ applyDatabaseMutation: (args) => this.applyDatabaseMutation(args),
1472
+ listCanvases: (options) => this.listCanvases(options),
1473
+ readCanvasViewport: (options) => this.readCanvasViewport(options),
1474
+ readCanvasObjects: (canvasId) => this.readCanvasObjects(canvasId),
1475
+ readCanvasSelection: (options) => this.readCanvasSelection(options),
1476
+ searchCanvas: (options) => this.searchCanvas(options),
1477
+ exportCanvasJsonCanvas: (options) => this.exportCanvasJsonCanvas(options),
1478
+ readCanvasObject: (canvasId, objectId) => this.readCanvasObject(canvasId, objectId),
1479
+ planCanvasJsonCanvasImport: (args) => this.planCanvasJsonCanvasImport(args),
1480
+ planCanvasMutation: (args) => this.planCanvasMutation(args),
1481
+ jsonResource: (uri, value) => this.jsonResource(uri, value)
1482
+ };
1483
+ /** The contributed (non-built-in) tools, with built-in name collisions removed. */
1484
+ getExtraTools() {
1485
+ const builtIn = new Set(this.builtInTools().map((t) => t.name));
1486
+ return [...this.extraTools.values()].filter((t) => !builtIn.has(t.name));
1487
+ }
1488
+ getResources() {
1489
+ return [
1490
+ createResource(
1491
+ "xnet://workspace/summary",
1492
+ "Workspace Summary",
1493
+ "High-level schema counts, recent nodes, and AI surface capabilities.",
1494
+ "application/json",
1495
+ "low",
1496
+ ["workspace.read"]
1497
+ ),
1498
+ createResource(
1499
+ "xnet://workspace/recent",
1500
+ "Recent Workspace Nodes",
1501
+ "Recent nodes with ids, schemas, titles, and revisions.",
1502
+ "application/json",
1503
+ "low",
1504
+ ["workspace.read"]
1505
+ ),
1506
+ createResource(
1507
+ "xnet://nodes",
1508
+ "All Nodes",
1509
+ "Limited list of nodes in the local store.",
1510
+ "application/json",
1511
+ "low",
1512
+ ["workspace.read"]
1513
+ ),
1514
+ createResource(
1515
+ "xnet://schemas",
1516
+ "All Schemas",
1517
+ "List of available schemas and property metadata.",
1518
+ "application/json",
1519
+ "low",
1520
+ ["workspace.read"]
1521
+ ),
1522
+ createResource(
1523
+ "xnet://page/{pageId}.md",
1524
+ "Page Markdown",
1525
+ "Markdown projection for a page, including xNet frontmatter identity.",
1526
+ "text/markdown",
1527
+ "low",
1528
+ ["page.read"],
1529
+ true
1530
+ ),
1531
+ createResource(
1532
+ "xnet://page/{pageId}/outline",
1533
+ "Page Outline",
1534
+ "Page heading outline extracted from the Markdown projection.",
1535
+ "application/json",
1536
+ "low",
1537
+ ["page.read"],
1538
+ true
1539
+ ),
1540
+ createResource(
1541
+ "xnet://database/{databaseId}/schema",
1542
+ "Database Schema",
1543
+ "Database node metadata, declared properties, and known column descriptors.",
1544
+ "application/json",
1545
+ "low",
1546
+ ["database.read"],
1547
+ true
1548
+ ),
1549
+ createResource(
1550
+ "xnet://database/{databaseId}/views",
1551
+ "Database Views",
1552
+ "Known database view descriptors from the database node projection.",
1553
+ "application/json",
1554
+ "low",
1555
+ ["database.read"],
1556
+ true
1557
+ ),
1558
+ createResource(
1559
+ "xnet://database/{databaseId}/sample?limit=10",
1560
+ "Database Sample Rows",
1561
+ "Bounded sample of rows for a database using NodeQueryDescriptor semantics.",
1562
+ "application/json",
1563
+ "low",
1564
+ ["database.read", "database.query"],
1565
+ true
1566
+ ),
1567
+ createResource(
1568
+ "xnet://canvas/{canvasId}/viewport?x=0&y=0&w=1000&h=800",
1569
+ "Canvas Viewport",
1570
+ "Viewport-scoped canvas objects and edges.",
1571
+ "application/json",
1572
+ "low",
1573
+ ["canvas.read"],
1574
+ true
1575
+ ),
1576
+ createResource(
1577
+ "xnet://canvas/{canvasId}/objects",
1578
+ "Canvas Objects",
1579
+ "Bounded list of canvas objects and connectors.",
1580
+ "application/json",
1581
+ "low",
1582
+ ["canvas.read"],
1583
+ true
1584
+ ),
1585
+ createResource(
1586
+ "xnet://canvas/{canvasId}/selection?ids=object-1,object-2",
1587
+ "Canvas Selection",
1588
+ "Selection-scoped canvas objects, edges, and source previews.",
1589
+ "application/json",
1590
+ "low",
1591
+ ["canvas.read"],
1592
+ true
1593
+ ),
1594
+ createResource(
1595
+ "xnet://canvas/{canvasId}/json-canvas",
1596
+ "Canvas JSON Canvas",
1597
+ "JSON Canvas projection with xNet source metadata sidecars.",
1598
+ "application/json",
1599
+ "low",
1600
+ ["canvas.read"],
1601
+ true
1602
+ )
1092
1603
  ];
1093
1604
  }
1605
+ /** The full tool surface: built-in `xnet_*` tools plus contributed extras. */
1606
+ getTools() {
1607
+ const extras = this.getExtraTools().map(
1608
+ ({ invoke: _invoke, ...def }) => def
1609
+ );
1610
+ return [...this.builtInTools(), ...extras];
1611
+ }
1612
+ builtInTools() {
1613
+ return BUILT_IN_TOOL_ENTRIES.map((entry) => entry.definition);
1614
+ }
1615
+ /**
1616
+ * Dispatch a tool call: the built-in registry first (a built-in `xnet_*`
1617
+ * name always wins), then contributed tools (plugin/connector `agentTools`,
1618
+ * exploration 0196).
1619
+ */
1094
1620
  async callTool(name, args = {}) {
1095
- switch (name) {
1096
- case "xnet_search":
1097
- return await this.search({
1098
- query: readRequiredString(args, "query"),
1099
- schemaId: readOptionalString(args, "schemaId") ?? readOptionalString(args, "schema"),
1100
- limit: readOptionalNumber(args, "limit"),
1101
- offset: readOptionalNumber(args, "offset")
1102
- });
1103
- case "xnet_graph_expand":
1104
- return await this.expandGraph({
1105
- nodeId: readRequiredString(args, "nodeId"),
1106
- hops: readOptionalNumber(args, "hops"),
1107
- limit: readOptionalNumber(args, "limit")
1108
- });
1109
- case "xnet_create_context_pack":
1110
- return await this.createContextPack({
1111
- query: readOptionalString(args, "query"),
1112
- seeds: readContextSeeds(args.seeds),
1113
- limit: readOptionalNumber(args, "limit")
1114
- });
1115
- case "xnet_create_external_context_resource":
1116
- return this.createExternalContextResource({
1117
- url: readRequiredString(args, "url"),
1118
- text: readRequiredString(args, "text"),
1119
- mimeType: readOptionalString(args, "mimeType")
1120
- });
1121
- case "xnet_read_page_markdown": {
1122
- const content = await this.readPageMarkdown(
1123
- readRequiredString(args, "pageId"),
1124
- readOptionalBoolean(args, "includeFrontmatter") ?? true
1125
- );
1126
- return { markdown: content.text, mimeType: content.mimeType, uri: content.uri };
1127
- }
1128
- case "xnet_validate_page_markdown": {
1129
- const pageId = readOptionalString(args, "pageId");
1130
- const node = pageId ? await this.getNodeOrThrow(pageId) : null;
1131
- return validateXNetPageMarkdown(readRequiredString(args, "markdown"), {
1132
- pageId,
1133
- schemaId: node?.schemaId,
1134
- baseRevision: readOptionalString(args, "baseRevision")
1135
- });
1136
- }
1137
- case "xnet_plan_page_patch":
1138
- return await this.planPagePatch(args);
1139
- case "xnet_apply_page_markdown":
1140
- return await this.applyPageMarkdown(args);
1141
- case "xnet_get_audit_log":
1142
- return this.getAuditLog({
1143
- planId: readOptionalString(args, "planId"),
1144
- limit: readOptionalNumber(args, "limit")
1145
- });
1146
- case "xnet_rollback_page_markdown":
1147
- return await this.rollbackPageMarkdown(args);
1148
- case "xnet_database_describe":
1149
- return await this.describeDatabase(readRequiredString(args, "databaseId"), {
1150
- includeSample: readOptionalBoolean(args, "includeSample") ?? false
1151
- });
1152
- case "xnet_database_query":
1153
- return await this.queryDatabase({
1154
- databaseId: readRequiredString(args, "databaseId"),
1155
- schemaId: readOptionalString(args, "schemaId"),
1156
- descriptor: readOptionalRecord(args, "descriptor"),
1157
- where: readOptionalRecord(args, "where"),
1158
- search: args.search,
1159
- orderBy: readOptionalRecord(args, "orderBy"),
1160
- materializedView: args.materializedView,
1161
- count: readOptionalString(args, "count"),
1162
- limit: readOptionalNumber(args, "limit"),
1163
- offset: readOptionalNumber(args, "offset")
1164
- });
1165
- case "xnet_database_sample":
1166
- return await this.sampleDatabase({
1167
- databaseId: readRequiredString(args, "databaseId"),
1168
- schemaId: readOptionalString(args, "schemaId"),
1169
- descriptor: readOptionalRecord(args, "descriptor"),
1170
- sampleSize: readOptionalNumber(args, "sampleSize")
1171
- });
1172
- case "xnet_database_explain_query":
1173
- return await this.explainDatabaseQuery({
1174
- databaseId: readRequiredString(args, "databaseId"),
1175
- schemaId: readOptionalString(args, "schemaId"),
1176
- descriptor: readOptionalRecord(args, "descriptor"),
1177
- limit: readOptionalNumber(args, "limit"),
1178
- offset: readOptionalNumber(args, "offset")
1179
- });
1180
- case "xnet_plan_database_mutation":
1181
- return await this.planDatabaseMutation(args);
1182
- case "xnet_apply_database_mutation":
1183
- return await this.applyDatabaseMutation(args);
1184
- case "xnet_canvas_list":
1185
- return await this.listCanvases({
1186
- limit: readOptionalNumber(args, "limit"),
1187
- offset: readOptionalNumber(args, "offset")
1188
- });
1189
- case "xnet_canvas_read_viewport":
1190
- return await this.readCanvasViewport({
1191
- canvasId: readRequiredString(args, "canvasId"),
1192
- x: readOptionalNumber(args, "x"),
1193
- y: readOptionalNumber(args, "y"),
1194
- w: readOptionalNumber(args, "w"),
1195
- h: readOptionalNumber(args, "h"),
1196
- tileSize: readOptionalNumber(args, "tileSize"),
1197
- tileIds: readStringArray(args.tileIds),
1198
- includeSourcePreviews: readOptionalBoolean(args, "includeSourcePreviews") ?? false
1199
- });
1200
- case "xnet_canvas_read_selection":
1201
- return await this.readCanvasSelection({
1202
- canvasId: readRequiredString(args, "canvasId"),
1203
- objectIds: readRequiredStringArray(args.objectIds, "objectIds"),
1204
- includeSourcePreviews: readOptionalBoolean(args, "includeSourcePreviews") ?? false
1205
- });
1206
- case "xnet_canvas_search":
1207
- return await this.searchCanvas({
1208
- canvasId: readRequiredString(args, "canvasId"),
1209
- query: readRequiredString(args, "query"),
1210
- limit: readOptionalNumber(args, "limit")
1211
- });
1212
- case "xnet_canvas_export_json_canvas":
1213
- return await this.exportCanvasJsonCanvas({
1214
- canvasId: readRequiredString(args, "canvasId"),
1215
- includeXNetMetadata: readOptionalBoolean(args, "includeXNetMetadata") ?? true,
1216
- x: readOptionalNumber(args, "x"),
1217
- y: readOptionalNumber(args, "y"),
1218
- w: readOptionalNumber(args, "w"),
1219
- h: readOptionalNumber(args, "h")
1220
- });
1221
- case "xnet_canvas_plan_json_canvas_import":
1222
- return await this.planCanvasJsonCanvasImport(args);
1223
- case "xnet_plan_canvas_mutation":
1224
- return await this.planCanvasMutation(args);
1225
- case "xnet_validate_mutation_plan": {
1226
- const validation = validateAiMutationPlan(args.plan);
1227
- return { validation };
1228
- }
1229
- default: {
1230
- const extra = this.extraTools.get(name);
1231
- if (extra) return await extra.invoke(args);
1232
- throw new Error(`Unknown AI surface tool: ${name}`);
1233
- }
1234
- }
1621
+ const builtIn = BUILT_IN_TOOLS_BY_NAME.get(name);
1622
+ if (builtIn) return await builtIn.execute(this.host, args);
1623
+ const extra = this.extraTools.get(name);
1624
+ if (extra) return await extra.invoke(args);
1625
+ throw new Error(`Unknown AI surface tool: ${name}`);
1235
1626
  }
1236
1627
  async readResource(uri) {
1237
- const parsed = parseXNetUri(uri);
1238
- if (uri === "xnet://nodes") {
1239
- const nodes = await this.config.store.list({ limit: this.limits.maxListLimit });
1240
- return this.jsonResource(uri, { nodes, count: nodes.length, limit: this.limits.maxListLimit });
1241
- }
1242
- if (uri === "xnet://schemas") {
1243
- return this.jsonResource(uri, { schemas: await this.getSchemaSummaries(true) });
1244
- }
1245
- if (parsed.host === "workspace" && parsed.parts[0] === "summary") {
1246
- return this.jsonResource(uri, await this.getWorkspaceSummary());
1247
- }
1248
- if (parsed.host === "workspace" && parsed.parts[0] === "recent") {
1249
- return this.jsonResource(uri, await this.getRecentNodes());
1250
- }
1251
- if (parsed.host === "workspace" && parsed.parts[0] === "search") {
1252
- return this.jsonResource(
1253
- uri,
1254
- await this.search({
1255
- query: parsed.searchParams.get("q") ?? "",
1256
- schemaId: parsed.searchParams.get("schema") ?? void 0,
1257
- limit: readUrlNumber(parsed.searchParams, "limit"),
1258
- offset: readUrlNumber(parsed.searchParams, "offset")
1259
- })
1260
- );
1261
- }
1262
- if (parsed.host === "node" && parsed.parts[0]) {
1263
- return this.jsonResource(uri, await this.getNodeProjection(parsed.parts[0]));
1264
- }
1265
- if (parsed.host === "page" && parsed.parts[0]) {
1266
- const pageId = parsed.parts[0].endsWith(".md") ? parsed.parts[0].slice(0, -".md".length) : parsed.parts[0];
1267
- if (parsed.parts.length === 1 || parsed.parts[0].endsWith(".md")) {
1268
- return await this.readPageMarkdown(pageId, true, uri);
1269
- }
1270
- if (parsed.parts[1] === "outline") {
1271
- return this.jsonResource(uri, await this.readPageOutline(pageId));
1272
- }
1273
- if (parsed.parts[1] === "context-pack") {
1274
- return this.jsonResource(
1275
- uri,
1276
- await this.createContextPack({ seeds: [{ kind: "page", id: pageId }] })
1277
- );
1278
- }
1279
- }
1280
- if (parsed.host === "database" && parsed.parts[0]) {
1281
- const databaseId = parsed.parts[0];
1282
- if (parsed.parts[1] === "schema") {
1283
- return this.jsonResource(uri, await this.describeDatabase(databaseId));
1284
- }
1285
- if (parsed.parts[1] === "views") {
1286
- return this.jsonResource(uri, await this.readDatabaseViews(databaseId));
1287
- }
1288
- if (parsed.parts[1] === "sample") {
1289
- return this.jsonResource(
1290
- uri,
1291
- await this.sampleDatabase({
1292
- databaseId,
1293
- sampleSize: readUrlNumber(parsed.searchParams, "limit")
1294
- })
1295
- );
1296
- }
1297
- if (parsed.parts[1] === "query") {
1298
- return this.jsonResource(
1299
- uri,
1300
- await this.queryDatabase({
1301
- databaseId,
1302
- schemaId: parsed.searchParams.get("schema") ?? void 0,
1303
- search: parsed.searchParams.get("q") ?? void 0,
1304
- materializedView: parsed.searchParams.get("view") ? { viewId: parsed.searchParams.get("view") ?? "" } : void 0,
1305
- limit: readUrlNumber(parsed.searchParams, "limit"),
1306
- offset: readUrlNumber(parsed.searchParams, "offset")
1307
- })
1308
- );
1309
- }
1310
- }
1311
- if (parsed.host === "canvas" && parsed.parts[0]) {
1312
- const canvasId = parsed.parts[0];
1313
- if (parsed.parts[1] === "viewport") {
1314
- return this.jsonResource(
1315
- uri,
1316
- await this.readCanvasViewport({
1317
- canvasId,
1318
- x: readUrlNumber(parsed.searchParams, "x"),
1319
- y: readUrlNumber(parsed.searchParams, "y"),
1320
- w: readUrlNumber(parsed.searchParams, "w"),
1321
- h: readUrlNumber(parsed.searchParams, "h"),
1322
- tileSize: readUrlNumber(parsed.searchParams, "tileSize"),
1323
- tileIds: readCsvStringArray(parsed.searchParams.get("tileIds")),
1324
- includeSourcePreviews: parsed.searchParams.get("includeSourcePreviews") === "true"
1325
- })
1326
- );
1327
- }
1328
- if (parsed.parts[1] === "objects") {
1329
- return this.jsonResource(uri, await this.readCanvasObjects(canvasId));
1330
- }
1331
- if (parsed.parts[1] === "selection") {
1332
- return this.jsonResource(
1333
- uri,
1334
- await this.readCanvasSelection({
1335
- canvasId,
1336
- objectIds: readCsvStringArray(parsed.searchParams.get("ids")),
1337
- includeSourcePreviews: parsed.searchParams.get("includeSourcePreviews") !== "false"
1338
- })
1339
- );
1340
- }
1341
- if (parsed.parts[1] === "json-canvas") {
1342
- return this.jsonResource(
1343
- uri,
1344
- await this.exportCanvasJsonCanvas({
1345
- canvasId,
1346
- includeXNetMetadata: parsed.searchParams.get("includeXNetMetadata") !== "false"
1347
- })
1348
- );
1349
- }
1350
- if (parsed.parts[1] === "object" && parsed.parts[2]) {
1351
- return this.jsonResource(uri, await this.readCanvasObject(canvasId, parsed.parts[2]));
1352
- }
1353
- }
1354
- throw new Error(`Resource not found: ${uri}`);
1628
+ return await BUILT_IN_RESOURCE_ROUTES.resolve(this.host, uri);
1355
1629
  }
1356
1630
  toJsonText(value, format = "concise") {
1357
1631
  return this.stringifyJson(value, format);
@@ -1448,7 +1722,7 @@ var AiSurfaceService = class {
1448
1722
  const results = Array.isArray(search.results) ? search.results : [];
1449
1723
  const ids = [];
1450
1724
  for (const result of results) {
1451
- if (isRecord3(result) && typeof result.id === "string") ids.push(result.id);
1725
+ if (isRecord2(result) && typeof result.id === "string") ids.push(result.id);
1452
1726
  }
1453
1727
  return ids;
1454
1728
  }
@@ -2090,8 +2364,8 @@ var AiSurfaceService = class {
2090
2364
  h: options.h,
2091
2365
  includeSourcePreviews: false
2092
2366
  });
2093
- const objects = Array.isArray(viewport.objects) ? viewport.objects.filter(isRecord3) : [];
2094
- const edges = Array.isArray(viewport.edges) ? viewport.edges.filter(isRecord3) : [];
2367
+ const objects = Array.isArray(viewport.objects) ? viewport.objects.filter(isRecord2) : [];
2368
+ const edges = Array.isArray(viewport.edges) ? viewport.edges.filter(isRecord2) : [];
2095
2369
  return {
2096
2370
  canvasId: options.canvasId,
2097
2371
  revision: viewport.revision,
@@ -2133,7 +2407,7 @@ var AiSurfaceService = class {
2133
2407
  });
2134
2408
  const objects = Array.isArray(viewport.objects) ? viewport.objects : [];
2135
2409
  const object = objects.find(
2136
- (candidate) => isRecord3(candidate) && readRecordString(candidate, "id") === objectId
2410
+ (candidate) => isRecord2(candidate) && readRecordString(candidate, "id") === objectId
2137
2411
  );
2138
2412
  if (!object) throw new Error(`Canvas object not found: ${objectId}`);
2139
2413
  return {
@@ -2609,7 +2883,7 @@ function invalidPageApplyResult(plan, validation) {
2609
2883
  return {
2610
2884
  applied: false,
2611
2885
  pageId: "unknown",
2612
- planId: isRecord3(plan) && typeof plan.id === "string" ? plan.id : "unknown",
2886
+ planId: isRecord2(plan) && typeof plan.id === "string" ? plan.id : "unknown",
2613
2887
  mode: "node-property",
2614
2888
  baseRevision: "unknown",
2615
2889
  liveRevision: "unknown",
@@ -2632,7 +2906,7 @@ function rejectedPageApplyResult(input) {
2632
2906
  };
2633
2907
  }
2634
2908
  function isMutationPlan(value) {
2635
- return isRecord3(value) && typeof value.id === "string" && typeof value.actor === "string" && typeof value.intent === "string" && Array.isArray(value.changes) && isRecord3(value.validation);
2909
+ return isRecord2(value) && typeof value.id === "string" && typeof value.actor === "string" && typeof value.intent === "string" && Array.isArray(value.changes) && isRecord2(value.validation);
2636
2910
  }
2637
2911
  function createResource(uri, name, description, mimeType, risk, requiredScopes, dynamic = false) {
2638
2912
  return {
@@ -2645,22 +2919,6 @@ function createResource(uri, name, description, mimeType, risk, requiredScopes,
2645
2919
  dynamic
2646
2920
  };
2647
2921
  }
2648
- function parseXNetUri(uri) {
2649
- let parsed;
2650
- try {
2651
- parsed = new URL(uri);
2652
- } catch {
2653
- throw new Error(`Invalid xNet resource URI: ${uri}`);
2654
- }
2655
- if (parsed.protocol !== "xnet:") {
2656
- throw new Error(`Invalid xNet resource URI: ${uri}`);
2657
- }
2658
- return {
2659
- host: parsed.hostname,
2660
- parts: parsed.pathname.split("/").filter(Boolean).map((part) => decodeURIComponent(part)),
2661
- searchParams: parsed.searchParams
2662
- };
2663
- }
2664
2922
  function renderPageMarkdown(node, includeFrontmatter, exportedAt) {
2665
2923
  const title = readStringProperty(node, "title") ?? "Untitled Page";
2666
2924
  const body = readMarkdownBody(node);
@@ -2687,7 +2945,7 @@ function readMarkdownBody(node) {
2687
2945
  const value = readStringProperty(node, "markdown") ?? readStringProperty(node, "content") ?? readStringProperty(node, "body") ?? readStringProperty(node, "text") ?? readStringProperty(node, "description");
2688
2946
  if (value) return value;
2689
2947
  const richContent = node.properties.content;
2690
- if (isRecord3(richContent)) {
2948
+ if (isRecord2(richContent)) {
2691
2949
  return `\`\`\`json
2692
2950
  ${JSON.stringify(richContent, null, 2)}
2693
2951
  \`\`\``;
@@ -2748,7 +3006,7 @@ function relationFieldNames(schema) {
2748
3006
  if (!schema) return [];
2749
3007
  const fields = [];
2750
3008
  for (const [name, definition] of Object.entries(schema.properties)) {
2751
- if (isRecord3(definition) && definition.type === "relation") fields.push(name);
3009
+ if (isRecord2(definition) && definition.type === "relation") fields.push(name);
2752
3010
  }
2753
3011
  return fields;
2754
3012
  }
@@ -2778,7 +3036,7 @@ function readArrayProperty(node, property) {
2778
3036
  }
2779
3037
  function readNestedArrayProperty(node, objectProperty, arrayProperty) {
2780
3038
  const value = node.properties[objectProperty];
2781
- return isRecord3(value) && Array.isArray(value[arrayProperty]) ? value[arrayProperty] : void 0;
3039
+ return isRecord2(value) && Array.isArray(value[arrayProperty]) ? value[arrayProperty] : void 0;
2782
3040
  }
2783
3041
  function belongsToDatabase(node, databaseId) {
2784
3042
  return readStringProperty(node, "databaseId") === databaseId || readStringProperty(node, "database") === databaseId || readStringProperty(node, "parentDatabaseId") === databaseId;
@@ -2837,7 +3095,7 @@ function normalizeQuerySearch(value) {
2837
3095
  const text = value.trim();
2838
3096
  return text ? { text } : void 0;
2839
3097
  }
2840
- if (!isRecord3(value) || typeof value.text !== "string" || !value.text.trim()) {
3098
+ if (!isRecord2(value) || typeof value.text !== "string" || !value.text.trim()) {
2841
3099
  return void 0;
2842
3100
  }
2843
3101
  const fields = Array.isArray(value.fields) ? value.fields.filter(
@@ -2853,7 +3111,7 @@ function normalizeQueryMaterializedView(value) {
2853
3111
  const viewId2 = value.trim();
2854
3112
  return viewId2 ? { viewId: viewId2 } : void 0;
2855
3113
  }
2856
- if (!isRecord3(value)) return void 0;
3114
+ if (!isRecord2(value)) return void 0;
2857
3115
  const viewId = readRecordString(value, "viewId");
2858
3116
  if (!viewId) return void 0;
2859
3117
  const maxAgeMs = readRecordNumber(value, "maxAgeMs");
@@ -3126,7 +3384,7 @@ function invalidDatabaseApplyResult(plan, validation) {
3126
3384
  return {
3127
3385
  applied: false,
3128
3386
  databaseId: "unknown",
3129
- planId: isRecord3(plan) && typeof plan.id === "string" ? plan.id : "unknown",
3387
+ planId: isRecord2(plan) && typeof plan.id === "string" ? plan.id : "unknown",
3130
3388
  baseRevision: "unknown",
3131
3389
  liveRevision: "unknown",
3132
3390
  appliedChangeIds: [],
@@ -3144,7 +3402,7 @@ function readTransactionOperations(operation) {
3144
3402
  );
3145
3403
  }
3146
3404
  function readTransactionOperation(value, path) {
3147
- if (!isRecord3(value)) throw new Error(`${path} must be an object`);
3405
+ if (!isRecord2(value)) throw new Error(`${path} must be an object`);
3148
3406
  if (value.type === "create") {
3149
3407
  const options = readRecord(value, "options");
3150
3408
  const schemaId = options ? readRecordString(options, "schemaId") : void 0;
@@ -3237,15 +3495,15 @@ function databaseEntityMatchesId(entity, id) {
3237
3495
  }
3238
3496
  function readRecordArrayProperty(record, key) {
3239
3497
  const value = record[key];
3240
- return Array.isArray(value) ? value.filter(isRecord3) : [];
3498
+ return Array.isArray(value) ? value.filter(isRecord2) : [];
3241
3499
  }
3242
3500
  function isCanvasNode(node) {
3243
3501
  return node.schemaId.includes("/Canvas") || node.schemaId.includes("Canvas@") || Array.isArray(node.properties.objects) || Array.isArray(node.properties.nodes);
3244
3502
  }
3245
3503
  function readCanvasScene(canvas) {
3246
3504
  return {
3247
- objects: (readArrayProperty(canvas, "objects") ?? readArrayProperty(canvas, "nodes") ?? []).filter(isRecord3).map(normalizeCanvasObject),
3248
- edges: (readArrayProperty(canvas, "edges") ?? readArrayProperty(canvas, "connectors") ?? []).filter(isRecord3).map(normalizeCanvasEdge)
3505
+ objects: (readArrayProperty(canvas, "objects") ?? readArrayProperty(canvas, "nodes") ?? []).filter(isRecord2).map(normalizeCanvasObject),
3506
+ edges: (readArrayProperty(canvas, "edges") ?? readArrayProperty(canvas, "connectors") ?? []).filter(isRecord2).map(normalizeCanvasEdge)
3249
3507
  };
3250
3508
  }
3251
3509
  function normalizeCanvasObject(object) {
@@ -3388,8 +3646,8 @@ function canvasObjectFile(object) {
3388
3646
  return readRecordString(object, "file") ?? readRecordString(object, "filePath") ?? readRecordString(properties ?? {}, "file") ?? readRecordString(properties ?? {}, "filePath");
3389
3647
  }
3390
3648
  function jsonCanvasDocumentToCanvasOperations(document) {
3391
- const nodes = Array.isArray(document.nodes) ? document.nodes.filter(isRecord3) : [];
3392
- const edges = Array.isArray(document.edges) ? document.edges.filter(isRecord3) : [];
3649
+ const nodes = Array.isArray(document.nodes) ? document.nodes.filter(isRecord2) : [];
3650
+ const edges = Array.isArray(document.edges) ? document.edges.filter(isRecord2) : [];
3393
3651
  const nodeIds = new Set(nodes.map((node) => readRecordString(node, "id")).filter(Boolean));
3394
3652
  const warnings = edges.flatMap((edge) => {
3395
3653
  const fromNode = readRecordString(edge, "fromNode");
@@ -3621,10 +3879,10 @@ function readOperations(value) {
3621
3879
  throw new Error("operations must contain at least one operation");
3622
3880
  }
3623
3881
  return value.map((operation, index) => {
3624
- if (!isRecord3(operation) || typeof operation.op !== "string") {
3882
+ if (!isRecord2(operation) || typeof operation.op !== "string") {
3625
3883
  throw new Error(`operations[${index}].op must be a string`);
3626
3884
  }
3627
- const args = isRecord3(operation.args) ? operation.args : {};
3885
+ const args = isRecord2(operation.args) ? operation.args : {};
3628
3886
  return createAiOperation(
3629
3887
  operation.op,
3630
3888
  args,
@@ -3632,15 +3890,6 @@ function readOperations(value) {
3632
3890
  );
3633
3891
  });
3634
3892
  }
3635
- function readContextSeeds(value) {
3636
- if (!Array.isArray(value)) return [];
3637
- return value.map((seed) => {
3638
- if (!isRecord3(seed)) return null;
3639
- const kind = typeof seed.kind === "string" ? seed.kind : null;
3640
- const id = typeof seed.id === "string" ? seed.id : null;
3641
- return kind && id ? { kind, id } : null;
3642
- }).filter((seed) => seed !== null);
3643
- }
3644
3893
  function uriForSeed(seed) {
3645
3894
  switch (seed.kind) {
3646
3895
  case "node":
@@ -3700,75 +3949,6 @@ function stringifyTruncatedJson(text, maxCharacters) {
3700
3949
  function quoteYaml(value) {
3701
3950
  return JSON.stringify(value);
3702
3951
  }
3703
- function readRequiredString(record, key) {
3704
- const value = record[key];
3705
- if (typeof value !== "string" || !value.trim()) {
3706
- throw new Error(`${key} must be a non-empty string`);
3707
- }
3708
- return value;
3709
- }
3710
- function readRequiredRecord(record, key) {
3711
- const value = record[key];
3712
- if (!isRecord3(value)) {
3713
- throw new Error(`${key} must be an object`);
3714
- }
3715
- return value;
3716
- }
3717
- function readRequiredStringArray(value, key) {
3718
- const result = readStringArray(value);
3719
- if (result.length === 0) {
3720
- throw new Error(`${key} must contain at least one string`);
3721
- }
3722
- return result;
3723
- }
3724
- function readStringArray(value) {
3725
- if (!Array.isArray(value)) return [];
3726
- return value.filter((item) => typeof item === "string" && item.trim() !== "");
3727
- }
3728
- function readCsvStringArray(value) {
3729
- if (!value) return [];
3730
- return value.split(",").map((item) => item.trim()).filter(Boolean);
3731
- }
3732
- function readOptionalString(record, key) {
3733
- const value = record[key];
3734
- return typeof value === "string" && value.trim() ? value : void 0;
3735
- }
3736
- function readOptionalRecord(record, key) {
3737
- return readRecord(record, key);
3738
- }
3739
- function readOptionalNumber(record, key) {
3740
- const value = record[key];
3741
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3742
- }
3743
- function readOptionalBoolean(record, key) {
3744
- const value = record[key];
3745
- return typeof value === "boolean" ? value : void 0;
3746
- }
3747
- function readUrlNumber(params, key) {
3748
- const value = params.get(key);
3749
- if (value === null) return void 0;
3750
- const parsed = Number(value);
3751
- return Number.isFinite(parsed) ? parsed : void 0;
3752
- }
3753
- function readRecordString(record, key) {
3754
- const value = record[key];
3755
- return typeof value === "string" && value.trim() ? value : void 0;
3756
- }
3757
- function readRecord(record, key) {
3758
- const value = record[key];
3759
- return isRecord3(value) ? value : void 0;
3760
- }
3761
- function readRecordNumber(record, key) {
3762
- const value = record[key];
3763
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3764
- }
3765
- function readRecordBoolean(record, key) {
3766
- const value = record[key];
3767
- return typeof value === "boolean" ? value : void 0;
3768
- }
3769
- function isRecord3(value) {
3770
- return typeof value === "object" && value !== null && !Array.isArray(value);
3771
- }
3772
3952
 
3773
3953
  // src/ai-surface/skill.ts
3774
3954
  var XNET_AGENT_SKILL_MD = `---