@camstack/server 1.2.566 → 1.2.568

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.
@@ -10,7 +10,8 @@ exports.createAgentNodeCertificates = createAgentNodeCertificates;
10
10
  * leaves it) and keeps it in memory only. When the signed leaf arrives it is persisted
11
11
  * into `agent.json` (0600) BESIDE the old one (`meshTlsPrevious`) together with the
12
12
  * swap's state (`meshSwap`: which leaf, until when), then the agent reconnects through
13
- * the same `reconnect()` the UI uses, which rebuilds the broker from the file.
13
+ * the same `reconnect()` the UI uses, which replaces the broker transport from the file
14
+ * (the broker, its services and its child runners stay).
14
15
  *
15
16
  * A swap is PENDING from install until the hub confirms. Only while it is pending may
16
17
  * the node go back, and only to a previous leaf that is still unexpired:
@@ -213,7 +213,7 @@ async function startAgent(configPath) {
213
213
  // The HTTP/HTTPS listener on the agent port (D856); set once it is up.
214
214
  let agentHttpListener = null;
215
215
  console.log(`[Agent] Mesh transport: ${currentMeshTls !== null ? 'tls' : 'clear'}`);
216
- let broker = (0, system_1.createBroker)({
216
+ const broker = (0, system_1.createBroker)({
217
217
  nodeID: config.nodeId,
218
218
  mode: 'agent',
219
219
  hubAddress: config.hubAddress,
@@ -223,32 +223,40 @@ async function startAgent(configPath) {
223
223
  });
224
224
  meshJoin.attach(broker);
225
225
  /**
226
- * Reconnect: stop the current broker, reload config from file,
227
- * create a new broker with the updated hub/secret, and restart.
228
- * The HTTP server stays alive throughout.
226
+ * Reconnect: reload the config from file and replace the broker's TRANSPORT (the new hub
227
+ * address, secret or mesh leaf) IN PLACE. The broker itself is never rebuilt: its services
228
+ * (`$agent`, `$process`, `$agent-cap-fwd`, the event bus, the addon services), its registries and
229
+ * the forked child runners all outlive the wire. Rebuilding the broker stopped `$process`, which
230
+ * SIGTERMed every runner, and left the node capless with no `$agent-cap-fwd` (D856 amendment,
231
+ * 2026-10-15 incident). The HTTP server stays alive throughout.
229
232
  */
230
233
  // ONE reconnect at a time (serial-queue.ts): the UI, a mesh leaf swap and its rollback can
231
- // all ask for one, and two interleaved stop/rebuild cycles would leave two brokers or none.
234
+ // all ask for one, and two interleaved disconnect/connect cycles would leave two wires or none.
232
235
  const reconnect = (0, serial_queue_js_1.createSerialQueue)(() => reconnectNow(), {
233
236
  deadlineMs: RECONNECT_DEADLINE_MS,
234
237
  onTimeout: () => {
235
- // A broker whose start() never settles must not hold every later reconnect (including
236
- // the rollback of a failed leaf swap): tear down what was half-built and move on.
237
- consoleLogger.error('Reconnect did not finish in time - destroying the half-built broker', {
238
+ // A transport whose connect never settles must not hold every later reconnect (including
239
+ // the rollback of a failed leaf swap): drop the wire and move on. The broker, and the
240
+ // runners behind it, stay.
241
+ consoleLogger.error('Reconnect did not finish in time - dropping the half-connected transport', {
238
242
  meta: { deadlineMs: RECONNECT_DEADLINE_MS },
239
243
  });
240
- void broker.stop().catch(() => undefined);
244
+ void (0, system_1.disconnectBrokerTransport)(broker).catch(() => undefined);
241
245
  },
242
246
  });
243
247
  // The cycle itself runs through a generation guard: a run the queue abandoned at its deadline
244
- // that resumes later builds nothing (agent-reconnect.ts), so two brokers never share a node id.
248
+ // that resumes later installs nothing (agent-reconnect.ts), so two transports never share a broker.
245
249
  let pendingConfig = null;
246
250
  const reconnectNow = (0, agent_reconnect_js_1.createReconnectRunner)({
247
- current: () => broker,
248
- assign: (next) => {
249
- broker = next;
251
+ current: () => (0, system_1.currentBrokerTransporter)(broker),
252
+ assign: (next) => (0, system_1.installBrokerTransporter)(broker, next),
253
+ stop: async (target) => {
254
+ // Only the transport installed right now: a late, abandoned run must not tear down a
255
+ // newer run's transport.
256
+ if ((0, system_1.isInstalledTransporter)(broker, target)) {
257
+ await (0, system_1.disconnectBrokerTransport)(broker);
258
+ }
250
259
  },
251
- stop: (target) => target.stop(),
252
260
  prepare: () => {
253
261
  console.log('[Agent] Reconnecting with updated config...');
254
262
  // Reload config from file (UI may have written new hubAddress/secret)
@@ -274,24 +282,21 @@ async function startAgent(configPath) {
274
282
  const fresh = pendingConfig;
275
283
  if (fresh === null)
276
284
  throw new Error('reconnect: no configuration was prepared');
277
- const next = (0, system_1.createBroker)({
285
+ return (0, system_1.buildTransporter)((0, system_1.createAgentTransport)({
278
286
  nodeID: config.nodeId,
279
287
  mode: 'agent',
280
288
  hubAddress: fresh.hubAddress,
281
289
  logLevel: fresh.logLevel,
282
- middlewares: meshJoin.middlewares,
283
290
  ...(currentMeshTls !== null ? { meshTls: currentMeshTls } : {}),
284
- });
285
- meshJoin.attach(next);
286
- return next;
291
+ }));
287
292
  },
288
- // The new broker's hub connection runs a join, and its settle re-sends
289
- // the manifest (`registration.onJoinSettled`).
290
- start: async (next) => {
291
- await next.start();
293
+ // The new wire's hub connection runs a join, and its settle re-sends the manifest
294
+ // (`registration.onJoinSettled`): the subtree union, every child included (D3).
295
+ start: async () => {
296
+ await (0, system_1.connectBrokerTransport)(broker);
292
297
  console.log('[Agent] Reconnected successfully');
293
298
  },
294
- onSuperseded: (stage) => consoleLogger.warn('A superseded reconnect stopped without building a second broker', {
299
+ onSuperseded: (stage) => consoleLogger.warn('A superseded reconnect stopped without installing a second transport', {
295
300
  meta: { stage },
296
301
  }),
297
302
  });
@@ -461,6 +461,7 @@ function createCertificatesRouter(deps) {
461
461
  crl: crl(),
462
462
  peers,
463
463
  registrations,
464
+ ...(deps.nodeAddonIds !== undefined ? { expectedAddons: deps.nodeAddonIds } : {}),
464
465
  log: deps.log,
465
466
  ...(deps.reconnectTimeoutMs !== undefined
466
467
  ? { reconnectTimeoutMs: deps.reconnectTimeoutMs }
@@ -2,7 +2,7 @@
2
2
  // AUTO-GENERATED by scripts/generate-cap-mounts.ts — DO NOT EDIT
3
3
  // Re-run: npx tsx scripts/generate-cap-mounts.ts
4
4
  //
5
- // Mounted: 169 Skipped (legacy): 3
5
+ // Mounted: 170 Skipped (legacy): 3
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.LEGACY_SHAPE_SKIP = void 0;
8
8
  exports.mountAllCaps = mountAllCaps;
@@ -208,6 +208,7 @@ function mountAllCaps(services) {
208
208
  };
209
209
  }, remoteCapProxy),
210
210
  deviceExtension: (0, generated_cap_routers_js_1.createCapRouter_deviceExtension)((_ctx) => (0, device_collection_dispatch_js_1.requireDeviceCollection)(reg, 'device-extension'), remoteCapProxy),
211
+ deviceHistory: (0, generated_cap_routers_js_1.createCapRouter_deviceHistory)((_ctx) => reg?.getSingleton('device-history') ?? null, remoteCapProxy),
211
212
  deviceManager: (0, generated_cap_routers_js_1.createCapRouter_deviceManager)((_ctx) => reg?.getSingleton('device-manager') ?? null, remoteCapProxy),
212
213
  deviceOps: (0, generated_cap_routers_js_1.createCapRouter_deviceOps)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'device-ops'), remoteCapProxy),
213
214
  deviceProvider: (0, generated_cap_routers_js_1.createCapRouter_deviceProvider)((_ctx, addonId) => {