c8ctl-plugin-nano 1.51.0 → 1.53.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.
@@ -108,10 +108,18 @@ function negotiatedSupport(remoteAdvertisement) {
108
108
  * producer fences its stale predecessor on the hub's incarnation ring)
109
109
  * @param {{ claimRelease: boolean, steer: boolean }} params.support negotiated capabilities
110
110
  * @param {{ warn?: Function, debug?: Function }} [params.logger]
111
+ * @param {(state: 'connected' | 'disconnected') => void} [params.onConnectionState]
112
+ * optional observer invoked on connection-state transitions: `'connected'`
113
+ * once the transport opens and `'disconnected'` when it drops (fired at most
114
+ * once per drop). A throwing observer is caught and logged, never propagated.
111
115
  * @returns {import('./supervisor.dist.js').RawEmitClient}
112
116
  */
113
- function openHostConnection({ url, transportFactory, incarnation, support, logger }) {
117
+ function openHostConnection({ url, transportFactory, incarnation, support, logger, onConnectionState }) {
114
118
  const log = logger || {};
119
+ const notifyState = (state) => {
120
+ if (typeof onConnectionState !== 'function') return;
121
+ try { onConnectionState(state); } catch (err) { log.debug?.(`agentic onConnectionState threw — ${err?.message || err}`); }
122
+ };
115
123
  const openCbs = [];
116
124
  const closeCbs = [];
117
125
  let steerRoute = null;
@@ -139,7 +147,13 @@ function openHostConnection({ url, transportFactory, incarnation, support, logge
139
147
  closedFired = true;
140
148
  open = false;
141
149
  hasClosed = true;
142
- for (const cb of closeCbs) cb();
150
+ // Guard each subscriber: a throwing onClose callback must not abort the loop
151
+ // or prevent notifyState('disconnected') from firing (which would strand the
152
+ // connection-state observer and could crash the worker/supervisor on a drop).
153
+ for (const cb of closeCbs) {
154
+ try { cb(); } catch (err) { log.debug?.(`agentic onClose subscriber threw — ${err?.message || err}`); }
155
+ }
156
+ notifyState('disconnected');
143
157
  };
144
158
 
145
159
  const send = (lane, family, payload) => {
@@ -186,7 +200,12 @@ function openHostConnection({ url, transportFactory, incarnation, support, logge
186
200
  transport = transportFactory(url, {
187
201
  onOpen() {
188
202
  open = true;
189
- for (const cb of openCbs) cb();
203
+ // Guard each subscriber: a throwing onOpen callback must not abort the loop
204
+ // or prevent notifyState('connected') from firing.
205
+ for (const cb of openCbs) {
206
+ try { cb(); } catch (err) { log.debug?.(`agentic onOpen subscriber threw — ${err?.message || err}`); }
207
+ }
208
+ notifyState('connected');
190
209
  },
191
210
  onFrame(bytes) {
192
211
  handleInbound(bytes);
@@ -293,11 +312,14 @@ function openHostConnection({ url, transportFactory, incarnation, support, logge
293
312
  * the peer's advertised support; omit to assume full support (see {@link negotiatedSupport})
294
313
  * @param {number} [opts.incarnationBase] first transcript incarnation (default `Date.now()`)
295
314
  * @param {import('@nanobpm/urban-agent-client').TransportFactory} [opts.transportFactory] injectable transport (tests)
315
+ * @param {(state: 'connected'|'disconnected') => void} [opts.onConnectionState] observer fired
316
+ * when a connection opens/drops, so a caller can track the single host connection's
317
+ * liveness (e.g. the supervisor activity marker's agentic status)
296
318
  * @param {{ warn?: Function, debug?: Function }} [opts.logger]
297
319
  * @returns {Promise<() => import('./supervisor.dist.js').RawEmitClient>} a synchronous `connect` factory
298
320
  */
299
321
  export async function createRawEmitConnect(opts) {
300
- const { url, token, credential, remoteAdvertisement, incarnationBase, transportFactory, logger } = opts || {};
322
+ const { url, token, credential, remoteAdvertisement, incarnationBase, transportFactory, logger, onConnectionState } = opts || {};
301
323
  if (typeof url !== 'string' || url.trim() === '') {
302
324
  throw new Error('createRawEmitConnect requires an agentic channel base url');
303
325
  }
@@ -318,6 +340,7 @@ export async function createRawEmitConnect(opts) {
318
340
  incarnation: generation++,
319
341
  support,
320
342
  logger,
343
+ onConnectionState,
321
344
  });
322
345
  }
323
346