@agent-surface/core 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -102,29 +102,81 @@ function parseCapabilityId(id) {
102
102
  return void 0;
103
103
  }
104
104
  var MAX_WIRE_NAME_LENGTH = 64;
105
- function fnv1aHash36(input) {
106
- let hash = 2166136261;
107
- for (let i = 0; i < input.length; i++) {
108
- hash ^= input.charCodeAt(i);
109
- hash = Math.imul(hash, 16777619) >>> 0;
105
+ var SHORTENED_MARKER = "_0_";
106
+ var INSTANCE_MARKER = "_at_";
107
+ function hash36(input, length) {
108
+ let out = "";
109
+ for (let round = 0; out.length < length; round++) {
110
+ let hash = (2166136261 ^ round) >>> 0;
111
+ for (let i = 0; i < input.length; i++) {
112
+ hash ^= input.charCodeAt(i);
113
+ hash = Math.imul(hash, 16777619) >>> 0;
114
+ }
115
+ out += hash.toString(36).padStart(7, "0");
110
116
  }
111
- return hash.toString(36).padStart(7, "0").slice(0, 7);
117
+ return out.slice(0, length);
118
+ }
119
+ function rawWireName(id, instanceId) {
120
+ const encoded = id.replace(":", "_").replaceAll(".", "__");
121
+ return instanceId ? `${encoded}${INSTANCE_MARKER}${instanceId}` : encoded;
112
122
  }
113
123
  function encodeWireName(id) {
114
124
  return encodeWireNameForInstance(id);
115
125
  }
116
- function encodeWireNameForInstance(id, instanceId) {
117
- const raw = id.replace(":", "_").replaceAll(".", "__") + (instanceId ? `_at_${instanceId}` : "");
118
- if (raw.length <= MAX_WIRE_NAME_LENGTH) return raw;
119
- return `${raw.slice(0, 56)}_${fnv1aHash36(`${id}#${instanceId ?? ""}`)}`;
126
+ function encodeWireNameForInstance(id, instanceId, level = 0) {
127
+ const raw = rawWireName(id, instanceId);
128
+ if (level === 0 && raw.length <= MAX_WIRE_NAME_LENGTH) return raw;
129
+ const hashLength = 7 + level * 2;
130
+ const keep = MAX_WIRE_NAME_LENGTH - SHORTENED_MARKER.length - hashLength;
131
+ const hash = hash36(`${id}#${instanceId ?? ""}#${level}`, hashLength);
132
+ return `${raw.slice(0, keep)}${SHORTENED_MARKER}${hash}`;
133
+ }
134
+ function assignWireNames(entries) {
135
+ const keyOf = (e) => `${e.id}#${e.instanceId ?? ""}`;
136
+ const level = /* @__PURE__ */ new Map();
137
+ const MAX_LEVEL = 3;
138
+ let names = entries.map((e) => encodeWireNameForInstance(e.id, e.instanceId));
139
+ for (let round = 0; round <= MAX_LEVEL; round++) {
140
+ const byName2 = /* @__PURE__ */ new Map();
141
+ entries.forEach((entry, i) => {
142
+ const set = byName2.get(names[i]) ?? /* @__PURE__ */ new Set();
143
+ set.add(keyOf(entry));
144
+ byName2.set(names[i], set);
145
+ });
146
+ const colliding = /* @__PURE__ */ new Set();
147
+ for (const [, keys] of byName2) {
148
+ if (keys.size > 1) for (const key of keys) colliding.add(key);
149
+ }
150
+ if (colliding.size === 0) break;
151
+ if (round === MAX_LEVEL) {
152
+ const ranked = [...colliding].sort();
153
+ names = entries.map((entry, i) => {
154
+ const rank = ranked.indexOf(keyOf(entry));
155
+ if (rank < 0) return names[i];
156
+ const suffix = `${SHORTENED_MARKER}${rank}`;
157
+ const base = encodeWireNameForInstance(entry.id, entry.instanceId, MAX_LEVEL);
158
+ return `${base.slice(0, MAX_WIRE_NAME_LENGTH - suffix.length)}${suffix}`;
159
+ });
160
+ break;
161
+ }
162
+ for (const key of colliding) level.set(key, (level.get(key) ?? 0) + 1);
163
+ names = entries.map(
164
+ (entry) => encodeWireNameForInstance(entry.id, entry.instanceId, level.get(keyOf(entry)) ?? 0)
165
+ );
166
+ }
167
+ const byName = /* @__PURE__ */ new Map();
168
+ entries.forEach((entry, i) => byName.set(names[i], entry.id));
169
+ return { names, byName };
120
170
  }
121
171
  function decodeWireName(name) {
172
+ if (name.includes(SHORTENED_MARKER) || name.includes(INSTANCE_MARKER)) return void 0;
122
173
  const planeEnd = name.indexOf("_");
123
174
  if (planeEnd <= 0) return void 0;
124
175
  const plane = name.slice(0, planeEnd);
125
176
  if (plane !== "view" && plane !== "domain") return void 0;
126
- const rest = name.slice(planeEnd + 1).replaceAll("__", ".");
127
- return `${plane}:${rest}`;
177
+ const id = `${plane}:${name.slice(planeEnd + 1).replaceAll("__", ".")}`;
178
+ if (id.includes("_") || !parseCapabilityId(id) || encodeWireName(id) !== name) return void 0;
179
+ return id;
128
180
  }
129
181
 
130
182
  // src/utils.ts
@@ -645,7 +697,8 @@ var ACTION_KEYS = /* @__PURE__ */ new Set([
645
697
  "execute",
646
698
  "policies",
647
699
  "meta",
648
- "timeoutMs"
700
+ "timeoutMs",
701
+ "concurrency"
649
702
  ]);
650
703
  var VIEW_EFFECTS = /* @__PURE__ */ new Set(["local-state", "navigation"]);
651
704
  var SERVER_EFFECTS = /* @__PURE__ */ new Set([
@@ -666,6 +719,29 @@ function checkMeta(meta, where, limits) {
666
719
  fail("LIMIT_EXCEEDED", `${where}: meta exceeds ${limits.maxMetaBytes} bytes`);
667
720
  }
668
721
  }
722
+ function checkConcurrency(concurrency, where) {
723
+ if (concurrency === void 0) return;
724
+ if (typeof concurrency !== "object" || concurrency === null) {
725
+ fail("INVALID_DEFINITION", `${where}: concurrency must be an object`);
726
+ }
727
+ const { mode } = concurrency;
728
+ if (!["instance", "capability", "key", "parallel"].includes(mode)) {
729
+ fail("INVALID_DEFINITION", `${where}: invalid concurrency mode "${String(mode)}"`);
730
+ }
731
+ if (mode === "key" && (typeof concurrency.key !== "string" || concurrency.key.length === 0)) {
732
+ fail("INVALID_DEFINITION", `${where}: concurrency mode "key" requires a non-empty key`);
733
+ }
734
+ if (mode === "parallel" && (typeof concurrency.max !== "number" || !Number.isInteger(concurrency.max) || concurrency.max < 1)) {
735
+ fail(
736
+ "INVALID_DEFINITION",
737
+ `${where}: concurrency mode "parallel" requires an integer max \u2265 1 (unbounded parallelism is not offered)`
738
+ );
739
+ }
740
+ const depth = concurrency.queueDepth;
741
+ if (depth !== void 0 && (!Number.isInteger(depth) || depth < 0)) {
742
+ fail("INVALID_DEFINITION", `${where}: concurrency queueDepth must be a non-negative integer`);
743
+ }
744
+ }
669
745
  function checkSchema(schema, where, limits) {
670
746
  if (schema === void 0) return;
671
747
  if (typeof schema !== "object" || schema === null || typeof schema.parse !== "function" || typeof schema.jsonSchema !== "object") {
@@ -774,6 +850,7 @@ function validateComponentDefinition(def, limits, opts) {
774
850
  checkSchema(act.input, `${where} input`, limits);
775
851
  checkSchema(act.output, `${where} output`, limits);
776
852
  checkMeta(act.meta, where, limits);
853
+ checkConcurrency(act.concurrency, where);
777
854
  }
778
855
  const procedures = def.procedures ?? [];
779
856
  if (procedures.length > 0 && !opts.hasProcedureExecutor) {
@@ -803,6 +880,7 @@ function validateComponentDefinition(def, limits, opts) {
803
880
  fail("INVALID_DEFINITION", `procedure "${ref.path}": invalid confirmation escalation`);
804
881
  }
805
882
  checkMeta(binding.config.meta, `procedure "${ref.path}"`, limits);
883
+ checkConcurrency(binding.config.concurrency, `procedure "${ref.path}"`);
806
884
  }
807
885
  }
808
886
 
@@ -1354,7 +1432,8 @@ function normalizeRegistration(def, id) {
1354
1432
  auditLevel: act.audit ?? "metadata",
1355
1433
  meta: act.meta ? jsonClone(act.meta) : void 0,
1356
1434
  timeoutMs: act.timeoutMs,
1357
- policies: [...act.policies ?? []]
1435
+ policies: [...act.policies ?? []],
1436
+ concurrency: act.concurrency
1358
1437
  });
1359
1438
  }
1360
1439
  const hasView = observations.size > 0 || actions.size > 0;
@@ -1386,7 +1465,8 @@ function normalizeRegistration(def, id) {
1386
1465
  auditLevel: defaultAuditFor(effect),
1387
1466
  meta: binding.config.meta ? jsonClone(binding.config.meta) : void 0,
1388
1467
  policies: [...binding.config.policies ?? []],
1389
- contextLink: binding.contextLink ?? (hasView ? { type: def.type, instanceId } : void 0)
1468
+ contextLink: binding.contextLink ?? (hasView ? { type: def.type, instanceId } : void 0),
1469
+ concurrency: binding.config.concurrency
1390
1470
  };
1391
1471
  });
1392
1472
  return {
@@ -1410,9 +1490,27 @@ function normalizeRegistration(def, id) {
1410
1490
  enabled: def.enabled !== false,
1411
1491
  availabilityOverrides: /* @__PURE__ */ new Map(),
1412
1492
  inFlight: /* @__PURE__ */ new Set(),
1413
- actionQueue: { running: false, waiting: [] }
1493
+ concurrencyGroups: /* @__PURE__ */ new Map()
1414
1494
  };
1415
1495
  }
1496
+ function concurrencyGroupFor(cap, limits) {
1497
+ const declared = cap.kind === "action" ? cap.concurrency : cap.concurrency;
1498
+ const fallbackDepth = limits.actionQueueDepth;
1499
+ if (declared === void 0) {
1500
+ return cap.kind === "action" ? { key: "instance", max: 1, depth: fallbackDepth } : { key: `proc:${cap.capabilityId}`, max: 1, depth: fallbackDepth };
1501
+ }
1502
+ const depth = declared.queueDepth ?? fallbackDepth;
1503
+ switch (declared.mode) {
1504
+ case "instance":
1505
+ return { key: "instance", max: 1, depth };
1506
+ case "capability":
1507
+ return { key: `cap:${cap.capabilityId}`, max: 1, depth };
1508
+ case "key":
1509
+ return { key: `key:${declared.key}`, max: 1, depth };
1510
+ case "parallel":
1511
+ return { key: `par:${cap.capabilityId}`, max: declared.max, depth };
1512
+ }
1513
+ }
1416
1514
  function liveAvailabilityHooks(reg, cap) {
1417
1515
  if (cap.kind === "observation") {
1418
1516
  const live = reg.definition.observations?.[cap.name];
@@ -2000,7 +2098,7 @@ async function executeAction(internals, args, cap) {
2000
2098
  }
2001
2099
  }
2002
2100
  const queueStart = internals.now();
2003
- const slot = await acquireActionSlot(internals, reg);
2101
+ const slot = await acquireActionSlot(internals, reg, cap);
2004
2102
  args.setTimings({ queueWaitMs: internals.now() - queueStart });
2005
2103
  if (slot === "overflow") {
2006
2104
  return finalize({ status: "error", error: queueFull(250) });
@@ -2034,7 +2132,7 @@ async function executeAction(internals, args, cap) {
2034
2132
  args.setAuditPayload(void 0, output.value);
2035
2133
  return finalize({ status: "ok", output: output.value });
2036
2134
  } finally {
2037
- releaseActionSlot(reg);
2135
+ releaseActionSlot(internals, reg, cap);
2038
2136
  }
2039
2137
  };
2040
2138
  return runInvokePolicies(args, parsedInput, run);
@@ -2102,40 +2200,50 @@ async function executeProcedure(internals, args, cap) {
2102
2200
  effect: cap.effect
2103
2201
  });
2104
2202
  if ("error" in confirmation) return finalize({ status: "error", error: confirmation.error });
2105
- const executor = internals.executor;
2106
- if (!executor) {
2107
- return finalize({ status: "error", error: executionFailed("transport") });
2203
+ const queueStart = internals.now();
2204
+ const slot = await acquireActionSlot(internals, reg, cap);
2205
+ args.setTimings({ queueWaitMs: internals.now() - queueStart });
2206
+ if (slot === "overflow") {
2207
+ return finalize({ status: "error", error: queueFull(250) });
2208
+ }
2209
+ try {
2210
+ const executor = internals.executor;
2211
+ if (!executor) {
2212
+ return finalize({ status: "error", error: executionFailed("transport") });
2213
+ }
2214
+ const timeoutMs = options?.timeoutMs ?? internals.limits.procedureTimeoutMs;
2215
+ const executeStart = internals.now();
2216
+ const outcome = await executeWithGuards(internals, reg, {
2217
+ invocationId,
2218
+ capabilityId: cap.capabilityId,
2219
+ timeoutMs,
2220
+ externalSignal: options?.signal,
2221
+ idempotent: cap.idempotent,
2222
+ run: (signal) => executor.execute({
2223
+ path: cap.path,
2224
+ input: effective,
2225
+ info: {
2226
+ invocationId,
2227
+ consumer,
2228
+ signal,
2229
+ ...confirmation.evidence ? { confirmation: confirmation.evidence } : {}
2230
+ }
2231
+ }),
2232
+ procedureErrors: true
2233
+ });
2234
+ args.setTimings({ executionMs: internals.now() - executeStart });
2235
+ if (!outcome.ok) return finalize({ status: "error", error: outcome.payload });
2236
+ const output = settleOutput(
2237
+ internals,
2238
+ outcome.value,
2239
+ cap.outputJsonSchema ? fromJsonSchema(cap.outputJsonSchema) : void 0
2240
+ );
2241
+ if ("error" in output) return finalize({ status: "error", error: output.error });
2242
+ args.setAuditPayload(void 0, output.value);
2243
+ return finalize({ status: "ok", output: output.value });
2244
+ } finally {
2245
+ releaseActionSlot(internals, reg, cap);
2108
2246
  }
2109
- const timeoutMs = options?.timeoutMs ?? internals.limits.procedureTimeoutMs;
2110
- const executeStart = internals.now();
2111
- const outcome = await executeWithGuards(internals, reg, {
2112
- invocationId,
2113
- capabilityId: cap.capabilityId,
2114
- timeoutMs,
2115
- externalSignal: options?.signal,
2116
- idempotent: cap.idempotent,
2117
- run: (signal) => executor.execute({
2118
- path: cap.path,
2119
- input: effective,
2120
- info: {
2121
- invocationId,
2122
- consumer,
2123
- signal,
2124
- ...confirmation.evidence ? { confirmation: confirmation.evidence } : {}
2125
- }
2126
- }),
2127
- procedureErrors: true
2128
- });
2129
- args.setTimings({ executionMs: internals.now() - executeStart });
2130
- if (!outcome.ok) return finalize({ status: "error", error: outcome.payload });
2131
- const output = settleOutput(
2132
- internals,
2133
- outcome.value,
2134
- cap.outputJsonSchema ? fromJsonSchema(cap.outputJsonSchema) : void 0
2135
- );
2136
- if ("error" in output) return finalize({ status: "error", error: output.error });
2137
- args.setAuditPayload(void 0, output.value);
2138
- return finalize({ status: "ok", output: output.value });
2139
2247
  };
2140
2248
  return runInvokePolicies(args, effective, run);
2141
2249
  }
@@ -2384,21 +2492,32 @@ function executeWithGuards(internals, reg, opts) {
2384
2492
  }
2385
2493
  });
2386
2494
  }
2387
- async function acquireActionSlot(internals, reg) {
2388
- if (!reg.actionQueue.running) {
2389
- reg.actionQueue.running = true;
2495
+ async function acquireActionSlot(internals, reg, cap) {
2496
+ const { key, max, depth } = concurrencyGroupFor(cap, internals.limits);
2497
+ let group = reg.concurrencyGroups.get(key);
2498
+ if (!group) {
2499
+ group = { running: 0, max, depth, waiting: [] };
2500
+ reg.concurrencyGroups.set(key, group);
2501
+ }
2502
+ if (group.running < group.max) {
2503
+ group.running += 1;
2390
2504
  return "ok";
2391
2505
  }
2392
- if (reg.actionQueue.waiting.length >= internals.limits.actionQueueDepth) {
2506
+ if (group.waiting.length >= group.depth) {
2507
+ if (group.running === 0 && group.waiting.length === 0) reg.concurrencyGroups.delete(key);
2393
2508
  return "overflow";
2394
2509
  }
2395
- await new Promise((resolve) => reg.actionQueue.waiting.push(resolve));
2510
+ await new Promise((resolve) => group.waiting.push(resolve));
2396
2511
  return "ok";
2397
2512
  }
2398
- function releaseActionSlot(reg) {
2399
- const next = reg.actionQueue.waiting.shift();
2400
- if (next) next();
2401
- else reg.actionQueue.running = false;
2513
+ function releaseActionSlot(internals, reg, cap) {
2514
+ const { key } = concurrencyGroupFor(cap, internals.limits);
2515
+ const group = reg.concurrencyGroups.get(key);
2516
+ if (!group) return;
2517
+ const next = group.waiting.shift();
2518
+ if (!next) group.running -= 1;
2519
+ else next();
2520
+ if (group.running === 0 && group.waiting.length === 0) reg.concurrencyGroups.delete(key);
2402
2521
  }
2403
2522
  function acquireObservationSlot(internals, consumerKey) {
2404
2523
  const adm = internals.observationAdmission;
@@ -2453,6 +2572,12 @@ function drainObservationQueues(internals) {
2453
2572
 
2454
2573
  // src/snapshot.ts
2455
2574
  var DEFAULT_CONSUMER2 = { id: "anonymous", kind: "embedded" };
2575
+ function stableDescriptionOf(descriptor) {
2576
+ const note = descriptor.contextualNote;
2577
+ if (!note) return descriptor.description;
2578
+ if (descriptor.description === note) return "";
2579
+ return descriptor.description.endsWith(` ${note}`) ? descriptor.description.slice(0, -(note.length + 1)) : descriptor.description;
2580
+ }
2456
2581
  function matchesScope(type, scope) {
2457
2582
  if (!scope || scope.length === 0) return true;
2458
2583
  return scope.some((prefix) => type === prefix || type.startsWith(`${prefix}.`));
@@ -2558,18 +2683,20 @@ function createSnapshot(internals, ctx) {
2558
2683
  const available = availability.available && decision.decision === "expose";
2559
2684
  const reason = decision.decision === "disable" ? decision.reason : availability.reason;
2560
2685
  if (!available && !includeUnavailable) continue;
2561
- let description = proc.baseDescription;
2686
+ let contextualNote;
2562
2687
  const describe = proc.binding.config.describe;
2563
2688
  if (describe) {
2564
2689
  try {
2565
2690
  const contextual = describe();
2566
- if (contextual) description = `${description} ${contextual}`.trim();
2691
+ if (contextual) contextualNote = contextual;
2567
2692
  } catch {
2568
2693
  }
2569
2694
  }
2695
+ const description = contextualNote && internals.mergesContextualNote ? `${proc.baseDescription} ${contextualNote}`.trim() : proc.baseDescription;
2570
2696
  procedures.push({
2571
2697
  procedureId: proc.capabilityId,
2572
2698
  description,
2699
+ ...contextualNote !== void 0 ? { contextualNote } : {},
2573
2700
  inputSchema: proc.reducedInputSchema,
2574
2701
  ...proc.outputJsonSchema ? { outputSchema: proc.outputJsonSchema } : {},
2575
2702
  effect: proc.effect,
@@ -2636,6 +2763,7 @@ function createAgentSurfaceRegistry(options) {
2636
2763
  const internals = {
2637
2764
  environment: environment2,
2638
2765
  limits,
2766
+ mergesContextualNote: options?.snapshotMergesContextualNote ?? true,
2639
2767
  surfaceId: `srf_${randomBase62(22)}`,
2640
2768
  version: 0,
2641
2769
  registrations: /* @__PURE__ */ new Map(),
@@ -2927,14 +3055,22 @@ var EMPTY_INPUT_SCHEMA = {
2927
3055
  properties: {},
2928
3056
  additionalProperties: false
2929
3057
  };
2930
- function describePrefix(plane, effect, confirmation, available, unavailableReason) {
3058
+ function describePrefix(plane, effect, confirmation) {
2931
3059
  const parts = [plane, effect];
2932
3060
  if (confirmation === "required") parts.push("requires confirmation");
2933
- let prefix = `[${parts.join(" \xB7 ")}]`;
2934
- if (!available) {
2935
- prefix += ` [currently unavailable${unavailableReason ? `: ${unavailableReason}` : ""}]`;
2936
- }
2937
- return prefix;
3061
+ return `[${parts.join(" \xB7 ")}]`;
3062
+ }
3063
+ function legacyDescription(prefix, description, state) {
3064
+ const unavailable = state.available ? "" : ` [currently unavailable${state.unavailableReason ? `: ${state.unavailableReason}` : ""}]`;
3065
+ const note = state.note ? ` ${state.note}` : "";
3066
+ return `${prefix}${unavailable} ${description}${note}`;
3067
+ }
3068
+ function availabilityState(descriptor) {
3069
+ return {
3070
+ available: descriptor.available,
3071
+ ...descriptor.unavailableReason !== void 0 ? { unavailableReason: descriptor.unavailableReason } : {},
3072
+ ...descriptor.contextualNote !== void 0 ? { note: descriptor.contextualNote } : {}
3073
+ };
2938
3074
  }
2939
3075
  function createAgentToolset(registry, options) {
2940
3076
  const mode = options.mode ?? "direct";
@@ -2943,12 +3079,19 @@ function createAgentToolset(registry, options) {
2943
3079
  "createAgentToolset: declare a topology ('embedded' | 'remote') or an explicit confirmations mode ('wait' | 'two-phase'). Embedded loops default to 'wait', remote loops to 'two-phase' (docs/09 \xA7confirmation-topology)."
2944
3080
  );
2945
3081
  }
3082
+ if (options.budget !== void 0 && mode !== "meta") {
3083
+ throw new Error(
3084
+ "createAgentToolset: `budget` applies to mode 'meta' only \u2014 in 'direct' mode it would silently drop tools. Pass a `scope` to bound a direct catalog instead (docs/09 \xA7meta-tools-mode)."
3085
+ );
3086
+ }
2946
3087
  const confirmationsMode = options.confirmations ?? (options.topology === "remote" ? "two-phase" : "wait");
3088
+ const descriptionIncludesState = options.descriptionIncludesState ?? true;
2947
3089
  const listeners = /* @__PURE__ */ new Set();
2948
3090
  const pendingWaits = /* @__PURE__ */ new Set();
2949
3091
  let disposed = false;
2950
3092
  let cachedVersion;
2951
3093
  let cachedTools;
3094
+ let cachedWireNames = /* @__PURE__ */ new Map();
2952
3095
  let cachedSignature;
2953
3096
  async function waitForConfirmation(confirmationId) {
2954
3097
  if (disposed) return;
@@ -2960,15 +3103,16 @@ function createAgentToolset(registry, options) {
2960
3103
  pendingWaits.delete(controller);
2961
3104
  }
2962
3105
  }
2963
- async function invokeThroughSurface(entry, input, toolCallId) {
2964
- const invocationId = toolCallId ?? `inv_${randomBase62(12)}`;
3106
+ async function invokeThroughSurface(entry, input, toolCallId, overrides) {
3107
+ const invocationId = overrides?.invocationId ?? toolCallId ?? `inv_${randomBase62(12)}`;
2965
3108
  const base = {
2966
3109
  invocationId,
2967
3110
  capabilityId: entry.capabilityId,
2968
3111
  ...entry.instanceId !== void 0 ? { instanceId: entry.instanceId } : {},
2969
- registrationId: entry.registrationId,
3112
+ ...entry.registrationId !== void 0 ? { registrationId: entry.registrationId } : {},
2970
3113
  surfaceVersion: entry.surfaceVersion,
2971
- ...input !== void 0 ? { input } : {}
3114
+ ...input !== void 0 ? { input } : {},
3115
+ ...overrides?.confirmationId !== void 0 ? { confirmationId: overrides.confirmationId } : {}
2972
3116
  };
2973
3117
  let result = await registry.invoke(base, { consumer: options.consumer });
2974
3118
  if (confirmationsMode === "wait" && result.status === "error" && result.error.code === "CONFIRMATION_REQUIRED") {
@@ -2990,26 +3134,32 @@ function createAgentToolset(registry, options) {
2990
3134
  ...options.scope ? { scope: options.scope } : {},
2991
3135
  includeUnavailable: true
2992
3136
  });
2993
- const tools = [];
2994
- const push = (capabilityId, kind, registrationId, instanceId, description, inputSchema, nameSuffix) => {
2995
- const entry = {
2996
- capabilityId,
2997
- registrationId,
2998
- ...instanceId !== void 0 ? { instanceId } : {},
2999
- surfaceVersion: snapshot.surfaceVersion,
3000
- kind
3001
- };
3002
- tools.push({
3137
+ const pending = [];
3138
+ const push = (capabilityId, kind, registrationId, instanceId, prefix, description, inputSchema, state, nameSuffix) => {
3139
+ const suffix = nameSuffix ?? instanceId;
3140
+ pending.push({
3003
3141
  // Providers require unique tool names: multi-instance capabilities
3004
3142
  // are disambiguated with an `_at_<instance>` suffix (docs/09).
3005
- name: encodeWireNameForInstance(capabilityId, nameSuffix ?? instanceId),
3143
+ wire: { id: capabilityId, ...suffix !== void 0 ? { instanceId: suffix } : {} },
3144
+ entry: {
3145
+ capabilityId,
3146
+ registrationId,
3147
+ ...instanceId !== void 0 ? { instanceId } : {},
3148
+ surfaceVersion: snapshot.surfaceVersion,
3149
+ kind
3150
+ },
3151
+ prefix,
3006
3152
  description,
3007
3153
  inputSchema,
3008
- execute: (input, call) => invokeThroughSurface(entry, input, call.toolCallId)
3154
+ state
3009
3155
  });
3010
3156
  };
3157
+ const typeCounts = /* @__PURE__ */ new Map();
3158
+ for (const component of snapshot.components) {
3159
+ typeCounts.set(component.type, (typeCounts.get(component.type) ?? 0) + 1);
3160
+ }
3011
3161
  for (const component of snapshot.components) {
3012
- const multiInstance = snapshot.components.filter((c) => c.type === component.type).length > 1;
3162
+ const multiInstance = (typeCounts.get(component.type) ?? 0) > 1;
3013
3163
  const instanceId = multiInstance ? component.instanceId : void 0;
3014
3164
  for (const obs of component.observations) {
3015
3165
  push(
@@ -3017,8 +3167,10 @@ function createAgentToolset(registry, options) {
3017
3167
  "observation",
3018
3168
  component.registrationId,
3019
3169
  instanceId,
3020
- `${describePrefix("view", "read", "never", obs.available, obs.unavailableReason)} ${obs.description}`,
3021
- EMPTY_INPUT_SCHEMA
3170
+ describePrefix("view", "read", "never"),
3171
+ obs.description,
3172
+ EMPTY_INPUT_SCHEMA,
3173
+ availabilityState(obs)
3022
3174
  );
3023
3175
  }
3024
3176
  for (const act of component.actions) {
@@ -3027,8 +3179,10 @@ function createAgentToolset(registry, options) {
3027
3179
  "action",
3028
3180
  component.registrationId,
3029
3181
  instanceId,
3030
- `${describePrefix("view", act.effect, act.confirmation, act.available, act.unavailableReason)} ${act.description}`,
3031
- act.inputSchema
3182
+ describePrefix("view", act.effect, act.confirmation),
3183
+ act.description,
3184
+ act.inputSchema,
3185
+ availabilityState(act)
3032
3186
  );
3033
3187
  }
3034
3188
  }
@@ -3043,19 +3197,30 @@ function createAgentToolset(registry, options) {
3043
3197
  "procedure",
3044
3198
  proc.registrationId,
3045
3199
  void 0,
3046
- `${describePrefix("domain", proc.effect, proc.confirmation, proc.available, proc.unavailableReason)} ${proc.description}`,
3200
+ describePrefix("domain", proc.effect, proc.confirmation),
3201
+ // The stable half only: a contextual note travels in `state.note`.
3202
+ stableDescriptionOf(proc),
3047
3203
  proc.inputSchema,
3204
+ availabilityState(proc),
3048
3205
  needsSuffix ? proc.context?.instanceId ?? proc.registrationId.replace(/[^A-Za-z0-9_-]/g, "") : void 0
3049
3206
  );
3050
3207
  }
3051
- return tools;
3208
+ const assignment = assignWireNames(pending.map((p) => p.wire));
3209
+ const tools = pending.map((p, i) => ({
3210
+ name: assignment.names[i],
3211
+ description: descriptionIncludesState ? legacyDescription(p.prefix, p.description, p.state) : `${p.prefix} ${p.description}`,
3212
+ inputSchema: p.inputSchema,
3213
+ state: p.state,
3214
+ execute: (input, call) => invokeThroughSurface(p.entry, input, call.toolCallId)
3215
+ }));
3216
+ return { tools, wireNames: assignment.byName };
3052
3217
  }
3053
3218
  function buildMetaTools() {
3054
3219
  const snapshotFor = () => registry.snapshot({
3055
3220
  consumer: options.consumer,
3056
3221
  ...options.scope ? { scope: options.scope } : {}
3057
3222
  });
3058
- return [
3223
+ const verbs = [
3059
3224
  {
3060
3225
  name: "surface_discover",
3061
3226
  description: "[meta] Discover the current agent surface: components, capabilities, procedures, availability, schemas.",
@@ -3065,16 +3230,19 @@ function createAgentToolset(registry, options) {
3065
3230
  additionalProperties: false
3066
3231
  },
3067
3232
  async execute(input) {
3068
- const scope = input?.scope;
3233
+ const requested = input?.scope;
3234
+ const effective = intersectScope(options.scope, requested);
3069
3235
  const snapshot = registry.snapshot({
3070
3236
  consumer: options.consumer,
3071
- ...scope ? { scope } : options.scope ? { scope: options.scope } : {}
3237
+ ...effective.scope ? { scope: effective.scope } : {},
3238
+ ...options.budget ? { budget: options.budget } : {}
3072
3239
  });
3240
+ const projected = effective.empty ? { ...snapshot, components: [], procedures: [] } : snapshot;
3073
3241
  return {
3074
3242
  status: "ok",
3075
3243
  invocationId: `inv_${randomBase62(12)}`,
3076
3244
  capabilityId: "meta:surface.discover",
3077
- output: JSON.parse(JSON.stringify(snapshot)),
3245
+ output: JSON.parse(JSON.stringify(projected)),
3078
3246
  surfaceVersion: snapshot.surfaceVersion
3079
3247
  };
3080
3248
  }
@@ -3094,10 +3262,12 @@ function createAgentToolset(registry, options) {
3094
3262
  async execute(input, call) {
3095
3263
  const req = input;
3096
3264
  const snapshot = snapshotFor();
3265
+ const registrationId = findRegistrationId(snapshot, req.capabilityId, req.instanceId);
3097
3266
  return invokeThroughSurface(
3098
3267
  {
3099
3268
  capabilityId: req.capabilityId,
3100
- registrationId: findRegistrationId(snapshot, req.capabilityId, req.instanceId) ?? "",
3269
+ // Unresolved let the registry answer (AS-ADAPTER-003).
3270
+ ...registrationId !== void 0 ? { registrationId } : {},
3101
3271
  ...req.instanceId !== void 0 ? { instanceId: req.instanceId } : {},
3102
3272
  surfaceVersion: snapshot.surfaceVersion,
3103
3273
  kind: "observation"
@@ -3109,7 +3279,7 @@ function createAgentToolset(registry, options) {
3109
3279
  },
3110
3280
  {
3111
3281
  name: "surface_act",
3112
- description: "[meta] Invoke an action or procedure by capabilityId.",
3282
+ description: "[meta] Invoke an action or procedure by capabilityId. Echo the surfaceVersion you discovered so a surface that changed underneath a destructive plan is rejected rather than executed.",
3113
3283
  inputSchema: {
3114
3284
  type: "object",
3115
3285
  properties: {
@@ -3117,7 +3287,8 @@ function createAgentToolset(registry, options) {
3117
3287
  instanceId: { type: "string" },
3118
3288
  input: {},
3119
3289
  invocationId: { type: "string" },
3120
- confirmationId: { type: "string" }
3290
+ confirmationId: { type: "string" },
3291
+ surfaceVersion: { type: "string" }
3121
3292
  },
3122
3293
  required: ["capabilityId"],
3123
3294
  additionalProperties: false
@@ -3125,39 +3296,26 @@ function createAgentToolset(registry, options) {
3125
3296
  async execute(input, call) {
3126
3297
  const req = input;
3127
3298
  const snapshot = snapshotFor();
3128
- const invocationId = req.invocationId ?? call.toolCallId ?? `inv_${randomBase62(12)}`;
3129
- let result = await registry.invoke(
3299
+ const registrationId = findRegistrationId(snapshot, req.capabilityId, req.instanceId);
3300
+ return invokeThroughSurface(
3130
3301
  {
3131
- invocationId,
3132
3302
  capabilityId: req.capabilityId,
3303
+ ...registrationId !== void 0 ? { registrationId } : {},
3133
3304
  ...req.instanceId !== void 0 ? { instanceId: req.instanceId } : {},
3134
- ...findRegistrationId(snapshot, req.capabilityId, req.instanceId) ? { registrationId: findRegistrationId(snapshot, req.capabilityId, req.instanceId) } : {},
3135
- surfaceVersion: snapshot.surfaceVersion,
3136
- ...req.input !== void 0 ? { input: req.input } : {},
3137
- ...req.confirmationId !== void 0 ? { confirmationId: req.confirmationId } : {}
3305
+ surfaceVersion: req.surfaceVersion ?? snapshot.surfaceVersion,
3306
+ kind: "action"
3138
3307
  },
3139
- { consumer: options.consumer }
3308
+ req.input,
3309
+ call.toolCallId,
3310
+ {
3311
+ ...req.invocationId !== void 0 ? { invocationId: req.invocationId } : {},
3312
+ ...req.confirmationId !== void 0 ? { confirmationId: req.confirmationId } : {}
3313
+ }
3140
3314
  );
3141
- if (confirmationsMode === "wait" && result.status === "error" && result.error.code === "CONFIRMATION_REQUIRED" && typeof result.error.details?.confirmationId === "string") {
3142
- const confirmationId = result.error.details.confirmationId;
3143
- await waitForConfirmation(confirmationId);
3144
- if (disposed) return result;
3145
- result = await registry.invoke(
3146
- {
3147
- invocationId,
3148
- capabilityId: req.capabilityId,
3149
- ...req.instanceId !== void 0 ? { instanceId: req.instanceId } : {},
3150
- surfaceVersion: snapshot.surfaceVersion,
3151
- ...req.input !== void 0 ? { input: req.input } : {},
3152
- confirmationId
3153
- },
3154
- { consumer: options.consumer }
3155
- );
3156
- }
3157
- return result;
3158
3315
  }
3159
3316
  }
3160
3317
  ];
3318
+ return verbs.map((verb) => ({ ...verb, state: { available: true } }));
3161
3319
  }
3162
3320
  function computeTools() {
3163
3321
  if (mode === "meta") {
@@ -3166,18 +3324,21 @@ function createAgentToolset(registry, options) {
3166
3324
  }
3167
3325
  const version = registry.getVersion();
3168
3326
  if (cachedTools && cachedVersion === version) return cachedTools;
3169
- cachedTools = buildDirectTools();
3327
+ const built = buildDirectTools();
3328
+ cachedTools = built.tools;
3329
+ cachedWireNames = built.wireNames;
3170
3330
  cachedVersion = version;
3171
3331
  return cachedTools;
3172
3332
  }
3173
3333
  function signatureOf(tools) {
3174
3334
  return JSON.stringify(
3175
- tools.map((t) => [t.name, t.description, t.inputSchema])
3335
+ tools.map((t) => [t.name, t.description, t.inputSchema, t.state])
3176
3336
  );
3177
3337
  }
3178
3338
  const unsubscribe = registry.subscribe((event) => {
3179
3339
  if (disposed || event.type !== "surface-changed") return;
3180
3340
  cachedVersion = void 0;
3341
+ if (mode === "meta") return;
3181
3342
  const tools = computeTools();
3182
3343
  const signature = signatureOf(tools);
3183
3344
  if (signature === cachedSignature) return;
@@ -3195,6 +3356,11 @@ function createAgentToolset(registry, options) {
3195
3356
  cachedSignature ??= signatureOf(tools);
3196
3357
  return tools;
3197
3358
  },
3359
+ wireNameMap() {
3360
+ if (mode === "meta") return /* @__PURE__ */ new Map();
3361
+ computeTools();
3362
+ return cachedWireNames;
3363
+ },
3198
3364
  subscribe(listener) {
3199
3365
  listeners.add(listener);
3200
3366
  return () => {
@@ -3210,6 +3376,21 @@ function createAgentToolset(registry, options) {
3210
3376
  }
3211
3377
  };
3212
3378
  }
3379
+ function intersectScope(floor, requested) {
3380
+ const hasFloor = floor !== void 0 && floor.length > 0;
3381
+ if (requested === void 0 || requested.length === 0) {
3382
+ return hasFloor ? { scope: floor, empty: false } : { empty: false };
3383
+ }
3384
+ if (!hasFloor) return { scope: requested, empty: false };
3385
+ const out = /* @__PURE__ */ new Set();
3386
+ for (const f of floor) {
3387
+ for (const r of requested) {
3388
+ if (r === f || r.startsWith(`${f}.`)) out.add(r);
3389
+ else if (f.startsWith(`${r}.`)) out.add(f);
3390
+ }
3391
+ }
3392
+ return out.size > 0 ? { scope: [...out], empty: false } : { empty: true };
3393
+ }
3213
3394
  function findRegistrationId(snapshot, capabilityId, instanceId) {
3214
3395
  const matches = [];
3215
3396
  for (const component of snapshot.components) {
@@ -3235,6 +3416,7 @@ export {
3235
3416
  MAX_ID_LENGTH,
3236
3417
  MAX_WIRE_NAME_LENGTH,
3237
3418
  action,
3419
+ assignWireNames,
3238
3420
  audit,
3239
3421
  authenticated,
3240
3422
  composeInvokeChain,
@@ -3263,6 +3445,7 @@ export {
3263
3445
  parseCapabilityId,
3264
3446
  rateLimit,
3265
3447
  requireConfirmation,
3448
+ stableDescriptionOf,
3266
3449
  tenantBoundary,
3267
3450
  validateComponentDefinition,
3268
3451
  validateJsonSchemaDocument,