@camstack/core 0.1.13 → 0.1.15

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.
Files changed (161) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +220 -0
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js.map +1 -0
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +9 -0
  4. package/dist/builtins/addon-pages-aggregator/index.js +222 -0
  5. package/dist/builtins/addon-pages-aggregator/index.js.map +1 -0
  6. package/dist/builtins/addon-pages-aggregator/index.mjs +9 -0
  7. package/dist/builtins/addon-pages-aggregator/index.mjs.map +1 -0
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +200 -0
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -0
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +9 -0
  11. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -0
  12. package/dist/builtins/addon-widgets-aggregator/index.js +202 -0
  13. package/dist/builtins/addon-widgets-aggregator/index.js.map +1 -0
  14. package/dist/builtins/addon-widgets-aggregator/index.mjs +9 -0
  15. package/dist/builtins/addon-widgets-aggregator/index.mjs.map +1 -0
  16. package/dist/builtins/alerts/alerts.addon.js +443 -0
  17. package/dist/builtins/alerts/alerts.addon.js.map +1 -0
  18. package/dist/builtins/alerts/alerts.addon.mjs +9 -0
  19. package/dist/builtins/alerts/alerts.addon.mjs.map +1 -0
  20. package/dist/builtins/alerts/index.js +443 -0
  21. package/dist/builtins/alerts/index.js.map +1 -0
  22. package/dist/builtins/alerts/index.mjs +8 -0
  23. package/dist/builtins/alerts/index.mjs.map +1 -0
  24. package/dist/builtins/console-logging/index.js +242 -0
  25. package/dist/builtins/console-logging/index.js.map +1 -0
  26. package/dist/builtins/console-logging/index.mjs +11 -0
  27. package/dist/builtins/console-logging/index.mjs.map +1 -0
  28. package/dist/builtins/device-manager/device-manager.addon.js +2155 -0
  29. package/dist/builtins/device-manager/device-manager.addon.js.map +1 -0
  30. package/dist/builtins/device-manager/device-manager.addon.mjs +9 -0
  31. package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -0
  32. package/dist/builtins/device-manager/index.js +2157 -0
  33. package/dist/builtins/device-manager/index.js.map +1 -0
  34. package/dist/builtins/device-manager/index.mjs +10 -0
  35. package/dist/builtins/device-manager/index.mjs.map +1 -0
  36. package/dist/builtins/hub-forwarder/index.js +297 -0
  37. package/dist/builtins/hub-forwarder/index.js.map +1 -0
  38. package/dist/builtins/hub-forwarder/index.mjs +11 -0
  39. package/dist/builtins/hub-forwarder/index.mjs.map +1 -0
  40. package/dist/builtins/local-auth/index.js +623 -0
  41. package/dist/builtins/local-auth/index.js.map +1 -0
  42. package/dist/builtins/local-auth/index.mjs +8 -0
  43. package/dist/builtins/local-auth/index.mjs.map +1 -0
  44. package/dist/builtins/local-auth/local-auth.addon.js +623 -0
  45. package/dist/builtins/local-auth/local-auth.addon.js.map +1 -0
  46. package/dist/builtins/local-auth/local-auth.addon.mjs +9 -0
  47. package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -0
  48. package/dist/builtins/local-backup/index.js +53 -68
  49. package/dist/builtins/local-backup/index.js.map +1 -1
  50. package/dist/builtins/local-backup/index.mjs +1 -1
  51. package/dist/builtins/native-metrics/native-metrics.addon.js +898 -0
  52. package/dist/builtins/native-metrics/native-metrics.addon.js.map +1 -0
  53. package/dist/builtins/native-metrics/native-metrics.addon.mjs +7 -0
  54. package/dist/builtins/native-metrics/native-metrics.addon.mjs.map +1 -0
  55. package/dist/builtins/snapshot/index.js +504 -0
  56. package/dist/builtins/snapshot/index.js.map +1 -0
  57. package/dist/builtins/snapshot/index.mjs +477 -0
  58. package/dist/builtins/snapshot/index.mjs.map +1 -0
  59. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +16 -166
  60. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js.map +1 -1
  61. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  62. package/dist/builtins/sqlite-storage/index.js +554 -621
  63. package/dist/builtins/sqlite-storage/index.js.map +1 -1
  64. package/dist/builtins/sqlite-storage/index.mjs +9 -11
  65. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +368 -130
  66. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js.map +1 -1
  67. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  68. package/dist/builtins/system-config/index.js +189 -0
  69. package/dist/builtins/system-config/index.js.map +1 -0
  70. package/dist/builtins/system-config/index.mjs +10 -0
  71. package/dist/builtins/system-config/index.mjs.map +1 -0
  72. package/dist/builtins/system-config/system-config.addon.js +187 -0
  73. package/dist/builtins/system-config/system-config.addon.js.map +1 -0
  74. package/dist/builtins/system-config/system-config.addon.mjs +9 -0
  75. package/dist/builtins/system-config/system-config.addon.mjs.map +1 -0
  76. package/dist/builtins/winston-logging/index.js +185 -65
  77. package/dist/builtins/winston-logging/index.js.map +1 -1
  78. package/dist/builtins/winston-logging/index.mjs +2 -1
  79. package/dist/chunk-2CIYKDRN.mjs +1 -0
  80. package/dist/chunk-2CIYKDRN.mjs.map +1 -0
  81. package/dist/chunk-2F76X6NL.mjs +136 -0
  82. package/dist/chunk-2F76X6NL.mjs.map +1 -0
  83. package/dist/chunk-2QUFBZ7M.mjs +1 -0
  84. package/dist/chunk-2QUFBZ7M.mjs.map +1 -0
  85. package/dist/chunk-3BK2Y7GY.mjs +593 -0
  86. package/dist/chunk-3BK2Y7GY.mjs.map +1 -0
  87. package/dist/chunk-4OOHFJHT.mjs +421 -0
  88. package/dist/chunk-4OOHFJHT.mjs.map +1 -0
  89. package/dist/chunk-4XHB7IHT.mjs +809 -0
  90. package/dist/chunk-4XHB7IHT.mjs.map +1 -0
  91. package/dist/{chunk-2F3XZYRW.mjs → chunk-6M2HSSTQ.mjs} +16 -7
  92. package/dist/chunk-6M2HSSTQ.mjs.map +1 -0
  93. package/dist/{chunk-SO4LROOT.mjs → chunk-7FI7SQS7.mjs} +54 -69
  94. package/dist/chunk-7FI7SQS7.mjs.map +1 -0
  95. package/dist/chunk-ED57RCQE.mjs +171 -0
  96. package/dist/chunk-ED57RCQE.mjs.map +1 -0
  97. package/dist/chunk-FZN56HGQ.mjs +626 -0
  98. package/dist/chunk-FZN56HGQ.mjs.map +1 -0
  99. package/dist/chunk-GL4OOB25.mjs +51 -0
  100. package/dist/chunk-GL4OOB25.mjs.map +1 -0
  101. package/dist/chunk-KDG2NTDB.mjs +137 -0
  102. package/dist/chunk-KDG2NTDB.mjs.map +1 -0
  103. package/dist/chunk-NRBQWBDM.mjs +191 -0
  104. package/dist/chunk-NRBQWBDM.mjs.map +1 -0
  105. package/dist/chunk-O4V246GG.mjs +2137 -0
  106. package/dist/chunk-O4V246GG.mjs.map +1 -0
  107. package/dist/chunk-QT57H266.mjs +163 -0
  108. package/dist/chunk-QT57H266.mjs.map +1 -0
  109. package/dist/chunk-QX4RH25I.mjs +141 -0
  110. package/dist/chunk-QX4RH25I.mjs.map +1 -0
  111. package/dist/chunk-TB562PZX.mjs +86 -0
  112. package/dist/chunk-TB562PZX.mjs.map +1 -0
  113. package/dist/chunk-TDYPZXK5.mjs +1 -0
  114. package/dist/chunk-TDYPZXK5.mjs.map +1 -0
  115. package/dist/chunk-UJI4LN5P.mjs +36 -0
  116. package/dist/chunk-UJI4LN5P.mjs.map +1 -0
  117. package/dist/chunk-W6RTHQGP.mjs +1 -0
  118. package/dist/chunk-W6RTHQGP.mjs.map +1 -0
  119. package/dist/chunk-ZELBCPDC.mjs +369 -0
  120. package/dist/chunk-ZELBCPDC.mjs.map +1 -0
  121. package/dist/index.d.mts +1103 -544
  122. package/dist/index.d.ts +1103 -544
  123. package/dist/index.js +7032 -6033
  124. package/dist/index.js.map +1 -1
  125. package/dist/index.mjs +568 -2226
  126. package/dist/index.mjs.map +1 -1
  127. package/dist/resource-monitor-UZUGPIAU.mjs +9 -0
  128. package/dist/resource-monitor-UZUGPIAU.mjs.map +1 -0
  129. package/dist/storage-location-manager-HFNB3PCS.mjs +7 -0
  130. package/dist/storage-location-manager-HFNB3PCS.mjs.map +1 -0
  131. package/package.json +123 -2
  132. package/dist/builtins/local-backup/index.d.mts +0 -42
  133. package/dist/builtins/local-backup/index.d.ts +0 -42
  134. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.mts +0 -2
  135. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +0 -2
  136. package/dist/builtins/sqlite-storage/index.d.mts +0 -4
  137. package/dist/builtins/sqlite-storage/index.d.ts +0 -4
  138. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.mts +0 -2
  139. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +0 -2
  140. package/dist/builtins/winston-logging/index.d.mts +0 -30
  141. package/dist/builtins/winston-logging/index.d.ts +0 -30
  142. package/dist/chunk-2F3XZYRW.mjs.map +0 -1
  143. package/dist/chunk-LQFPAEQF.mjs +0 -147
  144. package/dist/chunk-LQFPAEQF.mjs.map +0 -1
  145. package/dist/chunk-R3DIIBBX.mjs +0 -532
  146. package/dist/chunk-R3DIIBBX.mjs.map +0 -1
  147. package/dist/chunk-SMNR44VG.mjs +0 -386
  148. package/dist/chunk-SMNR44VG.mjs.map +0 -1
  149. package/dist/chunk-SO4LROOT.mjs.map +0 -1
  150. package/dist/chunk-SPA4JBKN.mjs +0 -175
  151. package/dist/chunk-SPA4JBKN.mjs.map +0 -1
  152. package/dist/dist-3BY63UQ5.mjs +0 -2151
  153. package/dist/dist-3BY63UQ5.mjs.map +0 -1
  154. package/dist/filesystem-storage.addon-C42r589X.d.mts +0 -57
  155. package/dist/filesystem-storage.addon-C42r589X.d.ts +0 -57
  156. package/dist/sql-schema-CKz78rId.d.mts +0 -97
  157. package/dist/sql-schema-CKz78rId.d.ts +0 -97
  158. package/dist/sqlite-settings.addon-KwG-uKMP.d.mts +0 -79
  159. package/dist/sqlite-settings.addon-KwG-uKMP.d.ts +0 -79
  160. package/dist/storage-location-manager-KKDQNAKA.mjs +0 -7
  161. /package/dist/{storage-location-manager-KKDQNAKA.mjs.map → builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs.map} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,71 +1,10 @@
1
- import { IScopedLogger, ModelDownloadOptions, ModelDownloadResult, IAddonModelManager, ModelCatalogEntry, ModelFormat, IPythonEnvironment, PythonProbeResult, PythonEnvReady, PipelineConfig, ValidationResult, FrameInput, PipelineResult, AudioChunkInput, PipelineNode, ElementState as ElementState$1, LoggerFactory, ProcessConfig, ManagedProcessStatus, INetworkQualityTracker, ClientNetworkStats, DeviceNetworkStats, ReplResult, AgentInfo, AgentEntry, AgentStatus, AgentTask, TaskDispatchOptions, AgentTaskResult, LogEntry, LogFilter, AgentRegistrationInfo, AgentToHubMessage, HubToAgentMessage, AgentRuntimeStatus, ITaskHandler, TaskContext, IEventBus, FeatureManifest, FeatureFlag, SystemEvent, EventFilter, IStorageProvider as IStorageProvider$1, ISettingsBackend, TokenScope, ScopedToken, TokenPayload, UserRole, UserRecord, INotificationOutput, Notification, Toast, IAddonRouteProvider, IAddonHttpRoute, PlatformCapabilities, HardwareInfo, PlatformScore, ModelRequirement, ResolvedInferenceConfig, IIntegrationRegistry, CreateIntegrationInput, Integration, CreateDeviceInput, Device, ProviderListItem } from '@camstack/types';
2
- export { AgentEntry, AgentInfo, AgentResources, AgentStatus, AgentTask, AgentTaskResult, BackupManifest, ClientNetworkStats, DeviceNetworkStats, EventFilter, EventSource, FeatureFlag, FeatureManifest, INetworkQualityTracker, IScopedLogger, LogEntry, LogFilter, LogLevel, ManagedProcessStatus, ProcessConfig, ProcessStats, ProviderListItem, ReplResult, StreamNetworkStats, SystemEvent, TaskDispatchOptions, TokenPayload, UserRecord, UserRole, ValidationIssue, ValidationResult } from '@camstack/types';
1
+ import { FilesystemStorageProvider } from '@camstack/types/node';
2
+ export { FilesystemStorageProvider, PYTHON_VERSION, PlatformInfo, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from '@camstack/types/node';
3
+ import * as _camstack_types from '@camstack/types';
4
+ import { ModelCatalogEntry, ModelFormat, ModelDownloadOptions, ModelDownloadResult, IAddonModelManager, IPythonEnvironment, PythonProbeResult, PythonEnvReady, PipelineConfig, ValidationResult, IAddonResolver, FrameInput, PipelineResult, IEngineManager, IAddonCaller, INetworkQualityTracker, ClientNetworkStats, DeviceNetworkStats, ReplResult, BaseAddon, ProviderRegistration, ISettingsBackend, SettingsGetInput, SettingsSetInput, SettingsQueryInput, SettingsRecord, SettingsInsertInput, SettingsUpdateInput, SettingsDeleteInput, SettingsCountInput, SettingsIsEmptyInput, CollectionColumn, CollectionIndex, TableSchema, TableQueryOptions, ILogDestination as ILogDestination$1, LogEntry, LogFilter, IScopedLogger, IClusterBroker, IEventBus, BackupManifest, Alert, SystemResourceSnapshot, DiskSpaceInfo, MetricsGpuInfo, PidResourceStats, MemoryInfo, ConfigUISchemaWithValues, DeviceBindingEntry, FeatureManifest, FeatureFlag, SystemEvent, EventFilter, LogTags, LogExtras, IStorageProvider as IStorageProvider$1, SettingsStoreClient, TokenScope, ScopedToken, TokenPayload, UserRole, ApiKeyRecord, UserRecord, ICapabilityRegistry, INotificationOutput, Notification, Toast, IAddonRouteProvider, IAddonHttpRoute, IIntegrationRegistry, CreateIntegrationInput, Integration, CreateDeviceInput, PersistedDevice } from '@camstack/types';
5
+ export { ApiKeyRecord, BackupManifest, ClientNetworkStats, DeviceNetworkStats, EventFilter, EventSource, FeatureFlag, FeatureManifest, INetworkQualityTracker, IScopedLogger, LogEntry, LogFilter, LogLevel, ReplResult, StreamNetworkStats, SystemEvent, TokenPayload, UserRecord, UserRole, ValidationIssue, ValidationResult } from '@camstack/types';
3
6
  import { ChildProcess } from 'node:child_process';
4
- import { AddonLoader, AddonEngineManager, CapabilityRegistry } from '@camstack/kernel';
5
- export { A as AddonTableSchema, C as CORE_TABLE_DDL, F as FileSystemStorage, S as SettingsStore, a as SqliteStorageAddon, b as SqliteStorageProvider, c as addonTableToDdl } from './sql-schema-CKz78rId.js';
6
- export { WinstonDestination, WinstonLoggingAddon } from './builtins/winston-logging/index.js';
7
- export { BackupConfig, LocalBackupAddon, LocalBackupService } from './builtins/local-backup/index.js';
8
-
9
- interface PlatformInfo {
10
- readonly platform: NodeJS.Platform;
11
- readonly arch: NodeJS.Architecture;
12
- }
13
- declare function getPlatformInfo(): PlatformInfo;
14
- declare function buildBinaryPath(dataDir: string, name: string, platform?: string): string;
15
- /** Check if a binary exists in PATH */
16
- declare function findInPath(name: string): string | null;
17
- interface DownloadOptions {
18
- readonly name: string;
19
- readonly url: string;
20
- readonly targetDir: string;
21
- readonly targetName: string;
22
- readonly logger: IScopedLogger;
23
- readonly isArchive?: boolean;
24
- readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz';
25
- /** Relative path within archive to the binary (e.g., 'ffmpeg-6.1/bin/ffmpeg') */
26
- readonly archiveInnerPath?: string;
27
- }
28
- /**
29
- * Download a binary to the target directory.
30
- * Handles archives (zip, tar.gz, tar.xz) and raw binaries.
31
- */
32
- declare function downloadBinary(opts: DownloadOptions): Promise<string>;
33
- /**
34
- * Ensure a binary is available. Checks:
35
- * 1. Target path (already downloaded)
36
- * 2. System PATH
37
- * 3. Download from URL
38
- */
39
- declare function ensureBinary(opts: {
40
- readonly name: string;
41
- readonly targetDir: string;
42
- readonly downloadUrl: string;
43
- readonly logger: IScopedLogger;
44
- readonly isArchive?: boolean;
45
- readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz';
46
- readonly archiveInnerPath?: string;
47
- }): Promise<string>;
48
-
49
- declare function getFfmpegDownloadUrl(platform: string, arch: string): string;
50
- /**
51
- * Ensure ffmpeg binary is available.
52
- * Checks: deps dir → system PATH → download.
53
- */
54
- declare function ensureFfmpeg(dataDir: string, logger: IScopedLogger): Promise<string>;
55
-
56
- declare const PYTHON_VERSION = "3.12.12";
57
- declare function getPythonDownloadUrl(platform: string, arch: string): string;
58
- /**
59
- * Ensure a portable Python is available.
60
- * Checks: embedded python → system PATH → download portable.
61
- *
62
- * Returns the path to the python3 binary.
63
- */
64
- declare function ensurePython(dataDir: string, logger: IScopedLogger): Promise<string | null>;
65
- /**
66
- * Install Python packages into the portable Python environment.
67
- */
68
- declare function installPythonPackages(pythonPath: string, packages: readonly string[], logger: IScopedLogger): Promise<void>;
7
+ import Database from 'better-sqlite3';
69
8
 
70
9
  type EventCallback<T = unknown> = (data: T) => void;
71
10
  declare class EventBus {
@@ -93,6 +32,20 @@ declare function fetchJson(url: string): Promise<unknown>;
93
32
  * Legacy API preserved for backward compatibility -- delegates to downloadFile().
94
33
  */
95
34
  declare function downloadModel(options: ModelDownloadOptions): Promise<ModelDownloadResult>;
35
+ /** Progress callback: (downloadedBytes, totalBytes | undefined) */
36
+ type DownloadProgressCallback = (downloaded: number, total: number | undefined) => void;
37
+ /**
38
+ * Resolve a `ModelCatalogEntry` against `modelsDir`: download model file
39
+ * (or directory bundle) + extra files (labels JSON, charset dict, …),
40
+ * skip if already on disk. Returns the local model path.
41
+ */
42
+ declare function ensureModel(modelsDir: string, entry: ModelCatalogEntry, format: ModelFormat, onProgress?: DownloadProgressCallback): Promise<string>;
43
+ /** Compute the on-disk path for a given model + format, even when not yet downloaded. */
44
+ declare function getModelFilePath(modelsDir: string, entry: ModelCatalogEntry, format: ModelFormat): string | null;
45
+ /** True iff the model file (or `Manifest.json` for directory bundles) exists and is non-empty. */
46
+ declare function isModelDownloaded(modelsDir: string, entry: ModelCatalogEntry, format: ModelFormat): boolean;
47
+ /** Remove the on-disk model file/directory. Returns true if something was deleted. */
48
+ declare function deleteModelFromDisk(modelsDir: string, entry: ModelCatalogEntry, format: ModelFormat): boolean;
96
49
 
97
50
  /**
98
51
  * Unified model download service.
@@ -122,11 +75,6 @@ declare class ModelDownloadService implements IAddonModelManager {
122
75
  getModelsDir(): string;
123
76
  /** Check if a model file is already present on disk. */
124
77
  isDownloaded(modelId: string, format?: ModelFormat): boolean;
125
- /**
126
- * Legacy API: download a model by ID (delegates to ensure with default format).
127
- * Required by IAddonModelManager interface.
128
- */
129
- downloadModel(id: string): Promise<string>;
130
78
  /** Get the catalog entry for a model by ID. */
131
79
  getEntry(modelId: string): ModelCatalogEntry | undefined;
132
80
  private pickDefaultFormat;
@@ -141,7 +89,6 @@ declare class ModelDownloadService implements IAddonModelManager {
141
89
  }
142
90
 
143
91
  declare class PythonEnvManager implements IPythonEnvironment {
144
- private readonly dataDir;
145
92
  private venvPath;
146
93
  private cachedProbe;
147
94
  constructor(dataDir: string);
@@ -152,79 +99,59 @@ declare class PythonEnvManager implements IPythonEnvironment {
152
99
  spawn(script: string, args: readonly string[]): ChildProcess;
153
100
  }
154
101
 
102
+ /**
103
+ * Check if a pipeline step is available in the system.
104
+ * Implementations should check the capability registry for `pipeline-step:{stepId}`.
105
+ */
106
+ type StepExistsFn = (stepId: string) => boolean;
155
107
  declare class PipelineValidator {
156
- private readonly loader;
157
- constructor(loader: AddonLoader);
108
+ private readonly stepExists;
109
+ constructor(stepExists: StepExistsFn);
158
110
  validate(config: PipelineConfig): ValidationResult;
159
111
  private validateNode;
160
112
  }
161
113
 
162
114
  declare class PipelineRunner {
163
- private readonly engineManager;
164
- private readonly addonConfigs;
165
- constructor(engineManager: AddonEngineManager, addonConfigs: Map<string, Record<string, unknown>>);
115
+ private readonly addonResolver;
116
+ constructor(addonResolver: IAddonResolver);
166
117
  run(frame: FrameInput, config: PipelineConfig): Promise<PipelineResult>;
167
- /**
168
- * Run only the audio classification node on an audio chunk.
169
- * Used by the audio path in DetectionWiringService (separate from video pipeline).
170
- */
171
- runAudioNode(chunk: AudioChunkInput, audioNode: PipelineNode): Promise<PipelineResult>;
172
118
  private executeNode;
173
119
  private executeChildren;
174
120
  }
175
121
 
176
- type ProcessState = ElementState$1;
177
-
178
- /** Event emitter for process lifecycle events */
179
- type ProcessEventEmitter = {
180
- emitProcessCrashed(processId: string, code: number | null, signal: string | null, restartCount: number): void;
181
- emitProcessRestartScheduled(processId: string, restartCount: number, delayMs: number): void;
182
- emitProcessRestarted(processId: string, restartCount: number): void;
183
- };
184
- /** Re-export LoggerFactory as ProcessLoggerFactory for backward compatibility */
185
- type ProcessLoggerFactory = LoggerFactory;
186
-
187
- declare class ManagedProcess {
188
- private readonly config;
189
- private readonly events;
190
- private readonly logger;
191
- private childProcess;
192
- private _state;
193
- private _startedAt?;
194
- private restartTimer?;
195
- private _restartCount;
196
- private _lastCrashAt?;
197
- private _lastCrashError?;
198
- constructor(config: ProcessConfig, events: ProcessEventEmitter, logger: IScopedLogger);
199
- get state(): ProcessState;
200
- start(): Promise<void>;
201
- stop(): Promise<void>;
202
- getStatus(): ManagedProcessStatus;
203
- private scheduleRestart;
204
- private getStats;
205
- private getNextRestartTime;
122
+ /**
123
+ * Adapter that wraps an IEngineManager + addonConfigs as an IAddonResolver.
124
+ *
125
+ * Used for backwards compatibility — the in-process AddonEngineManager
126
+ * still uses the old getOrCreateEngine(addonId, globalConfig, override) API.
127
+ * This adapter bridges it to the new resolve(addonId, config) interface.
128
+ */
129
+ declare class EngineManagerResolver implements IAddonResolver {
130
+ private readonly engineManager;
131
+ private readonly addonConfigs;
132
+ constructor(engineManager: IEngineManager, addonConfigs: Map<string, Record<string, unknown>>);
133
+ resolve(addonId: string, config: Record<string, unknown>): Promise<IAddonCaller>;
134
+ shutdownAll(): Promise<void>;
206
135
  }
207
136
 
208
- declare class ProcessManager {
209
- private readonly events;
210
- private readonly loggerFactory;
211
- private readonly processes;
212
- constructor(events: ProcessEventEmitter, loggerFactory: ProcessLoggerFactory);
213
- register(config: ProcessConfig): ManagedProcess;
214
- start(id: string): Promise<void>;
215
- stop(id: string): Promise<void>;
216
- restart(id: string): Promise<void>;
217
- get(id: string): ManagedProcess;
218
- listAll(): readonly ManagedProcessStatus[];
219
- shutdownAll(): Promise<void>;
137
+ interface PidStats {
138
+ cpu: number;
139
+ memory: number;
220
140
  }
141
+ /**
142
+ * Get CPU% and memory RSS for one or more PIDs.
143
+ * Returns a map of pid → stats. Missing/dead PIDs are silently omitted.
144
+ */
145
+ declare function getPidStats(pids: number | readonly number[]): Promise<ReadonlyMap<number, PidStats>>;
146
+ /** Get CPU% and memory RSS for a single PID. Returns null if dead. */
147
+ declare function getSinglePidStats(pid: number): Promise<PidStats | null>;
221
148
 
222
149
  declare class NetworkQualityTracker implements INetworkQualityTracker {
223
150
  private readonly devices;
224
151
  private static readonly MAX_SAMPLES;
225
- reportStreamStats(deviceId: string, streamId: string, bitrateKbps: number, packetLoss?: number): void;
226
- reportClientStats(deviceId: string, stats: Omit<ClientNetworkStats, 'lastUpdated'>): void;
227
- getDeviceStats(deviceId: string): DeviceNetworkStats | null;
152
+ reportStreamStats(deviceId: number, streamId: string, bitrateKbps: number, packetLoss?: number): void;
153
+ reportClientStats(deviceId: number, stats: Omit<ClientNetworkStats, 'lastUpdated'>): void;
154
+ getDeviceStats(deviceId: number): DeviceNetworkStats | null;
228
155
  getAllStats(): readonly DeviceNetworkStats[];
229
156
  private getOrCreateDevice;
230
157
  }
@@ -232,7 +159,7 @@ declare class NetworkQualityTracker implements INetworkQualityTracker {
232
159
  interface ReplScope {
233
160
  readonly type: 'system' | 'provider' | 'device' | 'addon';
234
161
  readonly providerId?: string;
235
- readonly deviceId?: string;
162
+ readonly deviceId?: number;
236
163
  readonly addonId?: string;
237
164
  }
238
165
  interface ReplSessionContext {
@@ -248,12 +175,16 @@ interface IReplEngine {
248
175
  /**
249
176
  * Context provider that the server kernel implements.
250
177
  * The REPL engine calls this to build the sandbox for each scope type.
178
+ *
179
+ * Methods may return a Promise so the kernel can warm up async
180
+ * resources (e.g. `SystemManager.init()`) before exposing them — the
181
+ * engine awaits each before evaluating user code.
251
182
  */
252
183
  interface IReplContextProvider {
253
- getSystemSandbox(): Record<string, unknown>;
254
- getDeviceSandbox(deviceId: string): Record<string, unknown>;
255
- getProviderSandbox(providerId: string): Record<string, unknown>;
256
- getAddonSandbox(addonId: string): Record<string, unknown>;
184
+ getSystemSandbox(): Record<string, unknown> | Promise<Record<string, unknown>>;
185
+ getDeviceSandbox(deviceId: number): Record<string, unknown> | Promise<Record<string, unknown>>;
186
+ getProviderSandbox(providerId: string): Record<string, unknown> | Promise<Record<string, unknown>>;
187
+ getAddonSandbox(addonId: string): Record<string, unknown> | Promise<Record<string, unknown>>;
257
188
  }
258
189
 
259
190
  declare class ReplEngine implements IReplEngine {
@@ -264,203 +195,866 @@ declare class ReplEngine implements IReplEngine {
264
195
  private buildSandbox;
265
196
  }
266
197
 
267
- /** Event emitter for agent lifecycle and task events */
268
- type AgentEventEmitter = {
269
- emitAgentRegistered(agentId: string, capabilities: readonly string[]): void;
270
- emitAgentUnregistered(agentId: string): void;
271
- emitAgentOnline(agentId: string): void;
272
- emitAgentOffline(agentId: string): void;
273
- emitTaskDispatched(taskId: string, agentId: string, capability: string): void;
274
- };
198
+ interface FilesystemStorageConfig {
199
+ readonly rootPath: string;
200
+ }
201
+ /**
202
+ * Filesystem Storage addon — provides local disk storage for all location types.
203
+ * Capability: 'storage' (collection)
204
+ */
205
+ declare class FilesystemStorageAddon extends BaseAddon<FilesystemStorageConfig> {
206
+ private provider;
207
+ constructor();
208
+ protected onInitialize(): Promise<ProviderRegistration[]>;
209
+ protected onShutdown(): Promise<void>;
210
+ getProvider(): FilesystemStorageProvider | null;
211
+ }
275
212
 
276
- declare class AgentRegistry {
277
- private readonly events;
278
- private readonly heartbeatTimeoutMs;
279
- private readonly heartbeatCheckIntervalMs;
280
- private readonly agents;
281
- private readonly heartbeatIntervals;
282
- constructor(events: AgentEventEmitter, heartbeatTimeoutMs?: number, heartbeatCheckIntervalMs?: number);
283
- /** Register a new agent */
284
- registerAgent(info: AgentInfo): void;
285
- /** Remove agent */
286
- unregisterAgent(id: string): void;
287
- /** Update heartbeat timestamp */
288
- heartbeat(id: string): void;
289
- /** Find agents with a specific capability */
290
- getAgentsWithCapability(capability: string): readonly AgentEntry[];
291
- /** Get best agent for a task (least loaded) */
292
- selectAgent(capability: string, preferredId?: string): AgentEntry | null;
293
- /** List all agents with status */
294
- listAgents(): readonly AgentStatus[];
295
- /** Get single agent */
296
- getAgent(id: string): AgentEntry | null;
297
- /** Destroy all heartbeat intervals */
298
- destroy(): void;
299
- private startHeartbeatCheck;
213
+ /**
214
+ * SQLite implementation of ISettingsBackend.
215
+ *
216
+ * Every collection is structured: declared at boot (canonical KV
217
+ * collections) or via `declareCollection` (typed schemas like
218
+ * `pipeline-analytics:tracks`). The legacy auto-create-on-first-access
219
+ * path was removed — see commit history for the migration. Operations
220
+ * on undeclared collections throw at runtime so callers fail fast.
221
+ *
222
+ * WAL mode for concurrent reads.
223
+ */
224
+ declare class SqliteSettingsBackend implements ISettingsBackend {
225
+ private readonly dbPath;
226
+ private db;
227
+ private readonly structuredTables;
228
+ /** Map from scoped collection name set of column names (non-id) that
229
+ * the structured schema owns. Routes set/get/insert/update/query to
230
+ * typed columns. Every collection MUST be declared here before use. */
231
+ private readonly declaredCollections;
232
+ private readonly runtimeDefaults;
233
+ /**
234
+ * Canonical key/value collections declared with a `(id TEXT PK,
235
+ * data TEXT NOT NULL)` schema at boot so existing JSON-blob rows
236
+ * keep working through the structured path. Generates SQL identical
237
+ * to the previous legacy path; only the routing is unified.
238
+ */
239
+ private static readonly CANONICAL_KV_COLLECTIONS;
240
+ constructor(dbPath: string, runtimeDefaults?: Record<string, unknown>);
241
+ initialize(): Promise<void>;
242
+ private requireDeclared;
243
+ shutdown(): Promise<void>;
244
+ get({ namespace, collection, key }: SettingsGetInput): Promise<unknown>;
245
+ set({ namespace, collection, key, value }: SettingsSetInput): Promise<void>;
246
+ query<T extends object = Record<string, unknown>>({ namespace, collection, filter }: SettingsQueryInput): Promise<readonly SettingsRecord<T>[]>;
247
+ insert<T extends object = Record<string, unknown>>({ namespace, collection, record }: SettingsInsertInput<T>): Promise<void>;
248
+ update({ namespace, collection, id, data }: SettingsUpdateInput): Promise<void>;
249
+ delete({ namespace, collection, key }: SettingsDeleteInput): Promise<void>;
250
+ count({ namespace, collection, filter }: SettingsCountInput): Promise<number>;
251
+ isEmpty({ namespace, collection }: SettingsIsEmptyInput): Promise<boolean>;
252
+ private queryDeclared;
253
+ /** Get a system setting by dot-notation key */
254
+ getSystem(key: string): unknown;
255
+ /** Set a system setting */
256
+ setSystem(key: string, value: unknown): void;
257
+ /** Get all system settings as flat key-value */
258
+ getAllSystem(): Record<string, unknown>;
259
+ /** Get all settings for an addon */
260
+ getAllAddon(addonId: string): Record<string, unknown>;
261
+ /** Bulk-set all settings for an addon */
262
+ setAllAddon(addonId: string, config: Record<string, unknown>): void;
263
+ /** Get all settings for a provider */
264
+ getAllProvider(providerId: string): Record<string, unknown>;
265
+ /** Set a provider setting */
266
+ setProvider(providerId: string, key: string, value: unknown): void;
267
+ /** Get all settings for a device */
268
+ getAllDevice(deviceId: string): Record<string, unknown>;
269
+ /** Set a device setting */
270
+ setDevice(deviceId: string, key: string, value: unknown): void;
271
+ getAddonDevice(addonId: string, deviceId: string): Record<string, unknown>;
272
+ setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void;
273
+ clearAddonDevice(addonId: string, deviceId: string): void;
274
+ /** Seed system-settings with runtime defaults (first boot) */
275
+ private seedDefaults;
276
+ /**
277
+ * Expose the raw better-sqlite3 Database instance for components that
278
+ * need direct SQL access (e.g. DeviceStore, ConfigStore).
279
+ * Returns null if the backend has not been initialized yet.
280
+ */
281
+ getDatabase(): Database.Database | null;
282
+ private getDb;
283
+ private getAllScoped;
284
+ private setScopedKey;
285
+ private scopedName;
286
+ declareCollection(input: {
287
+ namespace?: string;
288
+ collection: string;
289
+ columns: readonly CollectionColumn[];
290
+ indexes?: readonly CollectionIndex[];
291
+ }): Promise<void>;
292
+ /** Serialise per-column values for SQL binding: objects → JSON, booleans → 0/1. */
293
+ private serializeColumnValue;
294
+ ensureTable(table: string, schema: TableSchema): Promise<void>;
295
+ tableInsert(table: string, row: Record<string, unknown>): Promise<void>;
296
+ tableUpdate(table: string, filter: Record<string, unknown>, updates: Record<string, unknown>): Promise<number>;
297
+ tableDelete(table: string, filter: Record<string, unknown>): Promise<number>;
298
+ tableQuery(table: string, options?: TableQueryOptions): Promise<readonly Record<string, unknown>[]>;
299
+ tableGet(table: string, filter: Record<string, unknown>): Promise<Record<string, unknown> | null>;
300
+ tableCount(table: string, filter?: Record<string, unknown>): Promise<number>;
301
+ private buildWhere;
300
302
  }
301
303
 
302
- declare class TaskDispatcher {
303
- private readonly agentRegistry;
304
- private readonly events;
305
- private readonly pendingTasks;
306
- private readonly localExecutors;
307
- constructor(agentRegistry: AgentRegistry, events: AgentEventEmitter);
308
- /** Dispatch a task to the best available agent */
309
- dispatch(task: AgentTask, options?: TaskDispatchOptions): Promise<AgentTaskResult>;
310
- /** Register a local executor for a capability (in-process fallback) */
311
- registerLocalExecutor(capability: string, executor: (input: Record<string, unknown>) => Promise<Record<string, unknown>>): void;
312
- /** Called when an agent returns a result */
313
- handleTaskResult(result: AgentTaskResult): void;
314
- private executeLocally;
315
- private sendToAgent;
304
+ /**
305
+ * SQLite Settings addon — provides persistent settings storage.
306
+ * Capability: 'settings-store' (singleton)
307
+ */
308
+ declare class SqliteSettingsAddon extends BaseAddon {
309
+ private backend;
310
+ constructor();
311
+ protected onInitialize(): Promise<ProviderRegistration[]>;
312
+ protected onShutdown(): Promise<void>;
313
+ getBackend(): SqliteSettingsBackend | null;
316
314
  }
317
315
 
318
- declare class LogRingBuffer {
319
- private readonly capacity;
320
- private readonly buffer;
321
- private head;
322
- private count;
323
- constructor(capacity?: number);
324
- push(entry: LogEntry): void;
325
- getAll(): LogEntry[];
326
- query(filter: LogFilter): LogEntry[];
316
+ /**
317
+ * Thin wrapper over better-sqlite3 that manages the four settings tables:
318
+ * system_settings, addon_settings, provider_settings, device_settings.
319
+ *
320
+ * All values are stored as JSON text and deserialized on read.
321
+ */
322
+ declare class SettingsStore {
323
+ private readonly db;
324
+ constructor(dbPath: string);
325
+ getSystem(key: string): unknown;
326
+ setSystem(key: string, value: unknown): void;
327
+ getAllSystem(): Record<string, unknown>;
328
+ getAddon(addonId: string, key: string): unknown;
329
+ setAddon(addonId: string, key: string, value: unknown): void;
330
+ getAllAddon(addonId: string): Record<string, unknown>;
331
+ /** Bulk-replace all keys for an addon (within a transaction). */
332
+ setAllAddon(addonId: string, config: Record<string, unknown>): void;
333
+ getProvider(providerId: string, key: string): unknown;
334
+ setProvider(providerId: string, key: string, value: unknown): void;
335
+ getAllProvider(providerId: string): Record<string, unknown>;
336
+ getDevice(deviceId: string, key: string): unknown;
337
+ setDevice(deviceId: string, key: string, value: unknown): void;
338
+ getAllDevice(deviceId: string): Record<string, unknown>;
339
+ getAddonDevice(addonId: string, deviceId: string): Record<string, unknown>;
340
+ /**
341
+ * Bulk-replace all per-device overrides for (addonId, deviceId) in a
342
+ * single transaction. Empty input clears the override set — caller
343
+ * should use `clearAddonDevice` for explicit resets.
344
+ */
345
+ setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void;
346
+ clearAddonDevice(addonId: string, deviceId: string): void;
347
+ /** Close the SQLite connection (call on shutdown). */
348
+ close(): void;
349
+ /** Check if system_settings is empty (used for first-boot seeding). */
350
+ isSystemSettingsEmpty(): boolean;
351
+ /** Seed system_settings with RUNTIME_DEFAULTS (only on first boot). */
352
+ seedDefaults(): void;
353
+ private initTables;
327
354
  }
328
355
 
329
- declare class ScopedLogger implements IScopedLogger {
330
- private readonly scope;
331
- private readonly writeFn;
332
- constructor(scope: string[], writeFn: (entry: LogEntry) => void);
333
- debug(message: string, meta?: Record<string, unknown>): void;
334
- info(message: string, meta?: Record<string, unknown>): void;
335
- warn(message: string, meta?: Record<string, unknown>): void;
336
- error(message: string, meta?: Record<string, unknown>): void;
337
- child(childScope: string): IScopedLogger;
338
- private write;
356
+ /** Core table DDL statements -- executed on first boot */
357
+ declare const CORE_TABLE_DDL: readonly string[];
358
+ /** Addon table schema declaration */
359
+ interface AddonTableSchema {
360
+ readonly name: string;
361
+ readonly columns: ReadonlyArray<{
362
+ readonly name: string;
363
+ readonly type: 'TEXT' | 'INTEGER' | 'REAL' | 'JSON';
364
+ readonly primaryKey?: boolean;
365
+ readonly notNull?: boolean;
366
+ }>;
367
+ readonly indexes?: ReadonlyArray<{
368
+ readonly name: string;
369
+ readonly columns: readonly string[];
370
+ readonly unique?: boolean;
371
+ }>;
372
+ }
373
+ /** Generate CREATE TABLE DDL from addon schema */
374
+ declare function addonTableToDdl(schema: AddonTableSchema): string[];
375
+
376
+ interface DeviceRow {
377
+ readonly stableId: string;
378
+ readonly type: string;
379
+ readonly name: string;
380
+ readonly parentStableId: string | null;
381
+ readonly enabled: boolean;
382
+ }
383
+ interface InsertInput {
384
+ readonly stableId: string;
385
+ readonly type: string;
386
+ readonly name: string;
387
+ readonly parentStableId: string | null;
388
+ }
389
+ declare class DeviceStore {
390
+ private readonly db;
391
+ constructor(db: Database.Database);
392
+ insert(addonId: string, device: InsertInput): void;
393
+ listByAddon(addonId: string): readonly DeviceRow[];
394
+ listChildren(addonId: string, parentStableId: string): readonly DeviceRow[];
395
+ remove(addonId: string, stableId: string): void;
339
396
  }
340
397
 
341
- interface AgentClientConfig {
342
- readonly hubUrl: string;
343
- readonly token?: string;
344
- readonly logger: IScopedLogger;
345
- readonly registrationInfo: AgentRegistrationInfo;
346
- }
347
- type MessageHandler = (msg: HubToAgentMessage) => void;
348
- type BinaryHandler = (data: Buffer) => void;
349
- type ConnectionHandler = () => void;
398
+ declare class ConfigStore {
399
+ private readonly db;
400
+ constructor(db: Database.Database);
401
+ save(addonId: string, stableId: string | null, data: Record<string, unknown>): void;
402
+ load(addonId: string, stableId: string | null): Record<string, unknown>;
403
+ remove(addonId: string, stableId: string | null): void;
404
+ }
405
+
406
+ interface WinstonConfig$1 {
407
+ readonly level: string;
408
+ readonly retentionDays: number;
409
+ /** Resolved absolute path to the logs directory (replaces the old dataPath field). */
410
+ readonly logsDir: string;
411
+ }
350
412
  /**
351
- * WebSocket client for agent mode. Framework-agnostic (no NestJS).
352
- * Connects to the hub, sends registration, handles messages,
353
- * reconnects with exponential backoff.
413
+ * Rotated-file log destination. Console output goes through the shared
414
+ * `formatLogLine` so every console sink (Winston, ConsoleDestination,
415
+ * HubForwarderDestination) emits identical lines. File output keeps a
416
+ * structured JSON shape so log tooling can query tags/meta.
354
417
  */
355
- declare class AgentClient {
356
- private ws;
357
- private reconnectAttempt;
358
- private reconnectTimer;
359
- private heartbeatTimer;
360
- private destroyed;
361
- private readonly messageHandlers;
362
- private readonly binaryHandlers;
363
- private readonly connectHandlers;
364
- private readonly disconnectHandlers;
365
- private readonly logger;
366
- private readonly hubUrl;
367
- private readonly token?;
368
- private readonly registrationInfo;
369
- private runtimeStatus;
370
- constructor(config: AgentClientConfig);
371
- /** Connect to the hub WebSocket */
372
- connect(): Promise<void>;
373
- /** Disconnect and stop reconnecting */
374
- disconnect(): void;
375
- /** Send a JSON control message to the hub */
376
- send(msg: AgentToHubMessage): void;
377
- /** Send a binary frame to the hub */
378
- sendBinary(data: Buffer): void;
379
- /** Register a handler for JSON messages from hub */
380
- onMessage(handler: MessageHandler): void;
381
- /** Register a handler for binary frames from hub */
382
- onBinaryFrame(handler: BinaryHandler): void;
383
- /** Register a handler for successful connection */
384
- onConnect(handler: ConnectionHandler): void;
385
- /** Register a handler for disconnection */
386
- onDisconnect(handler: ConnectionHandler): void;
387
- /** Update the runtime status (used in heartbeat) */
388
- updateStatus(status: AgentRuntimeStatus): void;
389
- /** Whether currently connected */
390
- get connected(): boolean;
391
- private doConnect;
392
- private handleBuiltinMessage;
393
- private scheduleReconnect;
394
- private startHeartbeat;
395
- private stopHeartbeat;
396
- private clearTimers;
418
+ declare class WinstonDestination implements ILogDestination$1 {
419
+ private logger;
420
+ initialize(config?: WinstonConfig$1): Promise<void>;
421
+ write(entry: LogEntry): void;
422
+ query(_filter: LogFilter): Promise<readonly LogEntry[]>;
423
+ shutdown(): Promise<void>;
424
+ }
425
+
426
+ interface WinstonConfig {
427
+ readonly level: string;
428
+ readonly retentionDays: number;
429
+ }
430
+ /**
431
+ * Winston logging addon — rotated log files + console output.
432
+ * Settings appear under Cluster → NodeDetail → Settings.
433
+ */
434
+ declare class WinstonLoggingAddon extends BaseAddon<WinstonConfig> {
435
+ private destination;
436
+ constructor();
437
+ protected onInitialize(): Promise<ProviderRegistration[]>;
438
+ protected onShutdown(): Promise<void>;
439
+ getDestination(): WinstonDestination;
440
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
441
+ }
442
+
443
+ /**
444
+ * Zero-dependency log destination that writes every entry to stdout /
445
+ * stderr via the shared `formatLogLine` formatter. Same surface as
446
+ * `WinstonDestination` (full `ILogDestination` contract) but without
447
+ * the rotating-file transport — ideal fallback when the Winston addon
448
+ * is disabled or unavailable (e.g. bare-bones agents, tests).
449
+ */
450
+
451
+ interface ConsoleConfig$1 {
452
+ /** Minimum log level to emit. Entries below this level are dropped. */
453
+ readonly level?: 'debug' | 'info' | 'warn' | 'error';
454
+ }
455
+ declare class ConsoleDestination implements ILogDestination$1 {
456
+ private minLevel;
457
+ initialize(config?: ConsoleConfig$1): Promise<void>;
458
+ write(entry: LogEntry): void;
459
+ query(_filter: LogFilter): Promise<readonly LogEntry[]>;
460
+ shutdown(): Promise<void>;
461
+ }
462
+
463
+ interface ConsoleConfig {
464
+ readonly level: 'debug' | 'info' | 'warn' | 'error';
465
+ }
466
+ /**
467
+ * Console logging addon — zero-dependency stdout/stderr sink that
468
+ * produces the same canonical line format as the Winston built-in.
469
+ * Registers as a `log-destination` capability provider so the shared
470
+ * LogManager fans every entry to both sinks when both are active.
471
+ *
472
+ * Settings live under Cluster → NodeDetail → Settings alongside the
473
+ * Winston addon. Disable one without disabling the other — the
474
+ * LogManager keeps working as long as at least one destination is
475
+ * registered.
476
+ */
477
+ declare class ConsoleLoggingAddon extends BaseAddon<ConsoleConfig> {
478
+ private destination;
479
+ constructor();
480
+ protected onInitialize(): Promise<ProviderRegistration[]>;
481
+ protected onShutdown(): Promise<void>;
482
+ getDestination(): ConsoleDestination;
483
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
484
+ }
485
+
486
+ /**
487
+ * Log destination for agent mode.
488
+ *
489
+ * - Writes to console ONLY when hub is not yet reachable (cold-start
490
+ * fallback). Forked-worker agents share stdout with their parent
491
+ * hub, so logging to local console AND forwarding to hub's
492
+ * `log-receiver` would print the entry twice on the hub terminal.
493
+ * Once the hub is discovered, local console output stops and the
494
+ * hub's own ConsoleDestination owns the render.
495
+ * - When a Moleculer broker is connected and the hub node is reachable,
496
+ * forwards every log entry to the hub via `log-receiver.ingest`.
497
+ * - Entries written before the hub is reachable are buffered internally
498
+ * (ring-buffered to `DEFAULT_OUTBOUND_BUFFER_SIZE`) and flushed on connect,
499
+ * so boot-time logs still reach the hub without any external replay trigger.
500
+ */
501
+ declare class HubForwarderDestination implements ILogDestination$1 {
502
+ private broker;
503
+ private hubConnected;
504
+ private readonly outboundBuffer;
505
+ private statusLogger;
506
+ initialize(): Promise<void>;
507
+ /**
508
+ * Provide a logger for the destination's own status messages
509
+ * (discovery, disconnect, reconnect). Routed through the agent's
510
+ * LogManager but never through this same destination — the owning
511
+ * addon must hand in a logger that does NOT route back here, or
512
+ * status writes will recurse.
513
+ */
514
+ setStatusLogger(logger: IScopedLogger): void;
515
+ private status;
516
+ /**
517
+ * Attach the Moleculer broker so hub forwarding can start.
518
+ * Called by the owning addon from `onInitialize(ctx)` with
519
+ * `ctx.kernel.cluster?.broker`.
520
+ */
521
+ connectBroker(broker: IClusterBroker): void;
522
+ write(entry: LogEntry): void;
523
+ query(_filter: LogFilter): Promise<readonly LogEntry[]>;
524
+ shutdown(): Promise<void>;
525
+ private bufferOutbound;
526
+ private flushOutbound;
527
+ private forward;
397
528
  }
398
529
 
399
530
  /**
400
- * Receives task assignments from hub, routes to registered ITaskHandler instances.
401
- * Maintains handler registry, manages task lifecycle.
402
- * Framework-agnostic (no NestJS).
531
+ * Agent-only addon that provides the `log-destination` capability.
532
+ * Writes all logs to the console and forwards to the hub via Moleculer when
533
+ * reachable. The broker is read from `ctx.kernel.cluster?.broker`; outbound
534
+ * log replay on hub-connect is handled internally by the destination.
403
535
  */
404
- declare class AgentTaskRunner {
405
- private readonly agentId;
406
- private readonly client;
407
- private readonly handlers;
408
- private readonly runningTasks;
536
+ declare class HubForwarderAddon extends BaseAddon {
537
+ private destination;
538
+ constructor();
539
+ protected onInitialize(): Promise<ProviderRegistration[]>;
540
+ getDestination(): HubForwarderDestination | null;
541
+ protected onShutdown(): Promise<void>;
542
+ }
543
+
544
+ interface BackupConfig {
545
+ readonly backupDir: string;
546
+ readonly retentionCount: number;
547
+ }
548
+ declare class LocalBackupService {
549
+ private readonly config;
409
550
  private readonly logger;
410
- constructor(agentId: string, client: AgentClient, logger: IScopedLogger);
411
- /** Register a task handler for a given task type */
412
- registerHandler(handler: ITaskHandler): void;
413
- /** Unregister a task handler */
414
- unregisterHandler(taskType: string): void;
415
- /** Get all registered task types */
416
- getTaskTypes(): readonly string[];
417
- /** Get a handler by task type */
418
- getHandler(taskType: string): ITaskHandler | undefined;
551
+ private readonly eventBus;
552
+ private manifests;
553
+ constructor(config: BackupConfig, logger: IScopedLogger, eventBus: IEventBus);
554
+ /** Create a backup of specified locations */
555
+ backup(options?: {
556
+ locations?: string[];
557
+ label?: string;
558
+ }): Promise<BackupManifest>;
559
+ /** Restore from a backup */
560
+ restore(backupId: string): Promise<void>;
561
+ /** List all backups sorted by timestamp descending */
562
+ list(): readonly BackupManifest[];
563
+ /** Delete a specific backup */
564
+ delete(backupId: string): Promise<void>;
565
+ private pruneOldBackups;
566
+ }
567
+
568
+ interface LocalBackupAddonConfig {
569
+ readonly retentionCount: number;
570
+ }
571
+ /**
572
+ * Local backup addon — snapshot-based backup/restore of camstack data.
573
+ * Settings appear under Cluster → NodeDetail → Settings.
574
+ */
575
+ declare class LocalBackupAddon extends BaseAddon<LocalBackupAddonConfig> {
576
+ private service;
577
+ constructor();
578
+ protected onInitialize(): Promise<ProviderRegistration[]>;
579
+ protected onShutdown(): Promise<void>;
580
+ getService(): LocalBackupService;
581
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
582
+ }
583
+
584
+ interface NativeMetricsConfig {
585
+ readonly samplingIntervalMs: number;
586
+ }
587
+ /**
588
+ * Native metrics — CPU, memory, disk usage sampling.
589
+ * Settings appear under Cluster → NodeDetail → Settings.
590
+ */
591
+ declare class NativeMetricsAddon extends BaseAddon<NativeMetricsConfig> {
592
+ private provider;
593
+ private startedAtMs;
594
+ private snapshotTimer;
419
595
  /**
420
- * Execute a task dispatched from the hub.
421
- * Sends result or error back to hub via the AgentClient.
596
+ * Snapshot-equality cache for the resources + processes emit.
597
+ * Stores the coarsened JSON and timestamp; a tick where the
598
+ * coarsened payload matches the cache (and the heartbeat hasn't
599
+ * elapsed) is skipped.
422
600
  */
423
- executeTask(taskId: string, taskType: string, payload: unknown): Promise<void>;
424
- /** Cancel a running task */
425
- cancelTask(taskId: string): Promise<void>;
426
- /** Number of currently running tasks */
427
- get activeTaskCount(): number;
428
- /** Destroy: cancel all running tasks */
429
- destroy(): Promise<void>;
601
+ private lastResourcesEmit;
602
+ private lastProcessesEmit;
603
+ constructor();
604
+ protected onInitialize(): Promise<ProviderRegistration[]>;
605
+ protected onShutdown(): Promise<void>;
606
+ /**
607
+ * Emit one `metrics.node-resources-snapshot` + one
608
+ * `metrics.node-processes-snapshot` for this node. UI consumers
609
+ * subscribe and read state directly from the payload.
610
+ */
611
+ private emitMetricsSnapshots;
612
+ protected onConfigChanged(): Promise<void>;
613
+ private listWorkerInstances;
614
+ private listAddonInstances;
615
+ private getAddonStats;
616
+ /**
617
+ * Walk the OS process table and classify each camstack-shaped process.
618
+ *
619
+ * Classification (ancestry-driven, NOT pattern-driven):
620
+ * - root — the current node's own pid (`process.pid`).
621
+ * - managed — pid is registered in the kernel's `$process.list`
622
+ * (forked addon worker spawned by this hub).
623
+ * - system — ancestry walk crosses a SUPERVISOR_BOUNDARY_RE match
624
+ * (tsx-watch launcher, agent CLI, concurrently, vite,
625
+ * npm exec wrapper). The process belongs to the dev
626
+ * tree even if not in `$process.list`. NEVER killable.
627
+ * - ghost — ancestry walk reaches `ppid=1` without crossing any
628
+ * supervisor boundary AND the parent isn't visible in
629
+ * `ps`. A truly orphaned camstack-shaped process. The
630
+ * ONLY classification that's eligible for kill.
631
+ *
632
+ * Old pattern-only ghost detection produced false positives: every
633
+ * monorepo-path process matched CAMSTACK_CMD_RE, ancestry walk
634
+ * stopping at ppid=hub returned false-positive ghosts whenever a
635
+ * concurrently sibling sat above hub. Ancestry-driven classification
636
+ * fixes that.
637
+ */
638
+ private listNodeProcesses;
639
+ /**
640
+ * Send SIGTERM / SIGKILL to a pid. Refuses pids that don't appear in
641
+ * `listNodeProcesses()` to prevent arbitrary system kills — a dedicated
642
+ * admin-path for resurrected zombies, not a generic shell replacement.
643
+ *
644
+ * `root`-classified pids (the running launcher / agent CLI / hub itself)
645
+ * are also refused: killing them tears down the whole node and the
646
+ * operator's intent is almost always to nuke a leaked child, not the
647
+ * supervisor that keeps the rest alive. Process restart goes through
648
+ * the dedicated `$process.restart` action, not this kill API.
649
+ */
650
+ private killProcess;
651
+ /** Raw `ps` scan returning every pid + command + resource stats. */
652
+ private runPs;
653
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
430
654
  }
431
655
 
656
+ interface AlertCenterConfig {
657
+ readonly maxAlerts: number;
658
+ readonly retentionDays: number;
659
+ }
432
660
  /**
433
- * Stub task handler for pipeline.decode.
434
- * In the full implementation, this would start a StreamBroker for a camera
435
- * and send decoded frames to the hub via the AgentClient.
661
+ * Alert Center addon core builtin that persists alerts in a
662
+ * structured SQLite table and serves them to the admin UI.
663
+ *
664
+ * Registers as an `alerts` collection provider and subscribes to
665
+ * important EventBus categories to create/update alerts automatically.
436
666
  */
437
- declare class DecodeTaskHandler implements ITaskHandler {
438
- readonly taskType = "pipeline.decode";
439
- readonly description = "Decode an RTSP stream and produce JPEG frames";
440
- handle(payload: unknown, context: TaskContext): Promise<unknown>;
441
- cancel(): Promise<void>;
667
+ declare class AlertCenterAddon extends BaseAddon<AlertCenterConfig> {
668
+ private unsubscribers;
669
+ constructor();
670
+ /**
671
+ * Per `(category, source.id)` counter used to rate-limit chatty
672
+ * events. Entries are pruned lazily when they age out of
673
+ * `RATE_LIMIT_WINDOW_MS`; see `shouldSuppressByRate()` below.
674
+ */
675
+ private readonly rateLimitCounters;
676
+ protected onInitialize(): Promise<ProviderRegistration[]>;
677
+ protected onShutdown(): Promise<void>;
678
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
679
+ listAlerts(filter?: {
680
+ unreadOnly?: boolean;
681
+ limit?: number;
682
+ }): Promise<readonly Alert[]>;
683
+ getUnreadCount(): Promise<number>;
684
+ markRead(alertId: string): Promise<void>;
685
+ markAllRead(): Promise<void>;
686
+ dismiss(alertId: string): Promise<void>;
687
+ private handleEvent;
688
+ private handleModelDownload;
689
+ /**
690
+ * `[ALERT-CENTER-EVENTS]` — rate limit a `(category, source.id)`
691
+ * tuple. Returns `true` when the caller should DROP this event
692
+ * without persisting an alert, and `false` when the alert should
693
+ * proceed normally.
694
+ *
695
+ * Heuristic: the first `RATE_LIMIT_MAX_PER_SOURCE` events within a
696
+ * `RATE_LIMIT_WINDOW_MS` window go through. Once the threshold is
697
+ * crossed, subsequent events are suppressed until the window rolls
698
+ * over. When the threshold is first crossed, the suppression is
699
+ * logged once so operators know why they're not seeing the follow-
700
+ * up alerts; after the window rolls over the next event is
701
+ * un-suppressed and starts a new window.
702
+ */
703
+ private shouldSuppressByRate;
704
+ private buildMessage;
705
+ private emitAlert;
706
+ private updateAlert;
707
+ /**
708
+ * Read-merge-write: SettingsBackend.update() replaces the entire `data` blob,
709
+ * so we must read the existing record, merge the patch, and write back.
710
+ */
711
+ private mergeUpdate;
712
+ private persistAlert;
713
+ private emitAlertCreatedEvent;
714
+ private emitAlertUpdatedEvent;
715
+ private enforceMaxAlerts;
716
+ private enforceRetention;
442
717
  }
718
+
719
+ interface SamplingDiagnostics {
720
+ warn(message: string, meta?: Record<string, unknown>): void;
721
+ }
722
+ declare class NativeMetricsProvider {
723
+ readonly id = "native";
724
+ private cachedSnapshot;
725
+ private samplingTimer;
726
+ private macMemTimer;
727
+ private prevCpu;
728
+ private diagnostics;
729
+ private consecutiveSampleErrors;
730
+ constructor();
731
+ /**
732
+ * Optional diagnostics sink for sampling errors. The first failure and
733
+ * every Nth failure thereafter are reported so a permanently-broken
734
+ * collector doesn't spam logs but is still observable.
735
+ */
736
+ setDiagnostics(diag: SamplingDiagnostics): void;
737
+ startSampling(intervalMs: number): void;
738
+ private reportSampleError;
739
+ stopSampling(): void;
740
+ collectSnapshot(): Promise<SystemResourceSnapshot>;
741
+ getCached(): Promise<SystemResourceSnapshot | null>;
742
+ getCurrent(): Promise<{
743
+ cpuPercent: number;
744
+ memoryPercent: number;
745
+ memoryUsedMB: number;
746
+ memoryTotalMB: number;
747
+ diskPercent?: number;
748
+ temperature?: number;
749
+ gpuPercent?: number;
750
+ gpuMemoryPercent?: number;
751
+ }>;
752
+ getDiskSpace(input: {
753
+ dirPath: string;
754
+ }): Promise<DiskSpaceInfo>;
755
+ getGpuInfo(): Promise<MetricsGpuInfo | null>;
756
+ getCpuTemperature(): Promise<number | null>;
757
+ getProcessStats(input: {
758
+ pids: number[];
759
+ }): Promise<PidResourceStats[]>;
760
+ private sampleCpu;
761
+ /** Cached macOS memory (set by sampleMacMemory timer). */
762
+ _cachedMacMem: MemoryInfo | null;
763
+ private getMemoryInfo;
764
+ }
765
+
766
+ declare class SystemConfigAddon extends BaseAddon {
767
+ readonly id = "system-config";
768
+ constructor();
769
+ protected onInitialize(): Promise<void>;
770
+ private buildGlobalSchema;
771
+ getGlobalSettings(): Promise<ConfigUISchemaWithValues>;
772
+ updateGlobalSettings(patch: Record<string, unknown>): Promise<void>;
773
+ }
774
+
443
775
  /**
444
- * Stub task handler for pipeline.detect.
445
- * In the full implementation, this would receive frames (via binary WS),
446
- * run them through the detection engine, and send results back.
776
+ * Local Auth Addon owns user accounts, API keys, scoped tokens,
777
+ * and local password authentication.
778
+ *
779
+ * Capabilities registered:
780
+ * - `auth-provider` (collection) — local password authentication
781
+ * - `user-management` (singleton) — user CRUD, API keys, scoped tokens
782
+ *
783
+ * Extension: an OIDC addon can register another `auth-provider` entry.
784
+ * The server's login flow iterates the `auth-provider` collection.
447
785
  */
448
- declare class DetectTaskHandler implements ITaskHandler {
449
- readonly taskType = "pipeline.detect";
450
- readonly description = "Run object detection on received frames";
451
- handle(payload: unknown, context: TaskContext): Promise<unknown>;
452
- cancel(): Promise<void>;
786
+
787
+ interface LocalAuthConfig {
788
+ jwtSecret?: string;
789
+ adminUsername?: string;
790
+ adminPassword?: string;
791
+ }
792
+ declare class LocalAuthAddon extends BaseAddon<LocalAuthConfig> {
793
+ private authManager;
794
+ private userManager;
795
+ private apiKeyManager;
796
+ private scopedTokenManager;
797
+ constructor();
798
+ protected onInitialize(): Promise<ProviderRegistration[]>;
799
+ protected onShutdown(): Promise<void>;
453
800
  }
801
+
454
802
  /**
455
- * Stub task handler for pipeline.record.
456
- * In the full implementation, this would start recording an RTSP stream
457
- * to disk in segments and report segment metadata back to the hub.
803
+ * Wire shape matching `z.infer<typeof SettingsSchemaWithValuesSchema>` —
804
+ * duplicated as a plain interface because importing the Zod schema across
805
+ * package boundaries confuses `tsc` when the types package pins a
806
+ * different `zod` minor than the core package. Keeping the shape local
807
+ * keeps the addon decoupled from the schema's internal type encoding.
458
808
  */
459
- declare class RecordTaskHandler implements ITaskHandler {
460
- readonly taskType = "pipeline.record";
461
- readonly description = "Record an RTSP stream to disk segments";
462
- handle(payload: unknown, context: TaskContext): Promise<unknown>;
463
- cancel(): Promise<void>;
809
+ interface ContributionShape {
810
+ tabs?: Array<{
811
+ id: string;
812
+ label: string;
813
+ icon: string;
814
+ order?: number;
815
+ }>;
816
+ sections: Array<{
817
+ id: string;
818
+ title: string;
819
+ description?: string;
820
+ style?: 'card' | 'accordion';
821
+ defaultCollapsed?: boolean;
822
+ columns?: 1 | 2 | 3 | 4;
823
+ tab?: string;
824
+ /** Where the section renders. Default 'settings' (Config tab); 'top-tab' hoists into the device-detail tab bar via DeviceDetail discovery. */
825
+ location?: 'settings' | 'top-tab';
826
+ order?: number;
827
+ fields: unknown[];
828
+ }>;
829
+ }
830
+
831
+ declare class DeviceManagerAddon extends BaseAddon {
832
+ constructor();
833
+ /** Shorthand for the kernel-injected capability registry. */
834
+ private get capabilityRegistry();
835
+ /**
836
+ * Parent-chain event propagator. Started in `onInitialize` once the
837
+ * hub's `deviceRegistry` is available; listens to every device-sourced
838
+ * event and re-emits a copy on each ancestor scope with `via[]`
839
+ * populated. Stopped in `onShutdown`.
840
+ */
841
+ private propagator;
842
+ /**
843
+ * Hub-side mirror of every device's cap-keyed runtime state.
844
+ * Populated whenever any caller writes via `deviceState.setCapSlice`
845
+ * (the canonical cross-layer write entrypoint) and on first load
846
+ * via `loadRuntimeState`. Cross-process consumers reach the mirror
847
+ * through the `deviceState` cap router; per-cap event subscribers
848
+ * (e.g. `battery.onStatusChanged`) get the same data via
849
+ * cap-specific events still emitted by the owning device.
850
+ *
851
+ * Key: deviceId. Value: per-cap slice map. Empty by default —
852
+ * slices show up as `setCapSlice` calls trickle in.
853
+ */
854
+ private readonly stateMirror;
855
+ /**
856
+ * Per-device disk-write debouncer for runtime-state. `setCapSlice`
857
+ * updates the in-memory mirror synchronously and emits the change
858
+ * event immediately, but the disk write is coalesced — frequent
859
+ * back-to-back writes (motion phase transitions, battery pushes,
860
+ * etc.) collapse to one `writeDeviceRuntimeState` per
861
+ * `RUNTIME_STATE_DEBOUNCE_MS` window. `flushRuntimeStateWrites`
862
+ * awaits any in-flight write + scheduled flush so shutdown is
863
+ * lossless.
864
+ */
865
+ private readonly runtimeStateDebounce;
866
+ private static readonly RUNTIME_STATE_DEBOUNCE_MS;
867
+ /**
868
+ * Cross-process native-provider cache: deviceId (numeric) → capName → { addonId, nodeId }.
869
+ * Kept in sync with `device.bindings-changed` events emitted by forked
870
+ * workers. Union'd into `getBindings` so hub-side consumers see every
871
+ * native cap regardless of which process owns the IDevice. No persistence
872
+ * — entries re-register when the worker restarts. Purged on
873
+ * `$node.disconnected` to avoid stale routing after a worker crash.
874
+ */
875
+ private remoteNativeCaps;
876
+ /** Wait for a device-provider by addonId, returning null on timeout. */
877
+ private waitDeviceProvider;
878
+ /** Require a device-provider by addonId — throws if not found. */
879
+ private requireDeviceProvider;
880
+ private readBindingsStore;
881
+ private writeBindingsStore;
882
+ private resolveWrapperNodeId;
883
+ /**
884
+ * Active discovery of native caps registered on a worker node.
885
+ * Called from the `$node.connected` handler to recover from lost
886
+ * `DeviceBindingsChanged` broadcasts after worker restart.
887
+ *
888
+ * Iterates the broker's service registry, picks
889
+ * `<addonId>.native-provider.<capName>` services owned by the
890
+ * connected node, calls each service's `$listDeviceIds` action to
891
+ * fetch the deviceIds the worker has registered that cap on, then
892
+ * writes entries into `remoteNativeCaps`.
893
+ *
894
+ * Best-effort: per-service failures are logged and swallowed so a
895
+ * single bad service doesn't abort the whole rebuild.
896
+ */
897
+ private discoverWorkerNativeCaps;
898
+ getBindings(input: {
899
+ deviceId: number;
900
+ }): Promise<{
901
+ deviceId: number;
902
+ entries: DeviceBindingEntry[];
903
+ }>;
904
+ /**
905
+ * Whole-fleet binding dump. Iterates every device known to the
906
+ * deviceRegistry and reuses the per-device `getBindings` resolver
907
+ * for each — same routing rules, single round-trip. Used by
908
+ * `SystemManager.init()` for warm-boot.
909
+ *
910
+ * Bindings change rarely (wrapper toggle, device add/remove) so
911
+ * clients invalidate via the existing
912
+ * `capability.binding-changed` event rather than re-fetching this
913
+ * payload periodically.
914
+ */
915
+ getAllBindings(): Promise<Array<{
916
+ deviceId: number;
917
+ entries: DeviceBindingEntry[];
918
+ }>>;
919
+ /**
920
+ * Resolve a numeric deviceId to a stableId via persisted meta.
921
+ * Used only by the device-identity section of the device-details
922
+ * aggregator (see `buildBaseDeviceSection`) to surface the stableId as
923
+ * a readonly display field. All runtime/registry lookups are keyed by
924
+ * numeric deviceId; this helper is display-only.
925
+ */
926
+ private lookupPersistedStableId;
927
+ getDeviceAggregate(deviceId: number, kind: 'settings' | 'live'): Promise<ContributionShape | null>;
928
+ /**
929
+ * Build the device-manager's own contribution to the aggregator — the
930
+ * device identity (id, stableId, addonId, type, online) + the
931
+ * driver-specific config exposed by the device class via
932
+ * `zodEntriesToConfigUI`.
933
+ *
934
+ * Two paths, deliberately symmetric with `getSettingsSchema`:
935
+ *
936
+ * - Hub-local: device's IDevice instance lives in this process'
937
+ * DeviceRegistry, we read config + schema directly by reference.
938
+ * - Cross-process: device lives in a forked worker (RtspCamera on
939
+ * provider-rtsp, ONVIF on provider-onvif, …). We ask the worker's
940
+ * `device-ops.getSettingsSchema` native provider for a wire-
941
+ * serializable ConfigUISchema and merge it in under the same
942
+ * "Driver Config" section, so the UI sees the same shape regardless
943
+ * of where the IDevice physically runs.
944
+ *
945
+ * Returns `null` only when the device genuinely doesn't exist anywhere
946
+ * (no hub-local, no persisted ownership, no device-ops native). The
947
+ * aggregator falls back to contributor sections only in that case.
948
+ */
949
+ private buildBaseDeviceSection;
950
+ /**
951
+ * Lookup the native owner for `device-ops` on `deviceId` — the native-cap
952
+ * registry (hub-local and remote) is keyed by numeric id.
953
+ */
954
+ private resolveNativeDeviceOwner;
955
+ /**
956
+ * Aggregate `status` across every registered cap for a device.
957
+ *
958
+ * Walks the supplied cap list (or `CAP_NAMES_WITH_STATUS` when
959
+ * omitted), looks up a native provider per cap via the capability
960
+ * registry, calls `provider.getStatus({ deviceId })`, and validates
961
+ * the return against the cap's own `status.schema`. Validation
962
+ * failures log a warning and yield `null` for that cap so the
963
+ * overall aggregate stays usable — a single misbehaving provider
964
+ * must not blank out a device's entire status view.
965
+ *
966
+ * Returned shape is `Record<capName, unknown | null>`; the client-
967
+ * side hook tightens this to `CapStatusTypeMap` via the generated
968
+ * `cap-status-types.ts`.
969
+ */
970
+ private getDeviceStatusAggregate;
971
+ /**
972
+ * Return the driver-specific device-settings contribution. Hub-local
973
+ * devices call `getSettingsUISchema()` directly; forked-worker devices
974
+ * go through the `device-ops.getSettingsSchema` cap method on the
975
+ * numeric-id-keyed native registry.
976
+ */
977
+ private resolveDriverConfigSchema;
978
+ updateDeviceField(input: {
979
+ deviceId: number;
980
+ writerCapName: string;
981
+ writerAddonId: string;
982
+ key: string;
983
+ value: unknown;
984
+ }): Promise<{
985
+ success: true;
986
+ }>;
987
+ /**
988
+ * Batched counterpart of `updateDeviceField`. Groups changes by
989
+ * `(writerCapName, writerAddonId)` so each contributor receives a
990
+ * single `applyDeviceSettingsPatch` with all of its updates merged —
991
+ * avoids N round-trips for simultaneous edits in the same save.
992
+ *
993
+ * Per-provider failures are captured in the `failures[]` output so the
994
+ * admin UI can highlight which sections didn't persist; a failure on
995
+ * one provider does NOT abort the others.
996
+ */
997
+ updateDeviceFieldsBatch(input: {
998
+ deviceId: number;
999
+ changes: ReadonlyArray<{
1000
+ writerCapName: string;
1001
+ writerAddonId: string;
1002
+ key: string;
1003
+ value: unknown;
1004
+ }>;
1005
+ }): Promise<{
1006
+ success: true;
1007
+ failures: {
1008
+ writerCapName: string;
1009
+ writerAddonId: string;
1010
+ error: string;
1011
+ }[];
1012
+ }>;
1013
+ /** Apply a single grouped patch to the appropriate provider. Mirrors
1014
+ * `updateDeviceField` routing (special-case device-manager, else
1015
+ * registry lookup). Used by `updateDeviceFieldsBatch`. */
1016
+ private applyGroupPatch;
1017
+ listWrappersForCap(input: {
1018
+ capName: string;
1019
+ }): Promise<string[]>;
1020
+ listBindableCapsForDeviceType(input: {
1021
+ deviceType: string;
1022
+ }): Promise<Array<{
1023
+ capName: string;
1024
+ wrappers: string[];
1025
+ }>>;
1026
+ setWrapperActive(input: {
1027
+ deviceId: number;
1028
+ capName: string;
1029
+ wrapperAddonId: string;
1030
+ active: boolean;
1031
+ }): Promise<void>;
1032
+ protected onInitialize(): Promise<ProviderRegistration[] | void>;
1033
+ /**
1034
+ * Single-cap mirror update — diff against the current mirror,
1035
+ * persist the new slice in-memory, emit `DeviceStateChanged` for
1036
+ * this cap. No-op on identical writes (both same shape and same
1037
+ * values). Called by `setCapSlice` provider.
1038
+ */
1039
+ private applySingleCapUpdate;
1040
+ /**
1041
+ * Debounced disk writer. Coalesces frequent writes (motion phase
1042
+ * transitions, battery pushes) into one `writeDeviceRuntimeState`
1043
+ * per `RUNTIME_STATE_DEBOUNCE_MS` window. Reads the per-device
1044
+ * blob from the live mirror at flush time so the disk picture is
1045
+ * always the latest state — no risk of writing a stale snapshot.
1046
+ */
1047
+ private scheduleRuntimeStateDiskWrite;
1048
+ /**
1049
+ * One-shot mirror seed used by `loadRuntimeState` at boot so the
1050
+ * hub knows about every persisted slice without waiting for the
1051
+ * first `setCapSlice` call. No events emitted — this is
1052
+ * initial-state population, not a transition.
1053
+ */
1054
+ private seedMirror;
1055
+ private snapshotForDevice;
1056
+ private emitStateChanged;
1057
+ protected onShutdown(): Promise<void>;
464
1058
  }
465
1059
 
466
1060
  type ElementState = 'stopped' | 'starting' | 'running' | 'stopping' | 'error' | 'disabled';
@@ -506,10 +1100,52 @@ declare class SystemEventBus implements IEventBus {
506
1100
  private readonly subscribers;
507
1101
  constructor(bufferSize?: number);
508
1102
  emit(event: SystemEvent): void;
1103
+ /**
1104
+ * Subscribe to events matching a filter.
1105
+ * When called with a typed category filter, the handler receives typed event data.
1106
+ */
509
1107
  subscribe(filter: EventFilter, handler: (event: SystemEvent) => void): () => void;
510
1108
  getRecent(filter?: EventFilter, limit?: number): readonly SystemEvent[];
511
1109
  }
512
1110
 
1111
+ declare class LogRingBuffer {
1112
+ private readonly capacity;
1113
+ private readonly buffer;
1114
+ private head;
1115
+ private count;
1116
+ constructor(capacity?: number);
1117
+ push(entry: LogEntry): void;
1118
+ getAll(): LogEntry[];
1119
+ query(filter: LogFilter): LogEntry[];
1120
+ /**
1121
+ * Drop entries that match `filter`. Returns the number of entries
1122
+ * removed. Triggered by the UI's "Clear logs" button so the operator
1123
+ * can wipe the historical buffer for a specific scope (per-device,
1124
+ * per-addon, per-agent) — without it, closing and reopening the
1125
+ * panel re-populates the cleared rows from the server's ring buffer.
1126
+ *
1127
+ * Filter semantics mirror `query` (same level / since / until / tags
1128
+ * handling) — whatever you'd see listed by `query(filter)` is what
1129
+ * gets removed.
1130
+ */
1131
+ clear(filter?: LogFilter): number;
1132
+ }
1133
+
1134
+ declare class ScopedLogger implements IScopedLogger {
1135
+ private readonly scope;
1136
+ private readonly writeFn;
1137
+ private readonly tags?;
1138
+ constructor(scope: string | undefined, writeFn: (entry: LogEntry) => void, tags?: LogTags | undefined);
1139
+ debug(message: string, extras?: LogExtras): void;
1140
+ info(message: string, extras?: LogExtras): void;
1141
+ warn(message: string, extras?: LogExtras): void;
1142
+ error(message: string, extras?: LogExtras): void;
1143
+ child(childScope: string): IScopedLogger;
1144
+ withTags(tags: LogTags): IScopedLogger;
1145
+ private write;
1146
+ private mergeTags;
1147
+ }
1148
+
513
1149
  interface ILogDestination {
514
1150
  initialize(): Promise<void>;
515
1151
  shutdown(): Promise<void>;
@@ -520,15 +1156,103 @@ declare class LogManager {
520
1156
  private readonly ringBuffer;
521
1157
  private readonly destinations;
522
1158
  private readonly subscribers;
1159
+ private deviceNameLookup;
523
1160
  constructor(bufferSize?: number);
524
- createLogger(scope: string): IScopedLogger;
1161
+ /**
1162
+ * Register a callback that resolves `deviceId → deviceName`. Called
1163
+ * for every emitted entry that carries `tags.deviceId` but no
1164
+ * explicit `tags.deviceName` — the LogManager injects the resolved
1165
+ * name directly into the entry's tags BEFORE ring-buffer / destination
1166
+ * write, so every destination (local console, file, remote forwarder,
1167
+ * addon-bundled copies of core) sees the enriched shape regardless of
1168
+ * which module instance the destination was built from.
1169
+ */
1170
+ setDeviceNameLookup(lookup: ((deviceId: number) => string | null) | null): void;
1171
+ createLogger(scope?: string): IScopedLogger;
1172
+ private enrichDeviceName;
525
1173
  /** Subscribe to live logs matching a filter. Returns an unsubscribe function. */
526
1174
  subscribe(filter: Partial<LogFilter>, callback: (entry: LogEntry) => void): () => void;
527
1175
  private matchesFilter;
528
- addDestination(dest: ILogDestination): void;
1176
+ /**
1177
+ * Register a log destination.
1178
+ *
1179
+ * @param opts.replay If true (default), replay every entry currently in
1180
+ * the ring buffer to the new destination before live
1181
+ * traffic starts. Lets a destination added mid-boot
1182
+ * still receive boot-time log entries.
1183
+ */
1184
+ addDestination(dest: ILogDestination, opts?: {
1185
+ replay?: boolean;
1186
+ }): void;
529
1187
  removeDestination(dest: ILogDestination): void;
530
1188
  query(filter: LogFilter): LogEntry[];
1189
+ /**
1190
+ * Drop ring-buffer entries matching `filter`. Returns the count
1191
+ * removed. Backs the UI's "Clear logs" admin action so an operator
1192
+ * can wipe the historical window for a scope (per-device, per-addon,
1193
+ * agent-level) without restarting the server. External destinations
1194
+ * (file, forwarder) are NOT touched — only the in-memory ring.
1195
+ */
1196
+ clear(filter?: LogFilter): number;
1197
+ }
1198
+
1199
+ /**
1200
+ * Canonical single-line formatter for every `LogEntry` that reaches a
1201
+ * console transport — hub-side `WinstonDestination` +
1202
+ * `ConsoleDestination`, agent-side `HubForwarderDestination`. One
1203
+ * definition so the three paths can't drift.
1204
+ *
1205
+ * Layout mirrors NestJS `ConsoleLogger` output:
1206
+ *
1207
+ * [Nest] 54994 - 04/20/2026, 12:45:53 AM LOG [InstanceLoader] TrpcModule dependencies initialized +0ms
1208
+ *
1209
+ * Mapping for camstack — cluster-aware:
1210
+ * - `[Nest]` (brand bracket) → `[<agent>/<addonId>]` so operators see
1211
+ * at a glance which node emitted the line
1212
+ * (matches the cluster topology)
1213
+ * - `54994` (pid) → `process.pid`
1214
+ * - `<timestamp>` → `M/D/YYYY, H:MM:SS AM`
1215
+ * - `LOG` / `WARN` / `ERROR` → `entry.level` upper-cased, colour per level
1216
+ * - `[InstanceLoader]` (ctx) → `[<device>]` — shown only when a device
1217
+ * tag is present; omitted otherwise
1218
+ * - `<message>` → `entry.message`
1219
+ * - trailing `+Nms` → we don't compute timing yet; omitted
1220
+ *
1221
+ * Rendered examples:
1222
+ * [hub/winston-logging] 71184 - 04/20/2026, 12:45:53 AM INFO Winston logging initialized
1223
+ * [hub/provider-rtsp] 71184 - 04/20/2026, 12:46:01 AM WARN [salone] probe failed {err="timeout"}
1224
+ * [dev-agent-0/detection] 12345 - 04/20/2026, 12:47:15 AM ERROR [cortile] no detector {codec=av1}
1225
+ */
1226
+
1227
+ /** Options for `formatLogLine`. */
1228
+ interface FormatLogLineOptions {
1229
+ /**
1230
+ * Emit ANSI colour codes. When omitted, falls back to the shell-env
1231
+ * heuristic (`NO_COLOR`/`FORCE_COLOR`/`isTTY`). Destinations that
1232
+ * always target a console (ConsoleDestination, HubForwarderDestination)
1233
+ * pass `true`; file sinks (Winston JSON) pass `false` so logs on disk
1234
+ * stay plain text regardless of the shell env.
1235
+ *
1236
+ * `NO_COLOR=1` in the environment still wins — consumer opt-in beats
1237
+ * producer opt-in, matching the https://no-color.org/ convention.
1238
+ */
1239
+ readonly colorize?: boolean;
531
1240
  }
1241
+ /**
1242
+ * Render a `LogEntry` as one complete console line — no trailing newline.
1243
+ * Caller appends `\n` (file sinks) or relies on `console.log` (stdout sink).
1244
+ *
1245
+ * PID column prefers `entry.tags.pid` when set (so forked-worker entries
1246
+ * carry the worker's OS pid even when the hub renders them), falling back
1247
+ * to the renderer's own `process.pid`.
1248
+ *
1249
+ * Colours (when enabled) follow the NestJS convention: the level token
1250
+ * AND the message body share the level colour (green/yellow/red/gray for
1251
+ * info/warn/error/debug). Brand, device context, scope and meta keep
1252
+ * their fixed hues so the structural fields stay visually distinct from
1253
+ * the message content.
1254
+ */
1255
+ declare function formatLogLine(entry: LogEntry, options?: FormatLogLineOptions): string;
532
1256
 
533
1257
  interface IStorageBackend {
534
1258
  /** Backend type identifier */
@@ -537,11 +1261,31 @@ interface IStorageBackend {
537
1261
  readonly basePath: string;
538
1262
  /** Resolve a subpath to an absolute path */
539
1263
  resolve(subpath: string): string;
540
- /** Check if the backend path exists and is writable */
1264
+ /**
1265
+ * Check whether the backend is usable: either the base directory
1266
+ * already exists and is writable, OR we can create it on first
1267
+ * write (i.e. the nearest existing ancestor is writable).
1268
+ */
541
1269
  isAvailable(): boolean;
542
- /** Ensure base directory exists (mkdir -p equivalent) */
1270
+ /** No-op for lazy backends. Kept for API compatibility. */
543
1271
  initialize(): Promise<void>;
544
1272
  }
1273
+ /**
1274
+ * Filesystem storage backend — lazy by design.
1275
+ *
1276
+ * `initialize()` does NOT create the base directory eagerly. Instead,
1277
+ * consumers that write via `FilesystemStorageProvider` (and other write
1278
+ * helpers in storage-manager.ts) run `fs.mkdir(dirname(filePath), { recursive: true })`
1279
+ * right before every `writeFile`, so the directory tree appears on demand.
1280
+ *
1281
+ * The previous behaviour eagerly mkdir'd every location declared in
1282
+ * `StorageLocationManager.initializeDefaults()` — six directories (`db`,
1283
+ * `media`, `recordings`, `models`, `cache`, `logs`) got created at boot
1284
+ * even if the installation had no cameras, no recording addon, no
1285
+ * analytics addon, etc. That meant every fresh install looked like it
1286
+ * was producing data when it had nothing to write. Laziness fixes that:
1287
+ * a directory only exists once an addon decides to put a file there.
1288
+ */
545
1289
  declare class FsStorageBackend implements IStorageBackend {
546
1290
  readonly type = "local";
547
1291
  readonly basePath: string;
@@ -634,6 +1378,7 @@ declare class StorageManager {
634
1378
  setProvider(provider: IStorageProvider | IStorageProvider$1): void;
635
1379
  setNewStorageProvider(provider: IStorageProvider$1): void;
636
1380
  setSettingsBackend(backend: ISettingsBackend): void;
1381
+ getSettingsBackend(): ISettingsBackend;
637
1382
  getProvider(): IStorageProvider;
638
1383
  setLocationManager(manager: StorageLocationManager): void;
639
1384
  getLocationManager(): StorageLocationManager;
@@ -650,38 +1395,18 @@ declare class StorageManager {
650
1395
 
651
1396
  /**
652
1397
  * Manages scoped API tokens with restricted addon/route/capability access.
653
- * Framework-agnostic — dependencies injected via constructor.
654
1398
  */
655
1399
  declare class ScopedTokenManager {
656
- private readonly storage;
657
- constructor(storage: IStructuredStorage);
658
- /**
659
- * Create a new scoped token. Returns the raw token string (shown once)
660
- * and the stored record (with hash, not the raw token).
661
- */
1400
+ private readonly store;
1401
+ constructor(store: SettingsStoreClient);
662
1402
  create(userId: string, name: string, scopes: TokenScope[], expiresAt?: number): Promise<{
663
1403
  token: string;
664
1404
  record: ScopedToken;
665
1405
  }>;
666
- /**
667
- * Validate a raw token string. Returns the token record if valid, null otherwise.
668
- */
669
1406
  validate(rawToken: string): Promise<ScopedToken | null>;
670
- /**
671
- * Check whether a token's scopes grant access to the given addon, route, or capability.
672
- */
673
1407
  matchesScope(token: ScopedToken, addonId?: string, routePath?: string, capability?: string): boolean;
674
- /**
675
- * Revoke a token by ID.
676
- */
677
1408
  revoke(tokenId: string): Promise<void>;
678
- /**
679
- * List all tokens for a user (without exposing the raw token).
680
- */
681
1409
  listForUser(userId: string): Promise<ScopedToken[]>;
682
- /**
683
- * Update the lastUsedAt timestamp for a token.
684
- */
685
1410
  updateLastUsed(tokenId: string): Promise<void>;
686
1411
  }
687
1412
 
@@ -693,7 +1418,8 @@ declare class AuthManager {
693
1418
  private readonly config;
694
1419
  private readonly jwtSecret;
695
1420
  private scopedTokenManager;
696
- constructor(config: AuthConfigReader);
1421
+ private readonly logger;
1422
+ constructor(config: AuthConfigReader, logger?: IScopedLogger);
697
1423
  signToken(payload: Omit<TokenPayload, 'iat' | 'exp'>): string;
698
1424
  verifyToken(token: string): TokenPayload;
699
1425
  hashPassword(password: string): Promise<string>;
@@ -728,31 +1454,20 @@ declare class AuthManager {
728
1454
  matchesScopedTokenScope(token: ScopedToken, addonId?: string, routePath?: string, capability?: string): boolean;
729
1455
  }
730
1456
 
731
- interface ApiKeyRecord {
732
- id: string;
733
- label: string;
734
- role: UserRole;
735
- allowedProviders: string[] | '*';
736
- allowedDevices: Record<string, string[] | '*'>;
737
- tokenHash: string;
738
- tokenPrefix: string;
739
- createdAt: number;
740
- lastUsedAt?: number;
741
- }
742
1457
  interface CreateApiKeyInput {
743
1458
  label: string;
744
1459
  role: UserRole;
745
1460
  allowedProviders?: string[] | '*';
746
1461
  allowedDevices?: Record<string, string[] | '*'>;
747
1462
  }
748
- type ApiKeyStorageAccess = {
749
- getStructuredStorage(): IStructuredStorage;
750
- };
1463
+ interface ApiKeyStorageAccess {
1464
+ getStore(): SettingsStoreClient;
1465
+ }
751
1466
  declare class ApiKeyManager {
752
1467
  private readonly storageAccess;
753
1468
  private readonly auth;
754
1469
  constructor(storageAccess: ApiKeyStorageAccess, auth: AuthManager);
755
- private get structured();
1470
+ private get store();
756
1471
  create(input: CreateApiKeyInput): Promise<{
757
1472
  record: ApiKeyRecord;
758
1473
  token: string;
@@ -771,18 +1486,18 @@ interface CreateUserInput {
771
1486
  allowedDevices?: Record<string, string[] | '*'>;
772
1487
  }
773
1488
  type UpdatableUserFields = Partial<Pick<UserRecord, 'role' | 'allowedProviders' | 'allowedDevices'>>;
774
- type UserStorageAccess = {
775
- getStructuredStorage(): IStructuredStorage;
776
- };
777
- type UserConfigReader = {
1489
+ interface UserStorageAccess {
1490
+ getStore(): SettingsStoreClient;
1491
+ }
1492
+ interface UserConfigReader {
778
1493
  get<T>(path: string): T;
779
- };
1494
+ }
780
1495
  declare class UserManager {
781
1496
  private readonly storageAccess;
782
1497
  private readonly auth;
783
1498
  private readonly config;
784
1499
  constructor(storageAccess: UserStorageAccess, auth: AuthManager, config: UserConfigReader);
785
- private get structured();
1500
+ private get store();
786
1501
  create(input: CreateUserInput): Promise<UserRecord>;
787
1502
  findByUsername(username: string): Promise<UserRecord | null>;
788
1503
  findById(id: string): Promise<UserRecord | null>;
@@ -798,7 +1513,7 @@ declare class UserManager {
798
1513
  * Central notification service that routes notifications to configured outputs.
799
1514
  * Framework-agnostic — dependencies injected via constructor.
800
1515
  *
801
- * Outputs are resolved from the CapabilityRegistry's 'notification-output'
1516
+ * Outputs are resolved from the ICapabilityRegistry's 'notification-output'
802
1517
  * collection on each call (proxy pattern). Falls back to a local map
803
1518
  * when no registry is provided (backward compat).
804
1519
  */
@@ -811,13 +1526,13 @@ declare class NotificationService {
811
1526
  private registry;
812
1527
  constructor(logger: IScopedLogger);
813
1528
  /** Set the registry for live output lookup. Called once during boot. */
814
- setRegistry(registry: CapabilityRegistry): void;
1529
+ setRegistry(registry: ICapabilityRegistry): void;
815
1530
  /** Resolve all outputs — prefers registry, falls back to local map */
816
1531
  private get outputs();
817
- /** @deprecated Use registry-based resolution. Kept for backward compat only. */
818
- addOutput(output: INotificationOutput): void;
819
- /** @deprecated Use registry-based resolution. Kept for backward compat only. */
820
- removeOutput(id: string): void;
1532
+ /** Register an output in the local fallback map (used when no registry is set). */
1533
+ registerLocalOutput(output: INotificationOutput): void;
1534
+ /** Remove an output from the local fallback map. */
1535
+ unregisterLocalOutput(id: string): void;
821
1536
  setRouting(category: string, outputIds: string[]): void;
822
1537
  setRateLimit(category: string, minIntervalMs: number): void;
823
1538
  notify(notification: Notification): Promise<void>;
@@ -888,54 +1603,6 @@ declare class AddonRouteRegistry {
888
1603
  }>;
889
1604
  }
890
1605
 
891
- interface IRegisteredDevice {
892
- readonly id: string;
893
- readonly name: string;
894
- readonly providerId: string;
895
- readonly capabilities: readonly string[];
896
- }
897
- declare class DeviceRegistry<T extends IRegisteredDevice = IRegisteredDevice> {
898
- private readonly eventBus;
899
- private readonly devices;
900
- private readonly logger;
901
- constructor(eventBus: IEventBus, loggingService: LoggerFactory);
902
- registerDevice(device: T): void;
903
- unregisterDevice(id: string): void;
904
- getDevice(id: string): T | null;
905
- listDevices(): T[];
906
- getDevicesByProvider(providerId: string): T[];
907
- getDevicesWithCapability(cap: string): T[];
908
- registerProviderDevices(providerId: string, devices: readonly T[]): void;
909
- unregisterProviderDevices(providerId: string): void;
910
- }
911
-
912
- interface IDeviceCapability {
913
- kind: string;
914
- }
915
- interface CapabilityBinding {
916
- source: 'native' | 'addon' | 'disabled';
917
- addonId?: string;
918
- config?: Record<string, unknown>;
919
- }
920
- interface IResolvableDevice {
921
- readonly id: string;
922
- readonly capabilities: string[];
923
- getCapability<T extends IDeviceCapability>(cap: string): T | null;
924
- }
925
- interface IAddonRegistryAccess {
926
- getAddon(addonId: string): unknown | null;
927
- }
928
- declare class CapabilityResolver {
929
- private readonly addonRegistry;
930
- private readonly bindings;
931
- constructor(addonRegistry: IAddonRegistryAccess);
932
- resolve<T extends IDeviceCapability>(device: IResolvableDevice, cap: string): T | null;
933
- setBinding(deviceId: string, cap: string, binding: CapabilityBinding): void;
934
- removeBinding(deviceId: string, cap: string): void;
935
- getBindings(deviceId: string): Partial<Record<string, CapabilityBinding>>;
936
- getEffectiveCapabilities(device: IResolvableDevice): string[];
937
- }
938
-
939
1606
  interface TlsCertPair {
940
1607
  readonly cert: Buffer;
941
1608
  readonly key: Buffer;
@@ -966,9 +1633,14 @@ declare function loadTlsCert(certPath: string, keyPath: string): TlsCertPair;
966
1633
  /**
967
1634
  * Factory for creating tRPC clients for addons.
968
1635
  *
969
- * Two modes:
970
- * - Direct caller (in-process): zero overhead, uses createCallerFactory
971
- * - WSS client (worker/agent): real WebSocket connection to the server
1636
+ * In-process only: `createDirectCaller()` uses tRPC's `createCallerFactory`
1637
+ * to call procedures directly (zero network overhead) and wraps the raw
1638
+ * caller in a proxy so it matches `CreateTRPCClient<AppRouter>` shape
1639
+ * addons use `context.api.namespace.method.query(input)` identically to
1640
+ * the cross-process path.
1641
+ *
1642
+ * Cross-process calls (forked workers, remote agents) ride the Moleculer
1643
+ * broker via `brokerTransportLink` in the kernel — not this factory.
972
1644
  */
973
1645
  interface DirectCallerOptions {
974
1646
  /** The tRPC router instance (from buildAppRouter) */
@@ -982,156 +1654,43 @@ interface DirectCallerOptions {
982
1654
  };
983
1655
  };
984
1656
  }
985
- interface WssClientOptions {
986
- /** WebSocket URL (e.g., wss://localhost:4443/trpc) */
987
- readonly url: string;
988
- /** Auth token for the connection */
989
- readonly token: string;
990
- }
991
1657
  declare class AddonApiFactory {
992
- /**
993
- * Build a WSS URL from host and port.
994
- */
995
- buildWssUrl(host: string, port: number): string;
996
1658
  /**
997
1659
  * Create a direct caller -- calls tRPC procedures directly in-process.
998
1660
  * Zero network overhead. Used for in-process addons and dev mode.
999
1661
  *
1000
- * @param options.router - The tRPC router from buildAppRouter()
1001
- * @param options.context - The auth context (service account)
1002
- * @returns A tRPC caller that can be used as context.api
1003
- */
1004
- createDirectCaller(options: DirectCallerOptions): Promise<unknown>;
1005
- /**
1006
- * Create a WSS tRPC client -- connects to the server via WebSocket.
1007
- * Used for forked workers and remote agents.
1008
- *
1009
- * @param options.url - WSS URL (e.g., wss://localhost:4443/trpc)
1010
- * @param options.token - Bearer token for authentication
1011
- * @returns A tRPC client that can be used as context.api
1662
+ * Returns `AddonApi` the same tRPC client surface that broker-routed
1663
+ * callers get. Callers treat it uniformly as `context.api` regardless
1664
+ * of the underlying transport.
1012
1665
  */
1013
- createWssClient(options: WssClientOptions): Promise<{
1014
- readonly client: unknown;
1015
- readonly close: () => void;
1016
- }>;
1017
- }
1018
-
1019
- declare class PlatformScorer {
1020
- private cached;
1021
- /** Probe hardware + runtimes and score all backend combos. Cached after first call. */
1022
- probe(): Promise<PlatformCapabilities>;
1023
- probeHardware(): Promise<HardwareInfo>;
1024
- private probeNodeBackends;
1025
- private probePythonBackends;
1026
- private scoreBackends;
1027
- }
1028
-
1029
- declare class InferenceConfigResolver {
1030
- private readonly scores;
1031
- private readonly hardware;
1032
- constructor(scores: readonly PlatformScore[], hardware: HardwareInfo);
1033
- /**
1034
- * Compute accuracy/backend weights based on available system RAM.
1035
- * availableRAM_MB is now sourced from systeminformation (reliable cross-platform),
1036
- * not os.freemem() which is broken on macOS.
1037
- *
1038
- * - > 16 GB available: prefer larger, more accurate models (accuracy 0.6, backend 0.4)
1039
- * - > 8 GB available: balanced (accuracy 0.5, backend 0.5)
1040
- * - <= 8 GB available: prefer speed (accuracy 0.4, backend 0.6)
1041
- */
1042
- private getWeights;
1043
- /**
1044
- * Given an addon's model requirements, pick the best model + runtime + backend.
1045
- *
1046
- * Algorithm:
1047
- * 1. Filter models by available RAM (minRAM_MB < 25% of available RAM)
1048
- * 2. For each remaining model, find the best platform score whose format
1049
- * is available in the model's formats
1050
- * 3. Pick the model with the highest combined score using RAM-adaptive weights:
1051
- * - High RAM (>16 GB): accuracy × 0.6 + backend × 0.4 (prefer accuracy)
1052
- * - Mid RAM (>8 GB): accuracy × 0.5 + backend × 0.5 (balanced)
1053
- * - Low RAM (<=8 GB): accuracy × 0.4 + backend × 0.6 (prefer speed)
1054
- */
1055
- resolve(requirements: readonly ModelRequirement[]): ResolvedInferenceConfig;
1666
+ createDirectCaller(options: DirectCallerOptions): Promise<_camstack_types.AddonApi>;
1056
1667
  }
1057
1668
 
1058
1669
  declare class IntegrationRegistry implements IIntegrationRegistry {
1059
1670
  private readonly backend;
1060
1671
  constructor(backend: ISettingsBackend);
1061
1672
  initialize(): Promise<void>;
1062
- createIntegration(input: CreateIntegrationInput): Integration;
1063
- getIntegration(id: string): Integration | null;
1064
- getIntegrationByAddonId(addonId: string): Integration | null;
1065
- listIntegrations(): readonly Integration[];
1066
- updateIntegration(id: string, updates: Partial<Pick<Integration, 'name' | 'enabled' | 'info'>>): Integration | null;
1067
- deleteIntegration(id: string): boolean;
1068
- getIntegrationSettings(integrationId: string): Record<string, unknown>;
1069
- setIntegrationSetting(integrationId: string, key: string, value: unknown): void;
1070
- setIntegrationSettings(integrationId: string, settings: Record<string, unknown>): void;
1071
- createDevice(input: CreateDeviceInput): Device;
1072
- getDevice(id: string): Device | null;
1073
- getDeviceByStableId(stableId: string): Device | null;
1074
- listDevices(integrationId?: string): readonly Device[];
1075
- listCameras(): readonly Device[];
1076
- updateDevice(id: string, updates: Partial<Pick<Device, 'name' | 'enabled' | 'info'>>): Device | null;
1077
- deleteDevice(id: string): boolean;
1078
- getDeviceSettings(deviceId: string): Record<string, unknown>;
1079
- setDeviceSetting(deviceId: string, key: string, value: unknown): void;
1080
- setDeviceSettings(deviceId: string, settings: Record<string, unknown>): void;
1673
+ createIntegration(input: CreateIntegrationInput): Promise<Integration>;
1674
+ getIntegration(id: string): Promise<Integration | null>;
1675
+ getIntegrationByAddonId(addonId: string): Promise<Integration | null>;
1676
+ listIntegrations(): Promise<readonly Integration[]>;
1677
+ updateIntegration(id: string, updates: Partial<Pick<Integration, 'name' | 'enabled' | 'info'>>): Promise<Integration | null>;
1678
+ deleteIntegration(id: string): Promise<boolean>;
1679
+ getIntegrationSettings(integrationId: string): Promise<Record<string, unknown>>;
1680
+ setIntegrationSetting(integrationId: string, key: string, value: unknown): Promise<void>;
1681
+ setIntegrationSettings(integrationId: string, settings: Record<string, unknown>): Promise<void>;
1682
+ createDevice(input: CreateDeviceInput): Promise<PersistedDevice>;
1683
+ getDevice(id: string): Promise<PersistedDevice | null>;
1684
+ getDeviceByStableId(stableId: string): Promise<PersistedDevice | null>;
1685
+ listDevices(integrationId?: string): Promise<readonly PersistedDevice[]>;
1686
+ listCameras(): Promise<readonly PersistedDevice[]>;
1687
+ updateDevice(id: string, updates: Partial<Pick<PersistedDevice, 'name' | 'enabled' | 'info'>>): Promise<PersistedDevice | null>;
1688
+ deleteDevice(id: string): Promise<boolean>;
1689
+ getDeviceSettings(deviceId: string): Promise<Record<string, unknown>>;
1690
+ setDeviceSetting(deviceId: string, key: string, value: unknown): Promise<void>;
1691
+ setDeviceSettings(deviceId: string, settings: Record<string, unknown>): Promise<void>;
1081
1692
  private mapIntegration;
1082
1693
  private mapDevice;
1083
1694
  }
1084
1695
 
1085
- /** Minimal device registry interface for provider management */
1086
- type ProviderDeviceRegistry = {
1087
- registerProviderDevices(providerId: string, devices: readonly IRegistrableDevice[]): void;
1088
- unregisterProviderDevices(providerId: string): void;
1089
- };
1090
- interface IRegistrableDevice {
1091
- readonly id: string;
1092
- readonly name: string;
1093
- readonly providerId: string;
1094
- readonly capabilities: readonly string[];
1095
- }
1096
- interface ProviderStatus {
1097
- connected: boolean;
1098
- error?: string;
1099
- deviceCount: number;
1100
- }
1101
- interface LiveEvent {
1102
- type: string;
1103
- camera: string;
1104
- timestamp: number;
1105
- data: Record<string, unknown>;
1106
- }
1107
- interface IManagedProvider {
1108
- readonly id: string;
1109
- readonly type: string;
1110
- readonly name: string;
1111
- start(): Promise<void>;
1112
- stop(): Promise<void>;
1113
- getStatus(): ProviderStatus;
1114
- getDevices(): readonly IRegistrableDevice[];
1115
- subscribeLiveEvents(callback: (event: LiveEvent) => void): () => void;
1116
- }
1117
-
1118
- declare class ProviderManager<P extends IManagedProvider = IManagedProvider> {
1119
- private readonly deviceRegistry;
1120
- private readonly eventBus;
1121
- private readonly loggingService;
1122
- private readonly providers;
1123
- private readonly logger;
1124
- constructor(deviceRegistry: ProviderDeviceRegistry, eventBus: IEventBus, loggingService: LoggerFactory);
1125
- registerProvider(provider: P): void;
1126
- startProvider(id: string): Promise<void>;
1127
- stopProvider(id: string): Promise<void>;
1128
- disableProvider(id: string): Promise<void>;
1129
- enableProvider(id: string): Promise<void>;
1130
- restartProvider(id: string): Promise<void>;
1131
- getProvider(id: string): P | null;
1132
- getProviderStatus(id: string): ElementStatus | null;
1133
- listProviders(): readonly ProviderListItem[];
1134
- shutdownAll(): Promise<void>;
1135
- }
1136
-
1137
- export { AddonApiFactory, AddonRouteRegistry, AgentClient, type AgentClientConfig, type AgentEventEmitter, AgentRegistry, AgentTaskRunner, ApiKeyManager, type ApiKeyRecord, type ApiKeyStorageAccess, type AuthConfigReader, AuthManager, type BinaryHandler, type CapabilityBinding, CapabilityResolver, type CertOptions, type ConnectionHandler, DecodeTaskHandler, DetectTaskHandler, DeviceRegistry, type DirectCallerOptions, type ElementState, type ElementStatus, type EnsureTlsResult, EventBus, type FeatureConfigReader, FeatureManager, FsStorageBackend, type IAddonRegistryAccess, type IStorageProvider as ICoreStorageProvider, type IDeviceCapability, type IFileStorage, type ILogDestination, type IManagedProvider, type IRegisteredDevice, type IReplContextProvider, type IReplEngine, type IResolvableDevice, type IStorageBackend, type IStorageLocation, type IStructuredStorage, InferenceConfigResolver, IntegrationRegistry, LifecycleStateMachine, LogManager, LogRingBuffer, ManagedProcess, type MessageHandler, ModelDownloadService, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PlatformScorer, type ProcessEventEmitter, type ProcessLoggerFactory, ProcessManager, type ProcessState, type ProviderDeviceRegistry, ProviderManager, PythonEnvManager, type QueryFilter, RecordTaskHandler, ReplEngine, type ReplScope, type ReplSessionContext, ScopedLogger, ScopedTokenManager, StorageLocationManager, type StorageLocationName, StorageManager, type StorageRecord, SystemEventBus, TaskDispatcher, type TlsCertPair, ToastService, type Unsubscribe, type UserConfigReader, UserManager, type UserStorageAccess, type WssClientOptions, buildBinaryPath, downloadBinary, downloadFile, downloadModel, ensureBinary, ensureFfmpeg, ensurePython, ensureTlsCert, fetchJson, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, loadTlsCert };
1696
+ export { AddonApiFactory, AddonRouteRegistry, type AddonTableSchema, AlertCenterAddon, ApiKeyManager, type ApiKeyStorageAccess, type AuthConfigReader, AuthManager, type BackupConfig, CORE_TABLE_DDL, type CertOptions, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, DeviceManagerAddon, type DeviceRow, DeviceStore, type DirectCallerOptions, type DownloadProgressCallback, type ElementState, type ElementStatus, EngineManagerResolver, type EnsureTlsResult, EventBus, type FeatureConfigReader, FeatureManager, FilesystemStorageAddon, FsStorageBackend, HubForwarderAddon, HubForwarderDestination, type IStorageProvider as ICoreStorageProvider, type IFileStorage, type ILogDestination, type IReplContextProvider, type IReplEngine, type IStorageBackend, type IStorageLocation, type IStructuredStorage, IntegrationRegistry, LifecycleStateMachine, LocalAuthAddon, LocalBackupAddon, LocalBackupService, LogManager, LogRingBuffer, ModelDownloadService, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, type PidStats, PipelineRunner, PipelineValidator, PythonEnvManager, type QueryFilter, ReplEngine, type ReplScope, type ReplSessionContext, ScopedLogger, ScopedTokenManager, SettingsStore, SqliteSettingsAddon, SqliteSettingsBackend, StorageLocationManager, type StorageLocationName, StorageManager, type StorageRecord, SystemConfigAddon, SystemEventBus, type TlsCertPair, ToastService, type Unsubscribe, type UserConfigReader, UserManager, type UserStorageAccess, WinstonDestination, WinstonLoggingAddon, addonTableToDdl, deleteModelFromDisk, downloadFile, downloadModel, ensureModel, ensureTlsCert, fetchJson, formatLogLine, getModelFilePath, getPidStats, getSinglePidStats, isModelDownloaded, loadTlsCert };