@agent-surface/core 0.7.0 → 0.8.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.
@@ -0,0 +1,915 @@
1
+ // src/errors.ts
2
+ var AGENT_CAPABILITY_ERROR_CODES = [
3
+ "CAPABILITY_NOT_FOUND",
4
+ "CAPABILITY_NOT_AVAILABLE",
5
+ "AMBIGUOUS_INSTANCE",
6
+ "COMPONENT_UNMOUNTED",
7
+ "STALE_CAPABILITY",
8
+ "INVOCATION_CONFLICT",
9
+ "INVALID_INPUT",
10
+ "NOT_AUTHENTICATED",
11
+ "NOT_AUTHORIZED",
12
+ "PRECONDITION_FAILED",
13
+ "CONFIRMATION_REQUIRED",
14
+ "CONFIRMATION_INVALID",
15
+ "RATE_LIMITED",
16
+ "TIMEOUT",
17
+ "CANCELLED",
18
+ "EXECUTION_FAILED"
19
+ ];
20
+ var AgentSurfaceError = class extends Error {
21
+ payload;
22
+ constructor(payload, opts) {
23
+ super(payload.message, opts);
24
+ this.name = "AgentSurfaceError";
25
+ this.payload = payload;
26
+ }
27
+ };
28
+ function isAgentSurfaceError(e) {
29
+ return e instanceof AgentSurfaceError || typeof e === "object" && e !== null && e.name === "AgentSurfaceError" && typeof e.payload === "object";
30
+ }
31
+ var AgentSurfaceDefinitionError = class extends Error {
32
+ code;
33
+ constructor(code, message) {
34
+ super(`[${code}] ${message}`);
35
+ this.name = "AgentSurfaceDefinitionError";
36
+ this.code = code;
37
+ }
38
+ };
39
+
40
+ // src/policy.ts
41
+ var CONFIRMATION_ESCALATION = /* @__PURE__ */ Symbol("agent-surface.confirmation-escalation");
42
+ function evaluateDiscovery(policies, ctx) {
43
+ let disable;
44
+ for (const policy of policies) {
45
+ if (!policy.onDiscovery) continue;
46
+ let decision;
47
+ try {
48
+ decision = policy.onDiscovery(ctx);
49
+ } catch {
50
+ return { decision: "hide" };
51
+ }
52
+ if (decision.decision === "hide") return decision;
53
+ if (decision.decision === "disable" && !disable) disable = decision;
54
+ }
55
+ return disable ?? { decision: "expose" };
56
+ }
57
+ function composeAuthorizeChain(policies, ctx, core) {
58
+ let index = -1;
59
+ const dispatch = (i) => {
60
+ if (i <= index) {
61
+ return Promise.reject(new Error("policy next() called multiple times"));
62
+ }
63
+ index = i;
64
+ const policy = policies[i];
65
+ if (!policy) return core();
66
+ if (!policy.onAuthorize) return dispatch(i + 1);
67
+ return policy.onAuthorize(ctx, () => dispatch(i + 1));
68
+ };
69
+ return dispatch(0);
70
+ }
71
+ function composeInvokeChain(policies, ctx, core) {
72
+ let index = -1;
73
+ const dispatch = (i) => {
74
+ if (i <= index) {
75
+ return Promise.reject(new Error("policy next() called multiple times"));
76
+ }
77
+ index = i;
78
+ const policy = policies[i];
79
+ if (!policy) return core();
80
+ if (!policy.onInvoke) return dispatch(i + 1);
81
+ return policy.onInvoke(ctx, () => dispatch(i + 1));
82
+ };
83
+ return dispatch(0);
84
+ }
85
+ function authenticated(opts) {
86
+ const key = opts?.key ?? "user";
87
+ return {
88
+ name: "authenticated",
89
+ onDiscovery(ctx) {
90
+ return ctx.host[key] ? { decision: "expose" } : { decision: "hide" };
91
+ },
92
+ async onAuthorize(ctx, next) {
93
+ if (!ctx.host[key]) {
94
+ throw new AgentSurfaceError({
95
+ code: "NOT_AUTHENTICATED",
96
+ message: "Sign-in is required before this capability can be used. Ask the user to sign in.",
97
+ retry: "no"
98
+ });
99
+ }
100
+ return next();
101
+ }
102
+ };
103
+ }
104
+ function hasPermission(permission, check) {
105
+ return {
106
+ name: `has-permission(${permission})`,
107
+ onDiscovery(ctx) {
108
+ return check({ ...ctx.host }, permission) ? { decision: "expose" } : { decision: "hide" };
109
+ },
110
+ async onAuthorize(ctx, next) {
111
+ if (!check({ ...ctx.host }, permission)) {
112
+ throw new AgentSurfaceError({
113
+ code: "NOT_AUTHORIZED",
114
+ message: "The current user is not authorized to use this capability.",
115
+ retry: "no",
116
+ details: { origin: "client" }
117
+ });
118
+ }
119
+ return next();
120
+ }
121
+ };
122
+ }
123
+ function tenantBoundary(opts) {
124
+ const matches = (ctx) => {
125
+ const current = opts.current({ ...ctx.host });
126
+ const expected = opts.expected(ctx);
127
+ return expected === void 0 || current === expected;
128
+ };
129
+ return {
130
+ name: "tenant-boundary",
131
+ onDiscovery(ctx) {
132
+ return matches(ctx) ? { decision: "expose" } : { decision: "hide" };
133
+ },
134
+ async onAuthorize(ctx, next) {
135
+ if (!matches(ctx)) {
136
+ throw new AgentSurfaceError({
137
+ code: "NOT_AUTHORIZED",
138
+ message: "This capability belongs to a different tenant.",
139
+ retry: "no",
140
+ details: { origin: "client" }
141
+ });
142
+ }
143
+ return next();
144
+ }
145
+ };
146
+ }
147
+ function environment(allowed) {
148
+ return {
149
+ name: "environment",
150
+ onDiscovery(ctx) {
151
+ return allowed.includes(ctx.environment) ? { decision: "expose" } : { decision: "hide" };
152
+ },
153
+ async onAuthorize(ctx, next) {
154
+ if (!allowed.includes(ctx.environment)) {
155
+ throw new AgentSurfaceError({
156
+ code: "CAPABILITY_NOT_FOUND",
157
+ message: "This capability does not exist in the current surface.",
158
+ retry: "after-refresh"
159
+ });
160
+ }
161
+ return next();
162
+ }
163
+ };
164
+ }
165
+ function rateLimit(opts) {
166
+ const hits = /* @__PURE__ */ new Map();
167
+ return {
168
+ name: "rate-limit",
169
+ async onAuthorize(ctx, next) {
170
+ const key = `${ctx.consumer.kind}:${ctx.consumer.id} ${ctx.capabilityId}`;
171
+ const now = ctx.now();
172
+ const windowStart = now - opts.windowMs;
173
+ const list = (hits.get(key) ?? []).filter((t) => t > windowStart);
174
+ if (list.length >= opts.limit) {
175
+ const retryAfterMs = Math.max(0, (list[0] ?? now) + opts.windowMs - now);
176
+ throw new AgentSurfaceError({
177
+ code: "RATE_LIMITED",
178
+ message: "Too many calls to this capability. Wait before retrying.",
179
+ retry: "after-delay",
180
+ details: { reason: "rate", retryAfterMs }
181
+ });
182
+ }
183
+ list.push(now);
184
+ hits.set(key, list);
185
+ return next();
186
+ }
187
+ };
188
+ }
189
+ function requireConfirmation(opts) {
190
+ const policy = {
191
+ name: "require-confirmation"
192
+ };
193
+ policy[CONFIRMATION_ESCALATION] = { if: opts?.if, summary: opts?.summary };
194
+ return policy;
195
+ }
196
+ function audit(sink, level = "metadata") {
197
+ return {
198
+ name: "audit",
199
+ async onInvoke(ctx, next) {
200
+ const startedAt = ctx.now();
201
+ sink?.record({
202
+ at: new Date(startedAt).toISOString(),
203
+ type: "invocation-started",
204
+ capabilityId: ctx.capabilityId,
205
+ registrationId: ctx.registrationId,
206
+ invocationId: ctx.invocationId,
207
+ consumerId: ctx.consumer.id,
208
+ ...level === "full" ? { payload: { input: ctx.effectiveInput } } : {}
209
+ });
210
+ const result = await next();
211
+ sink?.record({
212
+ at: new Date(ctx.now()).toISOString(),
213
+ type: "invocation-settled",
214
+ capabilityId: ctx.capabilityId,
215
+ registrationId: ctx.registrationId,
216
+ invocationId: ctx.invocationId,
217
+ consumerId: ctx.consumer.id,
218
+ status: result.status,
219
+ ...result.status === "error" ? { code: result.error.code } : {},
220
+ durationMs: ctx.now() - startedAt,
221
+ ...level === "full" && result.status === "ok" && result.output !== void 0 ? { payload: { output: result.output } } : {}
222
+ });
223
+ return result;
224
+ }
225
+ };
226
+ }
227
+
228
+ // src/ids.ts
229
+ var MAX_ID_LENGTH = 128;
230
+ var SEGMENT_RE = /^[a-z][a-z0-9-]*$/;
231
+ var CAPABILITY_NAME_RE = /^[a-z][A-Za-z0-9]*$/;
232
+ var INSTANCE_ID_RE = /^[A-Za-z0-9_-]+$/;
233
+ function isValidComponentType(type) {
234
+ if (type.length === 0 || type.length > MAX_ID_LENGTH) return false;
235
+ return type.split(".").every((seg) => SEGMENT_RE.test(seg));
236
+ }
237
+ function isValidCapabilityName(name) {
238
+ return CAPABILITY_NAME_RE.test(name);
239
+ }
240
+ function isValidInstanceId(id) {
241
+ return id.length > 0 && id.length <= MAX_ID_LENGTH && INSTANCE_ID_RE.test(id);
242
+ }
243
+ function formatViewCapabilityId(componentType, name) {
244
+ return `view:${componentType}.${name}`;
245
+ }
246
+ function formatDomainCapabilityId(path) {
247
+ return `domain:${path}`;
248
+ }
249
+ function parseCapabilityId(id) {
250
+ if (typeof id !== "string" || id.length > MAX_ID_LENGTH) return void 0;
251
+ if (id.startsWith("view:")) {
252
+ const rest = id.slice("view:".length);
253
+ const lastDot = rest.lastIndexOf(".");
254
+ if (lastDot <= 0) return void 0;
255
+ const componentType = rest.slice(0, lastDot);
256
+ const name = rest.slice(lastDot + 1);
257
+ if (!isValidComponentType(componentType) || !isValidCapabilityName(name)) {
258
+ return void 0;
259
+ }
260
+ return { plane: "view", componentType, name };
261
+ }
262
+ if (id.startsWith("domain:")) {
263
+ const path = id.slice("domain:".length);
264
+ if (path.length === 0) return void 0;
265
+ return { plane: "domain", path };
266
+ }
267
+ return void 0;
268
+ }
269
+ var MAX_WIRE_NAME_LENGTH = 64;
270
+ var SHORTENED_MARKER = "_0_";
271
+ var INSTANCE_MARKER = "_at_";
272
+ function hash36(input, length) {
273
+ let out = "";
274
+ for (let round = 0; out.length < length; round++) {
275
+ let hash = (2166136261 ^ round) >>> 0;
276
+ for (let i = 0; i < input.length; i++) {
277
+ hash ^= input.charCodeAt(i);
278
+ hash = Math.imul(hash, 16777619) >>> 0;
279
+ }
280
+ out += hash.toString(36).padStart(7, "0");
281
+ }
282
+ return out.slice(0, length);
283
+ }
284
+ function rawWireName(id, instanceId) {
285
+ const encoded = id.replace(":", "_").replaceAll(".", "__");
286
+ return instanceId ? `${encoded}${INSTANCE_MARKER}${instanceId}` : encoded;
287
+ }
288
+ function encodeWireName(id) {
289
+ return encodeWireNameForInstance(id);
290
+ }
291
+ function encodeWireNameForInstance(id, instanceId, level = 0) {
292
+ const raw = rawWireName(id, instanceId);
293
+ if (level === 0 && raw.length <= MAX_WIRE_NAME_LENGTH && !id.includes("_")) return raw;
294
+ const hashLength = 7 + level * 2;
295
+ const keep = MAX_WIRE_NAME_LENGTH - SHORTENED_MARKER.length - hashLength;
296
+ const hash = hash36(`${id}#${instanceId ?? ""}#${level}`, hashLength);
297
+ return `${raw.slice(0, keep)}${SHORTENED_MARKER}${hash}`;
298
+ }
299
+ function assignWireNames(entries) {
300
+ const keyOf = (e) => `${e.id}#${e.instanceId ?? ""}`;
301
+ const level = /* @__PURE__ */ new Map();
302
+ const MAX_LEVEL = 3;
303
+ let names = entries.map((e) => encodeWireNameForInstance(e.id, e.instanceId));
304
+ for (let round = 0; round <= MAX_LEVEL; round++) {
305
+ const byName2 = /* @__PURE__ */ new Map();
306
+ entries.forEach((entry, i) => {
307
+ const set = byName2.get(names[i]) ?? /* @__PURE__ */ new Set();
308
+ set.add(keyOf(entry));
309
+ byName2.set(names[i], set);
310
+ });
311
+ const colliding = /* @__PURE__ */ new Set();
312
+ for (const [, keys] of byName2) {
313
+ if (keys.size > 1) for (const key of keys) colliding.add(key);
314
+ }
315
+ if (colliding.size === 0) break;
316
+ if (round === MAX_LEVEL) {
317
+ const ranked = [...colliding].sort();
318
+ names = entries.map((entry, i) => {
319
+ const rank = ranked.indexOf(keyOf(entry));
320
+ if (rank < 0) return names[i];
321
+ const suffix = `${SHORTENED_MARKER}${rank}`;
322
+ const base = encodeWireNameForInstance(entry.id, entry.instanceId, MAX_LEVEL);
323
+ return `${base.slice(0, MAX_WIRE_NAME_LENGTH - suffix.length)}${suffix}`;
324
+ });
325
+ break;
326
+ }
327
+ for (const key of colliding) level.set(key, (level.get(key) ?? 0) + 1);
328
+ names = entries.map(
329
+ (entry) => encodeWireNameForInstance(entry.id, entry.instanceId, level.get(keyOf(entry)) ?? 0)
330
+ );
331
+ }
332
+ const byName = /* @__PURE__ */ new Map();
333
+ entries.forEach((entry, i) => byName.set(names[i], entry.id));
334
+ return { names, byName };
335
+ }
336
+ function decodeWireName(name) {
337
+ const planeEnd = name.indexOf("_");
338
+ if (planeEnd <= 0) return void 0;
339
+ const plane = name.slice(0, planeEnd);
340
+ if (plane !== "view" && plane !== "domain") return void 0;
341
+ const rest = name.slice(planeEnd + 1);
342
+ if (/_{3,}/.test(rest)) return void 0;
343
+ const path = rest.replaceAll("__", ".");
344
+ if (path.split(".").some((segment) => segment === "")) return void 0;
345
+ const id = `${plane}:${path}`;
346
+ if (id.includes("_") || !parseCapabilityId(id) || encodeWireName(id) !== name) return void 0;
347
+ return id;
348
+ }
349
+
350
+ // src/utils.ts
351
+ function jsonDeepEqual(a, b) {
352
+ if (a === b) return true;
353
+ if (a === void 0 || b === void 0) return false;
354
+ if (typeof a !== typeof b || a === null || b === null) return false;
355
+ if (Array.isArray(a) || Array.isArray(b)) {
356
+ return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => jsonDeepEqual(v, b[i]));
357
+ }
358
+ if (typeof a === "object" && typeof b === "object") {
359
+ const ka = Object.keys(a).sort();
360
+ const kb = Object.keys(b).sort();
361
+ return ka.length === kb.length && ka.every(
362
+ (k, i) => k === kb[i] && jsonDeepEqual(
363
+ a[k],
364
+ b[k]
365
+ )
366
+ );
367
+ }
368
+ return false;
369
+ }
370
+ function deepFreeze(value) {
371
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
372
+ Object.freeze(value);
373
+ for (const key of Object.keys(value)) {
374
+ deepFreeze(value[key]);
375
+ }
376
+ }
377
+ return value;
378
+ }
379
+ function jsonClone(value) {
380
+ return value === void 0 ? value : JSON.parse(JSON.stringify(value));
381
+ }
382
+ function isJsonValue(value, depth = 0) {
383
+ if (depth > 64) return false;
384
+ if (value === null) return true;
385
+ const t = typeof value;
386
+ if (t === "string" || t === "boolean") return true;
387
+ if (t === "number") return Number.isFinite(value);
388
+ if (Array.isArray(value)) return value.every((v) => isJsonValue(v, depth + 1));
389
+ if (t === "object") {
390
+ const proto = Object.getPrototypeOf(value);
391
+ if (proto !== Object.prototype && proto !== null) return false;
392
+ return Object.values(value).every(
393
+ (v) => v === void 0 || isJsonValue(v, depth + 1)
394
+ );
395
+ }
396
+ return false;
397
+ }
398
+ function byteLength(value) {
399
+ const s = JSON.stringify(value);
400
+ return s === void 0 ? 0 : s.length;
401
+ }
402
+ var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
403
+ function randomBase62(length) {
404
+ let out = "";
405
+ for (let i = 0; i < length; i++) {
406
+ out += ALPHABET[Math.floor(Math.random() * ALPHABET.length)];
407
+ }
408
+ return out;
409
+ }
410
+ function truncate(s, max) {
411
+ return s.length <= max ? s : s.slice(0, Math.max(0, max - 1)) + "\u2026";
412
+ }
413
+ function canonicalJson(value) {
414
+ if (value === void 0 || value === null) return "null";
415
+ const t = typeof value;
416
+ if (t === "number") {
417
+ if (!Number.isFinite(value)) {
418
+ throw new Error("canonicalJson: non-finite numbers are not JsonValues");
419
+ }
420
+ return JSON.stringify(Object.is(value, -0) ? 0 : value);
421
+ }
422
+ if (t === "string" || t === "boolean") return JSON.stringify(value);
423
+ if (Array.isArray(value)) {
424
+ return `[${value.map((v) => canonicalJson(v ?? null)).join(",")}]`;
425
+ }
426
+ const entries = Object.keys(value).sort().filter((k) => value[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`);
427
+ return `{${entries.join(",")}}`;
428
+ }
429
+ function fnv1a64(input) {
430
+ let hash = 0xcbf29ce484222325n;
431
+ const prime = 0x100000001b3n;
432
+ for (let i = 0; i < input.length; i++) {
433
+ hash ^= BigInt(input.charCodeAt(i));
434
+ hash = hash * prime & 0xffffffffffffffffn;
435
+ }
436
+ return hash.toString(16).padStart(16, "0");
437
+ }
438
+
439
+ // src/internal.ts
440
+ var DEV_WARN = /* @__PURE__ */ Symbol("agent-surface.dev-warn");
441
+ var INTERNALS = /* @__PURE__ */ Symbol("agent-surface.internals");
442
+ var DevDefectError = class extends Error {
443
+ constructor(message) {
444
+ super(message);
445
+ this.name = "AgentSurfaceDevDefectError";
446
+ }
447
+ };
448
+ function componentKey(type, instanceId) {
449
+ return `${type}\0${instanceId}`;
450
+ }
451
+ var CONFIRMATION_RANK = {
452
+ never: 0,
453
+ optional: 1,
454
+ required: 2
455
+ };
456
+ function maxConfirmation(...levels) {
457
+ return levels.reduce((acc, l) => CONFIRMATION_RANK[l] > CONFIRMATION_RANK[acc] ? l : acc, "never");
458
+ }
459
+ function defaultConfirmationFor(effect) {
460
+ switch (effect) {
461
+ case "server-query":
462
+ return "never";
463
+ case "server-mutation":
464
+ return "optional";
465
+ case "external-side-effect":
466
+ case "destructive":
467
+ return "required";
468
+ }
469
+ }
470
+ function defaultAuditFor(effect) {
471
+ return effect === "external-side-effect" || effect === "destructive" ? "full" : "metadata";
472
+ }
473
+ var registrationCounter = 0;
474
+ function nextRegistrationId(random) {
475
+ registrationCounter += 1;
476
+ return `reg_${registrationCounter.toString(36).padStart(4, "0")}${random()}`;
477
+ }
478
+ function normalizeRegistration(def, id) {
479
+ const instanceId = def.instanceId ?? "default";
480
+ const observations = /* @__PURE__ */ new Map();
481
+ for (const [name, obs] of Object.entries(def.observations ?? {})) {
482
+ observations.set(name, {
483
+ kind: "observation",
484
+ name,
485
+ capabilityId: formatViewCapabilityId(def.type, name),
486
+ description: obs.description,
487
+ outputSchema: obs.output,
488
+ jsonSchema: jsonClone(obs.output.jsonSchema),
489
+ meta: obs.meta ? jsonClone(obs.meta) : void 0,
490
+ timeoutMs: obs.timeoutMs,
491
+ policies: [...obs.policies ?? []],
492
+ auditLevel: "none"
493
+ });
494
+ }
495
+ const actions = /* @__PURE__ */ new Map();
496
+ for (const [name, act] of Object.entries(def.actions ?? {})) {
497
+ actions.set(name, {
498
+ kind: "action",
499
+ name,
500
+ capabilityId: formatViewCapabilityId(def.type, name),
501
+ description: act.description,
502
+ inputSchema: act.input,
503
+ inputJsonSchema: jsonClone(act.input.jsonSchema),
504
+ outputSchema: act.output,
505
+ outputJsonSchema: act.output ? jsonClone(act.output.jsonSchema) : void 0,
506
+ effect: act.effect,
507
+ idempotent: act.idempotent ?? false,
508
+ reversible: act.reversible ?? true,
509
+ confirmation: act.confirmation ?? "never",
510
+ auditLevel: act.audit ?? "metadata",
511
+ meta: act.meta ? jsonClone(act.meta) : void 0,
512
+ timeoutMs: act.timeoutMs,
513
+ policies: [...act.policies ?? []],
514
+ concurrency: act.concurrency
515
+ });
516
+ }
517
+ const hasView = observations.size > 0 || actions.size > 0;
518
+ const procedures = (def.procedures ?? []).map((binding) => {
519
+ const boundKeys = [...binding.boundKeys];
520
+ const overridable = new Set(binding.config.overridableFields ?? []);
521
+ const lockedKeys = binding.lockedKeys ? [...binding.lockedKeys] : boundKeys.filter((k) => !overridable.has(k));
522
+ const effect = binding.ref.effect;
523
+ return {
524
+ kind: "procedure",
525
+ binding,
526
+ capabilityId: binding.ref.id,
527
+ path: binding.ref.path,
528
+ effect,
529
+ requiresApproval: binding.ref.requiresApproval === true,
530
+ baseDescription: binding.ref.description,
531
+ fullInputSchema: jsonClone(binding.ref.inputSchema),
532
+ reducedInputSchema: jsonClone(binding.reducedInputSchema),
533
+ outputJsonSchema: binding.ref.outputSchema ? jsonClone(binding.ref.outputSchema) : void 0,
534
+ boundKeys,
535
+ lockedKeys,
536
+ overridableKeys: overridable,
537
+ confirmationFloor: maxConfirmation(
538
+ defaultConfirmationFor(effect),
539
+ binding.config.confirmation ?? "never",
540
+ binding.ref.requiresApproval === true ? "required" : "never"
541
+ ),
542
+ idempotent: effect === "server-query",
543
+ auditLevel: defaultAuditFor(effect),
544
+ meta: binding.config.meta ? jsonClone(binding.config.meta) : void 0,
545
+ policies: [...binding.config.policies ?? []],
546
+ contextLink: binding.contextLink ?? (hasView ? { type: def.type, instanceId } : void 0),
547
+ concurrency: binding.config.concurrency
548
+ };
549
+ });
550
+ return {
551
+ id,
552
+ key: componentKey(def.type, instanceId),
553
+ type: def.type,
554
+ instanceId,
555
+ description: def.description,
556
+ parent: def.parent ? { type: def.parent.type, instanceId: def.parent.instanceId ?? "default" } : void 0,
557
+ meta: def.meta ? jsonClone(def.meta) : void 0,
558
+ internal: Object.freeze({ ...def.internal ?? {} }),
559
+ origin: def.origin ?? "first-party",
560
+ priority: def.priority ?? 0,
561
+ definition: def,
562
+ componentPolicies: [...def.policies ?? []],
563
+ observations,
564
+ actions,
565
+ procedures,
566
+ procedureOnly: !hasView && procedures.length > 0,
567
+ status: "active",
568
+ enabled: def.enabled !== false,
569
+ availabilityOverrides: /* @__PURE__ */ new Map(),
570
+ inFlight: /* @__PURE__ */ new Set(),
571
+ concurrencyGroups: /* @__PURE__ */ new Map()
572
+ };
573
+ }
574
+ function concurrencyGroupFor(cap, limits) {
575
+ const declared = cap.kind === "action" ? cap.concurrency : cap.concurrency;
576
+ const fallbackDepth = limits.actionQueueDepth;
577
+ if (declared === void 0) {
578
+ return cap.kind === "action" ? { key: "instance", max: 1, depth: fallbackDepth } : { key: `proc:${cap.capabilityId}`, max: 1, depth: fallbackDepth };
579
+ }
580
+ const depth = declared.queueDepth ?? fallbackDepth;
581
+ switch (declared.mode) {
582
+ case "instance":
583
+ return { key: "instance", max: 1, depth };
584
+ case "capability":
585
+ return { key: `cap:${cap.capabilityId}`, max: 1, depth };
586
+ case "key":
587
+ return { key: `key:${declared.key}`, max: 1, depth };
588
+ case "parallel":
589
+ return { key: `par:${cap.capabilityId}`, max: declared.max, depth };
590
+ }
591
+ }
592
+ function liveAvailabilityHooks(reg, cap) {
593
+ if (cap.kind === "observation") {
594
+ const live = reg.definition.observations?.[cap.name];
595
+ return { when: live?.when, unavailableReason: live?.unavailableReason };
596
+ }
597
+ if (cap.kind === "action") {
598
+ const live = reg.definition.actions?.[cap.name];
599
+ return { when: live?.when, unavailableReason: live?.unavailableReason };
600
+ }
601
+ return { when: cap.binding.config.when, unavailableReason: cap.binding.config.unavailableReason };
602
+ }
603
+ function computeAvailability(internals, reg, cap) {
604
+ if (reg.status !== "active") {
605
+ return { available: false, reason: "component-unregistered" };
606
+ }
607
+ if (!reg.enabled) {
608
+ return { available: false, reason: "component-disabled" };
609
+ }
610
+ const overrideKey = cap.kind === "procedure" ? cap.path : cap.name;
611
+ const override = reg.availabilityOverrides.get(overrideKey) ?? reg.availabilityOverrides.get(cap.capabilityId);
612
+ if (override && override.available === false) {
613
+ return { available: false, reason: override.reason ?? "unavailable" };
614
+ }
615
+ const hooks = liveAvailabilityHooks(reg, cap);
616
+ if (hooks.when) {
617
+ let result;
618
+ try {
619
+ result = hooks.when() !== false;
620
+ } catch (err) {
621
+ internals.devWarn(
622
+ `[agent-surface] when() threw for ${cap.capabilityId}; treating as unavailable`,
623
+ err
624
+ );
625
+ return { available: false, reason: "when-error" };
626
+ }
627
+ if (!result) {
628
+ let reason = "Currently unavailable";
629
+ const ur = hooks.unavailableReason;
630
+ try {
631
+ if (typeof ur === "function") reason = ur();
632
+ else if (typeof ur === "string") reason = ur;
633
+ } catch {
634
+ }
635
+ return { available: false, reason };
636
+ }
637
+ }
638
+ return { available: true };
639
+ }
640
+ function policiesFor(internals, reg, cap) {
641
+ return [...internals.registryPolicies, ...reg.componentPolicies, ...cap.policies];
642
+ }
643
+ function buildPolicyContext(internals, reg, cap, consumer, host) {
644
+ return {
645
+ capabilityId: cap.capabilityId,
646
+ plane: cap.kind === "procedure" ? "domain" : "view",
647
+ kind: cap.kind,
648
+ effect: cap.kind === "observation" ? "read" : cap.effect,
649
+ registrationId: reg.id,
650
+ consumer,
651
+ host,
652
+ meta: { component: reg.meta, capability: cap.meta },
653
+ internal: reg.internal,
654
+ environment: internals.environment,
655
+ now: () => internals.now()
656
+ };
657
+ }
658
+ function consumerKeyOf(consumer) {
659
+ return `${consumer.kind}:${consumer.id}`;
660
+ }
661
+ function pruneTombstones(internals) {
662
+ const now = internals.now();
663
+ for (const [id, tomb] of internals.tombstones) {
664
+ if (tomb.expiresAt <= now) internals.tombstones.delete(id);
665
+ }
666
+ while (internals.tombstones.size > internals.limits.tombstoneSize) {
667
+ const oldest = internals.tombstones.keys().next().value;
668
+ if (oldest === void 0) break;
669
+ internals.tombstones.delete(oldest);
670
+ }
671
+ }
672
+ function addTombstone(internals, reg) {
673
+ const capabilityIds = /* @__PURE__ */ new Set();
674
+ for (const obs of reg.observations.values()) capabilityIds.add(obs.capabilityId);
675
+ for (const act of reg.actions.values()) capabilityIds.add(act.capabilityId);
676
+ for (const proc of reg.procedures) capabilityIds.add(proc.capabilityId);
677
+ internals.tombstones.set(reg.id, {
678
+ registrationId: reg.id,
679
+ type: reg.type,
680
+ instanceId: reg.instanceId,
681
+ capabilityIds,
682
+ expiresAt: internals.now() + internals.limits.tombstoneTtlMs
683
+ });
684
+ pruneTombstones(internals);
685
+ }
686
+
687
+ // src/snapshot.ts
688
+ var DEFAULT_CONSUMER = { id: "anonymous", kind: "embedded" };
689
+ function matchesScope(type, scope) {
690
+ if (!scope || scope.length === 0) return true;
691
+ return scope.some((prefix) => type === prefix || type.startsWith(`${prefix}.`));
692
+ }
693
+ function sortRegistrations(regs) {
694
+ return regs.sort((a, b) => {
695
+ if (a.priority !== b.priority) return b.priority - a.priority;
696
+ if (a.type !== b.type) return a.type < b.type ? -1 : 1;
697
+ return a.instanceId < b.instanceId ? -1 : a.instanceId > b.instanceId ? 1 : 0;
698
+ });
699
+ }
700
+ function createSnapshot(internals, ctx) {
701
+ const consumer = ctx?.consumer ?? DEFAULT_CONSUMER;
702
+ const includeUnavailable = ctx?.includeUnavailable ?? true;
703
+ const host = internals.host();
704
+ const regs = sortRegistrations(
705
+ [...internals.registrations.values()].filter((r) => r.status === "active")
706
+ );
707
+ const components = [];
708
+ const componentPriority = [];
709
+ const procedures = [];
710
+ for (const reg of regs) {
711
+ const inScopeForComponents = matchesScope(reg.type, ctx?.scope);
712
+ if (!reg.procedureOnly && inScopeForComponents) {
713
+ const observations = [];
714
+ const actions = [];
715
+ let definedCount = 0;
716
+ let hiddenCount = 0;
717
+ for (const obs of reg.observations.values()) {
718
+ definedCount += 1;
719
+ const chain = policiesFor(internals, reg, obs);
720
+ const policyCtx = buildPolicyContext(internals, reg, obs, consumer, host);
721
+ const decision = evaluateDiscovery(chain, policyCtx);
722
+ if (decision.decision === "hide") {
723
+ hiddenCount += 1;
724
+ continue;
725
+ }
726
+ const availability = computeAvailability(internals, reg, obs);
727
+ const available = availability.available && decision.decision === "expose";
728
+ const reason = decision.decision === "disable" ? decision.reason : availability.reason;
729
+ if (!available && !includeUnavailable) continue;
730
+ observations.push({
731
+ capabilityId: obs.capabilityId,
732
+ name: obs.name,
733
+ description: obs.description,
734
+ outputSchema: obs.jsonSchema,
735
+ available,
736
+ ...available ? {} : { unavailableReason: reason },
737
+ ...obs.meta ? { meta: obs.meta } : {}
738
+ });
739
+ }
740
+ for (const act of reg.actions.values()) {
741
+ definedCount += 1;
742
+ const chain = policiesFor(internals, reg, act);
743
+ const policyCtx = buildPolicyContext(internals, reg, act, consumer, host);
744
+ const decision = evaluateDiscovery(chain, policyCtx);
745
+ if (decision.decision === "hide") {
746
+ hiddenCount += 1;
747
+ continue;
748
+ }
749
+ const availability = computeAvailability(internals, reg, act);
750
+ const available = availability.available && decision.decision === "expose";
751
+ const reason = decision.decision === "disable" ? decision.reason : availability.reason;
752
+ if (!available && !includeUnavailable) continue;
753
+ actions.push({
754
+ capabilityId: act.capabilityId,
755
+ name: act.name,
756
+ description: act.description,
757
+ inputSchema: act.inputJsonSchema,
758
+ ...act.outputJsonSchema ? { outputSchema: act.outputJsonSchema } : {},
759
+ effect: act.effect,
760
+ idempotent: act.idempotent,
761
+ reversible: act.reversible,
762
+ confirmation: act.confirmation,
763
+ available,
764
+ ...available ? {} : { unavailableReason: reason },
765
+ ...act.meta ? { meta: act.meta } : {}
766
+ });
767
+ }
768
+ const allHidden = definedCount > 0 && hiddenCount === definedCount;
769
+ if (!allHidden) {
770
+ components.push({
771
+ type: reg.type,
772
+ instanceId: reg.instanceId,
773
+ registrationId: reg.id,
774
+ description: reg.description,
775
+ ...reg.parent ? { parent: reg.parent } : {},
776
+ ...reg.meta ? { meta: reg.meta } : {},
777
+ observations,
778
+ actions
779
+ });
780
+ componentPriority.push(reg.priority);
781
+ }
782
+ }
783
+ for (const proc of reg.procedures) {
784
+ const scopeMatch = proc.contextLink ? matchesScope(proc.contextLink.type, ctx?.scope) : matchesScope(proc.path, ctx?.scope);
785
+ if (!scopeMatch) continue;
786
+ const chain = policiesFor(internals, reg, proc);
787
+ const policyCtx = buildPolicyContext(internals, reg, proc, consumer, host);
788
+ const decision = evaluateDiscovery(chain, policyCtx);
789
+ if (decision.decision === "hide") continue;
790
+ const availability = computeAvailability(internals, reg, proc);
791
+ const available = availability.available && decision.decision === "expose";
792
+ const reason = decision.decision === "disable" ? decision.reason : availability.reason;
793
+ if (!available && !includeUnavailable) continue;
794
+ let contextualNote;
795
+ const describe = proc.binding.config.describe;
796
+ if (describe) {
797
+ try {
798
+ const contextual = describe();
799
+ if (contextual) contextualNote = contextual;
800
+ } catch {
801
+ }
802
+ }
803
+ procedures.push({
804
+ procedureId: proc.capabilityId,
805
+ // Never merged with `contextualNote` (D28): the manifest text is the
806
+ // stable half, and folding volatile text in is what churned the
807
+ // provider's cached prompt prefix.
808
+ description: proc.baseDescription,
809
+ ...contextualNote !== void 0 ? { contextualNote } : {},
810
+ inputSchema: proc.reducedInputSchema,
811
+ ...proc.outputJsonSchema ? { outputSchema: proc.outputJsonSchema } : {},
812
+ effect: proc.effect,
813
+ confirmation: proc.confirmationFloor,
814
+ available,
815
+ ...available ? {} : { unavailableReason: reason },
816
+ boundFields: proc.boundKeys.map((path) => ({
817
+ path,
818
+ locked: proc.lockedKeys.includes(path),
819
+ source: "ui-state"
820
+ })),
821
+ registrationId: reg.id,
822
+ ...proc.contextLink ? { context: proc.contextLink } : {},
823
+ ...proc.meta ? { meta: proc.meta } : {}
824
+ });
825
+ }
826
+ }
827
+ let dropped = 0;
828
+ const budget = ctx?.budget;
829
+ if (budget?.maxComponents !== void 0 && components.length > budget.maxComponents) {
830
+ dropped += components.length - budget.maxComponents;
831
+ dropLowestPriority(components, componentPriority, components.length - budget.maxComponents);
832
+ }
833
+ if (budget?.maxBytes !== void 0) {
834
+ while (components.length > 0 && byteLength(components) > budget.maxBytes) {
835
+ dropLowestPriority(components, componentPriority, 1);
836
+ dropped += 1;
837
+ }
838
+ }
839
+ const snapshot = {
840
+ surfaceId: internals.surfaceId,
841
+ surfaceVersion: String(internals.version),
842
+ capturedAt: new Date(internals.now()).toISOString(),
843
+ ...internals.routeFn?.() ? { route: internals.routeFn() } : {},
844
+ components,
845
+ procedures,
846
+ ...dropped > 0 ? { truncated: { droppedComponents: dropped } } : {}
847
+ };
848
+ return deepFreeze(snapshot);
849
+ }
850
+ function dropLowestPriority(components, priorities, count) {
851
+ for (let n = 0; n < count && components.length > 0; n++) {
852
+ let lowestIndex = 0;
853
+ for (let i = 1; i < priorities.length; i++) {
854
+ if ((priorities[i] ?? 0) <= (priorities[lowestIndex] ?? 0)) lowestIndex = i;
855
+ }
856
+ components.splice(lowestIndex, 1);
857
+ priorities.splice(lowestIndex, 1);
858
+ }
859
+ }
860
+
861
+ export {
862
+ AGENT_CAPABILITY_ERROR_CODES,
863
+ AgentSurfaceError,
864
+ isAgentSurfaceError,
865
+ AgentSurfaceDefinitionError,
866
+ CONFIRMATION_ESCALATION,
867
+ evaluateDiscovery,
868
+ composeAuthorizeChain,
869
+ composeInvokeChain,
870
+ authenticated,
871
+ hasPermission,
872
+ tenantBoundary,
873
+ environment,
874
+ rateLimit,
875
+ requireConfirmation,
876
+ audit,
877
+ MAX_ID_LENGTH,
878
+ isValidComponentType,
879
+ isValidCapabilityName,
880
+ isValidInstanceId,
881
+ formatViewCapabilityId,
882
+ formatDomainCapabilityId,
883
+ parseCapabilityId,
884
+ MAX_WIRE_NAME_LENGTH,
885
+ encodeWireName,
886
+ encodeWireNameForInstance,
887
+ assignWireNames,
888
+ decodeWireName,
889
+ jsonDeepEqual,
890
+ deepFreeze,
891
+ isJsonValue,
892
+ byteLength,
893
+ randomBase62,
894
+ truncate,
895
+ canonicalJson,
896
+ fnv1a64,
897
+ DEV_WARN,
898
+ INTERNALS,
899
+ DevDefectError,
900
+ componentKey,
901
+ maxConfirmation,
902
+ nextRegistrationId,
903
+ normalizeRegistration,
904
+ concurrencyGroupFor,
905
+ computeAvailability,
906
+ policiesFor,
907
+ buildPolicyContext,
908
+ consumerKeyOf,
909
+ addTombstone,
910
+ DEFAULT_CONSUMER,
911
+ matchesScope,
912
+ sortRegistrations,
913
+ createSnapshot
914
+ };
915
+ //# sourceMappingURL=chunk-77YRWAXY.js.map