@xnetjs/plugins 0.0.2 → 0.1.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.
@@ -1,3 +1,515 @@
1
+ import { NodeQueryDescriptor, NodeQueryResult } from '@xnetjs/data';
2
+ import { PublicWriteBudgetPolicy, AbuseSurface, AISignalProvenanceInput } from '@xnetjs/abuse';
3
+ import { IncomingMessage, ServerResponse } from 'node:http';
4
+
5
+ /**
6
+ * AI surface contract types for xNet resources, tools, mutation plans, and audit events.
7
+ */
8
+ type AiRiskLevel = 'low' | 'medium' | 'high' | 'critical';
9
+ type AiScope = 'workspace.read' | 'workspace.search' | 'page.read' | 'page.propose' | 'page.write' | 'database.read' | 'database.query' | 'database.propose' | 'database.write.rows' | 'database.write.schema' | 'canvas.read' | 'canvas.propose' | 'canvas.write' | 'storage.diagnostics' | 'storage.recovery' | 'network.fetch' | 'agent.workspace.export' | 'agent.workspace.import';
10
+ declare const AI_RISK_LEVELS: readonly AiRiskLevel[];
11
+ declare const AI_SCOPES: readonly AiScope[];
12
+ type AiTargetKind = 'workspace' | 'node' | 'page' | 'database' | 'databaseRows' | 'canvas' | 'storage';
13
+ declare const AI_TARGET_KINDS: readonly AiTargetKind[];
14
+ type AiJsonSchemaType = 'string' | 'number' | 'boolean' | 'object' | 'array';
15
+ type AiJsonSchema = {
16
+ type: AiJsonSchemaType;
17
+ description?: string;
18
+ enum?: readonly string[];
19
+ properties?: Record<string, AiJsonSchema>;
20
+ required?: readonly string[];
21
+ items?: AiJsonSchema;
22
+ additionalProperties?: boolean | AiJsonSchema;
23
+ };
24
+ type AiResource = {
25
+ uri: string;
26
+ name: string;
27
+ description?: string;
28
+ mimeType: string;
29
+ risk: AiRiskLevel;
30
+ requiredScopes: AiScope[];
31
+ dynamic?: boolean;
32
+ };
33
+ type AiToolDefinition = {
34
+ name: string;
35
+ title: string;
36
+ description: string;
37
+ risk: AiRiskLevel;
38
+ requiredScopes: AiScope[];
39
+ inputSchema: {
40
+ type: 'object';
41
+ properties: Record<string, AiJsonSchema>;
42
+ required?: readonly string[];
43
+ };
44
+ };
45
+ type AiToolCallResult = {
46
+ content: Array<{
47
+ type: 'text';
48
+ text: string;
49
+ }>;
50
+ };
51
+ /**
52
+ * A tool the AI surface can list and call but whose implementation lives outside
53
+ * the surface (a plugin/connector contribution, exploration 0196). `invoke`
54
+ * returns raw data; the surface serializes it for the model, keeping contributed
55
+ * tools symmetric with the built-in `xnet_*` tools.
56
+ */
57
+ type AiExtraTool = AiToolDefinition & {
58
+ invoke: (args: Record<string, unknown>) => unknown | Promise<unknown>;
59
+ };
60
+ type AiValidationResult = {
61
+ valid: boolean;
62
+ warnings: string[];
63
+ errors: string[];
64
+ };
65
+ type AiOperation<TArgs extends Record<string, unknown> = Record<string, unknown>> = {
66
+ op: string;
67
+ args: TArgs;
68
+ rationale?: string;
69
+ };
70
+ type AiChangeSet = {
71
+ targetKind: AiTargetKind;
72
+ targetId: string;
73
+ baseRevision: string;
74
+ operations: AiOperation[];
75
+ };
76
+ type AiMutationPlanStatus = 'proposed' | 'validated' | 'applied' | 'rejected';
77
+ type AiMutationPlan = {
78
+ id: string;
79
+ workspaceId?: string;
80
+ actor: string;
81
+ intent: string;
82
+ risk: AiRiskLevel;
83
+ requiredScopes: AiScope[];
84
+ changes: AiChangeSet[];
85
+ validation: AiValidationResult;
86
+ createdAt: string;
87
+ status: AiMutationPlanStatus;
88
+ };
89
+ type AiAuditEvent = {
90
+ id: string;
91
+ planId: string;
92
+ actor: string;
93
+ risk: AiRiskLevel;
94
+ requiredScopes: AiScope[];
95
+ validation: AiValidationResult;
96
+ appliedChangeIds: string[];
97
+ rollbackHandle?: string;
98
+ createdAt: string;
99
+ };
100
+ type AiContextSeed = {
101
+ kind: AiTargetKind;
102
+ id: string;
103
+ };
104
+ type AiContextPackResource = {
105
+ uri: string;
106
+ mimeType: string;
107
+ text: string;
108
+ trust: {
109
+ level: 'workspace' | 'external-untrusted';
110
+ instructionBoundary: string;
111
+ };
112
+ citation: {
113
+ kind: AiTargetKind;
114
+ id: string;
115
+ revision?: string;
116
+ };
117
+ };
118
+ type AiContextPack = {
119
+ id: string;
120
+ query?: string;
121
+ seeds: AiContextSeed[];
122
+ resources: AiContextPackResource[];
123
+ createdAt: string;
124
+ limits: {
125
+ maxResources: number;
126
+ maxCharactersPerResource: number;
127
+ };
128
+ };
129
+ declare function isAiRiskLevel(value: unknown): value is AiRiskLevel;
130
+ declare function isAiScope(value: unknown): value is AiScope;
131
+ declare function isAiTargetKind(value: unknown): value is AiTargetKind;
132
+
133
+ /**
134
+ * @xnetjs/plugins — agent tools contribution point (exploration 0196).
135
+ *
136
+ * 0194 Phase 2 (`contributionsAsAiTools`) let the AI call a plugin *command*.
137
+ * This adds first-class, model-facing tools that are NOT tied to a UI command —
138
+ * the shape a Connector uses to expose, say, `slack_search_mentions` that reads
139
+ * the governed node store. Register once via `contributes.agentTools`; the host
140
+ * folds them into the AI surface's `extraTools`, so they appear in the in-app
141
+ * AI, the MCP server, and the files-first skill alike.
142
+ *
143
+ * A tool's `invoke` returns raw data; the surface serializes it for the model,
144
+ * keeping contributed tools symmetric with the built-in `xnet_*` tools.
145
+ */
146
+
147
+ /** Input schema shape, shared with `AiToolDefinition`. */
148
+ type AgentToolInputSchema = AiToolDefinition['inputSchema'];
149
+ /**
150
+ * A plugin-contributed agent tool. Registered under `id`; exposed to the model
151
+ * under `name` (snake_case, plugin-namespaced by convention, e.g.
152
+ * `slack_search_mentions`).
153
+ */
154
+ interface AgentToolContribution {
155
+ /** Registry key, plugin-scoped (e.g. `dev.xnet.connector.slack.search`). */
156
+ id: string;
157
+ /** Model-facing tool name (snake_case), e.g. `slack_search_mentions`. */
158
+ name: string;
159
+ /** Human title for menus (defaults to `name`). */
160
+ title?: string;
161
+ /** What the tool does — shown to the model. */
162
+ description: string;
163
+ /** Risk surfaced to the agent + consent layer (default `medium`). */
164
+ risk?: AiRiskLevel;
165
+ /** AI scopes this tool requires (default none). */
166
+ requiredScopes?: AiScope[];
167
+ /** JSON schema for the tool args (default: empty object schema). */
168
+ inputSchema?: AgentToolInputSchema;
169
+ /** Execute the tool. Returns raw data; the surface serializes it for the model. */
170
+ invoke: (args: Record<string, unknown>) => unknown | Promise<unknown>;
171
+ }
172
+
173
+ /**
174
+ * Validation helpers for AI surface mutation plans.
175
+ */
176
+
177
+ declare function createAiValidationResult(errors?: string[], warnings?: string[]): AiValidationResult;
178
+ /**
179
+ * Validate a serialized mutation plan before it can be previewed or applied.
180
+ */
181
+ declare function validateAiMutationPlan(plan: unknown): AiValidationResult;
182
+ /**
183
+ * Return a copy of a plan with the latest validation result attached.
184
+ */
185
+ declare function attachAiPlanValidation(plan: AiMutationPlan): AiMutationPlan;
186
+ declare function serializeAiMutationPlan(plan: AiMutationPlan): string;
187
+ declare function parseAiMutationPlan(serialized: string): {
188
+ plan: AiMutationPlan | null;
189
+ validation: AiValidationResult;
190
+ };
191
+ declare function createAiOperation<TArgs extends Record<string, unknown>>(op: string, args: TArgs, rationale?: string): AiOperation<TArgs>;
192
+ declare function createAiChangeSet(input: AiChangeSet): AiChangeSet;
193
+
194
+ /**
195
+ * AI surface service for focused resources, context packs, and plan-only tools.
196
+ */
197
+
198
+ type AiResourceContent = {
199
+ uri: string;
200
+ mimeType: string;
201
+ text: string;
202
+ };
203
+ type AiSearchResult = {
204
+ id: string;
205
+ schemaId: string;
206
+ title: string;
207
+ snippet: string;
208
+ score: number;
209
+ revision: string;
210
+ updatedAt: number;
211
+ };
212
+ type AiSearchOptions = {
213
+ query: string;
214
+ schemaId?: string;
215
+ limit?: number;
216
+ offset?: number;
217
+ };
218
+ type AiSurfaceLimits = {
219
+ maxListLimit: number;
220
+ maxSearchScan: number;
221
+ maxSearchResults: number;
222
+ maxContextResources: number;
223
+ maxCharactersPerResource: number;
224
+ maxJsonCharacters: number;
225
+ maxCanvasObjects: number;
226
+ maxDatabaseRows: number;
227
+ };
228
+ type AiPageMarkdownApplyAdapterInput = {
229
+ pageId: string;
230
+ markdown: string;
231
+ bodyMarkdown: string;
232
+ baseRevision: string;
233
+ plan: AiMutationPlan;
234
+ operation: AiOperation;
235
+ };
236
+ type AiPageMarkdownApplyAdapterResult = {
237
+ mode: 'tiptap-yjs' | 'yjs' | 'custom';
238
+ yjsField?: string;
239
+ documentUpdate?: unknown;
240
+ warnings?: string[];
241
+ };
242
+ type AiPageMarkdownApplyAdapter = {
243
+ applyMarkdown(input: AiPageMarkdownApplyAdapterInput): Promise<AiPageMarkdownApplyAdapterResult>;
244
+ };
245
+ type AiPageMarkdownApplyResult = {
246
+ applied: boolean;
247
+ pageId: string;
248
+ planId: string;
249
+ mode: 'tiptap-yjs' | 'yjs' | 'custom' | 'node-property';
250
+ baseRevision: string;
251
+ liveRevision: string;
252
+ markdownHash: string;
253
+ bodyMarkdownHash: string;
254
+ validation: {
255
+ valid: boolean;
256
+ errors: string[];
257
+ warnings: string[];
258
+ };
259
+ auditEventId?: string;
260
+ rollbackHandle?: string;
261
+ yjsField?: string;
262
+ documentUpdate?: unknown;
263
+ };
264
+ type AiPageMarkdownRollbackResult = {
265
+ rolledBack: boolean;
266
+ pageId: string;
267
+ planId: string;
268
+ rollbackHandle: string;
269
+ auditEventId?: string;
270
+ validation: {
271
+ valid: boolean;
272
+ errors: string[];
273
+ warnings: string[];
274
+ };
275
+ };
276
+ /** A node surfaced by an external retriever, with optional provenance. */
277
+ type AiRetrievedNode = {
278
+ nodeId: string;
279
+ /** Human-readable graph path back to an entry node (for citation/provenance). */
280
+ pathLabel?: string;
281
+ };
282
+ /**
283
+ * Optional graph-aware retriever (exploration 0211). When provided, the `query`
284
+ * path of `createContextPack` uses it instead of the built-in linear keyword
285
+ * scan — i.e. hybrid vector + keyword entry search plus bounded graph expansion,
286
+ * budgeted. The app injects `@xnetjs/brain`'s `retrieve` here (mapping its items
287
+ * to `{ nodeId, pathLabel }`); absent it, context packs fall back to `search()`
288
+ * with no behavior change.
289
+ */
290
+ type AiContextRetriever = (query: string, options: {
291
+ limit: number;
292
+ }) => Promise<AiRetrievedNode[]>;
293
+ type AiSurfaceServiceConfig = {
294
+ store: NodeStoreAPI;
295
+ schemas: SchemaRegistryAPI;
296
+ limits?: Partial<AiSurfaceLimits>;
297
+ clock?: () => Date;
298
+ pageMarkdownAdapter?: AiPageMarkdownApplyAdapter;
299
+ /**
300
+ * Tools contributed from outside the surface — plugin/connector `agentTools`
301
+ * (exploration 0196). They are appended to `getTools()` and dispatched by
302
+ * `callTool()`, so every consumer (in-app AI, the MCP server, the files-first
303
+ * skill) sees them by registering once. A built-in `xnet_*` name always wins
304
+ * over a colliding extra tool.
305
+ */
306
+ extraTools?: AiExtraTool[];
307
+ /**
308
+ * Optional graph-aware context retriever (exploration 0211). Drives the
309
+ * `query` path of `createContextPack` when present; falls back to the built-in
310
+ * keyword `search()` otherwise.
311
+ */
312
+ retrieveContext?: AiContextRetriever;
313
+ };
314
+ declare class AiSurfaceService {
315
+ private readonly config;
316
+ private readonly limits;
317
+ private readonly clock;
318
+ private sequence;
319
+ private auditEvents;
320
+ private readonly rollbackSnapshots;
321
+ /** Contributed tools by name (exploration 0196), de-duped at construction. */
322
+ private readonly extraTools;
323
+ constructor(config: AiSurfaceServiceConfig);
324
+ /** The contributed (non-built-in) tools, with built-in name collisions removed. */
325
+ private getExtraTools;
326
+ getResources(): AiResource[];
327
+ /** The full tool surface: built-in `xnet_*` tools plus contributed extras. */
328
+ getTools(): AiToolDefinition[];
329
+ private builtInTools;
330
+ callTool(name: string, args?: Record<string, unknown>): Promise<unknown>;
331
+ readResource(uri: string): Promise<AiResourceContent>;
332
+ toJsonText(value: unknown, format?: 'concise' | 'detailed'): string;
333
+ private getWorkspaceSummary;
334
+ private getRecentNodes;
335
+ search(options: AiSearchOptions): Promise<Record<string, unknown>>;
336
+ /**
337
+ * Candidate node ids for a context-pack query: the injected graph-aware
338
+ * retriever (exploration 0211) when configured, else the built-in keyword
339
+ * `search()`. Order is preserved (best-first).
340
+ */
341
+ private candidateNodeIdsForQuery;
342
+ createContextPack(options: {
343
+ query?: string;
344
+ seeds?: AiContextSeed[];
345
+ limit?: number;
346
+ }): Promise<AiContextPack>;
347
+ /**
348
+ * Walk typed relation edges out from a node to its connected neighbors, bounded
349
+ * by `hops` (1–2) and a result `limit`. This is the just-in-time companion to
350
+ * `createContextPack` (exploration 0211): retrieval hands the agent a budgeted
351
+ * slice plus expandable node ids, and this lets it pull a specific node's
352
+ * connections on demand instead of over-fetching the whole graph up front.
353
+ */
354
+ private expandGraph;
355
+ private graphExpansion;
356
+ createExternalContextResource(options: {
357
+ url: string;
358
+ text: string;
359
+ mimeType?: string;
360
+ }): AiContextPackResource;
361
+ private readPageMarkdown;
362
+ private readPageOutline;
363
+ private planPagePatch;
364
+ private applyPageMarkdown;
365
+ private finalizeAppliedPageMarkdown;
366
+ private getAuditLog;
367
+ private rollbackPageMarkdown;
368
+ private recordAuditEvent;
369
+ private describeDatabase;
370
+ private readDatabaseViews;
371
+ private queryDatabase;
372
+ private sampleDatabase;
373
+ private explainDatabaseQuery;
374
+ private executeDatabaseQueryDescriptor;
375
+ private listCanvases;
376
+ private readCanvasObjects;
377
+ private readCanvasViewport;
378
+ private readCanvasSelection;
379
+ private searchCanvas;
380
+ private exportCanvasJsonCanvas;
381
+ private planCanvasJsonCanvasImport;
382
+ private readCanvasObject;
383
+ private hydrateSourcePreviews;
384
+ private planCanvasMutation;
385
+ private planDatabaseMutation;
386
+ private applyDatabaseMutation;
387
+ private applyDatabaseRowOperations;
388
+ private applyDatabaseRowTransactionOperation;
389
+ private rollbackAppliedDatabaseRowChanges;
390
+ private applyDatabaseSchemaOperations;
391
+ private planSurfaceMutation;
392
+ private validatedPlan;
393
+ private getNodeOrThrow;
394
+ private getNodeProjection;
395
+ private getSchemaSummaries;
396
+ private readSchemaSummary;
397
+ private contextResourceForSeed;
398
+ private jsonResource;
399
+ private stringifyJson;
400
+ private nextId;
401
+ private nowIso;
402
+ }
403
+ declare function createAiSurfaceService(config: AiSurfaceServiceConfig): AiSurfaceService;
404
+
405
+ /**
406
+ * The xNet agent skill: a single cross-harness SKILL.md (Claude Code, Codex,
407
+ * Gemini CLI, Cursor) describing the vault checkout and the xnet CLI.
408
+ *
409
+ * This is the primary agent interface contract. Keep it stable between
410
+ * releases (prompt caching) and under the token budget guarded by
411
+ * agent-token-budget tests (~1k tokens).
412
+ */
413
+ declare const XNET_AGENT_SKILL_MD = "---\nname: xnet\ndescription: Read, search, and edit the user's xNet workspace (pages, databases, canvases) via vault files and the xnet CLI.\n---\n\n# Working with xNet\n\nThis folder is a checkout of the user's xNet workspace. The xNet database\nstays the source of truth; these files are a working tree. Identity lives in\nfrontmatter and the manifest, never in filenames.\n\n## Layout\n\n- `Pages/<slug>.md` \u2014 pages as Markdown with YAML frontmatter\n (`xnet.id`, `xnet.revision`). Never edit frontmatter.\n- `Databases/<slug>.schema.json` + `<slug>.rows.jsonl` \u2014 one JSON object\n per line; edit, append, or remove lines to change rows.\n `<slug>.tsv` sidecars are read-only fast reads.\n- `Canvases/<slug>.canvas` \u2014 JSON Canvas projections.\n- Wikilinks `[[Title]]`, `:::xnet-database` blocks, and\n `{{xnet-ref ...}}` directives are live references \u2014 preserve them.\n\n## Workflow\n\n- Find things: Grep this checkout first; `xnet search \"<text>\"` for ranked\n results (TSV: id, slug, title, snippet).\n- Need more data? `xnet checkout --query \"<text>\"` (or `--schema <iri>`,\n `--kind page|database|canvas`, `--node <id>`) materializes a scoped\n slice into this folder. Never export everything.\n- Query tables: `xnet query <database> --where field=value --format tsv`\n (default TSV; `--format jsonl|json` when you need structure).\n- File edits become validated mutation plans. With `xnet daemon` running\n they are picked up automatically; otherwise run `xnet commit`.\n- `xnet status` lists pending plans and conflicts. Conflicts are quarantined\n in `.xnet/conflicts/` with a Markdown note explaining how to resolve;\n fix the file and save again. Stale edits never silently overwrite.\n- Bulk or aggregate work: `xnet run <script.js>` executes a sandboxed\n script with an `api` object (`api.nodes(schema)`, `api.search(text)`,\n `api.proposeUpdate(id, props)`, `api.proposeCreate(schema, props)`).\n Writes are proposals that flow through the same plan pipeline. Return a\n digest, not raw rows.\n";
414
+
415
+ /**
416
+ * Token-efficient output formats for agent-facing surfaces.
417
+ *
418
+ * TSV is ~2x cheaper than JSON for tabular reads; these helpers are shared by
419
+ * the workspace exporter (.tsv sidecars), the xnet CLI, and MCP responses.
420
+ */
421
+ /** Render rows as TSV for cheap agent reads. Tabs/newlines collapse to spaces. */
422
+ declare function toTsv(nodeRows: Record<string, unknown>[]): string;
423
+ /** Database rows are node-shaped; lift `properties` to top-level TSV columns. */
424
+ declare function flattenRowForTsv(row: Record<string, unknown>): Record<string, unknown>;
425
+
426
+ /**
427
+ * Page Markdown validation for AI-edited xNet page projections.
428
+ */
429
+
430
+ type XNetPageMarkdownFrontmatter = {
431
+ id?: string;
432
+ schemaId?: string;
433
+ revision?: string;
434
+ exportedAt?: string;
435
+ };
436
+ type XNetMarkdownDirective = {
437
+ kind: 'block' | 'inline' | 'wikilink';
438
+ name: string;
439
+ index: number;
440
+ payload?: Record<string, unknown>;
441
+ target?: string;
442
+ };
443
+ type XNetMarkdownDirectiveSpec = {
444
+ kind: 'block' | 'inline' | 'wikilink';
445
+ name: string;
446
+ editorExtension: string;
447
+ description: string;
448
+ };
449
+ type XNetPageMarkdownValidationOptions = {
450
+ pageId?: string;
451
+ schemaId?: string;
452
+ baseRevision?: string;
453
+ };
454
+ type XNetPageMarkdownValidation = {
455
+ frontmatter: XNetPageMarkdownFrontmatter | null;
456
+ directives: XNetMarkdownDirective[];
457
+ validation: AiValidationResult;
458
+ };
459
+ type XNetMarkdownDiffLineKind = 'context' | 'removed' | 'added';
460
+ type XNetMarkdownDiffLine = {
461
+ kind: XNetMarkdownDiffLineKind;
462
+ text: string;
463
+ beforeLine?: number;
464
+ afterLine?: number;
465
+ };
466
+ type XNetMarkdownReviewDiff = {
467
+ kind: 'markdown-diff';
468
+ format: 'line';
469
+ beforeLineCount: number;
470
+ afterLineCount: number;
471
+ lineCount: number;
472
+ unifiedDiff: string;
473
+ lines: XNetMarkdownDiffLine[];
474
+ };
475
+ declare const XNET_MARKDOWN_DIRECTIVE_SPECS: readonly [{
476
+ readonly kind: "block";
477
+ readonly name: "xnet-database";
478
+ readonly editorExtension: "DatabaseEmbedExtension";
479
+ readonly description: "Embeds a database view block with JSON view configuration.";
480
+ }, {
481
+ readonly kind: "block";
482
+ readonly name: "xnet-page";
483
+ readonly editorExtension: "PageEmbedExtension";
484
+ readonly description: "Embeds a page preview block with page identity and preview metadata.";
485
+ }, {
486
+ readonly kind: "block";
487
+ readonly name: "xnet-embed";
488
+ readonly editorExtension: "EmbedExtension";
489
+ readonly description: "Embeds rich external media with provider metadata.";
490
+ }, {
491
+ readonly kind: "inline";
492
+ readonly name: "xnet-ref";
493
+ readonly editorExtension: "SmartReferenceExtension";
494
+ readonly description: "Embeds an inline smart reference to an external or internal resource.";
495
+ }, {
496
+ readonly kind: "inline";
497
+ readonly name: "xnet-db-ref";
498
+ readonly editorExtension: "DatabaseReferenceExtension";
499
+ readonly description: "Embeds an inline database reference.";
500
+ }, {
501
+ readonly kind: "wikilink";
502
+ readonly name: "wikilink";
503
+ readonly editorExtension: "Wikilink";
504
+ readonly description: "Parses [[Page Title]] wikilinks into xNet page links.";
505
+ }];
506
+ declare function validateXNetPageMarkdown(markdown: string, options?: XNetPageMarkdownValidationOptions): XNetPageMarkdownValidation;
507
+ declare function getXNetMarkdownDirectiveSpecs(): readonly XNetMarkdownDirectiveSpec[];
508
+ declare function parseXNetPageFrontmatter(markdown: string): XNetPageMarkdownFrontmatter | null;
509
+ declare function stripXNetPageFrontmatter(markdown: string): string;
510
+ declare function renderMarkdownLineDiff(before: string, after: string): string;
511
+ declare function renderMarkdownReviewDiff(before: string, after: string): XNetMarkdownReviewDiff;
512
+
1
513
  /**
2
514
  * Local HTTP API Server
3
515
  *
@@ -6,6 +518,16 @@
6
518
  *
7
519
  * This is designed to run in the Electron main process.
8
520
  */
521
+
522
+ type LocalAPITokenScope = 'health.read' | 'nodes.read' | 'nodes.write' | 'schemas.read' | 'events.read' | 'ai.read' | 'ai.write' | 'admin';
523
+ type LocalAPITokenConfig = {
524
+ token: string;
525
+ label?: string;
526
+ scopes: LocalAPITokenScope[];
527
+ aiScopes?: AiScope[];
528
+ createdAt?: string;
529
+ };
530
+ type LocalAPITokenSummary = Omit<LocalAPITokenConfig, 'token'>;
9
531
  /**
10
532
  * Node store interface (minimal subset needed by API)
11
533
  */
@@ -16,6 +538,7 @@ interface NodeStoreAPI {
16
538
  limit?: number;
17
539
  offset?: number;
18
540
  }): Promise<NodeData[]>;
541
+ query?(descriptor: NodeQueryDescriptor): Promise<NodeQueryResult>;
19
542
  create(options: {
20
543
  schemaId: string;
21
544
  properties: Record<string, unknown>;
@@ -72,6 +595,8 @@ interface LocalAPIConfig {
72
595
  host?: string;
73
596
  /** API token for authentication (optional) */
74
597
  token?: string;
598
+ /** Scoped API tokens for local-only integrations. */
599
+ tokens?: LocalAPITokenConfig[];
75
600
  /**
76
601
  * Allowed CORS origins (default: none).
77
602
  * Set to specific origins like ['http://localhost:3000'] for local dev,
@@ -83,6 +608,10 @@ interface LocalAPIConfig {
83
608
  store: NodeStoreAPI;
84
609
  /** SchemaRegistry instance */
85
610
  schemas: SchemaRegistryAPI;
611
+ /** Shared AI surface service. A default service is created when omitted. */
612
+ aiSurface?: AiSurfaceService;
613
+ /** Optional output and pagination limits for the default AI surface. */
614
+ aiLimits?: Partial<AiSurfaceLimits>;
86
615
  }
87
616
  /**
88
617
  * Local HTTP API server for xNet integrations.
@@ -109,6 +638,18 @@ declare class LocalAPIServer {
109
638
  * Check if server is running
110
639
  */
111
640
  get isRunning(): boolean;
641
+ /**
642
+ * Return redacted token metadata for UI permission management.
643
+ */
644
+ getTokenSummaries(): LocalAPITokenSummary[];
645
+ /**
646
+ * Replace scoped local API tokens.
647
+ */
648
+ setTokens(tokens: LocalAPITokenConfig[]): LocalAPITokenSummary[];
649
+ /**
650
+ * Rotate to a new single scoped token.
651
+ */
652
+ rotateToken(token: string, options?: Partial<Omit<LocalAPITokenConfig, 'token'>>): LocalAPITokenSummary;
112
653
  private handleRequest;
113
654
  private handleHealth;
114
655
  private handleListNodes;
@@ -120,6 +661,13 @@ declare class LocalAPIServer {
120
661
  private handleGetEvents;
121
662
  private handleListSchemas;
122
663
  private handleGetSchema;
664
+ private handleListAIResources;
665
+ private handleReadAIResource;
666
+ private handleCallAITool;
667
+ private handleAISearch;
668
+ private handleAIContextPack;
669
+ private handleAIPlanValidation;
670
+ private authenticateRequest;
123
671
  private parseBody;
124
672
  private sendJSON;
125
673
  private sendError;
@@ -144,6 +692,92 @@ declare class LocalAPIServer {
144
692
  */
145
693
  declare function createLocalAPI(config: LocalAPIConfig): LocalAPIServer;
146
694
 
695
+ /**
696
+ * Write guardrail for the MCP server (exploration 0175, "boundary hardening").
697
+ *
698
+ * The 0175 pitch is that xNet's guardrail makes an autonomous agent safe to
699
+ * point at a workspace. But the generic `xnet_create/update/delete` tools used
700
+ * to mutate the store directly, bypassing the mutation-plan guardrail that the
701
+ * page/database tools enforce. This closes that gap for *every* MCP client
702
+ * (OpenClaw, Claude Code, …) at the boundary, independent of the client's own
703
+ * (often weak) safety model:
704
+ *
705
+ * - **Risk classification.** Deletes are `high`; creating an outward-facing node
706
+ * (e.g. a chat message — "sending") is `high`; ordinary creates/updates are
707
+ * `low`.
708
+ * - **Confirmation gate.** `high`/`critical` and outward-facing writes return
709
+ * `needs-confirmation` instead of mutating, until the caller re-issues with
710
+ * `confirm: true` (after the human approves — see the ClawHub skill).
711
+ * - **Cost budget.** Writes are charged against a per-surface budget
712
+ * (`@xnetjs/abuse`); a runaway agent is throttled, not unbounded.
713
+ * - **Provenance + audit.** Applied writes are recorded with their risk and an
714
+ * optional AI-provenance evidence ref, queryable for review.
715
+ */
716
+
717
+ type McpWriteKind = 'create' | 'update' | 'delete';
718
+ interface McpWriteRequest {
719
+ kind: McpWriteKind;
720
+ /** Schema IRI (for create). Used to detect outward-facing writes. */
721
+ schemaId?: string;
722
+ /** Target node id (for update/delete). */
723
+ nodeId?: string;
724
+ /** Caller confirmation; required to apply high-risk / outward-facing writes. */
725
+ confirm?: boolean;
726
+ /** Optional provenance from the calling agent (model that authored the write). */
727
+ provenance?: AISignalProvenanceInput;
728
+ }
729
+ type McpWriteVerdict = {
730
+ decision: 'allow';
731
+ risk: AiRiskLevel;
732
+ outwardFacing: boolean;
733
+ provenanceRef: string | null;
734
+ } | {
735
+ decision: 'needs-confirmation';
736
+ risk: AiRiskLevel;
737
+ outwardFacing: boolean;
738
+ reason: string;
739
+ } | {
740
+ decision: 'blocked';
741
+ risk: AiRiskLevel;
742
+ reason: string;
743
+ };
744
+ interface McpWriteAuditEvent {
745
+ kind: McpWriteKind;
746
+ risk: AiRiskLevel;
747
+ outwardFacing: boolean;
748
+ schemaId?: string;
749
+ nodeId?: string;
750
+ provenanceRef?: string;
751
+ at: number;
752
+ }
753
+ interface McpWriteGuardrailOptions {
754
+ /** Schema IRIs whose creation is outward-facing (raises risk, requires confirm). */
755
+ outwardFacingSchemas?: readonly string[];
756
+ /** Cost budget policy. Defaults to 120 writes / 60s on the surface. */
757
+ budgetPolicy?: PublicWriteBudgetPolicy;
758
+ /** Abuse surface for budget keys. Defaults to `localApi`. */
759
+ surface?: AbuseSurface;
760
+ /** Injectable clock (ms). Defaults to `Date.now`. */
761
+ clock?: () => number;
762
+ }
763
+ declare class McpWriteGuardrail {
764
+ private readonly outward;
765
+ private readonly policy;
766
+ private readonly surface;
767
+ private readonly now;
768
+ private usage;
769
+ private auditLog;
770
+ constructor(options?: McpWriteGuardrailOptions);
771
+ /** Classify and gate a write. Does not mutate anything; charges budget only on `allow`. */
772
+ evaluate(req: McpWriteRequest): McpWriteVerdict;
773
+ /** Record an applied write for audit/rollback review. Returns the event. */
774
+ recordApplied(req: McpWriteRequest, verdict: Extract<McpWriteVerdict, {
775
+ decision: 'allow';
776
+ }>, nodeId?: string): McpWriteAuditEvent;
777
+ getAuditLog(limit?: number): McpWriteAuditEvent[];
778
+ private classify;
779
+ }
780
+
147
781
  /**
148
782
  * MCP Server for xNet
149
783
  *
@@ -158,7 +792,15 @@ declare function createLocalAPI(config: LocalAPIConfig): LocalAPIServer;
158
792
  */
159
793
  interface MCPTool {
160
794
  name: string;
795
+ title?: string;
161
796
  description: string;
797
+ risk?: string;
798
+ requiredScopes?: string[];
799
+ /**
800
+ * Tool Search Tool hint (advanced-tool-use): deferred tools are discovered
801
+ * on demand instead of paying their definition tokens on every turn.
802
+ */
803
+ defer_loading?: boolean;
162
804
  inputSchema: {
163
805
  type: 'object';
164
806
  properties: Record<string, MCPPropertySchema>;
@@ -171,7 +813,11 @@ interface MCPTool {
171
813
  interface MCPPropertySchema {
172
814
  type: 'string' | 'number' | 'boolean' | 'object' | 'array';
173
815
  description?: string;
816
+ enum?: readonly string[];
817
+ properties?: Record<string, MCPPropertySchema>;
818
+ required?: string[];
174
819
  items?: MCPPropertySchema;
820
+ additionalProperties?: boolean | MCPPropertySchema;
175
821
  }
176
822
  /**
177
823
  * MCP Resource definition
@@ -181,6 +827,9 @@ interface MCPResource {
181
827
  name: string;
182
828
  description?: string;
183
829
  mimeType?: string;
830
+ risk?: string;
831
+ requiredScopes?: string[];
832
+ dynamic?: boolean;
184
833
  }
185
834
  /**
186
835
  * MCP Request from client
@@ -210,6 +859,23 @@ interface MCPResponse {
210
859
  interface MCPServerConfig {
211
860
  store: NodeStoreAPI;
212
861
  schemas: SchemaRegistryAPI;
862
+ /** Shared AI surface service. A default service is created when omitted. */
863
+ aiSurface?: AiSurfaceService;
864
+ /** Optional output and pagination limits for the default AI surface. */
865
+ aiLimits?: Partial<AiSurfaceLimits>;
866
+ /**
867
+ * Plugin/connector agent tools to expose (exploration 0196). Folded into the
868
+ * default AI surface's `extraTools`, so they appear in `tools/list` (deferred,
869
+ * since not in {@link MCP_CORE_TOOL_NAMES}) and dispatch through `tools/call`.
870
+ * Ignored when a pre-built `aiSurface` is supplied — wire `extraTools` there.
871
+ */
872
+ agentTools?: AgentToolContribution[];
873
+ /**
874
+ * Write guardrail for the generic + first-class write tools. A default
875
+ * guardrail (delete/outward writes need confirmation, cost budget, audit) is
876
+ * created when omitted. Pass a configured instance to tune it.
877
+ */
878
+ guardrail?: McpWriteGuardrail;
213
879
  /** Server name (default: 'xnet') */
214
880
  name?: string;
215
881
  /** Server version (default: '1.0.0') */
@@ -240,6 +906,7 @@ interface MCPServerConfig {
240
906
  declare class MCPServer {
241
907
  private config;
242
908
  private tools;
909
+ private aiToolNames;
243
910
  private running;
244
911
  constructor(config: MCPServerConfig);
245
912
  /**
@@ -273,12 +940,22 @@ declare class MCPServer {
273
940
  * Reads JSON-RPC requests from stdin and writes responses to stdout.
274
941
  */
275
942
  startStdio(): Promise<void>;
943
+ /** Parse one stdin line as a JSON-RPC request and write the response. */
944
+ private handleStdioLine;
276
945
  /**
277
946
  * Stop the stdio server
278
947
  */
279
948
  stop(): void;
280
949
  private registerTools;
281
950
  private handleToolCall;
951
+ /**
952
+ * Route a write through the guardrail. Returns a gate result
953
+ * (`requiresConfirmation` / `blocked`) without mutating, or applies + records
954
+ * an audit entry and returns the applied value.
955
+ */
956
+ private guardedWrite;
957
+ /** Shared create path for the first-class write tools. */
958
+ private createNode;
282
959
  private handleResourceRead;
283
960
  }
284
961
  /**
@@ -300,6 +977,535 @@ declare class MCPServer {
300
977
  */
301
978
  declare function createMCPServer(config: MCPServerConfig): MCPServer;
302
979
 
980
+ /**
981
+ * HTTP transport for the xNet MCP server (exploration 0175).
982
+ *
983
+ * The {@link MCPServer} ships a stdio transport for process-spawned clients
984
+ * (Claude Code, Codex, OpenClaw `mcp.servers` with `transport: "stdio"`). A
985
+ * browser or an HTTP-only MCP client (OpenClaw `transport: "streamable-http"`)
986
+ * cannot speak stdio, so this exposes the same `handleRequest` JSON-RPC surface
987
+ * over HTTP.
988
+ *
989
+ * Because the same surface can mutate a user's workspace, the transport is a
990
+ * trust boundary and is hardened accordingly (exploration 0175, "boundary
991
+ * hardening"):
992
+ *
993
+ * - **Loopback only.** Binds `127.0.0.1`/`::1`; refuses to start on a
994
+ * non-loopback host so the substrate is never exposed to the network the way
995
+ * OpenClaw's own `0.0.0.0:18789` default famously is.
996
+ * - **Pairing token.** Every JSON-RPC request must carry the shared
997
+ * `x-xnet-pairing` secret; compared in constant time.
998
+ * - **Origin allowlist.** Browser requests (those carrying an `Origin`) must
999
+ * match the allowlist; never reflects `*`. Non-browser clients (no `Origin`)
1000
+ * are allowed through to the token check.
1001
+ * - **Private Network Access.** Emits `Access-Control-Allow-Private-Network`
1002
+ * and answers `OPTIONS` preflights so Chrome's Local Network Access flow can
1003
+ * reach a loopback substrate from an https origin.
1004
+ *
1005
+ * The guardrail itself (mutation plans, approval, audit) lives in the
1006
+ * `AiSurfaceService` behind `MCPServer`, so it is preserved across transports —
1007
+ * this layer only governs *who* may talk to the server.
1008
+ */
1009
+
1010
+ interface McpHttpServerConfig {
1011
+ /** The MCP server whose `handleRequest` is exposed over HTTP. */
1012
+ server: MCPServer;
1013
+ /**
1014
+ * Shared secret required in the `x-xnet-pairing` header. A cryptographically
1015
+ * random token is generated when omitted; read it back from
1016
+ * {@link McpHttpServerHandle.pairingToken} to hand to the client.
1017
+ */
1018
+ pairingToken?: string;
1019
+ /**
1020
+ * Browser origins permitted to call the server. Requests that send an
1021
+ * `Origin` header not in this list are rejected with 403. Requests without an
1022
+ * `Origin` (CLI/native MCP clients) bypass this check and are gated by the
1023
+ * pairing token alone. Defaults to none (browser-less).
1024
+ */
1025
+ allowedOrigins?: readonly string[];
1026
+ /** Loopback host to bind. Defaults to `127.0.0.1`. Non-loopback hosts throw. */
1027
+ host?: string;
1028
+ /** Port to bind. Defaults to `31416`; pass `0` for an ephemeral port. */
1029
+ port?: number;
1030
+ /** JSON-RPC endpoint path. Defaults to `/mcp`. */
1031
+ path?: string;
1032
+ }
1033
+ interface McpHttpServerHandle {
1034
+ /** Resolved base URL, e.g. `http://127.0.0.1:31416`. Valid after `start()`. */
1035
+ readonly url: string;
1036
+ /** Bound port. Valid after `start()` (reflects the OS-assigned port if 0). */
1037
+ readonly port: number;
1038
+ /** The pairing token clients must present. */
1039
+ readonly pairingToken: string;
1040
+ /** JSON-RPC endpoint path. */
1041
+ readonly path: string;
1042
+ start(): Promise<void>;
1043
+ stop(): Promise<void>;
1044
+ /** The raw Node request handler — exposed for tests and embedding. */
1045
+ readonly handler: (req: IncomingMessage, res: ServerResponse) => void;
1046
+ }
1047
+ /**
1048
+ * Create (but do not start) an HTTP transport for an {@link MCPServer}.
1049
+ *
1050
+ * @throws if `host` is not a loopback address.
1051
+ */
1052
+ declare function createMcpHttpServer(config: McpHttpServerConfig): McpHttpServerHandle;
1053
+
1054
+ /**
1055
+ * Node-only AI workspace folder exporter.
1056
+ */
1057
+
1058
+ type AiWorkspaceExportKind = 'page' | 'database' | 'canvas' | 'node';
1059
+ type AiWorkspaceExportScope = {
1060
+ nodeIds?: string[];
1061
+ schemaIds?: string[];
1062
+ /** Search query; matching nodes are materialized into the checkout. */
1063
+ query?: string;
1064
+ /** Folder-style scope: export every node of these kinds (bounded by limit). */
1065
+ kinds?: AiWorkspaceExportKind[];
1066
+ limit?: number;
1067
+ };
1068
+ type AiWorkspaceExportOptions = {
1069
+ rootDir: string;
1070
+ workspaceName?: string;
1071
+ scope?: AiWorkspaceExportScope;
1072
+ };
1073
+ type AiWorkspaceCheckoutOptions = {
1074
+ rootDir: string;
1075
+ workspaceName?: string;
1076
+ /** Required scope: a checkout always materializes an explicit slice. */
1077
+ scope: AiWorkspaceExportScope;
1078
+ };
1079
+ type AiWorkspaceManifestEntry = {
1080
+ path: string;
1081
+ kind: AiWorkspaceExportKind;
1082
+ id: string;
1083
+ schemaId: string;
1084
+ revision: string;
1085
+ sha256: string;
1086
+ exportedAt: string;
1087
+ };
1088
+ type AiWorkspaceExportResult = {
1089
+ rootDir: string;
1090
+ files: string[];
1091
+ manifestEntries: AiWorkspaceManifestEntry[];
1092
+ exportedAt: string;
1093
+ incremental: boolean;
1094
+ skippedNodeIds: string[];
1095
+ };
1096
+ type AiWorkspaceExporterConfig = {
1097
+ store: NodeStoreAPI;
1098
+ schemas: SchemaRegistryAPI;
1099
+ aiSurface?: AiSurfaceService;
1100
+ clock?: () => Date;
1101
+ /**
1102
+ * Generate a read-optimized `.tsv` sidecar for databases with at least this
1103
+ * many rows. JSONL stays the write format; sidecars are read-only.
1104
+ */
1105
+ tsvSidecarMinRows?: number;
1106
+ };
1107
+ type AiWorkspaceChangedFileStatus = 'modified' | 'missing';
1108
+ type AiWorkspaceChangedFile = {
1109
+ path: string;
1110
+ kind: AiWorkspaceExportKind;
1111
+ id: string;
1112
+ status: AiWorkspaceChangedFileStatus;
1113
+ previousSha256: string;
1114
+ currentSha256?: string;
1115
+ };
1116
+ type AiWorkspacePendingPlan = {
1117
+ path: string;
1118
+ planPath: string;
1119
+ plan: AiMutationPlan;
1120
+ currentSha256: string;
1121
+ };
1122
+ type AiWorkspaceConflictKind = 'missing-file' | 'stale-export' | 'invalid-json' | 'invalid-jsonl' | 'invalid-plan' | 'validation-warning' | 'unsupported-change' | 'tool-error';
1123
+ type AiWorkspaceConflict = {
1124
+ kind: AiWorkspaceConflictKind;
1125
+ path: string;
1126
+ id?: string;
1127
+ message: string;
1128
+ conflictPath?: string;
1129
+ /** Human/agent-readable Markdown note with resolution instructions. */
1130
+ notePath?: string;
1131
+ detectedAt: string;
1132
+ };
1133
+ type AiWorkspaceReviewStatus = 'needs-review';
1134
+ type AiWorkspaceReviewAction = 'approve' | 'reject' | 'request-revision';
1135
+ type AiWorkspaceReviewEntryKind = 'pending-plan' | 'conflict';
1136
+ type AiWorkspaceReviewEntry = {
1137
+ kind: AiWorkspaceReviewEntryKind;
1138
+ status: AiWorkspaceReviewStatus;
1139
+ path: string;
1140
+ id?: string;
1141
+ title: string;
1142
+ message: string;
1143
+ artifactPath?: string;
1144
+ planId?: string;
1145
+ planPath?: string;
1146
+ conflictKind?: AiWorkspaceConflictKind;
1147
+ conflictPath?: string;
1148
+ risk?: AiRiskLevel;
1149
+ requiredScopes?: AiScope[];
1150
+ suggestedActions: AiWorkspaceReviewAction[];
1151
+ createdAt: string;
1152
+ };
1153
+ type AiWorkspaceReviewIndex = {
1154
+ rootDir: string;
1155
+ generatedAt: string;
1156
+ entries: AiWorkspaceReviewEntry[];
1157
+ };
1158
+ type AiWorkspaceWatcherScanOptions = {
1159
+ rootDir: string;
1160
+ actor?: string;
1161
+ writePendingPlans?: boolean;
1162
+ writeConflicts?: boolean;
1163
+ writeReviewIndex?: boolean;
1164
+ };
1165
+ type AiWorkspaceWatcherScanResult = {
1166
+ rootDir: string;
1167
+ scannedAt: string;
1168
+ changedFiles: AiWorkspaceChangedFile[];
1169
+ pendingPlans: AiWorkspacePendingPlan[];
1170
+ conflicts: AiWorkspaceConflict[];
1171
+ review: AiWorkspaceReviewIndex;
1172
+ };
1173
+ type AiWorkspaceWatchOptions = AiWorkspaceWatcherScanOptions & {
1174
+ /** Force interval polling instead of fs.watch (reliable cross-platform). */
1175
+ usePolling?: boolean;
1176
+ /** Poll interval when polling is active. Defaults to 2000ms. */
1177
+ pollIntervalMs?: number;
1178
+ };
1179
+ type AiWorkspaceWatchHandle = {
1180
+ close(): void;
1181
+ /** True when the handle fell back to (or was configured for) polling. */
1182
+ isPolling(): boolean;
1183
+ };
1184
+ declare class AiWorkspaceExporter {
1185
+ private readonly config;
1186
+ private readonly aiSurface;
1187
+ private readonly clock;
1188
+ constructor(config: AiWorkspaceExporterConfig);
1189
+ exportWorkspace(options: AiWorkspaceExportOptions): Promise<AiWorkspaceExportResult>;
1190
+ exportWorkspaceIncremental(options: AiWorkspaceExportOptions): Promise<AiWorkspaceExportResult>;
1191
+ /**
1192
+ * Lazily materialize an explicit slice of the workspace into the checkout.
1193
+ *
1194
+ * Unlike exportWorkspace there is no default "export everything up to the
1195
+ * limit" behavior: a checkout always names what it wants (query, schema,
1196
+ * node ids, or kind folders). Repeated checkouts are incremental and merge
1197
+ * with whatever is already checked out.
1198
+ */
1199
+ checkout(options: AiWorkspaceCheckoutOptions): Promise<AiWorkspaceExportResult>;
1200
+ private exportWorkspaceProjection;
1201
+ private resolveNodes;
1202
+ private resolveNodeIdScope;
1203
+ private resolveSchemaScope;
1204
+ private resolveQueryScope;
1205
+ private resolveKindScope;
1206
+ private ensureBaseFolders;
1207
+ private writeSupportFiles;
1208
+ private exportPage;
1209
+ private exportDatabase;
1210
+ private exportCanvas;
1211
+ private exportNode;
1212
+ private writeManifest;
1213
+ private writeText;
1214
+ }
1215
+ declare class AiWorkspaceWatcher {
1216
+ private readonly config;
1217
+ private readonly aiSurface;
1218
+ private readonly clock;
1219
+ constructor(config: AiWorkspaceExporterConfig);
1220
+ scanChangedFiles(options: AiWorkspaceWatcherScanOptions): Promise<AiWorkspaceWatcherScanResult>;
1221
+ watchWorkspace(options: AiWorkspaceWatchOptions, onScan: (result: AiWorkspaceWatcherScanResult) => void | Promise<void>): AiWorkspaceWatchHandle;
1222
+ private planChangedFile;
1223
+ private createStaleExportConflict;
1224
+ private planDatabaseProjection;
1225
+ private planCanvasProjection;
1226
+ private createGenericProjectionPlan;
1227
+ private expectPlan;
1228
+ private writePendingPlan;
1229
+ private createConflict;
1230
+ private writeReviewIndex;
1231
+ }
1232
+ declare function createAiWorkspaceExporter(config: AiWorkspaceExporterConfig): AiWorkspaceExporter;
1233
+ declare function createAiWorkspaceWatcher(config: AiWorkspaceExporterConfig): AiWorkspaceWatcher;
1234
+
1235
+ /**
1236
+ * ScriptContext - Safe API surface for user scripts
1237
+ *
1238
+ * Provides a frozen, read-only context with utility functions.
1239
+ * Scripts cannot modify nodes directly - they return values/mutations
1240
+ * that the runtime applies.
1241
+ */
1242
+ /**
1243
+ * A flat node representation (simplified for script access)
1244
+ */
1245
+ interface FlatNode {
1246
+ id: string;
1247
+ schemaIRI: string;
1248
+ [key: string]: unknown;
1249
+ }
1250
+ /**
1251
+ * Format helper functions (no side effects)
1252
+ */
1253
+ interface FormatHelpers {
1254
+ /** Format a timestamp as a date string */
1255
+ date: (timestamp: number, options?: Intl.DateTimeFormatOptions) => string;
1256
+ /** Format a number with options */
1257
+ number: (value: number, options?: Intl.NumberFormatOptions) => string;
1258
+ /** Format a number as currency */
1259
+ currency: (value: number, currency?: string, locale?: string) => string;
1260
+ /** Format a timestamp as relative time (e.g., "5m ago") */
1261
+ relative: (timestamp: number) => string;
1262
+ /** Format bytes as human-readable size */
1263
+ bytes: (bytes: number) => string;
1264
+ }
1265
+ /**
1266
+ * Math helper functions (pure functions)
1267
+ */
1268
+ interface MathHelpers {
1269
+ /** Sum an array of numbers */
1270
+ sum: (values: number[]) => number;
1271
+ /** Average of an array of numbers */
1272
+ avg: (values: number[]) => number;
1273
+ /** Minimum value */
1274
+ min: (values: number[]) => number;
1275
+ /** Maximum value */
1276
+ max: (values: number[]) => number;
1277
+ /** Round to decimal places */
1278
+ round: (value: number, decimals?: number) => number;
1279
+ /** Clamp value between min and max */
1280
+ clamp: (value: number, min: number, max: number) => number;
1281
+ /** Absolute value */
1282
+ abs: (value: number) => number;
1283
+ /** Floor */
1284
+ floor: (value: number) => number;
1285
+ /** Ceiling */
1286
+ ceil: (value: number) => number;
1287
+ }
1288
+ /**
1289
+ * Text helper functions (pure functions)
1290
+ */
1291
+ interface TextHelpers {
1292
+ /** Convert to URL-safe slug */
1293
+ slugify: (text: string) => string;
1294
+ /** Truncate with ellipsis */
1295
+ truncate: (text: string, maxLength: number) => string;
1296
+ /** Capitalize first letter */
1297
+ capitalize: (text: string) => string;
1298
+ /** Title case */
1299
+ titleCase: (text: string) => string;
1300
+ /** Check if text contains search (case-insensitive) */
1301
+ contains: (text: string, search: string) => boolean;
1302
+ /** Simple template substitution: {key} replaced by vars[key] */
1303
+ template: (template: string, vars: Record<string, unknown>) => string;
1304
+ /** Trim whitespace */
1305
+ trim: (text: string) => string;
1306
+ /** Convert to lowercase */
1307
+ lower: (text: string) => string;
1308
+ /** Convert to uppercase */
1309
+ upper: (text: string) => string;
1310
+ }
1311
+ /**
1312
+ * Array helper functions (pure functions)
1313
+ */
1314
+ interface ArrayHelpers {
1315
+ /** Get first element */
1316
+ first: <T>(items: T[]) => T | undefined;
1317
+ /** Get last element */
1318
+ last: <T>(items: T[]) => T | undefined;
1319
+ /** Sort by property */
1320
+ sortBy: <T>(items: T[], key: keyof T, desc?: boolean) => T[];
1321
+ /** Group by property */
1322
+ groupBy: <T>(items: T[], key: keyof T) => Record<string, T[]>;
1323
+ /** Unique values */
1324
+ unique: <T>(items: T[]) => T[];
1325
+ /** Count items */
1326
+ count: <T>(items: T[]) => number;
1327
+ /** Filter truthy */
1328
+ compact: <T>(items: (T | null | undefined)[]) => T[];
1329
+ }
1330
+ /**
1331
+ * The context object passed to scripts.
1332
+ * All properties and nested objects are frozen (immutable).
1333
+ */
1334
+ interface ScriptContext {
1335
+ /** The current node being processed (read-only, frozen) */
1336
+ node: Readonly<FlatNode>;
1337
+ /**
1338
+ * Query sibling nodes by schema.
1339
+ * Returns a frozen array of frozen nodes.
1340
+ */
1341
+ nodes: (schemaIRI?: string) => ReadonlyArray<Readonly<FlatNode>>;
1342
+ /** Current timestamp in milliseconds */
1343
+ now: () => number;
1344
+ /** Format helpers */
1345
+ format: Readonly<FormatHelpers>;
1346
+ /** Math helpers */
1347
+ math: Readonly<MathHelpers>;
1348
+ /** Text helpers */
1349
+ text: Readonly<TextHelpers>;
1350
+ /** Array helpers */
1351
+ array: Readonly<ArrayHelpers>;
1352
+ }
1353
+
1354
+ /**
1355
+ * @xnet/agent-api - Code-execution surface for agent scripts (`xnet run`).
1356
+ *
1357
+ * Extends the read-only ScriptContext with an `api` object that can query the
1358
+ * loaded workspace slice and *propose* writes. Proposals never mutate the
1359
+ * store directly; they are lifted into mutation plans that flow through the
1360
+ * same plan/validate/apply pipeline as file edits and MCP tools.
1361
+ */
1362
+
1363
+ type AgentSearchResult = {
1364
+ id: string;
1365
+ schemaIRI: string;
1366
+ title: string;
1367
+ snippet: string;
1368
+ };
1369
+ type AgentWriteProposal = {
1370
+ kind: 'update';
1371
+ nodeId: string;
1372
+ properties: Record<string, unknown>;
1373
+ baseRevision: string;
1374
+ rationale?: string;
1375
+ } | {
1376
+ kind: 'create';
1377
+ schemaId: string;
1378
+ properties: Record<string, unknown>;
1379
+ rationale?: string;
1380
+ };
1381
+ interface AgentApi {
1382
+ /** Query the loaded workspace slice by schema IRI. */
1383
+ nodes(schemaIRI?: string): ReadonlyArray<Readonly<FlatNode>>;
1384
+ /** Search titles and string properties of the loaded slice. */
1385
+ search(text: string): ReadonlyArray<Readonly<AgentSearchResult>>;
1386
+ /** Propose a property update; becomes a mutation plan, never a direct write. */
1387
+ proposeUpdate(nodeId: string, properties: Record<string, unknown>, rationale?: string): void;
1388
+ /** Propose a new node; becomes a mutation plan, never a direct write. */
1389
+ proposeCreate(schemaId: string, properties: Record<string, unknown>, rationale?: string): void;
1390
+ }
1391
+ type AgentScriptContext = ScriptContext & {
1392
+ api: Readonly<AgentApi>;
1393
+ };
1394
+ type CreateAgentScriptContextInput = {
1395
+ /** Workspace slice available to the script (already bounded by the caller). */
1396
+ nodes: FlatNode[];
1397
+ /** Optional current node; defaults to a synthetic agent-script node. */
1398
+ node?: FlatNode;
1399
+ /** Cap on proposals per run; guards against runaway scripts. */
1400
+ maxProposals?: number;
1401
+ };
1402
+ type AgentScriptSession = {
1403
+ context: AgentScriptContext;
1404
+ getProposals(): AgentWriteProposal[];
1405
+ /** Lift accumulated proposals into a validated mutation plan (or none). */
1406
+ toMutationPlan(input: {
1407
+ actor: string;
1408
+ intent?: string;
1409
+ clock?: () => Date;
1410
+ }): AiMutationPlan | null;
1411
+ };
1412
+ declare function createAgentScriptContext(input: CreateAgentScriptContextInput): AgentScriptSession;
1413
+
1414
+ /**
1415
+ * ScriptSandbox - Isolated execution environment for user scripts
1416
+ *
1417
+ * Executes scripts with:
1418
+ * - AST validation before execution
1419
+ * - Timeout protection
1420
+ * - Global shadowing to prevent escape
1421
+ * - Output sanitization
1422
+ */
1423
+
1424
+ /**
1425
+ * Duck-typed telemetry interface to avoid circular dependencies.
1426
+ */
1427
+ interface TelemetryReporter {
1428
+ reportPerformance(metricName: string, durationMs: number): void;
1429
+ reportUsage(metricName: string, count: number): void;
1430
+ reportCrash(error: Error, context?: Record<string, unknown>): void;
1431
+ }
1432
+ interface SandboxOptions {
1433
+ /** Maximum execution time in milliseconds (default: 1000) */
1434
+ timeoutMs?: number;
1435
+ /** Whether to run AST validation (default: true) */
1436
+ validateAST?: boolean;
1437
+ /** Optional telemetry reporter */
1438
+ telemetry?: TelemetryReporter;
1439
+ }
1440
+ /**
1441
+ * Sandbox for executing user scripts safely.
1442
+ *
1443
+ * Scripts are validated via AST analysis and executed in an environment
1444
+ * where dangerous globals are shadowed to undefined.
1445
+ *
1446
+ * @example
1447
+ * ```typescript
1448
+ * const sandbox = new ScriptSandbox({ timeoutMs: 500 })
1449
+ *
1450
+ * const context = createScriptContext(node, queryFn)
1451
+ * const result = await sandbox.execute('(node) => node.amount * 1.08', context)
1452
+ * ```
1453
+ */
1454
+ declare class ScriptSandbox {
1455
+ private options;
1456
+ constructor(options?: SandboxOptions);
1457
+ /**
1458
+ * Execute a script in the sandbox.
1459
+ *
1460
+ * @param code - JavaScript code (expression or arrow function)
1461
+ * @param context - The frozen ScriptContext
1462
+ * @returns The script's return value (sanitized)
1463
+ * @throws ScriptValidationError if AST validation fails
1464
+ * @throws ScriptTimeoutError if execution exceeds timeout
1465
+ * @throws ScriptError for other execution errors
1466
+ */
1467
+ execute(code: string, context: ScriptContext): Promise<unknown>;
1468
+ /**
1469
+ * Execute a script synchronously (for computed columns).
1470
+ * Use with caution - no timeout protection in sync mode.
1471
+ */
1472
+ executeSync(code: string, context: ScriptContext): unknown;
1473
+ /**
1474
+ * Create an isolated function from user code.
1475
+ *
1476
+ * The function receives context values as arguments and has dangerous
1477
+ * globals shadowed to undefined in its scope.
1478
+ */
1479
+ private createIsolatedFunction;
1480
+ /**
1481
+ * Execute a function with a timeout.
1482
+ */
1483
+ private executeWithTimeout;
1484
+ /**
1485
+ * Sanitize script output to ensure only safe values are returned.
1486
+ *
1487
+ * Allowed types: null, undefined, string, number, boolean, plain objects, arrays
1488
+ * Stripped: functions, symbols, circular references, dunder properties
1489
+ */
1490
+ private sanitizeOutput;
1491
+ }
1492
+
1493
+ /**
1494
+ * In-memory NodeStore/SchemaRegistry backend.
1495
+ *
1496
+ * Shared by tests, the agent-surface benchmark, and CLI fixtures so each
1497
+ * consumer does not re-implement the same mock store.
1498
+ */
1499
+
1500
+ type MemoryNodeStore = NodeStoreAPI & {
1501
+ /** Test helper: update properties and bump updatedAt without going through update(). */
1502
+ setNode(id: string, properties: Record<string, unknown>): void;
1503
+ };
1504
+ declare function createMemoryNodeStore(initialNodes: NodeData[]): MemoryNodeStore;
1505
+ declare function createMemorySchemaRegistry(schemas: SchemaData[]): SchemaRegistryAPI;
1506
+ /** The Page/Database/Canvas schema set most agent-surface fixtures use. */
1507
+ declare function createWorkspaceFixtureSchemas(): SchemaRegistryAPI;
1508
+
303
1509
  /**
304
1510
  * Service Plugin Types
305
1511
  *
@@ -580,4 +1786,4 @@ declare const SERVICE_IPC_CHANNELS: {
580
1786
  readonly OUTPUT: "xnet:service:output";
581
1787
  };
582
1788
 
583
- export { type IProcessManager, type LocalAPIConfig, LocalAPIServer, type MCPPropertySchema, type MCPRequest, type MCPResource, type MCPResponse, MCPServer, type MCPServerConfig, type MCPTool, type NodeChangeEventData, type NodeData, type NodeStoreAPI, ProcessManager, type ProcessManagerEvents, SERVICE_IPC_CHANNELS, type SchemaData, type SchemaRegistryAPI, type ServiceClient, type ServiceCommunication, type ServiceDefinition, type ServiceHealthCheck, type ServiceLifecycle, type ServiceOutputEvent, type ServiceProcessConfig, type ServiceProvides, type ServiceState, type ServiceStatus, type ServiceStatusEvent, createLocalAPI, createMCPServer };
1789
+ export { AI_RISK_LEVELS, AI_SCOPES, AI_TARGET_KINDS, type AgentApi, type AgentScriptContext, type AgentScriptSession, type AgentSearchResult, type AgentWriteProposal, type AiAuditEvent, type AiChangeSet, type AiContextPack, type AiContextPackResource, type AiContextSeed, type AiJsonSchema, type AiJsonSchemaType, type AiMutationPlan, type AiMutationPlanStatus, type AiOperation, type AiPageMarkdownApplyAdapter, type AiPageMarkdownApplyAdapterInput, type AiPageMarkdownApplyAdapterResult, type AiPageMarkdownApplyResult, type AiPageMarkdownRollbackResult, type AiResource, type AiResourceContent, type AiRiskLevel, type AiScope, type AiSearchOptions, type AiSearchResult, type AiSurfaceLimits, AiSurfaceService, type AiSurfaceServiceConfig, type AiTargetKind, type AiToolCallResult, type AiToolDefinition, type AiValidationResult, type AiWorkspaceChangedFile, type AiWorkspaceChangedFileStatus, type AiWorkspaceCheckoutOptions, type AiWorkspaceConflict, type AiWorkspaceConflictKind, type AiWorkspaceExportKind, type AiWorkspaceExportOptions, type AiWorkspaceExportResult, type AiWorkspaceExportScope, AiWorkspaceExporter, type AiWorkspaceExporterConfig, type AiWorkspaceManifestEntry, type AiWorkspacePendingPlan, type AiWorkspaceReviewAction, type AiWorkspaceReviewEntry, type AiWorkspaceReviewEntryKind, type AiWorkspaceReviewIndex, type AiWorkspaceReviewStatus, type AiWorkspaceWatchHandle, type AiWorkspaceWatchOptions, AiWorkspaceWatcher, type AiWorkspaceWatcherScanOptions, type AiWorkspaceWatcherScanResult, type CreateAgentScriptContextInput, type FlatNode, type IProcessManager, type LocalAPIConfig, LocalAPIServer, type LocalAPITokenConfig, type LocalAPITokenScope, type LocalAPITokenSummary, type MCPPropertySchema, type MCPRequest, type MCPResource, type MCPResponse, MCPServer, type MCPServerConfig, type MCPTool, type McpHttpServerConfig, type McpHttpServerHandle, type MemoryNodeStore, type NodeChangeEventData, type NodeData, type NodeStoreAPI, ProcessManager, type ProcessManagerEvents, SERVICE_IPC_CHANNELS, type SchemaData, type SchemaRegistryAPI, ScriptSandbox, type ServiceClient, type ServiceCommunication, type ServiceDefinition, type ServiceHealthCheck, type ServiceLifecycle, type ServiceOutputEvent, type ServiceProcessConfig, type ServiceProvides, type ServiceState, type ServiceStatus, type ServiceStatusEvent, XNET_AGENT_SKILL_MD, XNET_MARKDOWN_DIRECTIVE_SPECS, type XNetMarkdownDiffLine, type XNetMarkdownDiffLineKind, type XNetMarkdownDirective, type XNetMarkdownDirectiveSpec, type XNetMarkdownReviewDiff, type XNetPageMarkdownFrontmatter, type XNetPageMarkdownValidation, type XNetPageMarkdownValidationOptions, attachAiPlanValidation, createAgentScriptContext, createAiChangeSet, createAiOperation, createAiSurfaceService, createAiValidationResult, createAiWorkspaceExporter, createAiWorkspaceWatcher, createLocalAPI, createMCPServer, createMcpHttpServer, createMemoryNodeStore, createMemorySchemaRegistry, createWorkspaceFixtureSchemas, flattenRowForTsv, getXNetMarkdownDirectiveSpecs, isAiRiskLevel, isAiScope, isAiTargetKind, parseAiMutationPlan, parseXNetPageFrontmatter, renderMarkdownLineDiff, renderMarkdownReviewDiff, serializeAiMutationPlan, stripXNetPageFrontmatter, toTsv, validateAiMutationPlan, validateXNetPageMarkdown };