@drisp/cli 0.5.11 → 0.5.13

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.
@@ -2039,6 +2039,39 @@ function instantiateAdapter(sidecar) {
2039
2039
  return { ok: true, adapter: module.create(parsed.config, sidecar.instanceId) };
2040
2040
  }
2041
2041
 
2042
+ // src/gateway/channelReconcilePlan.ts
2043
+ function planChannelReconciliation(input) {
2044
+ const actions = [];
2045
+ for (const error2 of input.loadErrors) {
2046
+ actions.push({
2047
+ kind: "load-error",
2048
+ id: pathIdFromSidecarPath(error2.path),
2049
+ path: error2.path,
2050
+ reason: error2.reason
2051
+ });
2052
+ }
2053
+ if (input.unregisterStale) {
2054
+ const desiredIds = new Set(
2055
+ input.desired.map((sidecar) => sidecar.instanceId)
2056
+ );
2057
+ for (const id of input.currentChannelIds) {
2058
+ if (desiredIds.has(id)) continue;
2059
+ actions.push({ kind: "unregister-stale", id });
2060
+ }
2061
+ }
2062
+ const currentIds = new Set(input.currentChannelIds);
2063
+ for (const sidecar of input.desired) {
2064
+ actions.push(
2065
+ currentIds.has(sidecar.instanceId) ? { kind: "replace", sidecar } : { kind: "register", sidecar }
2066
+ );
2067
+ }
2068
+ return { actions };
2069
+ }
2070
+ function pathIdFromSidecarPath(filePath) {
2071
+ const base = filePath.split(/[\\/]/).pop() ?? filePath;
2072
+ return base.endsWith(".json") ? base.slice(0, -".json".length) : base;
2073
+ }
2074
+
2042
2075
  // src/gateway/channelSidecarReconciler.ts
2043
2076
  var ChannelSidecarReconciler = class {
2044
2077
  channelManager;
@@ -2056,124 +2089,120 @@ var ChannelSidecarReconciler = class {
2056
2089
  this.stderr = opts.stderr ?? ((message) => process.stderr.write(message));
2057
2090
  }
2058
2091
  async reconcile(opts) {
2059
- const results = [];
2060
2092
  const { sidecars, errors } = this.loadSidecars(this.home);
2061
- for (const err of errors) {
2062
- const id = pathIdFromSidecarPath(err.path);
2063
- results.push({
2064
- id,
2065
- ok: false,
2066
- action: "failed",
2067
- reason: err.reason
2068
- });
2069
- if (opts.logFailures) {
2070
- this.stderr(`athena-gateway: skipping ${err.path}: ${err.reason}
2071
- `);
2072
- }
2073
- }
2074
- if (opts.unregisterStale) {
2075
- const sidecarIds = new Set(sidecars.map((sidecar) => sidecar.instanceId));
2076
- for (const channel of this.channelManager.listChannels()) {
2077
- if (sidecarIds.has(channel.id)) continue;
2078
- try {
2079
- await this.channelManager.unregister(channel.id, "shutdown");
2080
- results.push({
2081
- id: channel.id,
2082
- ok: true,
2083
- action: "unregistered"
2084
- });
2085
- } catch (err) {
2086
- const reason = errorReason(err);
2087
- results.push({
2088
- id: channel.id,
2093
+ const plan = planChannelReconciliation({
2094
+ desired: sidecars,
2095
+ currentChannelIds: this.channelManager.listChannels().map((channel) => channel.id),
2096
+ loadErrors: errors,
2097
+ unregisterStale: opts.unregisterStale
2098
+ });
2099
+ const outcomes = [];
2100
+ for (const action of plan.actions) {
2101
+ outcomes.push(await this.executeAction(action));
2102
+ }
2103
+ for (const outcome of outcomes) {
2104
+ if (!outcome.log) continue;
2105
+ const enabled = outcome.log.stream === "err" ? opts.logFailures : opts.logRegistrations;
2106
+ if (!enabled) continue;
2107
+ const write = outcome.log.stream === "err" ? this.stderr : this.stdout;
2108
+ write(outcome.log.message);
2109
+ }
2110
+ return { results: outcomes.map((outcome) => outcome.result) };
2111
+ }
2112
+ async executeAction(action) {
2113
+ switch (action.kind) {
2114
+ case "load-error":
2115
+ return {
2116
+ result: {
2117
+ id: action.id,
2089
2118
  ok: false,
2090
2119
  action: "failed",
2091
- reason
2092
- });
2093
- if (opts.logFailures) {
2094
- this.stderr(
2095
- `athena-gateway: unregister ${channel.id} failed: ${reason}
2120
+ reason: action.reason
2121
+ },
2122
+ log: {
2123
+ stream: "err",
2124
+ message: `athena-gateway: skipping ${action.path}: ${action.reason}
2096
2125
  `
2097
- );
2098
2126
  }
2127
+ };
2128
+ case "unregister-stale":
2129
+ return this.executeUnregisterStale(action.id);
2130
+ case "replace":
2131
+ return this.executeApply(action.sidecar, true);
2132
+ case "register":
2133
+ return this.executeApply(action.sidecar, false);
2134
+ }
2135
+ }
2136
+ async executeUnregisterStale(id) {
2137
+ try {
2138
+ await this.channelManager.unregister(id, "shutdown");
2139
+ return { result: { id, ok: true, action: "unregistered" } };
2140
+ } catch (err) {
2141
+ const reason = errorReason(err);
2142
+ return {
2143
+ result: { id, ok: false, action: "failed", reason },
2144
+ log: {
2145
+ stream: "err",
2146
+ message: `athena-gateway: unregister ${id} failed: ${reason}
2147
+ `
2099
2148
  }
2100
- }
2149
+ };
2101
2150
  }
2102
- for (const sidecar of sidecars) {
2103
- const existed = this.channelManager.listChannels().some((channel) => channel.id === sidecar.instanceId);
2104
- if (existed) {
2105
- try {
2106
- await this.channelManager.unregister(sidecar.instanceId, "shutdown");
2107
- } catch (err) {
2108
- const reason = errorReason(err);
2109
- results.push({
2110
- id: sidecar.instanceId,
2111
- ok: false,
2112
- action: "failed",
2113
- reason
2114
- });
2115
- if (opts.logFailures) {
2116
- this.stderr(
2117
- `athena-gateway: unregister ${sidecar.instanceId} failed: ${reason}
2151
+ }
2152
+ async executeApply(sidecar, existed) {
2153
+ const id = sidecar.instanceId;
2154
+ if (existed) {
2155
+ try {
2156
+ await this.channelManager.unregister(id, "shutdown");
2157
+ } catch (err) {
2158
+ const reason = errorReason(err);
2159
+ return {
2160
+ result: { id, ok: false, action: "failed", reason },
2161
+ log: {
2162
+ stream: "err",
2163
+ message: `athena-gateway: unregister ${id} failed: ${reason}
2118
2164
  `
2119
- );
2120
2165
  }
2121
- continue;
2122
- }
2166
+ };
2123
2167
  }
2124
- const built = this.instantiateAdapter(sidecar);
2125
- if (!built.ok) {
2126
- results.push({
2127
- id: sidecar.instanceId,
2128
- ok: false,
2129
- action: "failed",
2130
- reason: built.reason
2131
- });
2132
- if (opts.logFailures) {
2133
- this.stderr(
2134
- `athena-gateway: ${sidecar.instanceId}: ${built.reason}
2168
+ }
2169
+ const built = this.instantiateAdapter(sidecar);
2170
+ if (!built.ok) {
2171
+ return {
2172
+ result: { id, ok: false, action: "failed", reason: built.reason },
2173
+ log: {
2174
+ stream: "err",
2175
+ message: `athena-gateway: ${id}: ${built.reason}
2135
2176
  `
2136
- );
2137
2177
  }
2138
- continue;
2139
- }
2140
- try {
2141
- await this.channelManager.register(
2142
- built.adapter,
2143
- sidecar.attachmentId !== void 0 ? { attachmentId: sidecar.attachmentId } : {}
2144
- );
2145
- results.push({
2146
- id: sidecar.instanceId,
2147
- ok: true,
2148
- action: existed ? "replaced" : "registered"
2149
- });
2150
- if (opts.logRegistrations) {
2151
- this.stdout(`athena-gateway: registered ${sidecar.instanceId}
2152
- `);
2178
+ };
2179
+ }
2180
+ try {
2181
+ await this.channelManager.register(
2182
+ built.adapter,
2183
+ sidecar.attachmentId !== void 0 ? { attachmentId: sidecar.attachmentId } : {}
2184
+ );
2185
+ return {
2186
+ result: { id, ok: true, action: existed ? "replaced" : "registered" },
2187
+ log: {
2188
+ stream: "out",
2189
+ message: `athena-gateway: registered ${id}
2190
+ `
2153
2191
  }
2154
- } catch (err) {
2155
- const reason = errorReason(err);
2156
- results.push({
2157
- id: sidecar.instanceId,
2158
- ok: false,
2159
- action: "failed",
2160
- reason
2161
- });
2162
- if (opts.logFailures) {
2163
- this.stderr(
2164
- `athena-gateway: register ${sidecar.instanceId} failed: ${reason}
2192
+ };
2193
+ } catch (err) {
2194
+ const reason = errorReason(err);
2195
+ return {
2196
+ result: { id, ok: false, action: "failed", reason },
2197
+ log: {
2198
+ stream: "err",
2199
+ message: `athena-gateway: register ${id} failed: ${reason}
2165
2200
  `
2166
- );
2167
2201
  }
2168
- }
2202
+ };
2169
2203
  }
2170
- return { results };
2171
2204
  }
2172
2205
  };
2173
- function pathIdFromSidecarPath(filePath) {
2174
- const base = filePath.split(/[\\/]/).pop() ?? filePath;
2175
- return base.endsWith(".json") ? base.slice(0, -".json".length) : base;
2176
- }
2177
2206
  function errorReason(err) {
2178
2207
  return err instanceof Error ? err.message : String(err);
2179
2208
  }
@@ -2248,7 +2277,14 @@ var RuntimeBindingStore = class {
2248
2277
  epoch,
2249
2278
  ...maybeLastRebindAt(lastRebindAt)
2250
2279
  };
2251
- const slot = existing ? { ...existing, runtime, binding: newBinding } : { runtime, binding: newBinding, staleTimer: null, staleSince: null };
2280
+ const push = input.push ?? existing?.push ?? null;
2281
+ const slot = existing ? { ...existing, runtime, binding: newBinding, push } : {
2282
+ runtime,
2283
+ binding: newBinding,
2284
+ push,
2285
+ staleTimer: null,
2286
+ staleSince: null
2287
+ };
2252
2288
  this.clearStaleTimerForSlot(slot);
2253
2289
  this.slots.set(key, slot);
2254
2290
  if (wasStale && staleSince !== null) {
@@ -2271,9 +2307,10 @@ var RuntimeBindingStore = class {
2271
2307
  this.observers.onRuntimeConnectionLost?.({ runtimeId, graceful: true });
2272
2308
  }
2273
2309
  /**
2274
- * Called when the transport connection closes.
2275
- * Returns the runtimeId if the close was for a current binding (caller should
2276
- * clear the push handle); returns null if the connectionId was not recognised.
2310
+ * Called when the transport connection closes. Drops the slot's push handle
2311
+ * (immediately deleting the slot when no grace period is configured, otherwise
2312
+ * marking it stale). Returns the runtimeId if the close matched a current
2313
+ * binding; returns null if the connectionId was not recognised.
2277
2314
  */
2278
2315
  notifyConnectionClosed(connectionId) {
2279
2316
  const entry = this.findSlotByConnectionId(connectionId);
@@ -2294,6 +2331,7 @@ var RuntimeBindingStore = class {
2294
2331
  this.observers.onRuntimeConnectionLost?.({ runtimeId, graceful: false });
2295
2332
  return runtimeId;
2296
2333
  }
2334
+ slot.push = null;
2297
2335
  slot.staleSince = now;
2298
2336
  slot.staleTimer = setTimeout(() => {
2299
2337
  this.expireStaleBinding(key, runtimeId);
@@ -2328,6 +2366,28 @@ var RuntimeBindingStore = class {
2328
2366
  const entry = this.findSlotByConnectionId(connectionId);
2329
2367
  return entry ? entry.slot.runtime.runtimeId : null;
2330
2368
  }
2369
+ /**
2370
+ * Deliver a control-push envelope to the runtime occupying `key`. Returns
2371
+ * false (and pushes nothing) when the slot is empty or its connection has
2372
+ * already been lost.
2373
+ */
2374
+ pushTo(key, env) {
2375
+ const slot = this.slots.get(key);
2376
+ if (!slot || !slot.push) return false;
2377
+ slot.push(env);
2378
+ return true;
2379
+ }
2380
+ /**
2381
+ * Reportable state for every slot — both attachment-keyed and the legacy
2382
+ * fallback — so status reporting sees all registered runtimes.
2383
+ */
2384
+ snapshot() {
2385
+ const entries = [];
2386
+ for (const [key, slot] of this.slots) {
2387
+ entries.push({ key, runtime: slot.runtime, binding: slot.binding });
2388
+ }
2389
+ return entries;
2390
+ }
2331
2391
  /**
2332
2392
  * Returns the attachment slot key (or `undefined` for the legacy slot) that
2333
2393
  * holds the given runtime, or `null` if no slot does. Lets callers
@@ -2380,7 +2440,7 @@ var cachedVersion = null;
2380
2440
  function readVersion() {
2381
2441
  if (cachedVersion !== null) return cachedVersion;
2382
2442
  try {
2383
- const injected = "0.5.11";
2443
+ const injected = "0.5.13";
2384
2444
  if (typeof injected === "string" && injected.length > 0) {
2385
2445
  cachedVersion = injected;
2386
2446
  return cachedVersion;
@@ -2655,29 +2715,25 @@ function createDispatcher(deps) {
2655
2715
  return handle;
2656
2716
  }
2657
2717
  function runtimeStatusEntries(pipeline) {
2658
- const runtime = pipeline?.getCurrentRuntime();
2659
- if (!runtime || !pipeline) return [];
2660
- const binding = pipeline.getBinding();
2661
- return [
2662
- {
2663
- runtimeId: runtime.runtimeId,
2664
- defaultAgentId: runtime.defaultAgentId,
2665
- pid: runtime.pid,
2666
- registeredAt: runtime.registeredAt,
2667
- binding: binding?.state === "active" ? {
2668
- state: "active",
2669
- boundAt: binding.boundAt,
2670
- epoch: binding.epoch,
2671
- ...maybeLastRebindAt(binding.lastRebindAt)
2672
- } : binding?.state === "stale" ? {
2673
- state: "stale",
2674
- staleSince: binding.staleSince,
2675
- epoch: binding.epoch,
2676
- ...maybeLastRebindAt(binding.lastRebindAt)
2677
- } : { state: "none" },
2678
- pendingDispatchCount: pipeline.pendingDispatchCount()
2679
- }
2680
- ];
2718
+ if (!pipeline) return [];
2719
+ return pipeline.snapshotRuntimes().map(({ runtime, binding }) => ({
2720
+ runtimeId: runtime.runtimeId,
2721
+ defaultAgentId: runtime.defaultAgentId,
2722
+ pid: runtime.pid,
2723
+ registeredAt: runtime.registeredAt,
2724
+ binding: binding?.state === "active" ? {
2725
+ state: "active",
2726
+ boundAt: binding.boundAt,
2727
+ epoch: binding.epoch,
2728
+ ...maybeLastRebindAt(binding.lastRebindAt)
2729
+ } : binding?.state === "stale" ? {
2730
+ state: "stale",
2731
+ staleSince: binding.staleSince,
2732
+ epoch: binding.epoch,
2733
+ ...maybeLastRebindAt(binding.lastRebindAt)
2734
+ } : { state: "none" },
2735
+ pendingDispatchCount: pipeline.pendingDispatchCountFor(runtime.runtimeId)
2736
+ }));
2681
2737
  }
2682
2738
  function ok(envelope, ts, payload) {
2683
2739
  return { request_id: envelope.request_id, ts, ok: true, payload };
@@ -2959,13 +3015,6 @@ function deriveSessionKey(loc) {
2959
3015
 
2960
3016
  // src/gateway/sessionRegistry.ts
2961
3017
  import { randomUUID } from "crypto";
2962
- var UnknownDispatchError = class extends Error {
2963
- code = "unknown_dispatch";
2964
- constructor(id) {
2965
- super(`unknown dispatchId: ${id}`);
2966
- this.name = "UnknownDispatchError";
2967
- }
2968
- };
2969
3018
  var SessionRegistry = class {
2970
3019
  dispatches = /* @__PURE__ */ new Map();
2971
3020
  idFactory;
@@ -2980,25 +3029,54 @@ var SessionRegistry = class {
2980
3029
  dispatchId,
2981
3030
  sessionKey: input.sessionKey,
2982
3031
  agentId: input.agentId,
3032
+ runtimeId: input.runtimeId,
3033
+ ...input.attachmentKey !== void 0 ? { attachmentKey: input.attachmentKey } : {},
2983
3034
  location: input.location,
2984
3035
  createdAt: this.now()
2985
3036
  };
2986
3037
  this.dispatches.set(dispatchId, entry);
2987
3038
  return entry;
2988
3039
  }
2989
- completeDispatch(dispatchId) {
3040
+ /**
3041
+ * Resolve a parked turn for the runtime claiming to complete it. The entry is
3042
+ * consumed only when the claiming runtime matches the one the turn was
3043
+ * dispatched to — a mismatched runtime cannot cancel or steal another
3044
+ * runtime's turn, and an unknown id is reported rather than thrown.
3045
+ */
3046
+ completeDispatch(dispatchId, by) {
2990
3047
  const entry = this.dispatches.get(dispatchId);
2991
3048
  if (!entry) {
2992
- throw new UnknownDispatchError(dispatchId);
3049
+ return { kind: "unknown" };
3050
+ }
3051
+ if (entry.runtimeId !== by.runtimeId) {
3052
+ return { kind: "runtime_mismatch", entry };
2993
3053
  }
2994
3054
  this.dispatches.delete(dispatchId);
2995
- return entry;
3055
+ return { kind: "completed", entry };
2996
3056
  }
2997
3057
  pendingDispatchCount() {
2998
3058
  return this.dispatches.size;
2999
3059
  }
3000
- clearDispatches() {
3001
- this.dispatches.clear();
3060
+ /** Number of parked turns owned by the given runtime. */
3061
+ pendingDispatchCountFor(runtimeId) {
3062
+ let count = 0;
3063
+ for (const entry of this.dispatches.values()) {
3064
+ if (entry.runtimeId === runtimeId) count += 1;
3065
+ }
3066
+ return count;
3067
+ }
3068
+ /**
3069
+ * Remove only the parked turns owned by the given runtime, leaving every other
3070
+ * runtime's in-flight dispatches intact. Used when a single Registered runtime
3071
+ * unregisters or its connection is lost — clearing the correct slot's turns
3072
+ * without a global wipe.
3073
+ */
3074
+ clearDispatchesFor(runtimeId) {
3075
+ for (const [dispatchId, entry] of this.dispatches) {
3076
+ if (entry.runtimeId === runtimeId) {
3077
+ this.dispatches.delete(dispatchId);
3078
+ }
3079
+ }
3002
3080
  }
3003
3081
  };
3004
3082
 
@@ -3125,8 +3203,6 @@ var DispatchPipeline = class {
3125
3203
  log;
3126
3204
  now;
3127
3205
  idFactory;
3128
- pushes = /* @__PURE__ */ new Map();
3129
- connectionToKey = /* @__PURE__ */ new Map();
3130
3206
  constructor(opts) {
3131
3207
  this.bindingStore = new RuntimeBindingStore({
3132
3208
  gracePeriodMs: opts.gracePeriodMs,
@@ -3207,6 +3283,8 @@ var DispatchPipeline = class {
3207
3283
  const entry = this.registry.beginDispatch({
3208
3284
  sessionKey,
3209
3285
  agentId,
3286
+ runtimeId: current.runtimeId,
3287
+ ...key !== void 0 ? { attachmentKey: key } : {},
3210
3288
  location: inbound.location
3211
3289
  });
3212
3290
  this.pushDispatch(key, {
@@ -3218,9 +3296,7 @@ var DispatchPipeline = class {
3218
3296
  return { kind: "dispatched", dispatchId: entry.dispatchId, sessionKey };
3219
3297
  }
3220
3298
  pushDispatch(key, payload) {
3221
- const handle = this.pushes.get(key);
3222
- if (!handle) return;
3223
- handle.push({
3299
+ this.bindingStore.pushTo(key, {
3224
3300
  push_id: this.idFactory(),
3225
3301
  ts: this.now(),
3226
3302
  kind: "session.dispatch.turn",
@@ -3235,17 +3311,9 @@ var DispatchPipeline = class {
3235
3311
  defaultAgentId: input.defaultAgentId,
3236
3312
  pid: input.pid,
3237
3313
  connectionId: input.connectionId,
3314
+ push: input.push,
3238
3315
  ...input.attachmentId !== void 0 ? { attachmentId: input.attachmentId } : {}
3239
3316
  });
3240
- const previous = this.pushes.get(key);
3241
- if (previous && previous.connectionId !== input.connectionId) {
3242
- this.connectionToKey.delete(previous.connectionId);
3243
- }
3244
- this.pushes.set(key, {
3245
- connectionId: input.connectionId,
3246
- push: input.push
3247
- });
3248
- this.connectionToKey.set(input.connectionId, key);
3249
3317
  writeGatewayTrace(
3250
3318
  `pipeline registered runtime runtimeId=${input.runtimeId} connectionId=${input.connectionId}`
3251
3319
  );
@@ -3253,27 +3321,16 @@ var DispatchPipeline = class {
3253
3321
  return { registeredAt: result.registeredAt };
3254
3322
  }
3255
3323
  unregisterRuntime(runtimeId) {
3256
- const slot = this.findSlotByRuntimeId(runtimeId);
3257
3324
  this.bindingStore.unbind(runtimeId);
3258
- this.registry.clearDispatches();
3259
- if (slot) {
3260
- const handle = this.pushes.get(slot.key);
3261
- this.pushes.delete(slot.key);
3262
- if (handle) this.connectionToKey.delete(handle.connectionId);
3263
- }
3325
+ this.registry.clearDispatchesFor(runtimeId);
3264
3326
  writeGatewayTrace(`pipeline unregistered runtime runtimeId=${runtimeId}`);
3265
3327
  }
3266
3328
  notifyConnectionClosed(connectionId) {
3267
- const key = this.connectionToKey.get(connectionId);
3268
3329
  const runtimeId = this.bindingStore.notifyConnectionClosed(connectionId);
3269
3330
  if (runtimeId === null) return;
3270
3331
  writeGatewayTrace(
3271
3332
  `pipeline runtime connection closed runtimeId=${runtimeId} connectionId=${connectionId}`
3272
3333
  );
3273
- if (key !== void 0 || this.pushes.has(key)) {
3274
- this.pushes.delete(key);
3275
- this.connectionToKey.delete(connectionId);
3276
- }
3277
3334
  }
3278
3335
  /**
3279
3336
  * Streaming run-event from a registered runtime to its outbound channel
@@ -3313,32 +3370,35 @@ var DispatchPipeline = class {
3313
3370
  if (!slot) {
3314
3371
  throw new Error("runtime mismatch on session.turn.complete");
3315
3372
  }
3316
- let entry;
3317
- try {
3318
- entry = this.registry.completeDispatch(payload.dispatchId);
3319
- } catch (err) {
3320
- if (err instanceof UnknownDispatchError) {
3321
- writeGatewayTrace(
3322
- `pipeline turn.complete unknown dispatchId=${payload.dispatchId}`
3323
- );
3324
- return { delivered: false };
3325
- }
3326
- throw err;
3373
+ const completion = this.registry.completeDispatch(payload.dispatchId, {
3374
+ runtimeId: payload.runtimeId
3375
+ });
3376
+ if (completion.kind === "unknown") {
3377
+ writeGatewayTrace(
3378
+ `pipeline turn.complete unknown dispatchId=${payload.dispatchId}`
3379
+ );
3380
+ return { delivered: false };
3381
+ }
3382
+ if (completion.kind === "runtime_mismatch") {
3383
+ writeGatewayTrace(
3384
+ `pipeline turn.complete runtime mismatch dispatchId=${payload.dispatchId} authorized=${completion.entry.runtimeId} claimed=${payload.runtimeId}`
3385
+ );
3386
+ return { delivered: false };
3327
3387
  }
3328
- const result = await this.sendOutbound(entry.location, payload);
3388
+ const result = await this.sendOutbound(completion.entry.location, payload);
3329
3389
  writeGatewayTrace(
3330
3390
  `pipeline sendOutbound delivered dispatchId=${payload.dispatchId} providerMessageId=${result.providerMessageId}`
3331
3391
  );
3332
3392
  return { delivered: true, providerMessageId: result.providerMessageId };
3333
3393
  }
3334
- async sendOutbound(_parkedLocation, payload) {
3394
+ async sendOutbound(parkedLocation, payload) {
3335
3395
  const out = {
3336
- location: payload.location,
3396
+ location: parkedLocation,
3337
3397
  text: payload.text,
3338
3398
  idempotencyKey: payload.idempotencyKey
3339
3399
  };
3340
3400
  const result = await this.outboundDispatcher.dispatch(
3341
- payload.location.channelId,
3401
+ parkedLocation.channelId,
3342
3402
  out
3343
3403
  );
3344
3404
  if (result.kind === "sent") return result.result;
@@ -3373,6 +3433,10 @@ var DispatchPipeline = class {
3373
3433
  getCurrentRuntime() {
3374
3434
  return this.bindingStore.getCurrent();
3375
3435
  }
3436
+ /** Reportable state for every registered-runtime slot (legacy + attachment-keyed). */
3437
+ snapshotRuntimes() {
3438
+ return this.bindingStore.snapshot();
3439
+ }
3376
3440
  getCurrentRuntimeByAttachment(attachmentId) {
3377
3441
  return this.bindingStore.getCurrentByAttachment(attachmentId);
3378
3442
  }
@@ -3388,6 +3452,9 @@ var DispatchPipeline = class {
3388
3452
  pendingDispatchCount() {
3389
3453
  return this.registry.pendingDispatchCount();
3390
3454
  }
3455
+ pendingDispatchCountFor(runtimeId) {
3456
+ return this.registry.pendingDispatchCountFor(runtimeId);
3457
+ }
3391
3458
  pendingInboundCount() {
3392
3459
  return this.inboundQueue.size();
3393
3460
  }