@camstack/server 1.2.593 → 1.2.594
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/addon-root-inventory.js +11 -50
- package/dist/agent/addon-scan.js +6 -44
- package/dist/agent/agent-addon-runtime.js +3 -8
- package/dist/agent/agent-addon-updates.js +12 -52
- package/dist/agent/agent-cap-dispatch-service.js +3 -5
- package/dist/agent/agent-config.js +7 -42
- package/dist/agent/agent-deploy-swap.js +5 -41
- package/dist/agent/agent-group-runner.js +8 -47
- package/dist/agent/agent-http-auth.js +10 -22
- package/dist/agent/agent-http.js +69 -115
- package/dist/agent/agent-hub-addon-update.js +10 -16
- package/dist/agent/agent-hub-registration.js +4 -7
- package/dist/agent/agent-infra-capabilities.js +5 -9
- package/dist/agent/agent-json-file.js +5 -42
- package/dist/agent/agent-mesh-gate.js +5 -8
- package/dist/agent/agent-node-certificates.js +25 -28
- package/dist/agent/agent-pairing-local.js +10 -47
- package/dist/agent/agent-pairing-routes.js +16 -20
- package/dist/agent/agent-pairing-store.js +18 -58
- package/dist/agent/agent-reconnect.js +1 -4
- package/dist/agent/agent-service.js +42 -78
- package/dist/agent/agent-sniffing-listener.js +11 -48
- package/dist/agent/agent-undeploy.js +5 -41
- package/dist/agent/agent-update-routes.js +14 -19
- package/dist/agent/agent-update-service.js +38 -80
- package/dist/agent/apply-model-distribution.js +1 -4
- package/dist/agent/apply-stage-dev-uploads.js +6 -9
- package/dist/agent/derive-hub-url.js +3 -8
- package/dist/agent/fetch-bundle-from-hub.js +6 -9
- package/dist/agent/hubs-found.js +6 -43
- package/dist/agent/infra-boot-guard.js +1 -4
- package/dist/agent/infra-boot-plan.js +2 -6
- package/dist/agent/main.js +158 -186
- package/dist/agent/register-agent-cap-dispatch.js +3 -6
- package/dist/agent/serial-queue.js +2 -7
- package/dist/agent-status-page.js +1 -4
- package/dist/api/addon-upload.js +22 -61
- package/dist/api/agent-addon-delivery.js +6 -11
- package/dist/api/auth-whoami.js +1 -4
- package/dist/api/backup-download-pump.js +5 -9
- package/dist/api/backup-download.js +7 -10
- package/dist/api/core/addon-settings.router.js +16 -19
- package/dist/api/core/agent-pairing.router.js +76 -79
- package/dist/api/core/agents.router.js +21 -24
- package/dist/api/core/auth-admin-tokens.procedures.js +48 -52
- package/dist/api/core/auth-device-cert.procedures.js +62 -67
- package/dist/api/core/auth-limits.js +16 -25
- package/dist/api/core/auth-reauth.procedures.js +32 -37
- package/dist/api/core/auth-revoke-credentials.procedures.js +22 -26
- package/dist/api/core/auth-sessions.procedures.js +52 -56
- package/dist/api/core/auth.router.js +300 -304
- package/dist/api/core/cap-providers.js +74 -121
- package/dist/api/core/capabilities.router.js +34 -37
- package/dist/api/core/capability-preference-writes.js +10 -17
- package/dist/api/core/certificates.router.js +106 -109
- package/dist/api/core/cluster-nodes.router.js +15 -18
- package/dist/api/core/collection-preference.js +2 -6
- package/dist/api/core/event-bus-proxy.router.js +34 -37
- package/dist/api/core/failure-contributions.js +4 -8
- package/dist/api/core/hwaccel.router.js +17 -20
- package/dist/api/core/integration-secrets.js +8 -12
- package/dist/api/core/lifecycle-job-runner.js +13 -49
- package/dist/api/core/live-events.router.js +29 -32
- package/dist/api/core/load-contributions.js +4 -8
- package/dist/api/core/logging-settings.js +30 -43
- package/dist/api/core/logs.router.js +40 -43
- package/dist/api/core/notifications.router.js +15 -18
- package/dist/api/core/public-event-filter.js +4 -8
- package/dist/api/core/repl.router.js +18 -21
- package/dist/api/core/request-census-settings.js +11 -16
- package/dist/api/core/server-management.provider.js +1 -4
- package/dist/api/core/server-name.js +9 -17
- package/dist/api/core/site-location.js +16 -24
- package/dist/api/core/stream-probe.router.js +21 -24
- package/dist/api/core/system-events.router.js +23 -26
- package/dist/api/cors/cors-policy.js +20 -29
- package/dist/api/cors/register-cors-policy.js +9 -14
- package/dist/api/deploy-stage-registry.js +6 -10
- package/dist/api/dev-uploads-bundle.js +12 -50
- package/dist/api/health/adaptive-probe.routes.js +9 -13
- package/dist/api/health/health.routes.js +8 -13
- package/dist/api/hub-cluster-url.js +3 -9
- package/dist/api/model-distributor.js +12 -50
- package/dist/api/oauth2/consent-page.js +10 -13
- package/dist/api/oauth2/oauth2-routes.js +17 -25
- package/dist/api/oauth2/private-host.js +1 -4
- package/dist/api/server-upload-propagate.js +15 -51
- package/dist/api/server-upload.js +13 -50
- package/dist/api/sse-no-compression.js +1 -4
- package/dist/api/static/frame-policy.js +4 -9
- package/dist/api/static/precompressed-asset.js +15 -57
- package/dist/api/static/spa-mount.js +8 -47
- package/dist/api/static/spa-static.js +5 -12
- package/dist/api/tarball-manifest.js +7 -44
- package/dist/api/trpc/cap-mount-helpers.js +7 -12
- package/dist/api/trpc/cap-route-error-formatter.js +10 -13
- package/dist/api/trpc/cap-router-runtime.js +40 -51
- package/dist/api/trpc/client-ip.js +9 -20
- package/dist/api/trpc/core-cap-bridge.js +32 -42
- package/dist/api/trpc/device-collection-dispatch.js +23 -27
- package/dist/api/trpc/device-config-secret-redaction.js +24 -40
- package/dist/api/trpc/generated-cap-mounts.js +218 -222
- package/dist/api/trpc/generated-cap-routers.js +4688 -4850
- package/dist/api/trpc/http-request-census-hook.js +8 -11
- package/dist/api/trpc/http-request-census.js +34 -41
- package/dist/api/trpc/http-request-census.singleton.js +4 -8
- package/dist/api/trpc/localized-schema-paths.js +1 -4
- package/dist/api/trpc/principal-visibility.js +14 -22
- package/dist/api/trpc/remote-addon-update-grant.js +1 -4
- package/dist/api/trpc/scope-access.js +10 -15
- package/dist/api/trpc/server-provided-caps.js +3 -7
- package/dist/api/trpc/share-view-access.js +6 -14
- package/dist/api/trpc/trpc-error-device-tags.js +3 -6
- package/dist/api/trpc/trpc-error-principal.js +2 -6
- package/dist/api/trpc/trpc.context.js +20 -25
- package/dist/api/trpc/trpc.middleware.js +38 -46
- package/dist/api/trpc/trpc.router.js +58 -99
- package/dist/api/trpc/user-error.js +5 -8
- package/dist/api/trpc/ws-request-census.js +17 -24
- package/dist/api/trpc/ws-slow-consumer-guard.js +11 -16
- package/dist/api/upload-auth.js +5 -9
- package/dist/auth/addon-route-jwt-gate.js +4 -7
- package/dist/auth/addon-route-share-gate.js +7 -12
- package/dist/auth/app-confirm-page.js +17 -22
- package/dist/auth/app-confirm-routes.js +26 -31
- package/dist/auth/app-confirmations.js +7 -44
- package/dist/auth/app-page-shell.js +17 -30
- package/dist/auth/app-page-strings.js +11 -17
- package/dist/auth/app-passkey-page.js +16 -21
- package/dist/auth/app-passkey-routes.js +37 -41
- package/dist/auth/app-refused-route.js +13 -18
- package/dist/auth/auth-network-policy.js +11 -15
- package/dist/auth/auth-provider-gate.js +4 -9
- package/dist/auth/auth-rate-limit.js +3 -7
- package/dist/auth/client-address.js +6 -9
- package/dist/auth/device-enroll-proofs.js +6 -43
- package/dist/auth/external-link-intents.js +3 -7
- package/dist/auth/hub-sessions.js +6 -12
- package/dist/auth/native-link-completions.js +7 -11
- package/dist/auth/native-return.js +9 -13
- package/dist/auth/passkey-provider.js +26 -32
- package/dist/auth/second-factors.js +6 -10
- package/dist/auth/session-connections.js +6 -12
- package/dist/auth/session-cookie.js +12 -23
- package/dist/auth/session-lifetimes.js +8 -12
- package/dist/auth/session-mint.js +4 -11
- package/dist/auth/sso-finish-app.js +13 -16
- package/dist/auth/sso-finish-deps.js +21 -27
- package/dist/auth/sso-finish.js +28 -38
- package/dist/auth/sso-routes.js +22 -25
- package/dist/auth/used-bridge-ids.js +1 -5
- package/dist/boot/boot-config.js +16 -55
- package/dist/boot/init-sequence.js +1 -4
- package/dist/boot/integration-id-backfill.js +3 -8
- package/dist/boot/platform-keychain-key.js +5 -42
- package/dist/boot/post-boot.service.js +14 -18
- package/dist/boot/reconcile-lifecycle-jobs.js +3 -6
- package/dist/boot/retired-public-url-env.js +4 -8
- package/dist/bootstrap-packages.js +1 -4
- package/dist/closure-node-modules.js +2 -38
- package/dist/closure-patches/patch-reach.json +6 -2
- package/dist/core/addon/addon-call-gateway.js +1 -5
- package/dist/core/addon/addon-package.service.js +83 -123
- package/dist/core/addon/addon-process-view.js +2 -6
- package/dist/core/addon/addon-realign-plan.js +1 -4
- package/dist/core/addon/addon-registry.service.js +199 -185
- package/dist/core/addon/addon-row-manifest.js +1 -4
- package/dist/core/addon/addon-search.service.js +3 -7
- package/dist/core/addon/addon-settings-provider.js +1 -3
- package/dist/core/addon/addon-uninstall.js +4 -11
- package/dist/core/addon/addon.tokens.js +2 -5
- package/dist/core/addon/device-meta-mirror.js +8 -13
- package/dist/core/addon/forked-custom-actions.js +6 -11
- package/dist/core/addon/integration-visibility.js +3 -6
- package/dist/core/addon/materialize-route-path.js +1 -4
- package/dist/core/addon/package-dir-utils.js +8 -45
- package/dist/core/addon/prune-misplaced-addons.js +5 -43
- package/dist/core/addon/runner-convergence.js +1 -4
- package/dist/core/addon/runner-serialization.js +1 -4
- package/dist/core/addon/runner-spawn-fanout.js +3 -8
- package/dist/core/addon/settings-door-wiring.js +5 -8
- package/dist/core/addon/single-flight-refresh.js +1 -4
- package/dist/core/addon-bridge/addon-bridge.service.js +7 -44
- package/dist/core/addon-pages/addon-pages.service.js +3 -40
- package/dist/core/addon-widgets/addon-widgets.service.js +4 -42
- package/dist/core/agent/agent-addon-backfill.js +9 -17
- package/dist/core/agent/agent-delivery-ledger.js +3 -7
- package/dist/core/agent/agent-liveness-event.js +2 -6
- package/dist/core/agent/agent-pairing-http.js +41 -45
- package/dist/core/agent/agent-registry.service.js +53 -92
- package/dist/core/agent/cluster-node-history-mirror.js +3 -7
- package/dist/core/agent/cluster-node-history-store.js +113 -58
- package/dist/core/agent/hub-agent-pairing.js +1 -5
- package/dist/core/agent/node-certificates-client.js +3 -6
- package/dist/core/agent/node-directory.js +1 -4
- package/dist/core/app-links/well-known-app-links.js +2 -6
- package/dist/core/auth/auth.service.js +3 -8
- package/dist/core/auth/handoff-code.service.js +11 -49
- package/dist/core/auth/record-kinds.js +35 -0
- package/dist/core/auth/share-token.service.js +174 -122
- package/dist/core/capability/capability.service.js +1 -5
- package/dist/core/config/config.schema.js +1 -6
- package/dist/core/config/config.service.js +2 -6
- package/dist/core/events/event-bus.service.js +3 -7
- package/dist/core/feature/feature.service.js +2 -6
- package/dist/core/lifecycle/lifecycle-runner.singleton.js +5 -10
- package/dist/core/lifecycle/lifecycle-state-machine.js +1 -5
- package/dist/core/logging/log-ring-buffer.js +1 -5
- package/dist/core/logging/logging.service.js +5 -9
- package/dist/core/logging/scoped-logger.js +1 -5
- package/dist/core/moleculer/agent-readiness-pull.js +1 -4
- package/dist/core/moleculer/agent-registration-request.js +3 -7
- package/dist/core/moleculer/cap-call-fn.js +39 -5
- package/dist/core/moleculer/cap-route-authority.js +3 -8
- package/dist/core/moleculer/hub-mesh-guard.js +5 -9
- package/dist/core/moleculer/moleculer.service.js +108 -119
- package/dist/core/network/network-quality.service.js +2 -6
- package/dist/core/notification/notification-wrapper.service.js +3 -7
- package/dist/core/notification/toast-wrapper.service.js +3 -7
- package/dist/core/provider/provider.tokens.js +1 -4
- package/dist/core/repl/repl-engine.service.js +4 -8
- package/dist/core/runner-health/runner-boot-check.js +7 -15
- package/dist/core/runner-health/update-preflight.js +9 -14
- package/dist/core/server-update/server-update.service.js +12 -49
- package/dist/core/server-update/system-apply-closure-patches.js +6 -42
- package/dist/core/server-update/system-ensure-prebuilds.js +3 -6
- package/dist/core/server-update/system-exec-npm.js +2 -5
- package/dist/core/storage/fs-storage-backend.js +1 -5
- package/dist/core/storage/storage-location-manager.js +1 -5
- package/dist/core/storage/storage.service.js +2 -6
- package/dist/core/streaming/ffmpeg-stream-info.js +3 -8
- package/dist/core/streaming/stream-probe.service.js +12 -16
- package/dist/core/streaming/ttl-cache.js +1 -5
- package/dist/core/topology/topology-emitter.service.js +19 -23
- package/dist/core/update-availability-emitter.js +3 -7
- package/dist/core/update-availability-store.js +8 -45
- package/dist/core/updates/agent-installed-packages.js +5 -9
- package/dist/core/updates/restart-marker.js +3 -8
- package/dist/core/updates/update-check-scheduler.js +13 -18
- package/dist/core/updates/wrapper-update-checker.js +3 -7
- package/dist/first-boot-addon-plan.js +2 -6
- package/dist/framework-nodepath.js +2 -38
- package/dist/i18n/request-lang.js +7 -11
- package/dist/launcher.js +30 -65
- package/dist/lifecycle-journal-path.js +2 -38
- package/dist/main.js +287 -324
- package/dist/manual-boot.js +120 -157
- package/dist/node-role.js +1 -4
- package/dist/package-inventory.js +7 -48
- package/dist/pending-restore.js +7 -43
- package/dist/single-copy-cleanup-runner.js +9 -45
- package/dist/single-copy-cleanup.js +7 -15
- package/dist/sqlite-restore.js +9 -48
- package/dist/starter.js +4 -39
- package/dist/ui-serving/lazy-ui-serving.js +3 -39
- package/package.json +13 -13
|
@@ -1,65 +1,26 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access -- pre-existing lint debt across this 2200-line orchestration class. The flagged sites (StorageService.setLocationManager / setSettingsBackend, LoggingService.addDestination, RouteRegistry, etc.) are typed as `unknown` by their owning services to break circular construction-order dependencies; runtime contracts are validated structurally. Tracked separately; do not amend in unrelated edits. */
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.AddonRegistryService = void 0;
|
|
38
|
-
exports.shouldEvictMissingOnDisk = shouldEvictMissingOnDisk;
|
|
39
|
-
exports.addonUpdatePayload = addonUpdatePayload;
|
|
40
|
-
exports.parseDataPlaneEndpoints = parseDataPlaneEndpoints;
|
|
41
|
-
exports.shouldEmitProviderRegisteredReady = shouldEmitProviderRegisteredReady;
|
|
42
|
-
const addon_process_view_js_1 = require("./addon-process-view.js");
|
|
43
|
-
const node_crypto_1 = require("node:crypto");
|
|
44
|
-
const fs = __importStar(require("node:fs"));
|
|
45
|
-
const os = __importStar(require("node:os"));
|
|
46
|
-
const path = __importStar(require("node:path"));
|
|
47
|
-
const system_1 = require("@camstack/system");
|
|
48
|
-
const types_1 = require("@camstack/types");
|
|
49
|
-
const client_1 = require("@trpc/client");
|
|
50
|
-
const addon_call_gateway_js_1 = require("./addon-call-gateway.js");
|
|
51
|
-
const addon_row_manifest_1 = require("./addon-row-manifest");
|
|
52
|
-
const addon_settings_provider_js_1 = require("./addon-settings-provider.js");
|
|
53
|
-
const device_meta_mirror_js_1 = require("./device-meta-mirror.js");
|
|
54
|
-
const forked_custom_actions_js_1 = require("./forked-custom-actions.js");
|
|
55
|
-
const integration_visibility_js_1 = require("./integration-visibility.js");
|
|
56
|
-
const materialize_route_path_js_1 = require("./materialize-route-path.js");
|
|
57
|
-
const package_dir_utils_1 = require("./package-dir-utils");
|
|
58
|
-
const prune_misplaced_addons_js_1 = require("./prune-misplaced-addons.js");
|
|
59
|
-
const runner_convergence_1 = require("./runner-convergence");
|
|
60
|
-
const runner_serialization_1 = require("./runner-serialization");
|
|
61
|
-
const runner_spawn_fanout_js_1 = require("./runner-spawn-fanout.js");
|
|
62
|
-
const settings_door_wiring_js_1 = require("./settings-door-wiring.js");
|
|
2
|
+
import { describeAddonProcess, NEVER_LOADED_PROCESS, } from './addon-process-view.js';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as os from 'node:os';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { AddonDepsManager, AddonHealthMonitor, AddonLoader, adaptBrokerToCluster, addonSettingsCapability, brokerTransportLink, CapabilityHandle, CapabilityRegistry, CustomActionRegistry, canServeDataPlane, createAddonDataPlaneFacility, createAddonPeerBytesFacility, createAddonService, createBrokerDeviceManagerApi, createKernelHwAccel, DeviceRegistry, describeProviderKindDrift, catalogNamespaceOf, deviceCapResolverLink, getCatalogRegistry, INFRA_CAPABILITIES, IntegrationRegistry, clusterAgentRecordKinds, clusterNodeHistoryRecordKind, integrationRegistryRecordKinds, localProviderLink, partitionIsolatedBuiltinIds, runHubAddonBoot, scopeKey, validateProviderRegistrations, waitUntilReady, asBrokerLike, } from '@camstack/system';
|
|
8
|
+
import { ALL_CAPABILITY_DEFINITIONS, createDeviceProxy, createRecordsAccess, createSecretsAccess, DisposerChain, EventCategory, errMsg, isAgentOnlyPlacement, isIsolatedBuiltin, normalizeAddonInitResult, resolveAddonPlacement, resolveRunnerId, SHARE_VIEW_KINDS, } from '@camstack/types';
|
|
9
|
+
import { createTRPCClient } from '@trpc/client';
|
|
10
|
+
import { AddonCallGateway } from './addon-call-gateway.js';
|
|
11
|
+
import { overlayDeclaration } from './addon-row-manifest';
|
|
12
|
+
import { createAddonSettingsProvider } from './addon-settings-provider.js';
|
|
13
|
+
import { DeviceMetaMirror } from './device-meta-mirror.js';
|
|
14
|
+
import { childCatalogsFingerprint, summarizeChildCatalogs, toForkedCustomActionsSpec, } from './forked-custom-actions.js';
|
|
15
|
+
import { createFilteredIntegrationRegistry } from './integration-visibility.js';
|
|
16
|
+
import { coreAuthRecordKinds } from '../auth/record-kinds.js';
|
|
17
|
+
import { materializeRoutePath } from './materialize-route-path.js';
|
|
18
|
+
import { resolveHubClosurePackageDir } from './package-dir-utils';
|
|
19
|
+
import { decidePrune, prunePlacementLeftovers } from './prune-misplaced-addons.js';
|
|
20
|
+
import { planRunnerConvergence } from './runner-convergence';
|
|
21
|
+
import { createRunnerSerializer } from './runner-serialization';
|
|
22
|
+
import { spawnPlanConcurrently } from './runner-spawn-fanout.js';
|
|
23
|
+
import { wireSettingsDoor } from './settings-door-wiring.js';
|
|
63
24
|
/**
|
|
64
25
|
* Decide whether an in-memory addon entry should be evicted because it is no
|
|
65
26
|
* longer on disk (the uninstall path in `loadNewAddons`).
|
|
@@ -71,7 +32,7 @@ const settings_door_wiring_js_1 = require("./settings-door-wiring.js");
|
|
|
71
32
|
* let a deploy of an unrelated addon silently unregister loki-logging on the
|
|
72
33
|
* live hub.
|
|
73
34
|
*/
|
|
74
|
-
function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
|
|
35
|
+
export function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
|
|
75
36
|
return (entry.source === 'installed' && entry.packageName !== '@camstack/system' && !onDiskIds.has(id));
|
|
76
37
|
}
|
|
77
38
|
/**
|
|
@@ -90,12 +51,18 @@ function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
|
|
|
90
51
|
* settings save, which emits the same category carrying no versions at all
|
|
91
52
|
* (`parseAddonUpdateComplete`).
|
|
92
53
|
*/
|
|
93
|
-
function addonUpdatePayload(fromVersion, toVersion) {
|
|
54
|
+
export function addonUpdatePayload(fromVersion, toVersion) {
|
|
94
55
|
return {
|
|
95
56
|
...(fromVersion !== null ? { fromVersion } : {}),
|
|
96
57
|
toVersion,
|
|
97
58
|
};
|
|
98
59
|
}
|
|
60
|
+
function isAddonDeclaration(value) {
|
|
61
|
+
if (typeof value !== 'object' || value === null)
|
|
62
|
+
return false;
|
|
63
|
+
const id = Reflect.get(value, 'id');
|
|
64
|
+
return typeof id === 'string' && id.length > 0;
|
|
65
|
+
}
|
|
99
66
|
/**
|
|
100
67
|
* Type predicate: true when an `ISettingsBackend` also satisfies the
|
|
101
68
|
* sync `ISettingsStore` contract (system/addon/device key-value ops)
|
|
@@ -191,7 +158,7 @@ function parseSerializableRouteDescriptors(raw) {
|
|
|
191
158
|
* into typed data-plane endpoint descriptors (`prefix/access/baseUrl/secret`).
|
|
192
159
|
* Pure data — the addon's `ctx.dataPlane` facility produced them.
|
|
193
160
|
*/
|
|
194
|
-
function parseDataPlaneEndpoints(raw) {
|
|
161
|
+
export function parseDataPlaneEndpoints(raw) {
|
|
195
162
|
if (!Array.isArray(raw)) {
|
|
196
163
|
throw new Error('data-planes: child returned a non-array endpoint set');
|
|
197
164
|
}
|
|
@@ -232,7 +199,7 @@ function parseDataPlaneShare(value) {
|
|
|
232
199
|
});
|
|
233
200
|
}
|
|
234
201
|
function isShareViewKind(value) {
|
|
235
|
-
return typeof value === 'string' &&
|
|
202
|
+
return typeof value === 'string' && SHARE_VIEW_KINDS.includes(value);
|
|
236
203
|
}
|
|
237
204
|
// Phase 11 (settings redesign): `stripValue` helper and the legacy
|
|
238
205
|
// `getAddonConfigSchema / getAddonConfig / updateAddonConfig` adapter
|
|
@@ -249,7 +216,7 @@ function isShareViewKind(value) {
|
|
|
249
216
|
* accelerator-blind results. The addon emits `starting`/`ready`/`down`
|
|
250
217
|
* itself (`autoEmitReadiness=false`, ready on probe-done).
|
|
251
218
|
*/
|
|
252
|
-
function shouldEmitProviderRegisteredReady(capability) {
|
|
219
|
+
export function shouldEmitProviderRegisteredReady(capability) {
|
|
253
220
|
return capability !== 'platform-probe';
|
|
254
221
|
}
|
|
255
222
|
/**
|
|
@@ -260,11 +227,11 @@ function shouldEmitProviderRegisteredReady(capability) {
|
|
|
260
227
|
* invisible for a week. Built once — `ALL_CAPABILITY_DEFINITIONS` is a frozen
|
|
261
228
|
* generated array.
|
|
262
229
|
*/
|
|
263
|
-
const DEVICE_SCOPED_CAP_NAMES = new Set(
|
|
230
|
+
const DEVICE_SCOPED_CAP_NAMES = new Set(ALL_CAPABILITY_DEFINITIONS.filter((def) => def.scope === 'device').map((def) => def.name));
|
|
264
231
|
function isDeviceScopedCapName(capName) {
|
|
265
232
|
return DEVICE_SCOPED_CAP_NAMES.has(capName);
|
|
266
233
|
}
|
|
267
|
-
class AddonRegistryService {
|
|
234
|
+
export class AddonRegistryService {
|
|
268
235
|
loggingService;
|
|
269
236
|
eventBusService;
|
|
270
237
|
configService;
|
|
@@ -279,7 +246,7 @@ class AddonRegistryService {
|
|
|
279
246
|
// Task 7.1: hub-wide registry of addon custom actions. Populated on
|
|
280
247
|
// each addon's initialize() (in-process path); Task 7.2 will dispatch
|
|
281
248
|
// through this from the `api.addons.custom` tRPC procedure.
|
|
282
|
-
customActionRegistry = new
|
|
249
|
+
customActionRegistry = new CustomActionRegistry();
|
|
283
250
|
/**
|
|
284
251
|
* Fingerprint of the custom-action catalogs each UDS child last DESCRIBED
|
|
285
252
|
* (keyed by child id), and the addon ids registered from that description.
|
|
@@ -333,9 +300,9 @@ class AddonRegistryService {
|
|
|
333
300
|
const resolver = {
|
|
334
301
|
getByName: (capName) => reg.getSingleton(capName) ?? reg.getAllProviders(capName)[0] ?? null,
|
|
335
302
|
};
|
|
336
|
-
const client =
|
|
303
|
+
const client = createTRPCClient({
|
|
337
304
|
links: [
|
|
338
|
-
|
|
305
|
+
localProviderLink(resolver),
|
|
339
306
|
// Device-scoped caps go through the hub's CapRouteResolver, which is
|
|
340
307
|
// the only authority that can route `(cap, deviceId)` into a FORKED
|
|
341
308
|
// child — and forked children are broker-less, so the Moleculer
|
|
@@ -344,8 +311,8 @@ class AddonRegistryService {
|
|
|
344
311
|
// `Service 'ptz.ptz.getOptions' is not found`, which is how the
|
|
345
312
|
// autotrack builtin came up switched on and unable to move a camera
|
|
346
313
|
// (2026-09-18).
|
|
347
|
-
|
|
348
|
-
|
|
314
|
+
deviceCapResolverLink((capName, method, input) => this.moleculer.callCapabilityOnNode(this.moleculer.broker.nodeID, capName, method, input), isDeviceScopedCapName),
|
|
315
|
+
brokerTransportLink(this.moleculer.broker),
|
|
349
316
|
],
|
|
350
317
|
});
|
|
351
318
|
this.brokerApi = client;
|
|
@@ -360,9 +327,12 @@ class AddonRegistryService {
|
|
|
360
327
|
activeStorageProvider = null;
|
|
361
328
|
activeSettingsBackend = null;
|
|
362
329
|
integrationRegistry = null;
|
|
330
|
+
clusterNodeHistoryStore = null;
|
|
331
|
+
clusterAgentRecords = null;
|
|
332
|
+
shareTokenService = null;
|
|
363
333
|
// Device architecture — in-memory registry of live IDevice instances.
|
|
364
334
|
// Persistence is owned by the `device-manager` capability addon.
|
|
365
|
-
deviceRegistry = new
|
|
335
|
+
deviceRegistry = new DeviceRegistry();
|
|
366
336
|
/**
|
|
367
337
|
* Typed accessor for the Moleculer broker. The Moleculer `index.d.ts`
|
|
368
338
|
* chains through `eventemitter2`, whose package.json has no `types`
|
|
@@ -375,7 +345,7 @@ class AddonRegistryService {
|
|
|
375
345
|
* `this.broker.call(...)` / `this.broker.nodeID` lint clean.
|
|
376
346
|
*/
|
|
377
347
|
get broker() {
|
|
378
|
-
return this.moleculer.broker;
|
|
348
|
+
return asBrokerLike(this.moleculer.broker);
|
|
379
349
|
}
|
|
380
350
|
constructor(loggingService, eventBusService, configService, storageService, capabilityService, moleculer, streamProbe) {
|
|
381
351
|
this.loggingService = loggingService;
|
|
@@ -386,8 +356,8 @@ class AddonRegistryService {
|
|
|
386
356
|
this.moleculer = moleculer;
|
|
387
357
|
this.streamProbe = streamProbe;
|
|
388
358
|
this.logger = this.loggingService.createLogger('AddonRegistry');
|
|
389
|
-
this.addonLoader = new
|
|
390
|
-
this.deviceMirror = new
|
|
359
|
+
this.addonLoader = new AddonLoader(this.loggingService.createLogger('AddonLoader'));
|
|
360
|
+
this.deviceMirror = new DeviceMetaMirror(async (deviceIds) => this.getBrokerApi().deviceManager.listAll.query({
|
|
391
361
|
projection: 'slim',
|
|
392
362
|
...(deviceIds === null ? {} : { deviceIds: [...deviceIds] }),
|
|
393
363
|
}), {
|
|
@@ -402,13 +372,13 @@ class AddonRegistryService {
|
|
|
402
372
|
// reattempt failed addons. Operator visibility comes through
|
|
403
373
|
// AddonLoadFailed / AddonLoadRecovered events consumed by
|
|
404
374
|
// AlertCenter (see addon-health-monitor.ts spec).
|
|
405
|
-
this.healthMonitor = new
|
|
375
|
+
this.healthMonitor = new AddonHealthMonitor({
|
|
406
376
|
eventBus: this.eventBusService,
|
|
407
377
|
logger: this.loggingService.createLogger('AddonHealthMonitor'),
|
|
408
378
|
retryFn: (packageName) => this.tryReloadPackage(packageName),
|
|
409
379
|
});
|
|
410
380
|
// Create capability registry with config reader for singleton preferences
|
|
411
|
-
this.capabilityRegistry = new
|
|
381
|
+
this.capabilityRegistry = new CapabilityRegistry(this.loggingService.createLogger('CapabilityRegistry'), this.eventBusService);
|
|
412
382
|
// The preference readers answer "no preference" while the persisted
|
|
413
383
|
// preferences cannot be read yet — before the settings door exists, or
|
|
414
384
|
// while its one boot read is in flight (D675: `get` then THROWS rather
|
|
@@ -421,7 +391,7 @@ class AddonRegistryService {
|
|
|
421
391
|
}
|
|
422
392
|
catch (err) {
|
|
423
393
|
this.logger.debug('capability preference not readable yet — default applies until replay', {
|
|
424
|
-
meta: { capability, error:
|
|
394
|
+
meta: { capability, error: errMsg(err) },
|
|
425
395
|
});
|
|
426
396
|
return undefined;
|
|
427
397
|
}
|
|
@@ -434,7 +404,7 @@ class AddonRegistryService {
|
|
|
434
404
|
catch (err) {
|
|
435
405
|
this.logger.debug('capability preference not readable yet — default applies', {
|
|
436
406
|
tags: { nodeId },
|
|
437
|
-
meta: { capability, error:
|
|
407
|
+
meta: { capability, error: errMsg(err) },
|
|
438
408
|
});
|
|
439
409
|
return undefined;
|
|
440
410
|
}
|
|
@@ -460,7 +430,7 @@ class AddonRegistryService {
|
|
|
460
430
|
}
|
|
461
431
|
catch (err) {
|
|
462
432
|
this.logger.debug('collection preference not readable yet, or malformed', {
|
|
463
|
-
meta: { capability, error:
|
|
433
|
+
meta: { capability, error: errMsg(err) },
|
|
464
434
|
});
|
|
465
435
|
return undefined;
|
|
466
436
|
}
|
|
@@ -493,7 +463,7 @@ class AddonRegistryService {
|
|
|
493
463
|
// Register the addon-settings singleton provider — replaces the
|
|
494
464
|
// former `$addonHost` Moleculer service. The provider resolves
|
|
495
465
|
// addonId → local addon instance (hub) or remote Moleculer call.
|
|
496
|
-
this.capabilityRegistry.declareCapability(
|
|
466
|
+
this.capabilityRegistry.declareCapability(addonSettingsCapability);
|
|
497
467
|
// The SINGLE router for addon-level calls (routes / custom / settings).
|
|
498
468
|
// It classifies in-process | hub-local-child (UDS) | remote-agent
|
|
499
469
|
// (Moleculer) in ONE place — `resolveNode` reports only the BASE node
|
|
@@ -503,7 +473,7 @@ class AddonRegistryService {
|
|
|
503
473
|
// down a dead `<addonId>.settings.*` Moleculer path because the old
|
|
504
474
|
// `resolveNode` marked them non-local; now they route over UDS like
|
|
505
475
|
// their routes + custom-actions already do.
|
|
506
|
-
this.addonCallGateway = new
|
|
476
|
+
this.addonCallGateway = new AddonCallGateway({
|
|
507
477
|
hubNodeId: this.broker.nodeID,
|
|
508
478
|
resolveNode: (addonId) => {
|
|
509
479
|
const entry = this.addonEntries.get(addonId);
|
|
@@ -520,7 +490,7 @@ class AddonRegistryService {
|
|
|
520
490
|
// active-singleton preference can select a grouped addon (whose child id
|
|
521
491
|
// is its `execution.group`, not its own id). Identity for ungrouped addons.
|
|
522
492
|
this.moleculer.setRunnerIdResolver((addonId) => this.childIdForAddon(addonId));
|
|
523
|
-
const settingsProvider =
|
|
493
|
+
const settingsProvider = createAddonSettingsProvider({
|
|
524
494
|
getAddon: (addonId) => {
|
|
525
495
|
const entry = this.addonEntries.get(addonId);
|
|
526
496
|
return entry?.addon ?? null;
|
|
@@ -562,7 +532,7 @@ class AddonRegistryService {
|
|
|
562
532
|
// `syncSystemAddonsCopy` race. Non-framework addons still load from the
|
|
563
533
|
// addons dir unchanged. The addons-dir `@camstack/system` copy (if present,
|
|
564
534
|
// seeded by the image entrypoint) is SKIPPED to avoid a double-load.
|
|
565
|
-
const closureSystemDir =
|
|
535
|
+
const closureSystemDir = resolveHubClosurePackageDir('@camstack/system');
|
|
566
536
|
if (closureSystemDir) {
|
|
567
537
|
try {
|
|
568
538
|
await this.addonLoader.loadFromAddonDir(closureSystemDir);
|
|
@@ -591,10 +561,10 @@ class AddonRegistryService {
|
|
|
591
561
|
// FAILED forever on a node where it cannot run. Fixing the installer was
|
|
592
562
|
// necessary and not sufficient — nothing removed what was already there,
|
|
593
563
|
// and a permanent red entry teaches people to ignore red entries.
|
|
594
|
-
|
|
564
|
+
prunePlacementLeftovers(decidePrune(loadedAddons.map((a) => ({
|
|
595
565
|
packageName: a.packageName,
|
|
596
566
|
dir: path.join(addonsDir, a.packageName),
|
|
597
|
-
placement:
|
|
567
|
+
placement: resolveAddonPlacement(a.declaration),
|
|
598
568
|
})), 'hub'), path.join(addonsDir, '@camstack'), {
|
|
599
569
|
info: (msg, meta) => this.logger.info(msg, { meta }),
|
|
600
570
|
warn: (msg, meta) => this.logger.warn(msg, { meta }),
|
|
@@ -602,7 +572,7 @@ class AddonRegistryService {
|
|
|
602
572
|
for (const registered of loadedAddons) {
|
|
603
573
|
// Skip agent-only addons — they never run on the hub, only on remote
|
|
604
574
|
// agents that opt in via `execution.placement: 'agent-only'`.
|
|
605
|
-
if (
|
|
575
|
+
if (isAgentOnlyPlacement(registered.declaration)) {
|
|
606
576
|
this.logger.info('Skipping agent-only addon on hub', {
|
|
607
577
|
tags: { addonId: registered.declaration.id },
|
|
608
578
|
meta: { packageName: registered.packageName },
|
|
@@ -642,7 +612,7 @@ class AddonRegistryService {
|
|
|
642
612
|
this.loadAddonCatalogs(registered.packageName, this.addonEntries.get(registered.declaration.id)?.addonDir);
|
|
643
613
|
}
|
|
644
614
|
catch (err) {
|
|
645
|
-
const msg =
|
|
615
|
+
const msg = errMsg(err);
|
|
646
616
|
this.logger.error('Failed to create instance of addon', {
|
|
647
617
|
tags: { addonId: registered.declaration.id },
|
|
648
618
|
meta: { error: msg },
|
|
@@ -688,7 +658,7 @@ class AddonRegistryService {
|
|
|
688
658
|
// genuinely has no cross-process provider and the fallback returns null — wrappers
|
|
689
659
|
// cleanly fall through to their own secondary strategy.
|
|
690
660
|
{
|
|
691
|
-
const { buildNativeCapProxy } = await
|
|
661
|
+
const { buildNativeCapProxy } = await import('@camstack/system');
|
|
692
662
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- moleculer types unresolvable; see `broker` getter docstring
|
|
693
663
|
const broker = this.moleculer.broker;
|
|
694
664
|
this.capabilityRegistry.setNativeFallback((capName, deviceId) => {
|
|
@@ -786,7 +756,7 @@ class AddonRegistryService {
|
|
|
786
756
|
// that IS load-bearing is BETWEEN the waves in `runHubAddonBoot`, not
|
|
787
757
|
// inside one.
|
|
788
758
|
const spawnRunnerPlan = async (plan) => {
|
|
789
|
-
await
|
|
759
|
+
await spawnPlanConcurrently(plan, async (runnerId, runnerAddons) => {
|
|
790
760
|
try {
|
|
791
761
|
await this.initializeAddonGroup(runnerId, runnerAddons);
|
|
792
762
|
for (const { addonId } of runnerAddons) {
|
|
@@ -794,7 +764,7 @@ class AddonRegistryService {
|
|
|
794
764
|
}
|
|
795
765
|
}
|
|
796
766
|
catch (error) {
|
|
797
|
-
const msg =
|
|
767
|
+
const msg = errMsg(error);
|
|
798
768
|
for (const { addonId } of runnerAddons) {
|
|
799
769
|
this.emitAddonLifecycleEvent('addon.error', addonId, {
|
|
800
770
|
error: msg,
|
|
@@ -816,10 +786,10 @@ class AddonRegistryService {
|
|
|
816
786
|
const entry = this.addonEntries.get(id);
|
|
817
787
|
return (entry?.packageName === '@camstack/system' &&
|
|
818
788
|
entry.declaration !== undefined &&
|
|
819
|
-
|
|
789
|
+
isIsolatedBuiltin(entry.declaration));
|
|
820
790
|
});
|
|
821
791
|
const isolatedBuiltins = new Set(isolatedBuiltinIds);
|
|
822
|
-
const { infra: isolatedInfraIds, consumers: isolatedConsumerIds } =
|
|
792
|
+
const { infra: isolatedInfraIds, consumers: isolatedConsumerIds } = partitionIsolatedBuiltinIds(isolatedBuiltinIds, (id) => {
|
|
823
793
|
const entry = this.addonEntries.get(id);
|
|
824
794
|
return entry?.declaredCapabilities ?? [];
|
|
825
795
|
});
|
|
@@ -839,7 +809,7 @@ class AddonRegistryService {
|
|
|
839
809
|
// still mint the named hostname (Cloudflare 530, 2026-08-25).
|
|
840
810
|
// `runHubAddonBoot` is the only sequence; a timeout on the engine wait
|
|
841
811
|
// must NOT throw (1.2.151 crash-looped the starter).
|
|
842
|
-
await
|
|
812
|
+
await runHubAddonBoot({
|
|
843
813
|
spawnIsolatedInfra: async () => {
|
|
844
814
|
if (isolatedInfraIds.length === 0)
|
|
845
815
|
return;
|
|
@@ -852,7 +822,7 @@ class AddonRegistryService {
|
|
|
852
822
|
if (isolatedInfraIds.length === 0)
|
|
853
823
|
return;
|
|
854
824
|
try {
|
|
855
|
-
await
|
|
825
|
+
await waitUntilReady(() => this.capabilityRegistry.getCollection('data-store-provider').length > 0, {
|
|
856
826
|
timeoutMs: 30_000,
|
|
857
827
|
intervalMs: 50,
|
|
858
828
|
what: 'isolated data-store-provider engine',
|
|
@@ -860,14 +830,14 @@ class AddonRegistryService {
|
|
|
860
830
|
}
|
|
861
831
|
catch (err) {
|
|
862
832
|
this.logger.error('Isolated data-store-provider engine did not register in time', {
|
|
863
|
-
meta: { error:
|
|
833
|
+
meta: { error: errMsg(err), addonIds: isolatedInfraIds },
|
|
864
834
|
});
|
|
865
835
|
}
|
|
866
836
|
},
|
|
867
837
|
bootInProcessInfra: async () => {
|
|
868
838
|
// Pass 1 — infrastructure builtins. A failed REQUIRED infra cap
|
|
869
839
|
// aborts boot: nothing downstream can run without storage/settings.
|
|
870
|
-
for (const infra of
|
|
840
|
+
for (const infra of INFRA_CAPABILITIES) {
|
|
871
841
|
const addonId = this.findAddonForCapability(infra.name, allIds);
|
|
872
842
|
if (addonId) {
|
|
873
843
|
const entry = this.addonEntries.get(addonId);
|
|
@@ -881,7 +851,7 @@ class AddonRegistryService {
|
|
|
881
851
|
this.wireCapabilities(addonId);
|
|
882
852
|
}
|
|
883
853
|
catch (error) {
|
|
884
|
-
const msg =
|
|
854
|
+
const msg = errMsg(error);
|
|
885
855
|
this.emitAddonLifecycleEvent('addon.error', addonId, {
|
|
886
856
|
error: msg,
|
|
887
857
|
phase: 'init',
|
|
@@ -908,7 +878,7 @@ class AddonRegistryService {
|
|
|
908
878
|
bootInProcessConsumers: async () => {
|
|
909
879
|
// Pass 2 — remaining core builtins, in capability-dependency order.
|
|
910
880
|
const bootOrder = this.capabilityRegistry.getBootOrder();
|
|
911
|
-
const infraNames = new Set(
|
|
881
|
+
const infraNames = new Set(INFRA_CAPABILITIES.map((c) => c.name));
|
|
912
882
|
for (const capName of bootOrder) {
|
|
913
883
|
if (infraNames.has(capName))
|
|
914
884
|
continue; // Handled in pass 1
|
|
@@ -924,7 +894,7 @@ class AddonRegistryService {
|
|
|
924
894
|
this.wireCapabilities(id);
|
|
925
895
|
}
|
|
926
896
|
catch (error) {
|
|
927
|
-
const msg =
|
|
897
|
+
const msg = errMsg(error);
|
|
928
898
|
this.emitAddonLifecycleEvent('addon.error', id, {
|
|
929
899
|
error: msg,
|
|
930
900
|
phase: 'init',
|
|
@@ -945,7 +915,7 @@ class AddonRegistryService {
|
|
|
945
915
|
this.wireCapabilities(id);
|
|
946
916
|
}
|
|
947
917
|
catch (error) {
|
|
948
|
-
const msg =
|
|
918
|
+
const msg = errMsg(error);
|
|
949
919
|
this.emitAddonLifecycleEvent('addon.error', id, {
|
|
950
920
|
error: msg,
|
|
951
921
|
phase: 'init',
|
|
@@ -1003,7 +973,7 @@ class AddonRegistryService {
|
|
|
1003
973
|
for (const [id, entry] of this.addonEntries.entries()) {
|
|
1004
974
|
if (!entry.declaration)
|
|
1005
975
|
continue;
|
|
1006
|
-
const runnerId =
|
|
976
|
+
const runnerId = resolveRunnerId(entry.declaration, id);
|
|
1007
977
|
if (`${this.broker.nodeID}/${runnerId}` !== nodeId)
|
|
1008
978
|
continue;
|
|
1009
979
|
// Skip operator-initiated restarts — `restartAddon` already
|
|
@@ -1025,7 +995,7 @@ class AddonRegistryService {
|
|
|
1025
995
|
for (const [id, entry] of this.addonEntries.entries()) {
|
|
1026
996
|
if (!entry.declaration)
|
|
1027
997
|
continue;
|
|
1028
|
-
const runnerId =
|
|
998
|
+
const runnerId = resolveRunnerId(entry.declaration, id);
|
|
1029
999
|
if (`${this.broker.nodeID}/${runnerId}` !== nodeId)
|
|
1030
1000
|
continue;
|
|
1031
1001
|
this.healthMonitor.recordSuccess(entry.packageName, id);
|
|
@@ -1033,10 +1003,10 @@ class AddonRegistryService {
|
|
|
1033
1003
|
});
|
|
1034
1004
|
}
|
|
1035
1005
|
this.eventBusService.emit({
|
|
1036
|
-
id:
|
|
1006
|
+
id: randomUUID(),
|
|
1037
1007
|
timestamp: new Date(),
|
|
1038
1008
|
source: { type: 'core', id: 'addon-registry' },
|
|
1039
|
-
category:
|
|
1009
|
+
category: EventCategory.SystemAddonsReady,
|
|
1040
1010
|
data: { activeAddons: initializedIds },
|
|
1041
1011
|
});
|
|
1042
1012
|
}
|
|
@@ -1081,7 +1051,7 @@ class AddonRegistryService {
|
|
|
1081
1051
|
this.healthMonitor.recordSuccess(packageName, id);
|
|
1082
1052
|
}
|
|
1083
1053
|
catch (err) {
|
|
1084
|
-
errors.push(`${id}: ${
|
|
1054
|
+
errors.push(`${id}: ${errMsg(err)}`);
|
|
1085
1055
|
this.healthMonitor.recordFailure(packageName, err, id);
|
|
1086
1056
|
}
|
|
1087
1057
|
}
|
|
@@ -1160,7 +1130,7 @@ class AddonRegistryService {
|
|
|
1160
1130
|
* values (D787). Never handed to an addon.
|
|
1161
1131
|
*/
|
|
1162
1132
|
secretsForOwner(owner) {
|
|
1163
|
-
return
|
|
1133
|
+
return createSecretsAccess({
|
|
1164
1134
|
owner,
|
|
1165
1135
|
transport: this.inProcessKeychainTransport(owner),
|
|
1166
1136
|
eventBus: this.eventBusService,
|
|
@@ -1173,7 +1143,7 @@ class AddonRegistryService {
|
|
|
1173
1143
|
* reaches a different owner.
|
|
1174
1144
|
*/
|
|
1175
1145
|
recordsForOwner(owner, secrets) {
|
|
1176
|
-
return
|
|
1146
|
+
return createRecordsAccess({
|
|
1177
1147
|
owner,
|
|
1178
1148
|
transport: this.inProcessRecordsTransport(owner),
|
|
1179
1149
|
transportForOwner: (other) => this.inProcessRecordsTransport(other),
|
|
@@ -1209,7 +1179,7 @@ class AddonRegistryService {
|
|
|
1209
1179
|
}
|
|
1210
1180
|
childIdForAddon(addonId) {
|
|
1211
1181
|
const decl = this.addonEntries.get(addonId)?.declaration;
|
|
1212
|
-
return decl ?
|
|
1182
|
+
return decl ? resolveRunnerId(decl, addonId) : addonId;
|
|
1213
1183
|
}
|
|
1214
1184
|
async mountAddonDataPlanes(addonId) {
|
|
1215
1185
|
const registry = this.dataPlaneRegistry;
|
|
@@ -1232,7 +1202,7 @@ class AddonRegistryService {
|
|
|
1232
1202
|
// Dropping is the safe direction: a data-plane that is absent from the
|
|
1233
1203
|
// registry falls through to the control-route path, while a stale one
|
|
1234
1204
|
// sends live traffic at a dead socket.
|
|
1235
|
-
if (!
|
|
1205
|
+
if (!canServeDataPlane({
|
|
1236
1206
|
hasEntry: entry !== undefined,
|
|
1237
1207
|
isForked: entry !== undefined && this.isForkedAddonEntry(entry),
|
|
1238
1208
|
childKnown: childRegistry !== null && childRegistry.isChildKnown(this.childIdForAddon(addonId)),
|
|
@@ -1317,16 +1287,16 @@ class AddonRegistryService {
|
|
|
1317
1287
|
*/
|
|
1318
1288
|
wireDeviceParentMirror() {
|
|
1319
1289
|
for (const category of [
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1290
|
+
EventCategory.DeviceMetaChanged,
|
|
1291
|
+
EventCategory.DeviceRegistered,
|
|
1292
|
+
EventCategory.DeviceUnregistered,
|
|
1293
|
+
EventCategory.DeviceProvisioned,
|
|
1324
1294
|
]) {
|
|
1325
1295
|
this.eventBusService.subscribe({ category }, (event) => {
|
|
1326
1296
|
this.deviceMirror.onDeviceEvent(event.data);
|
|
1327
1297
|
});
|
|
1328
1298
|
}
|
|
1329
|
-
this.eventBusService.subscribe({ category:
|
|
1299
|
+
this.eventBusService.subscribe({ category: EventCategory.SystemAddonsReady }, () => {
|
|
1330
1300
|
void this.deviceMirror.refreshAll();
|
|
1331
1301
|
});
|
|
1332
1302
|
}
|
|
@@ -1334,6 +1304,43 @@ class AddonRegistryService {
|
|
|
1334
1304
|
loadCollectionPreferences() {
|
|
1335
1305
|
// TODO: implement CapabilityRegistry.loadDisabledProviders() to restore persisted preferences
|
|
1336
1306
|
}
|
|
1307
|
+
/** Durable cluster-node history (A1) — bind records kind after orphan declare. */
|
|
1308
|
+
setClusterNodeHistoryStore(store) {
|
|
1309
|
+
this.clusterNodeHistoryStore = store;
|
|
1310
|
+
this.bindClusterNodeHistoryRecordsKind();
|
|
1311
|
+
}
|
|
1312
|
+
bindClusterNodeHistoryRecordsKind() {
|
|
1313
|
+
const clusterStore = this.clusterNodeHistoryStore;
|
|
1314
|
+
const records = this.clusterAgentRecords;
|
|
1315
|
+
if (clusterStore === null || records === null)
|
|
1316
|
+
return;
|
|
1317
|
+
const kind = records.kind(clusterNodeHistoryRecordKind);
|
|
1318
|
+
clusterStore.bindRecordsKind(() => kind);
|
|
1319
|
+
}
|
|
1320
|
+
setShareTokenService(service) {
|
|
1321
|
+
this.shareTokenService = service;
|
|
1322
|
+
}
|
|
1323
|
+
async wireHubMainOrphanRecords(store) {
|
|
1324
|
+
const recordsCap = this.capabilityRegistry.getSingleton('records');
|
|
1325
|
+
if (recordsCap === null || recordsCap === undefined)
|
|
1326
|
+
return;
|
|
1327
|
+
const integrationRecords = this.recordsForOwner('integration-registry', this.secretsForOwner('integration-registry'));
|
|
1328
|
+
await integrationRecords.declare(integrationRegistryRecordKinds);
|
|
1329
|
+
this.integrationRegistry = new IntegrationRegistry(store, IntegrationRegistry.recordStoresOf(integrationRecords));
|
|
1330
|
+
await this.integrationRegistry.initialize();
|
|
1331
|
+
this.logger.info('IntegrationRegistry initialized (records)', { meta: { phase: 'v2' } });
|
|
1332
|
+
const clusterRecords = this.recordsForOwner('cluster-agent', this.secretsForOwner('cluster-agent'));
|
|
1333
|
+
await clusterRecords.declare(clusterAgentRecordKinds);
|
|
1334
|
+
this.clusterAgentRecords = clusterRecords;
|
|
1335
|
+
this.bindClusterNodeHistoryRecordsKind();
|
|
1336
|
+
const shareTokens = this.shareTokenService;
|
|
1337
|
+
if (shareTokens !== null) {
|
|
1338
|
+
const coreRecords = this.recordsForOwner('core-auth', this.secretsForOwner('core-auth'));
|
|
1339
|
+
await coreRecords.declare(coreAuthRecordKinds);
|
|
1340
|
+
const shareKind = coreRecords.kind(coreAuthRecordKinds['share-view-tokens']);
|
|
1341
|
+
shareTokens.bindShareTokensKind(() => shareKind);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1337
1344
|
/**
|
|
1338
1345
|
* Returns the IntegrationRegistry filtered to exclude orphaned integrations
|
|
1339
1346
|
* (where the addon is not currently installed). Orphaned data stays in DB
|
|
@@ -1365,7 +1372,7 @@ class AddonRegistryService {
|
|
|
1365
1372
|
* `integration-visibility.ts`, on its own, with its own tests.
|
|
1366
1373
|
*/
|
|
1367
1374
|
createFilteredRegistry(raw) {
|
|
1368
|
-
return
|
|
1375
|
+
return createFilteredIntegrationRegistry({
|
|
1369
1376
|
raw,
|
|
1370
1377
|
// The LIVE map, not a snapshot: an addon registered after this wrapper
|
|
1371
1378
|
// was built is installed, and a stale copy would refuse its first write.
|
|
@@ -1407,8 +1414,8 @@ class AddonRegistryService {
|
|
|
1407
1414
|
loadAddonCatalogs(packageName, addonDir) {
|
|
1408
1415
|
if (addonDir === undefined)
|
|
1409
1416
|
return;
|
|
1410
|
-
const ns =
|
|
1411
|
-
const catalogs =
|
|
1417
|
+
const ns = catalogNamespaceOf(packageName);
|
|
1418
|
+
const catalogs = getCatalogRegistry((message) => this.logger.warn(message, { meta: { ns } }));
|
|
1412
1419
|
catalogs.clear(ns);
|
|
1413
1420
|
catalogs.loadAddonDir(ns, addonDir);
|
|
1414
1421
|
}
|
|
@@ -1462,7 +1469,7 @@ class AddonRegistryService {
|
|
|
1462
1469
|
await this.ensureForkedRunner(id, { restart: false });
|
|
1463
1470
|
}
|
|
1464
1471
|
catch (err) {
|
|
1465
|
-
const msg =
|
|
1472
|
+
const msg = errMsg(err);
|
|
1466
1473
|
this.logger.error('Failed to spawn isolated runner for addon', {
|
|
1467
1474
|
tags: { addonId: id },
|
|
1468
1475
|
meta: { error: msg },
|
|
@@ -1486,7 +1493,7 @@ class AddonRegistryService {
|
|
|
1486
1493
|
// resolves native addon ids for getBindings, lists registered wrappers,
|
|
1487
1494
|
// etc. Inject before initialize() runs so the addon's onInitialize can
|
|
1488
1495
|
// rely on it.
|
|
1489
|
-
const initResult =
|
|
1496
|
+
const initResult = normalizeAddonInitResult(await entry.addon.initialize(context));
|
|
1490
1497
|
// NOTE: `postBrokerStart()` is deliberately NOT called here. It used to
|
|
1491
1498
|
// fire right after `initialize()` and would emit readiness events whose
|
|
1492
1499
|
// subscribers (remote workers) immediately called `ctx.api.<addon>.*` —
|
|
@@ -1500,7 +1507,7 @@ class AddonRegistryService {
|
|
|
1500
1507
|
// D4: wrapper behaviour is read from the cap DEFINITION. The runtime
|
|
1501
1508
|
// ProviderRegistration.kind/defaultActive are deprecated hints — if an
|
|
1502
1509
|
// addon still sets them and they disagree, warn (the cap def wins).
|
|
1503
|
-
const kindDrift =
|
|
1510
|
+
const kindDrift = describeProviderKindDrift(capName, reg.capability, {
|
|
1504
1511
|
// eslint-disable-next-line @typescript-eslint/no-deprecated -- this IS the drift detector; it reads the deprecated hint on purpose
|
|
1505
1512
|
kind: reg.kind,
|
|
1506
1513
|
// eslint-disable-next-line @typescript-eslint/no-deprecated -- this IS the drift detector; it reads the deprecated hint on purpose
|
|
@@ -1532,7 +1539,7 @@ class AddonRegistryService {
|
|
|
1532
1539
|
catch (err) {
|
|
1533
1540
|
this.logger.error('Failed to register custom actions for addon', {
|
|
1534
1541
|
tags: { addonId: id },
|
|
1535
|
-
meta: { error:
|
|
1542
|
+
meta: { error: errMsg(err) },
|
|
1536
1543
|
});
|
|
1537
1544
|
throw err;
|
|
1538
1545
|
}
|
|
@@ -1552,7 +1559,7 @@ class AddonRegistryService {
|
|
|
1552
1559
|
if (provider)
|
|
1553
1560
|
capturedProviders.set(capName, provider);
|
|
1554
1561
|
}
|
|
1555
|
-
|
|
1562
|
+
validateProviderRegistrations(id, entry.declaration, capturedProviders, this.logger);
|
|
1556
1563
|
}
|
|
1557
1564
|
await this.restoreAddonDevices(id, entry.addon);
|
|
1558
1565
|
entry.initialized = true;
|
|
@@ -1567,7 +1574,7 @@ class AddonRegistryService {
|
|
|
1567
1574
|
return Object.keys(def.methods);
|
|
1568
1575
|
};
|
|
1569
1576
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- createAddonService return type unresolvable due to upstream eventemitter2/moleculer type chain
|
|
1570
|
-
const schema =
|
|
1577
|
+
const schema = createAddonService(entry.addon, entry.declaration, {
|
|
1571
1578
|
methodResolver,
|
|
1572
1579
|
providers: capturedProviders,
|
|
1573
1580
|
});
|
|
@@ -1576,7 +1583,7 @@ class AddonRegistryService {
|
|
|
1576
1583
|
catch (err) {
|
|
1577
1584
|
this.logger.warn('Failed to register Moleculer service', {
|
|
1578
1585
|
tags: { addonId: id },
|
|
1579
|
-
meta: { error:
|
|
1586
|
+
meta: { error: errMsg(err) },
|
|
1580
1587
|
});
|
|
1581
1588
|
}
|
|
1582
1589
|
}
|
|
@@ -1635,7 +1642,7 @@ class AddonRegistryService {
|
|
|
1635
1642
|
catch (err) {
|
|
1636
1643
|
this.logger.error('restoreDevices failed for addon', {
|
|
1637
1644
|
tags: { addonId },
|
|
1638
|
-
meta: { error:
|
|
1645
|
+
meta: { error: errMsg(err) },
|
|
1639
1646
|
});
|
|
1640
1647
|
}
|
|
1641
1648
|
}
|
|
@@ -1735,7 +1742,7 @@ class AddonRegistryService {
|
|
|
1735
1742
|
errors.push(res.error ?? `restart of "${addonId}" reported failure`);
|
|
1736
1743
|
}
|
|
1737
1744
|
catch (err) {
|
|
1738
|
-
errors.push(`restart of "${addonId}" threw: ${
|
|
1745
|
+
errors.push(`restart of "${addonId}" threw: ${errMsg(err)}`);
|
|
1739
1746
|
}
|
|
1740
1747
|
}
|
|
1741
1748
|
return { success: errors.length === 0, restarted, errors };
|
|
@@ -1760,7 +1767,7 @@ class AddonRegistryService {
|
|
|
1760
1767
|
// suppressing the siblings too, each would flash a spurious "Failed to load".
|
|
1761
1768
|
const suppressed = new Set([addonId]);
|
|
1762
1769
|
if (this.isForkedAddonEntry(entry)) {
|
|
1763
|
-
const runnerId =
|
|
1770
|
+
const runnerId = resolveRunnerId(entry.declaration, addonId);
|
|
1764
1771
|
for (const { addonId: siblingId } of this.buildAddonGroupPlan([
|
|
1765
1772
|
...this.addonEntries.keys(),
|
|
1766
1773
|
]).get(runnerId) ?? []) {
|
|
@@ -1894,7 +1901,7 @@ class AddonRegistryService {
|
|
|
1894
1901
|
return { success: true };
|
|
1895
1902
|
}
|
|
1896
1903
|
catch (err) {
|
|
1897
|
-
const msg =
|
|
1904
|
+
const msg = errMsg(err);
|
|
1898
1905
|
this.logger.error('Failed to restart addon', { tags: { addonId }, meta: { error: msg } });
|
|
1899
1906
|
this.emitAddonLifecycleEvent('addon.error', addonId, {
|
|
1900
1907
|
error: msg,
|
|
@@ -2086,10 +2093,10 @@ class AddonRegistryService {
|
|
|
2086
2093
|
// The real process-service state of each addon's runner (D877) - the last
|
|
2087
2094
|
// `refreshRunnerStates()` answer; `unknown` before the first one.
|
|
2088
2095
|
const runnerStates = this.runnerStates;
|
|
2089
|
-
const processFor = (id, entry) =>
|
|
2096
|
+
const processFor = (id, entry) => describeAddonProcess({
|
|
2090
2097
|
forked: this.runsInOwnRunner(entry),
|
|
2091
2098
|
initialized: entry.initialized === true,
|
|
2092
|
-
runnerId: entry.declaration ?
|
|
2099
|
+
runnerId: entry.declaration ? resolveRunnerId(entry.declaration, id) : id,
|
|
2093
2100
|
runners: runnerStates,
|
|
2094
2101
|
hubPid: process.pid,
|
|
2095
2102
|
});
|
|
@@ -2111,7 +2118,7 @@ class AddonRegistryService {
|
|
|
2111
2118
|
}
|
|
2112
2119
|
catch (err) {
|
|
2113
2120
|
this.logger.debug('Failed to read install-source marker for addon', {
|
|
2114
|
-
meta: { error:
|
|
2121
|
+
meta: { error: errMsg(err) },
|
|
2115
2122
|
});
|
|
2116
2123
|
}
|
|
2117
2124
|
}
|
|
@@ -2124,7 +2131,7 @@ class AddonRegistryService {
|
|
|
2124
2131
|
// `overlayDeclaration` — this is what keeps Home Assistant
|
|
2125
2132
|
// (post broker rework: broker + device-adoption) visible in the
|
|
2126
2133
|
// "+ New Integration" picker.
|
|
2127
|
-
...
|
|
2134
|
+
...overlayDeclaration(entry.addon.manifest, entry.declaration),
|
|
2128
2135
|
packageName: entry.packageName,
|
|
2129
2136
|
packageVersion: entry.packageVersion,
|
|
2130
2137
|
packageDisplayName: entry.packageDisplayName,
|
|
@@ -2210,11 +2217,12 @@ class AddonRegistryService {
|
|
|
2210
2217
|
...(bundleRaw.icon !== undefined ? { icon: bundleRaw.icon } : {}),
|
|
2211
2218
|
}
|
|
2212
2219
|
: undefined;
|
|
2213
|
-
for (const
|
|
2214
|
-
|
|
2215
|
-
const name = typeof decl['name'] === 'string' ? decl['name'] : id;
|
|
2216
|
-
if (!id)
|
|
2220
|
+
for (const rawDecl of addons) {
|
|
2221
|
+
if (!isAddonDeclaration(rawDecl))
|
|
2217
2222
|
continue;
|
|
2223
|
+
const decl = rawDecl;
|
|
2224
|
+
const id = decl.id;
|
|
2225
|
+
const name = typeof decl.name === 'string' ? decl.name : id;
|
|
2218
2226
|
const manifest = {
|
|
2219
2227
|
...decl,
|
|
2220
2228
|
id,
|
|
@@ -2231,7 +2239,7 @@ class AddonRegistryService {
|
|
|
2231
2239
|
declaration: decl,
|
|
2232
2240
|
source: 'installed',
|
|
2233
2241
|
...(installSource ? { installSource } : {}),
|
|
2234
|
-
process:
|
|
2242
|
+
process: NEVER_LOADED_PROCESS,
|
|
2235
2243
|
});
|
|
2236
2244
|
}
|
|
2237
2245
|
};
|
|
@@ -2283,7 +2291,7 @@ class AddonRegistryService {
|
|
|
2283
2291
|
const dataDir = path.resolve(process.env.CAMSTACK_DATA ?? 'camstack-data');
|
|
2284
2292
|
const addonsDir = process.env.CAMSTACK_ADDONS_DIR ?? path.resolve(dataDir, 'addons');
|
|
2285
2293
|
// Create a fresh loader to discover what's on disk (for diffing against existing)
|
|
2286
|
-
const freshLoader = new
|
|
2294
|
+
const freshLoader = new AddonLoader(this.loggingService.createLogger('AddonLoader'));
|
|
2287
2295
|
await freshLoader.loadFromDirectory(addonsDir);
|
|
2288
2296
|
const loaded = [];
|
|
2289
2297
|
const failed = [];
|
|
@@ -2314,7 +2322,7 @@ class AddonRegistryService {
|
|
|
2314
2322
|
// hub-side every agent-only provider (e.g. `hub-forwarder`),
|
|
2315
2323
|
// and a hub-resident `hub-forwarder` creates a write→ingest→
|
|
2316
2324
|
// write feedback loop because the hub IS the log-receiver.
|
|
2317
|
-
if (
|
|
2325
|
+
if (isAgentOnlyPlacement(registered.declaration)) {
|
|
2318
2326
|
this.logger.debug('loadNewAddons: skipping agent-only addon on hub', {
|
|
2319
2327
|
tags: { addonId: registered.declaration.id },
|
|
2320
2328
|
});
|
|
@@ -2352,7 +2360,7 @@ class AddonRegistryService {
|
|
|
2352
2360
|
this.emitAddonLifecycleEvent('addon.installed', registered.declaration.id);
|
|
2353
2361
|
}
|
|
2354
2362
|
catch (err) {
|
|
2355
|
-
const msg =
|
|
2363
|
+
const msg = errMsg(err);
|
|
2356
2364
|
const stack = err instanceof Error ? err.stack : undefined;
|
|
2357
2365
|
this.logger.error('Failed to hot-load addon', {
|
|
2358
2366
|
tags: { addonId: registered.declaration.id },
|
|
@@ -2392,7 +2400,7 @@ class AddonRegistryService {
|
|
|
2392
2400
|
catch (err) {
|
|
2393
2401
|
this.logger.warn('Non-fatal: failed to stop runner for removed addon', {
|
|
2394
2402
|
tags: { addonId: id },
|
|
2395
|
-
meta: { error:
|
|
2403
|
+
meta: { error: errMsg(err) },
|
|
2396
2404
|
});
|
|
2397
2405
|
}
|
|
2398
2406
|
}
|
|
@@ -2406,7 +2414,7 @@ class AddonRegistryService {
|
|
|
2406
2414
|
catch (err) {
|
|
2407
2415
|
this.logger.debug('Non-fatal: shutdown error for removed addon', {
|
|
2408
2416
|
tags: { addonId: id },
|
|
2409
|
-
meta: { error:
|
|
2417
|
+
meta: { error: errMsg(err) },
|
|
2410
2418
|
});
|
|
2411
2419
|
}
|
|
2412
2420
|
await this.drainDisposerChain(id);
|
|
@@ -2483,7 +2491,7 @@ class AddonRegistryService {
|
|
|
2483
2491
|
}
|
|
2484
2492
|
catch (err) {
|
|
2485
2493
|
this.logger.warn('emitReady failed', {
|
|
2486
|
-
meta: { capability, addonId, error:
|
|
2494
|
+
meta: { capability, addonId, error: errMsg(err) },
|
|
2487
2495
|
});
|
|
2488
2496
|
}
|
|
2489
2497
|
}
|
|
@@ -2545,14 +2553,21 @@ class AddonRegistryService {
|
|
|
2545
2553
|
const store = this.capabilityRegistry.getProviderByAddon('settings-store', addonId);
|
|
2546
2554
|
if (!store)
|
|
2547
2555
|
return;
|
|
2548
|
-
void
|
|
2556
|
+
void wireSettingsDoor({
|
|
2549
2557
|
configService: this.configService,
|
|
2550
2558
|
registry: this.capabilityRegistry,
|
|
2551
2559
|
logger: this.logger,
|
|
2552
2560
|
}, store);
|
|
2553
|
-
this.
|
|
2554
|
-
|
|
2555
|
-
|
|
2561
|
+
void this.wireHubMainOrphanRecords(store).catch((err) => {
|
|
2562
|
+
this.logger.warn('hub-main records wiring failed (best-effort)', {
|
|
2563
|
+
meta: { error: errMsg(err) },
|
|
2564
|
+
});
|
|
2565
|
+
this.integrationRegistry = new IntegrationRegistry(store);
|
|
2566
|
+
void this.integrationRegistry.initialize().then(() => {
|
|
2567
|
+
this.logger.info('IntegrationRegistry initialized (legacy)', {
|
|
2568
|
+
meta: { phase: 'v2' },
|
|
2569
|
+
});
|
|
2570
|
+
});
|
|
2556
2571
|
});
|
|
2557
2572
|
this.loadCollectionPreferences();
|
|
2558
2573
|
// DeviceStore/ConfigStore are owned by the `device-persistence`
|
|
@@ -2589,7 +2604,7 @@ class AddonRegistryService {
|
|
|
2589
2604
|
// subscriber unobserved.
|
|
2590
2605
|
void this.mountAddonRoutes(addonId).catch((err) => {
|
|
2591
2606
|
this.logger.error('Failed to mount addon routes', {
|
|
2592
|
-
meta: { phase: 'v2', addonId, error:
|
|
2607
|
+
meta: { phase: 'v2', addonId, error: errMsg(err) },
|
|
2593
2608
|
});
|
|
2594
2609
|
});
|
|
2595
2610
|
break;
|
|
@@ -2603,7 +2618,7 @@ class AddonRegistryService {
|
|
|
2603
2618
|
// restart (the child re-handshakes → caps re-register → this re-fires).
|
|
2604
2619
|
void this.mountAddonDataPlanes(addonId).catch((err) => {
|
|
2605
2620
|
this.logger.error('Failed to mount addon data-planes', {
|
|
2606
|
-
meta: { phase: 'v2', addonId, error:
|
|
2621
|
+
meta: { phase: 'v2', addonId, error: errMsg(err) },
|
|
2607
2622
|
});
|
|
2608
2623
|
});
|
|
2609
2624
|
});
|
|
@@ -2714,7 +2729,7 @@ class AddonRegistryService {
|
|
|
2714
2729
|
method: route.method,
|
|
2715
2730
|
// The CONCRETE path, not the pattern: the child re-matches it and
|
|
2716
2731
|
// would otherwise bind every param to its own `:placeholder`.
|
|
2717
|
-
path:
|
|
2732
|
+
path: materializeRoutePath(route.path, req.params),
|
|
2718
2733
|
params: req.params,
|
|
2719
2734
|
query: req.query,
|
|
2720
2735
|
body: req.body,
|
|
@@ -2753,7 +2768,7 @@ class AddonRegistryService {
|
|
|
2753
2768
|
emitAddonLifecycleEvent(eventType, addonId, data) {
|
|
2754
2769
|
const entry = this.addonEntries.get(addonId);
|
|
2755
2770
|
this.eventBusService.emit({
|
|
2756
|
-
id:
|
|
2771
|
+
id: randomUUID(),
|
|
2757
2772
|
timestamp: new Date(),
|
|
2758
2773
|
source: {
|
|
2759
2774
|
type: 'addon',
|
|
@@ -2785,10 +2800,10 @@ class AddonRegistryService {
|
|
|
2785
2800
|
const declaredCaps = entry.declaredCapabilities.map((c) => c.name);
|
|
2786
2801
|
if (declaredCaps.includes('addon-pages-source')) {
|
|
2787
2802
|
this.eventBusService.emit({
|
|
2788
|
-
id:
|
|
2803
|
+
id: randomUUID(),
|
|
2789
2804
|
timestamp: new Date(),
|
|
2790
2805
|
source: { type: 'addon', id: addonId },
|
|
2791
|
-
category:
|
|
2806
|
+
category: EventCategory.AddonPageReady,
|
|
2792
2807
|
data: { addonId, packageName: entry.packageName },
|
|
2793
2808
|
});
|
|
2794
2809
|
}
|
|
@@ -2799,10 +2814,10 @@ class AddonRegistryService {
|
|
|
2799
2814
|
// reload.
|
|
2800
2815
|
if (declaredCaps.includes('addon-widgets-source')) {
|
|
2801
2816
|
this.eventBusService.emit({
|
|
2802
|
-
id:
|
|
2817
|
+
id: randomUUID(),
|
|
2803
2818
|
timestamp: new Date(),
|
|
2804
2819
|
source: { type: 'addon', id: addonId },
|
|
2805
|
-
category:
|
|
2820
|
+
category: EventCategory.AddonWidgetReady,
|
|
2806
2821
|
data: { addonId, packageName: entry.packageName },
|
|
2807
2822
|
});
|
|
2808
2823
|
}
|
|
@@ -2879,7 +2894,7 @@ class AddonRegistryService {
|
|
|
2879
2894
|
probe: (url, options) => this.streamProbe.probe(url, options),
|
|
2880
2895
|
probeField: (key, value) => this.streamProbe.probeField(key, value),
|
|
2881
2896
|
};
|
|
2882
|
-
const deviceManagerApi =
|
|
2897
|
+
const deviceManagerApi = createBrokerDeviceManagerApi({
|
|
2883
2898
|
api: this.getBrokerApi(),
|
|
2884
2899
|
addonId,
|
|
2885
2900
|
nodeId: this.broker.nodeID,
|
|
@@ -2897,11 +2912,11 @@ class AddonRegistryService {
|
|
|
2897
2912
|
const rr = this.moleculer.readinessRegistry;
|
|
2898
2913
|
const capHandleCache = new Map();
|
|
2899
2914
|
function getOrCreateHandle(capName, scope, timeoutMs) {
|
|
2900
|
-
const key = `${capName}::${
|
|
2915
|
+
const key = `${capName}::${scopeKey(scope)}`;
|
|
2901
2916
|
const existing = capHandleCache.get(key);
|
|
2902
2917
|
if (existing)
|
|
2903
2918
|
return existing;
|
|
2904
|
-
const handle = new
|
|
2919
|
+
const handle = new CapabilityHandle(capName, scope, rr, timeoutMs);
|
|
2905
2920
|
capHandleCache.set(key, handle);
|
|
2906
2921
|
return handle;
|
|
2907
2922
|
}
|
|
@@ -2918,7 +2933,7 @@ class AddonRegistryService {
|
|
|
2918
2933
|
registry.dataPlaneRegistry?.registerAddon(id, endpoints);
|
|
2919
2934
|
},
|
|
2920
2935
|
};
|
|
2921
|
-
const dataPlaneFacility =
|
|
2936
|
+
const dataPlaneFacility = createAddonDataPlaneFacility({
|
|
2922
2937
|
addonId,
|
|
2923
2938
|
logger,
|
|
2924
2939
|
sink: dataPlaneSink,
|
|
@@ -2927,7 +2942,7 @@ class AddonRegistryService {
|
|
|
2927
2942
|
// Addon→addon byte transport (`ctx.peerBytes`, D613) for a co-located hub
|
|
2928
2943
|
// builtin, on the same terms a forked addon gets it. `broker.nodeID` IS the
|
|
2929
2944
|
// host node here — hub-main has no runner suffix.
|
|
2930
|
-
const peerBytesFacility =
|
|
2945
|
+
const peerBytesFacility = createAddonPeerBytesFacility({
|
|
2931
2946
|
addonId,
|
|
2932
2947
|
logger,
|
|
2933
2948
|
nodeId: this.broker.nodeID,
|
|
@@ -2937,7 +2952,7 @@ class AddonRegistryService {
|
|
|
2937
2952
|
// STATED (no wire, no stamp) - the one place the keychain trusts a claim.
|
|
2938
2953
|
// `forOwner` is the door a system builtin (device-manager,
|
|
2939
2954
|
// storage-orchestrator) uses to keep another addon's values.
|
|
2940
|
-
const secrets =
|
|
2955
|
+
const secrets = createSecretsAccess({
|
|
2941
2956
|
owner: addonId,
|
|
2942
2957
|
transport: this.inProcessKeychainTransport(addonId),
|
|
2943
2958
|
transportForOwner: (owner) => this.inProcessKeychainTransport(owner),
|
|
@@ -2980,7 +2995,7 @@ class AddonRegistryService {
|
|
|
2980
2995
|
return registry.capabilityRegistry.getSingleton(capName) ?? undefined;
|
|
2981
2996
|
},
|
|
2982
2997
|
},
|
|
2983
|
-
deps: new
|
|
2998
|
+
deps: new AddonDepsManager(dataDir, logger),
|
|
2984
2999
|
kernel: {
|
|
2985
3000
|
localNodeId: this.broker.nodeID,
|
|
2986
3001
|
// The keychain accepts a grouped runner's stamp (its group id) for the
|
|
@@ -2989,9 +3004,9 @@ class AddonRegistryService {
|
|
|
2989
3004
|
storage: storageProvider ?? undefined,
|
|
2990
3005
|
deviceRegistry: this.deviceRegistry ?? undefined,
|
|
2991
3006
|
devices: deviceManagerApi,
|
|
2992
|
-
cluster: { broker:
|
|
3007
|
+
cluster: { broker: adaptBrokerToCluster(this.moleculer.broker) },
|
|
2993
3008
|
streamProbe: kernelStreamProbe,
|
|
2994
|
-
hwaccel:
|
|
3009
|
+
hwaccel: createKernelHwAccel(),
|
|
2995
3010
|
capabilityRegistry: this.capabilityRegistry,
|
|
2996
3011
|
// Per-addon storage-location declarations across every installed
|
|
2997
3012
|
// addon — surfaced from the kernel's AddonLoader so the
|
|
@@ -3027,7 +3042,7 @@ class AddonRegistryService {
|
|
|
3027
3042
|
fetchDevice: async (deviceId) => {
|
|
3028
3043
|
const api = registry.getBrokerApi();
|
|
3029
3044
|
const binding = await api.deviceManager.getBindings.query({ deviceId });
|
|
3030
|
-
return
|
|
3045
|
+
return createDeviceProxy(api, binding);
|
|
3031
3046
|
},
|
|
3032
3047
|
settings: settingsView,
|
|
3033
3048
|
secrets,
|
|
@@ -3100,10 +3115,10 @@ class AddonRegistryService {
|
|
|
3100
3115
|
let chain = this.disposerChains.get(addonId);
|
|
3101
3116
|
if (chain == null) {
|
|
3102
3117
|
const log = this.loggingService.createLogger().withTags({ addonId });
|
|
3103
|
-
chain = new
|
|
3118
|
+
chain = new DisposerChain({
|
|
3104
3119
|
onError: (err, index) => {
|
|
3105
3120
|
log.error(`Disposer #${index} threw during teardown`, {
|
|
3106
|
-
meta: { error:
|
|
3121
|
+
meta: { error: errMsg(err) },
|
|
3107
3122
|
});
|
|
3108
3123
|
},
|
|
3109
3124
|
});
|
|
@@ -3162,7 +3177,7 @@ class AddonRegistryService {
|
|
|
3162
3177
|
continue;
|
|
3163
3178
|
if (!this.runsInOwnRunner(entry))
|
|
3164
3179
|
continue;
|
|
3165
|
-
const runnerId =
|
|
3180
|
+
const runnerId = resolveRunnerId(entry.declaration, id);
|
|
3166
3181
|
const bucket = plan.get(runnerId) ?? [];
|
|
3167
3182
|
bucket.push({ addonId: id, addonDir: entry.addonDir });
|
|
3168
3183
|
plan.set(runnerId, bucket);
|
|
@@ -3183,11 +3198,11 @@ class AddonRegistryService {
|
|
|
3183
3198
|
runsInOwnRunner(entry) {
|
|
3184
3199
|
if (!entry.declaration || !entry.addonDir)
|
|
3185
3200
|
return false;
|
|
3186
|
-
if (
|
|
3201
|
+
if (resolveAddonPlacement(entry.declaration) === 'agent-only')
|
|
3187
3202
|
return false;
|
|
3188
3203
|
if (entry.packageName !== '@camstack/system')
|
|
3189
3204
|
return true;
|
|
3190
|
-
return
|
|
3205
|
+
return isIsolatedBuiltin(entry.declaration);
|
|
3191
3206
|
}
|
|
3192
3207
|
/**
|
|
3193
3208
|
* Spawn ONE runner subprocess that hosts every addon in `addons` and
|
|
@@ -3225,7 +3240,7 @@ class AddonRegistryService {
|
|
|
3225
3240
|
// Unserialized the loser almost never got here — it spawned a SECOND
|
|
3226
3241
|
// runner instead, which is the defect. Serializing makes the loser
|
|
3227
3242
|
// reliable, so the promise has to be kept rather than documented.
|
|
3228
|
-
if (!/already running/i.test(
|
|
3243
|
+
if (!/already running/i.test(errMsg(err)))
|
|
3229
3244
|
throw err;
|
|
3230
3245
|
this.logger.info('initializeAddonGroup: runner already running — nothing to spawn', {
|
|
3231
3246
|
meta: { runnerId, addonIds: addons.map((a) => a.addonId) },
|
|
@@ -3234,7 +3249,7 @@ class AddonRegistryService {
|
|
|
3234
3249
|
});
|
|
3235
3250
|
}
|
|
3236
3251
|
catch (err) {
|
|
3237
|
-
throw new Error(`Failed to spawn runner "${runnerId}" (${addons.length} addons): ${
|
|
3252
|
+
throw new Error(`Failed to spawn runner "${runnerId}" (${addons.length} addons): ${errMsg(err)}`, { cause: err });
|
|
3238
3253
|
}
|
|
3239
3254
|
// Provider registration for cap methods is handled by
|
|
3240
3255
|
// `CapabilityBridge.onProviderConnected` once the runner's INFO heartbeat
|
|
@@ -3275,7 +3290,7 @@ class AddonRegistryService {
|
|
|
3275
3290
|
}
|
|
3276
3291
|
catch (err) {
|
|
3277
3292
|
this.logger.warn('could not list the runner processes - addon process state is unknown', {
|
|
3278
|
-
meta: { error:
|
|
3293
|
+
meta: { error: errMsg(err) },
|
|
3279
3294
|
});
|
|
3280
3295
|
return null;
|
|
3281
3296
|
}
|
|
@@ -3339,13 +3354,13 @@ class AddonRegistryService {
|
|
|
3339
3354
|
* two live episodes this closes and why the lock inside `process-service`
|
|
3340
3355
|
* could not.
|
|
3341
3356
|
*/
|
|
3342
|
-
serializeRunner =
|
|
3357
|
+
serializeRunner = createRunnerSerializer();
|
|
3343
3358
|
async ensureForkedRunner(addonId, opts) {
|
|
3344
3359
|
const entry = this.addonEntries.get(addonId);
|
|
3345
3360
|
if (!entry?.declaration || !entry.addonDir) {
|
|
3346
3361
|
throw new Error(`ensureForkedRunner("${addonId}") requires an on-disk forked addon`);
|
|
3347
3362
|
}
|
|
3348
|
-
const runnerId =
|
|
3363
|
+
const runnerId = resolveRunnerId(entry.declaration, addonId);
|
|
3349
3364
|
const roster = this.buildAddonGroupPlan([...this.addonEntries.keys()]).get(runnerId) ?? [
|
|
3350
3365
|
{ addonId, addonDir: entry.addonDir },
|
|
3351
3366
|
];
|
|
@@ -3368,12 +3383,12 @@ class AddonRegistryService {
|
|
|
3368
3383
|
// action atomic, and the unit that matters is the sequence.
|
|
3369
3384
|
return this.serializeRunner(runnerId, async () => {
|
|
3370
3385
|
const running = await this.listRunnerProcesses();
|
|
3371
|
-
const plan =
|
|
3386
|
+
const plan = planRunnerConvergence(runnerId, roster.map((r) => r.addonId), running, { restart: effectiveRestart });
|
|
3372
3387
|
// 1. Tear down every stale/mismatched runner that hosts a roster member.
|
|
3373
3388
|
for (const name of plan.stop) {
|
|
3374
3389
|
await this.broker.call('$process.stop', { name }).catch((err) => {
|
|
3375
3390
|
this.logger.warn('ensureForkedRunner: failed to stop stale runner (continuing)', {
|
|
3376
|
-
meta: { runnerId, stale: name, error:
|
|
3391
|
+
meta: { runnerId, stale: name, error: errMsg(err) },
|
|
3377
3392
|
});
|
|
3378
3393
|
});
|
|
3379
3394
|
}
|
|
@@ -3384,7 +3399,7 @@ class AddonRegistryService {
|
|
|
3384
3399
|
.catch((err) => {
|
|
3385
3400
|
// A concurrent path may have won the spawn race — treat "already
|
|
3386
3401
|
// running" as success (idempotent), rethrow anything else.
|
|
3387
|
-
if (!/already running/i.test(
|
|
3402
|
+
if (!/already running/i.test(errMsg(err)))
|
|
3388
3403
|
throw err;
|
|
3389
3404
|
});
|
|
3390
3405
|
this.recentGroupRespawns.set(runnerId, Date.now());
|
|
@@ -3444,7 +3459,7 @@ class AddonRegistryService {
|
|
|
3444
3459
|
const catalogs = child.customActions;
|
|
3445
3460
|
if (catalogs === undefined)
|
|
3446
3461
|
return;
|
|
3447
|
-
const fingerprint =
|
|
3462
|
+
const fingerprint = childCatalogsFingerprint(catalogs);
|
|
3448
3463
|
if (this.childCatalogFingerprints.get(child.childId) === fingerprint)
|
|
3449
3464
|
return;
|
|
3450
3465
|
this.childCatalogFingerprints.set(child.childId, fingerprint);
|
|
@@ -3459,9 +3474,9 @@ class AddonRegistryService {
|
|
|
3459
3474
|
this.customActionRegistry.unregisterAddon(addonId);
|
|
3460
3475
|
continue;
|
|
3461
3476
|
}
|
|
3462
|
-
this.customActionRegistry.registerAddon(addonId,
|
|
3477
|
+
this.customActionRegistry.registerAddon(addonId, toForkedCustomActionsSpec(descriptor), (action, input, caller) => this.dispatchForkedCustomAction(addonId, action, input, caller));
|
|
3463
3478
|
}
|
|
3464
|
-
const summary =
|
|
3479
|
+
const summary = summarizeChildCatalogs(catalogs);
|
|
3465
3480
|
this.logger.info('UDS child custom-action catalogs applied', {
|
|
3466
3481
|
meta: {
|
|
3467
3482
|
childId: child.childId,
|
|
@@ -3489,4 +3504,3 @@ class AddonRegistryService {
|
|
|
3489
3504
|
});
|
|
3490
3505
|
}
|
|
3491
3506
|
}
|
|
3492
|
-
exports.AddonRegistryService = AddonRegistryService;
|