@camstack/core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-2F3XZYRW.mjs +89 -0
- package/dist/chunk-2F3XZYRW.mjs.map +1 -0
- package/dist/chunk-LZOMFHX3.mjs +38 -0
- package/dist/chunk-LZOMFHX3.mjs.map +1 -0
- package/dist/index.d.mts +1536 -5
- package/dist/index.d.ts +1536 -5
- package/dist/index.js +8230 -526
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3929 -25
- package/dist/index.mjs.map +1 -1
- package/dist/storage-location-manager-F4YZMHGM.mjs +8 -0
- package/dist/storage-location-manager-F4YZMHGM.mjs.map +1 -0
- package/dist/wrapper-NTBY5HOA.mjs +3652 -0
- package/dist/wrapper-NTBY5HOA.mjs.map +1 -0
- package/package.json +20 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { ModelDownloadOptions, ModelDownloadResult, IPythonEnvironment, PythonProbeResult, PythonEnvReady, AddonDeclaration, ICamstackAddon, AddonContext, PipelineConfig, FrameInput, PipelineResult } from '@camstack/types';
|
|
1
|
+
import { ModelDownloadOptions, ModelDownloadResult, IPythonEnvironment, PythonProbeResult, PythonEnvReady, AddonDeclaration, ICamstackAddon, AddonContext, PipelineConfig, FrameInput, PipelineResult, AudioChunkInput, PipelineNode, IScopedLogger, CapabilityDeclaration, CapabilityConsumerRegistration, CapabilityMode, CapabilityInfo, LoggerFactory, AgentRegistrationInfo, AgentToHubMessage, HubToAgentMessage, AgentRuntimeStatus, ITaskHandler, TaskContext, IFileStorage as IFileStorage$1, IStorageProvider as IStorageProvider$1, StorageLocationName as StorageLocationName$1, IStorageLocation as IStorageLocation$1, IConfigurable, AddonManifest, CapabilityProviderMap, ConfigUISchema, ILogDestination as ILogDestination$1, LogEntry as LogEntry$1, LogFilter as LogFilter$1, IEventBus, SystemEvent, EventFilter, TokenScope, ScopedToken, INotificationOutput, Notification, Toast, IAddonRouteProvider, IAddonHttpRoute } from '@camstack/types';
|
|
2
|
+
export { EventFilter, EventSource, IScopedLogger, SystemEvent } from '@camstack/types';
|
|
2
3
|
import { ChildProcess } from 'node:child_process';
|
|
4
|
+
import { z } from 'zod';
|
|
3
5
|
|
|
4
6
|
type EventCallback<T = unknown> = (data: T) => void;
|
|
5
7
|
declare class EventBus {
|
|
@@ -47,6 +49,12 @@ declare class AddonInstaller {
|
|
|
47
49
|
private ensureAddonDirectory;
|
|
48
50
|
/** Install builtin packages if not already present */
|
|
49
51
|
private installBuiltins;
|
|
52
|
+
/**
|
|
53
|
+
* Ensure a set of packages are installed — installs any that are missing.
|
|
54
|
+
* This is the public entry-point used during boot to guarantee required
|
|
55
|
+
* addon packages are present before the loader tries to resolve them.
|
|
56
|
+
*/
|
|
57
|
+
ensureInstalled(packages: string[]): Promise<void>;
|
|
50
58
|
/** Check if a package is installed */
|
|
51
59
|
isInstalled(packageName: string): boolean;
|
|
52
60
|
/** Get installed package info */
|
|
@@ -59,12 +67,14 @@ declare class AddonInstaller {
|
|
|
59
67
|
uninstallPackage(packageName: string): Promise<void>;
|
|
60
68
|
/** Update a package to latest version */
|
|
61
69
|
updatePackage(packageName: string): Promise<void>;
|
|
70
|
+
/** Install an addon from a local .tgz file */
|
|
71
|
+
installFromTgz(tgzPath: string): Promise<void>;
|
|
62
72
|
/** Update all packages */
|
|
63
73
|
updateAll(): Promise<void>;
|
|
64
74
|
/** Get the node_modules path for require/import resolution */
|
|
65
75
|
getNodeModulesPath(): string;
|
|
66
76
|
}
|
|
67
|
-
/** Default builtin packages that are auto-installed */
|
|
77
|
+
/** Default builtin packages that are auto-installed on first boot */
|
|
68
78
|
declare const BUILTIN_PACKAGES: readonly string[];
|
|
69
79
|
|
|
70
80
|
interface RegisteredAddon {
|
|
@@ -77,7 +87,7 @@ declare class AddonLoader {
|
|
|
77
87
|
/** Load all addons from an npm package */
|
|
78
88
|
loadPackage(packageName: string): Promise<void>;
|
|
79
89
|
/** Load addon from a direct path (for development/testing) */
|
|
80
|
-
loadFromPath(addonId: string, modulePath: string, packageName: string): Promise<void>;
|
|
90
|
+
loadFromPath(addonId: string, modulePath: string, packageName: string, declaration?: Partial<AddonDeclaration>): Promise<void>;
|
|
81
91
|
/** Get a registered addon by ID */
|
|
82
92
|
getAddon(addonId: string): RegisteredAddon | undefined;
|
|
83
93
|
/** List all registered addons */
|
|
@@ -96,7 +106,7 @@ declare class AddonEngineManager {
|
|
|
96
106
|
private readonly loader;
|
|
97
107
|
private readonly baseContext;
|
|
98
108
|
private engines;
|
|
99
|
-
constructor(loader: AddonLoader, baseContext: Omit<AddonContext, 'addonConfig'
|
|
109
|
+
constructor(loader: AddonLoader, baseContext: Partial<Omit<AddonContext, 'addonConfig'>>);
|
|
100
110
|
/**
|
|
101
111
|
* Get or create an addon engine for the given effective config.
|
|
102
112
|
* Cameras with the same addonId + effective config share the same engine.
|
|
@@ -136,8 +146,1529 @@ declare class PipelineRunner {
|
|
|
136
146
|
private readonly addonConfigs;
|
|
137
147
|
constructor(engineManager: AddonEngineManager, addonConfigs: Map<string, Record<string, unknown>>);
|
|
138
148
|
run(frame: FrameInput, config: PipelineConfig): Promise<PipelineResult>;
|
|
149
|
+
/**
|
|
150
|
+
* Run only the audio classification node on an audio chunk.
|
|
151
|
+
* Used by the audio path in DetectionWiringService (separate from video pipeline).
|
|
152
|
+
*/
|
|
153
|
+
runAudioNode(chunk: AudioChunkInput, audioNode: PipelineNode): Promise<PipelineResult>;
|
|
139
154
|
private executeNode;
|
|
140
155
|
private executeChildren;
|
|
141
156
|
}
|
|
142
157
|
|
|
143
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Central registry for all capability providers and consumers.
|
|
160
|
+
* Lives in @camstack/core. Mode-aware: singleton (one active) or collection (all active).
|
|
161
|
+
*
|
|
162
|
+
* Uses injected LoggingService, NEVER NestJS static Logger.
|
|
163
|
+
*/
|
|
164
|
+
declare class CapabilityRegistry {
|
|
165
|
+
private readonly logger;
|
|
166
|
+
private readonly configReader;
|
|
167
|
+
private readonly capabilities;
|
|
168
|
+
/** Per-device singleton overrides: deviceId → (capability → addonId) */
|
|
169
|
+
private readonly deviceOverrides;
|
|
170
|
+
/** Per-device collection filters: deviceId → (capability → addonIds[]) */
|
|
171
|
+
private readonly deviceCollectionFilters;
|
|
172
|
+
constructor(logger: IScopedLogger, configReader: (capability: string) => string | undefined);
|
|
173
|
+
/**
|
|
174
|
+
* Declare a capability (typically called when addon manifests are loaded).
|
|
175
|
+
* Must be called before registerProvider/registerConsumer for that capability.
|
|
176
|
+
*/
|
|
177
|
+
declareCapability(declaration: CapabilityDeclaration): void;
|
|
178
|
+
/**
|
|
179
|
+
* Register a capability provider (called by addon loader when addon is enabled).
|
|
180
|
+
* For singleton: auto-activates if user-preferred or first registered.
|
|
181
|
+
* For collection: adds to active set and notifies consumers.
|
|
182
|
+
*/
|
|
183
|
+
registerProvider(capability: string, addonId: string, provider: unknown): void;
|
|
184
|
+
/**
|
|
185
|
+
* Unregister a provider (called when addon is disabled/uninstalled).
|
|
186
|
+
*/
|
|
187
|
+
unregisterProvider(capability: string, addonId: string): void;
|
|
188
|
+
/**
|
|
189
|
+
* Register a consumer that wants to be notified when providers change.
|
|
190
|
+
* If a provider is already active, the consumer is immediately notified.
|
|
191
|
+
* Returns a disposer function for cleanup.
|
|
192
|
+
*/
|
|
193
|
+
registerConsumer<T = unknown>(registration: CapabilityConsumerRegistration<T>): () => void;
|
|
194
|
+
/**
|
|
195
|
+
* Get the active singleton provider for a capability.
|
|
196
|
+
* Returns null if none set.
|
|
197
|
+
*/
|
|
198
|
+
getSingleton<T = unknown>(capability: string): T | null;
|
|
199
|
+
/**
|
|
200
|
+
* Get all active collection providers for a capability.
|
|
201
|
+
*/
|
|
202
|
+
getCollection<T = unknown>(capability: string): readonly T[];
|
|
203
|
+
/**
|
|
204
|
+
* Set which addon should be the active singleton for a capability.
|
|
205
|
+
* Call with `immediate: true` to also swap the runtime provider now
|
|
206
|
+
* (consumers' onSet will be awaited).
|
|
207
|
+
*/
|
|
208
|
+
setActiveSingleton(capability: string, addonId: string, immediate?: boolean): Promise<void>;
|
|
209
|
+
/**
|
|
210
|
+
* Get the mode declared for a capability.
|
|
211
|
+
*/
|
|
212
|
+
getMode(capability: string): CapabilityMode | undefined;
|
|
213
|
+
/**
|
|
214
|
+
* List all registered capabilities with their providers.
|
|
215
|
+
*/
|
|
216
|
+
listCapabilities(): CapabilityInfo[];
|
|
217
|
+
/**
|
|
218
|
+
* Check if all dependencies for a capability are satisfied (have active providers).
|
|
219
|
+
*/
|
|
220
|
+
areDependenciesMet(declaration: CapabilityDeclaration): boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Get the dependency-ordered list of capability names for boot sequencing.
|
|
223
|
+
* Returns capabilities sorted topologically by dependsOn.
|
|
224
|
+
* Throws if a cycle is detected.
|
|
225
|
+
*/
|
|
226
|
+
getBootOrder(): string[];
|
|
227
|
+
/**
|
|
228
|
+
* Set a per-device singleton override. When resolveForDevice is called for
|
|
229
|
+
* this device + capability, the specified addon's provider is returned
|
|
230
|
+
* instead of the global singleton.
|
|
231
|
+
*/
|
|
232
|
+
setDeviceOverride(deviceId: string, capability: string, addonId: string): void;
|
|
233
|
+
/**
|
|
234
|
+
* Clear a per-device singleton override, reverting to the global singleton.
|
|
235
|
+
*/
|
|
236
|
+
clearDeviceOverride(deviceId: string, capability: string): void;
|
|
237
|
+
/**
|
|
238
|
+
* Get all per-device singleton overrides for a device.
|
|
239
|
+
* Returns a Map of capability name to addon ID.
|
|
240
|
+
*/
|
|
241
|
+
getDeviceOverrides(deviceId: string): Map<string, string>;
|
|
242
|
+
/**
|
|
243
|
+
* Resolve a singleton provider for a specific device.
|
|
244
|
+
* 1. Check device override — return that addon's provider
|
|
245
|
+
* 2. Fallback to global singleton
|
|
246
|
+
*/
|
|
247
|
+
resolveForDevice<T = unknown>(capability: string, deviceId: string): T | null;
|
|
248
|
+
/**
|
|
249
|
+
* Set a per-device collection filter. When resolveCollectionForDevice is called
|
|
250
|
+
* for this device + capability, only providers from the specified addon IDs
|
|
251
|
+
* are returned instead of the full collection.
|
|
252
|
+
*/
|
|
253
|
+
setDeviceCollectionFilter(deviceId: string, capability: string, addonIds: string[]): void;
|
|
254
|
+
/**
|
|
255
|
+
* Clear a per-device collection filter, reverting to the full collection.
|
|
256
|
+
*/
|
|
257
|
+
clearDeviceCollectionFilter(deviceId: string, capability: string): void;
|
|
258
|
+
/**
|
|
259
|
+
* Resolve collection providers for a specific device.
|
|
260
|
+
* If a filter exists for the device + capability, only those addon's providers are returned.
|
|
261
|
+
* If no filter exists, the full collection is returned.
|
|
262
|
+
*/
|
|
263
|
+
resolveCollectionForDevice<T = unknown>(capability: string, deviceId: string): readonly T[];
|
|
264
|
+
/**
|
|
265
|
+
* Get a specific addon's provider by addon ID, regardless of whether it's the active singleton.
|
|
266
|
+
* Useful for per-device overrides that need to look up any registered provider.
|
|
267
|
+
*/
|
|
268
|
+
getProviderByAddonId<T = unknown>(capability: string, addonId: string): T | null;
|
|
269
|
+
private activateSingleton;
|
|
270
|
+
private activateSingletonAsync;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
interface InfraCapability {
|
|
274
|
+
/** Capability name */
|
|
275
|
+
readonly name: string;
|
|
276
|
+
/** If true, boot aborts when this capability's addon fails to initialize */
|
|
277
|
+
readonly required: boolean;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Infrastructure capabilities that must boot before all other addons.
|
|
281
|
+
* Enabled in Phase 1 of the boot sequence. Order matters: storage before logging.
|
|
282
|
+
*/
|
|
283
|
+
declare const INFRA_CAPABILITIES: readonly InfraCapability[];
|
|
284
|
+
/** Check if a capability name is an infrastructure capability */
|
|
285
|
+
declare function isInfraCapability(name: string): boolean;
|
|
286
|
+
|
|
287
|
+
type ProcessState = 'stopped' | 'starting' | 'running' | 'stopping' | 'error';
|
|
288
|
+
interface ProcessConfig {
|
|
289
|
+
readonly id: string;
|
|
290
|
+
readonly label: string;
|
|
291
|
+
readonly command?: string;
|
|
292
|
+
readonly modulePath?: string;
|
|
293
|
+
readonly args?: string[];
|
|
294
|
+
readonly env?: Record<string, string>;
|
|
295
|
+
readonly autoRestart: boolean;
|
|
296
|
+
readonly maxRestarts?: number;
|
|
297
|
+
readonly healthCheck?: {
|
|
298
|
+
readonly intervalMs: number;
|
|
299
|
+
readonly timeoutMs: number;
|
|
300
|
+
readonly failureThreshold: number;
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
interface ProcessStats {
|
|
304
|
+
readonly pid: number;
|
|
305
|
+
readonly cpu: number;
|
|
306
|
+
readonly memory: number;
|
|
307
|
+
readonly uptime: number;
|
|
308
|
+
readonly restartCount: number;
|
|
309
|
+
}
|
|
310
|
+
interface ManagedProcessStatus {
|
|
311
|
+
readonly id: string;
|
|
312
|
+
readonly label: string;
|
|
313
|
+
readonly state: ProcessState;
|
|
314
|
+
readonly pid?: number;
|
|
315
|
+
readonly stats?: ProcessStats;
|
|
316
|
+
readonly lastCrashAt?: number;
|
|
317
|
+
readonly lastCrashError?: string;
|
|
318
|
+
readonly restartCount: number;
|
|
319
|
+
readonly nextRestartAt?: number;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Event emitter for process lifecycle events */
|
|
323
|
+
type ProcessEventEmitter = {
|
|
324
|
+
emitProcessCrashed(processId: string, code: number | null, signal: string | null, restartCount: number): void;
|
|
325
|
+
emitProcessRestartScheduled(processId: string, restartCount: number, delayMs: number): void;
|
|
326
|
+
emitProcessRestarted(processId: string, restartCount: number): void;
|
|
327
|
+
};
|
|
328
|
+
/** Re-export LoggerFactory as ProcessLoggerFactory for backward compatibility */
|
|
329
|
+
type ProcessLoggerFactory = LoggerFactory;
|
|
330
|
+
|
|
331
|
+
declare class ManagedProcess {
|
|
332
|
+
private readonly config;
|
|
333
|
+
private readonly events;
|
|
334
|
+
private readonly logger;
|
|
335
|
+
private childProcess;
|
|
336
|
+
private _state;
|
|
337
|
+
private _startedAt?;
|
|
338
|
+
private restartTimer?;
|
|
339
|
+
private _restartCount;
|
|
340
|
+
private _lastCrashAt?;
|
|
341
|
+
private _lastCrashError?;
|
|
342
|
+
constructor(config: ProcessConfig, events: ProcessEventEmitter, logger: IScopedLogger);
|
|
343
|
+
get state(): ProcessState;
|
|
344
|
+
start(): Promise<void>;
|
|
345
|
+
stop(): Promise<void>;
|
|
346
|
+
getStatus(): ManagedProcessStatus;
|
|
347
|
+
private scheduleRestart;
|
|
348
|
+
private getStats;
|
|
349
|
+
private getNextRestartTime;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
declare class ProcessManager {
|
|
353
|
+
private readonly events;
|
|
354
|
+
private readonly loggerFactory;
|
|
355
|
+
private readonly processes;
|
|
356
|
+
constructor(events: ProcessEventEmitter, loggerFactory: ProcessLoggerFactory);
|
|
357
|
+
register(config: ProcessConfig): ManagedProcess;
|
|
358
|
+
start(id: string): Promise<void>;
|
|
359
|
+
stop(id: string): Promise<void>;
|
|
360
|
+
restart(id: string): Promise<void>;
|
|
361
|
+
get(id: string): ManagedProcess;
|
|
362
|
+
listAll(): readonly ManagedProcessStatus[];
|
|
363
|
+
shutdownAll(): Promise<void>;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
interface StreamNetworkStats {
|
|
367
|
+
readonly nominalBitrateKbps: number;
|
|
368
|
+
readonly observedBitrateKbps: number;
|
|
369
|
+
readonly peakBitrateKbps: number;
|
|
370
|
+
readonly packetLossPercent: number;
|
|
371
|
+
readonly lastUpdated: number;
|
|
372
|
+
}
|
|
373
|
+
interface ClientNetworkStats {
|
|
374
|
+
readonly rttMs: number;
|
|
375
|
+
readonly jitterMs: number;
|
|
376
|
+
readonly estimatedBandwidthKbps: number;
|
|
377
|
+
readonly lastUpdated: number;
|
|
378
|
+
}
|
|
379
|
+
interface DeviceNetworkStats {
|
|
380
|
+
readonly deviceId: string;
|
|
381
|
+
readonly streams: Readonly<Record<string, StreamNetworkStats>>;
|
|
382
|
+
readonly client?: ClientNetworkStats;
|
|
383
|
+
}
|
|
384
|
+
interface INetworkQualityTracker {
|
|
385
|
+
reportStreamStats(deviceId: string, streamId: string, bitrateKbps: number, packetLoss?: number): void;
|
|
386
|
+
reportClientStats(deviceId: string, stats: Omit<ClientNetworkStats, 'lastUpdated'>): void;
|
|
387
|
+
getDeviceStats(deviceId: string): DeviceNetworkStats | null;
|
|
388
|
+
getAllStats(): readonly DeviceNetworkStats[];
|
|
389
|
+
}
|
|
390
|
+
declare class NetworkQualityTracker implements INetworkQualityTracker {
|
|
391
|
+
private readonly devices;
|
|
392
|
+
private static readonly MAX_SAMPLES;
|
|
393
|
+
reportStreamStats(deviceId: string, streamId: string, bitrateKbps: number, packetLoss?: number): void;
|
|
394
|
+
reportClientStats(deviceId: string, stats: Omit<ClientNetworkStats, 'lastUpdated'>): void;
|
|
395
|
+
getDeviceStats(deviceId: string): DeviceNetworkStats | null;
|
|
396
|
+
getAllStats(): readonly DeviceNetworkStats[];
|
|
397
|
+
private getOrCreateDevice;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
interface ReplScope {
|
|
401
|
+
readonly type: 'system' | 'provider' | 'device' | 'addon';
|
|
402
|
+
readonly providerId?: string;
|
|
403
|
+
readonly deviceId?: string;
|
|
404
|
+
readonly addonId?: string;
|
|
405
|
+
}
|
|
406
|
+
interface ReplSessionContext {
|
|
407
|
+
readonly scope: ReplScope;
|
|
408
|
+
/** Pre-populated variables available in the REPL session */
|
|
409
|
+
readonly variables: Record<string, unknown>;
|
|
410
|
+
}
|
|
411
|
+
interface ReplResult {
|
|
412
|
+
readonly output: string;
|
|
413
|
+
readonly type: 'value' | 'error' | 'void';
|
|
414
|
+
readonly duration: number;
|
|
415
|
+
}
|
|
416
|
+
interface IReplEngine {
|
|
417
|
+
execute(code: string, context: ReplSessionContext): Promise<ReplResult>;
|
|
418
|
+
getCompletions(partial: string, context: ReplSessionContext): Promise<string[]>;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Context provider that the server kernel implements.
|
|
422
|
+
* The REPL engine calls this to build the sandbox for each scope type.
|
|
423
|
+
*/
|
|
424
|
+
interface IReplContextProvider {
|
|
425
|
+
getSystemSandbox(): Record<string, unknown>;
|
|
426
|
+
getDeviceSandbox(deviceId: string): Record<string, unknown>;
|
|
427
|
+
getProviderSandbox(providerId: string): Record<string, unknown>;
|
|
428
|
+
getAddonSandbox(addonId: string): Record<string, unknown>;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
declare class ReplEngine implements IReplEngine {
|
|
432
|
+
private readonly contextProvider;
|
|
433
|
+
constructor(contextProvider: IReplContextProvider);
|
|
434
|
+
execute(code: string, context: ReplSessionContext): Promise<ReplResult>;
|
|
435
|
+
getCompletions(partial: string, context: ReplSessionContext): Promise<string[]>;
|
|
436
|
+
private buildSandbox;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
interface AgentInfo {
|
|
440
|
+
readonly id: string;
|
|
441
|
+
readonly name: string;
|
|
442
|
+
readonly capabilities: readonly string[];
|
|
443
|
+
readonly host: string;
|
|
444
|
+
readonly port: number;
|
|
445
|
+
readonly resources?: AgentResources;
|
|
446
|
+
}
|
|
447
|
+
interface AgentResources {
|
|
448
|
+
readonly cpuCores?: number;
|
|
449
|
+
readonly memoryMB?: number;
|
|
450
|
+
readonly gpuAvailable?: boolean;
|
|
451
|
+
readonly gpuModel?: string;
|
|
452
|
+
}
|
|
453
|
+
interface AgentStatus {
|
|
454
|
+
readonly id: string;
|
|
455
|
+
readonly name: string;
|
|
456
|
+
readonly state: 'online' | 'offline' | 'degraded';
|
|
457
|
+
readonly capabilities: readonly string[];
|
|
458
|
+
readonly lastHeartbeat: number;
|
|
459
|
+
readonly connectedSince?: number;
|
|
460
|
+
readonly resources?: AgentResources;
|
|
461
|
+
readonly activeTaskCount: number;
|
|
462
|
+
readonly completedTaskCount: number;
|
|
463
|
+
readonly failedTaskCount: number;
|
|
464
|
+
}
|
|
465
|
+
interface AgentTask {
|
|
466
|
+
readonly id: string;
|
|
467
|
+
readonly capability: string;
|
|
468
|
+
readonly input: Record<string, unknown>;
|
|
469
|
+
readonly timeout: number;
|
|
470
|
+
readonly priority: 'low' | 'normal' | 'high';
|
|
471
|
+
readonly label?: string;
|
|
472
|
+
}
|
|
473
|
+
interface AgentTaskResult {
|
|
474
|
+
readonly taskId: string;
|
|
475
|
+
readonly agentId: string;
|
|
476
|
+
readonly status: 'success' | 'error' | 'timeout';
|
|
477
|
+
readonly output?: Record<string, unknown>;
|
|
478
|
+
readonly error?: string;
|
|
479
|
+
readonly durationMs: number;
|
|
480
|
+
}
|
|
481
|
+
interface TaskDispatchOptions {
|
|
482
|
+
readonly preferredAgent?: string;
|
|
483
|
+
readonly capability: string;
|
|
484
|
+
readonly remoteOnly?: boolean;
|
|
485
|
+
}
|
|
486
|
+
interface AgentEntry {
|
|
487
|
+
readonly info: AgentInfo;
|
|
488
|
+
state: 'online' | 'offline' | 'degraded';
|
|
489
|
+
connectedSince: number;
|
|
490
|
+
lastHeartbeat: number;
|
|
491
|
+
activeTaskCount: number;
|
|
492
|
+
completedTaskCount: number;
|
|
493
|
+
failedTaskCount: number;
|
|
494
|
+
}
|
|
495
|
+
/** Event emitter for agent lifecycle and task events */
|
|
496
|
+
type AgentEventEmitter = {
|
|
497
|
+
emitAgentRegistered(agentId: string, capabilities: readonly string[]): void;
|
|
498
|
+
emitAgentUnregistered(agentId: string): void;
|
|
499
|
+
emitAgentOnline(agentId: string): void;
|
|
500
|
+
emitAgentOffline(agentId: string): void;
|
|
501
|
+
emitTaskDispatched(taskId: string, agentId: string, capability: string): void;
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
declare class AgentRegistry {
|
|
505
|
+
private readonly events;
|
|
506
|
+
private readonly heartbeatTimeoutMs;
|
|
507
|
+
private readonly heartbeatCheckIntervalMs;
|
|
508
|
+
private readonly agents;
|
|
509
|
+
private readonly heartbeatIntervals;
|
|
510
|
+
constructor(events: AgentEventEmitter, heartbeatTimeoutMs?: number, heartbeatCheckIntervalMs?: number);
|
|
511
|
+
/** Register a new agent */
|
|
512
|
+
registerAgent(info: AgentInfo): void;
|
|
513
|
+
/** Remove agent */
|
|
514
|
+
unregisterAgent(id: string): void;
|
|
515
|
+
/** Update heartbeat timestamp */
|
|
516
|
+
heartbeat(id: string): void;
|
|
517
|
+
/** Find agents with a specific capability */
|
|
518
|
+
getAgentsWithCapability(capability: string): readonly AgentEntry[];
|
|
519
|
+
/** Get best agent for a task (least loaded) */
|
|
520
|
+
selectAgent(capability: string, preferredId?: string): AgentEntry | null;
|
|
521
|
+
/** List all agents with status */
|
|
522
|
+
listAgents(): readonly AgentStatus[];
|
|
523
|
+
/** Get single agent */
|
|
524
|
+
getAgent(id: string): AgentEntry | null;
|
|
525
|
+
/** Destroy all heartbeat intervals */
|
|
526
|
+
destroy(): void;
|
|
527
|
+
private startHeartbeatCheck;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
declare class TaskDispatcher {
|
|
531
|
+
private readonly agentRegistry;
|
|
532
|
+
private readonly events;
|
|
533
|
+
private readonly pendingTasks;
|
|
534
|
+
private readonly localExecutors;
|
|
535
|
+
constructor(agentRegistry: AgentRegistry, events: AgentEventEmitter);
|
|
536
|
+
/** Dispatch a task to the best available agent */
|
|
537
|
+
dispatch(task: AgentTask, options?: TaskDispatchOptions): Promise<AgentTaskResult>;
|
|
538
|
+
/** Register a local executor for a capability (in-process fallback) */
|
|
539
|
+
registerLocalExecutor(capability: string, executor: (input: Record<string, unknown>) => Promise<Record<string, unknown>>): void;
|
|
540
|
+
/** Called when an agent returns a result */
|
|
541
|
+
handleTaskResult(result: AgentTaskResult): void;
|
|
542
|
+
private executeLocally;
|
|
543
|
+
private sendToAgent;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
547
|
+
interface LogEntry {
|
|
548
|
+
timestamp: Date;
|
|
549
|
+
level: LogLevel;
|
|
550
|
+
scope: string[];
|
|
551
|
+
message: string;
|
|
552
|
+
meta?: Record<string, unknown>;
|
|
553
|
+
}
|
|
554
|
+
interface LogFilter {
|
|
555
|
+
scope?: string[];
|
|
556
|
+
level?: LogLevel;
|
|
557
|
+
since?: Date;
|
|
558
|
+
until?: Date;
|
|
559
|
+
limit?: number;
|
|
560
|
+
}
|
|
561
|
+
declare class LogRingBuffer {
|
|
562
|
+
private readonly capacity;
|
|
563
|
+
private readonly buffer;
|
|
564
|
+
private head;
|
|
565
|
+
private count;
|
|
566
|
+
constructor(capacity?: number);
|
|
567
|
+
push(entry: LogEntry): void;
|
|
568
|
+
getAll(): LogEntry[];
|
|
569
|
+
query(filter: LogFilter): LogEntry[];
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
declare class ScopedLogger implements IScopedLogger {
|
|
573
|
+
private readonly scope;
|
|
574
|
+
private readonly writeFn;
|
|
575
|
+
constructor(scope: string[], writeFn: (entry: LogEntry) => void);
|
|
576
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
577
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
578
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
579
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
580
|
+
child(childScope: string): IScopedLogger;
|
|
581
|
+
private write;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
interface AgentClientConfig {
|
|
585
|
+
readonly hubUrl: string;
|
|
586
|
+
readonly token?: string;
|
|
587
|
+
readonly logger: IScopedLogger;
|
|
588
|
+
readonly registrationInfo: AgentRegistrationInfo;
|
|
589
|
+
}
|
|
590
|
+
type MessageHandler = (msg: HubToAgentMessage) => void;
|
|
591
|
+
type BinaryHandler = (data: Buffer) => void;
|
|
592
|
+
type ConnectionHandler = () => void;
|
|
593
|
+
/**
|
|
594
|
+
* WebSocket client for agent mode. Framework-agnostic (no NestJS).
|
|
595
|
+
* Connects to the hub, sends registration, handles messages,
|
|
596
|
+
* reconnects with exponential backoff.
|
|
597
|
+
*/
|
|
598
|
+
declare class AgentClient {
|
|
599
|
+
private ws;
|
|
600
|
+
private reconnectAttempt;
|
|
601
|
+
private reconnectTimer;
|
|
602
|
+
private heartbeatTimer;
|
|
603
|
+
private destroyed;
|
|
604
|
+
private readonly messageHandlers;
|
|
605
|
+
private readonly binaryHandlers;
|
|
606
|
+
private readonly connectHandlers;
|
|
607
|
+
private readonly disconnectHandlers;
|
|
608
|
+
private readonly logger;
|
|
609
|
+
private readonly hubUrl;
|
|
610
|
+
private readonly token?;
|
|
611
|
+
private readonly registrationInfo;
|
|
612
|
+
private runtimeStatus;
|
|
613
|
+
constructor(config: AgentClientConfig);
|
|
614
|
+
/** Connect to the hub WebSocket */
|
|
615
|
+
connect(): Promise<void>;
|
|
616
|
+
/** Disconnect and stop reconnecting */
|
|
617
|
+
disconnect(): void;
|
|
618
|
+
/** Send a JSON control message to the hub */
|
|
619
|
+
send(msg: AgentToHubMessage): void;
|
|
620
|
+
/** Send a binary frame to the hub */
|
|
621
|
+
sendBinary(data: Buffer): void;
|
|
622
|
+
/** Register a handler for JSON messages from hub */
|
|
623
|
+
onMessage(handler: MessageHandler): void;
|
|
624
|
+
/** Register a handler for binary frames from hub */
|
|
625
|
+
onBinaryFrame(handler: BinaryHandler): void;
|
|
626
|
+
/** Register a handler for successful connection */
|
|
627
|
+
onConnect(handler: ConnectionHandler): void;
|
|
628
|
+
/** Register a handler for disconnection */
|
|
629
|
+
onDisconnect(handler: ConnectionHandler): void;
|
|
630
|
+
/** Update the runtime status (used in heartbeat) */
|
|
631
|
+
updateStatus(status: AgentRuntimeStatus): void;
|
|
632
|
+
/** Whether currently connected */
|
|
633
|
+
get connected(): boolean;
|
|
634
|
+
private doConnect;
|
|
635
|
+
private handleBuiltinMessage;
|
|
636
|
+
private scheduleReconnect;
|
|
637
|
+
private startHeartbeat;
|
|
638
|
+
private stopHeartbeat;
|
|
639
|
+
private clearTimers;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Receives task assignments from hub, routes to registered ITaskHandler instances.
|
|
644
|
+
* Maintains handler registry, manages task lifecycle.
|
|
645
|
+
* Framework-agnostic (no NestJS).
|
|
646
|
+
*/
|
|
647
|
+
declare class AgentTaskRunner {
|
|
648
|
+
private readonly agentId;
|
|
649
|
+
private readonly client;
|
|
650
|
+
private readonly handlers;
|
|
651
|
+
private readonly runningTasks;
|
|
652
|
+
private readonly logger;
|
|
653
|
+
constructor(agentId: string, client: AgentClient, logger: IScopedLogger);
|
|
654
|
+
/** Register a task handler for a given task type */
|
|
655
|
+
registerHandler(handler: ITaskHandler): void;
|
|
656
|
+
/** Unregister a task handler */
|
|
657
|
+
unregisterHandler(taskType: string): void;
|
|
658
|
+
/** Get all registered task types */
|
|
659
|
+
getTaskTypes(): readonly string[];
|
|
660
|
+
/** Get a handler by task type */
|
|
661
|
+
getHandler(taskType: string): ITaskHandler | undefined;
|
|
662
|
+
/**
|
|
663
|
+
* Execute a task dispatched from the hub.
|
|
664
|
+
* Sends result or error back to hub via the AgentClient.
|
|
665
|
+
*/
|
|
666
|
+
executeTask(taskId: string, taskType: string, payload: unknown): Promise<void>;
|
|
667
|
+
/** Cancel a running task */
|
|
668
|
+
cancelTask(taskId: string): Promise<void>;
|
|
669
|
+
/** Number of currently running tasks */
|
|
670
|
+
get activeTaskCount(): number;
|
|
671
|
+
/** Destroy: cancel all running tasks */
|
|
672
|
+
destroy(): Promise<void>;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Stub task handler for pipeline.decode.
|
|
677
|
+
* In the full implementation, this would start a StreamBroker for a camera
|
|
678
|
+
* and send decoded frames to the hub via the AgentClient.
|
|
679
|
+
*/
|
|
680
|
+
declare class DecodeTaskHandler implements ITaskHandler {
|
|
681
|
+
readonly taskType = "pipeline.decode";
|
|
682
|
+
readonly description = "Decode an RTSP stream and produce JPEG frames";
|
|
683
|
+
handle(payload: unknown, context: TaskContext): Promise<unknown>;
|
|
684
|
+
cancel(): Promise<void>;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Stub task handler for pipeline.detect.
|
|
688
|
+
* In the full implementation, this would receive frames (via binary WS),
|
|
689
|
+
* run them through the detection engine, and send results back.
|
|
690
|
+
*/
|
|
691
|
+
declare class DetectTaskHandler implements ITaskHandler {
|
|
692
|
+
readonly taskType = "pipeline.detect";
|
|
693
|
+
readonly description = "Run object detection on received frames";
|
|
694
|
+
handle(payload: unknown, context: TaskContext): Promise<unknown>;
|
|
695
|
+
cancel(): Promise<void>;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Stub task handler for pipeline.record.
|
|
699
|
+
* In the full implementation, this would start recording an RTSP stream
|
|
700
|
+
* to disk in segments and report segment metadata back to the hub.
|
|
701
|
+
*/
|
|
702
|
+
declare class RecordTaskHandler implements ITaskHandler {
|
|
703
|
+
readonly taskType = "pipeline.record";
|
|
704
|
+
readonly description = "Record an RTSP stream to disk segments";
|
|
705
|
+
handle(payload: unknown, context: TaskContext): Promise<unknown>;
|
|
706
|
+
cancel(): Promise<void>;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
declare class FileSystemStorage implements IFileStorage$1 {
|
|
710
|
+
private readonly basePath;
|
|
711
|
+
constructor(basePath: string);
|
|
712
|
+
readFile(filePath: string): Promise<Buffer>;
|
|
713
|
+
writeFile(filePath: string, data: Buffer): Promise<void>;
|
|
714
|
+
deleteFile(filePath: string): Promise<void>;
|
|
715
|
+
listFiles(prefix?: string): Promise<readonly string[]>;
|
|
716
|
+
getFileUrl(_path: string): Promise<string>;
|
|
717
|
+
exists(filePath: string): Promise<boolean>;
|
|
718
|
+
}
|
|
719
|
+
declare class SqliteStorageProvider implements IStorageProvider$1 {
|
|
720
|
+
private mainDb;
|
|
721
|
+
private sharedStructured;
|
|
722
|
+
private readonly locations;
|
|
723
|
+
initialize(): Promise<void>;
|
|
724
|
+
/**
|
|
725
|
+
* Configure all storage locations.
|
|
726
|
+
* ONE single SQLite database (camstack.db) is used for ALL structured storage.
|
|
727
|
+
* File-based locations use the filesystem at their configured path.
|
|
728
|
+
*/
|
|
729
|
+
configure(config: {
|
|
730
|
+
locations: Record<string, string>;
|
|
731
|
+
}): Promise<void>;
|
|
732
|
+
getLocation(name: StorageLocationName$1): IStorageLocation$1;
|
|
733
|
+
shutdown(): Promise<void>;
|
|
734
|
+
export(_locationName: StorageLocationName$1): Promise<Buffer>;
|
|
735
|
+
import(_locationName: StorageLocationName$1, _data: Buffer): Promise<void>;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
declare class SqliteStorageAddon implements ICamstackAddon, IConfigurable {
|
|
739
|
+
readonly manifest: AddonManifest;
|
|
740
|
+
private provider;
|
|
741
|
+
initialize(context: AddonContext): Promise<void>;
|
|
742
|
+
shutdown(): Promise<void>;
|
|
743
|
+
getProvider(): SqliteStorageProvider;
|
|
744
|
+
getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
|
|
745
|
+
getConfigSchema(): ConfigUISchema;
|
|
746
|
+
getConfig(): Record<string, unknown>;
|
|
747
|
+
onConfigChange(_config: Record<string, unknown>): Promise<void>;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
interface WinstonConfig {
|
|
751
|
+
readonly level: string;
|
|
752
|
+
readonly retentionDays: number;
|
|
753
|
+
/** Resolved absolute path to the logs directory (replaces the old dataPath field). */
|
|
754
|
+
readonly logsDir: string;
|
|
755
|
+
}
|
|
756
|
+
declare class WinstonDestination implements ILogDestination$1 {
|
|
757
|
+
private logger;
|
|
758
|
+
initialize(config?: WinstonConfig): Promise<void>;
|
|
759
|
+
write(entry: LogEntry$1): void;
|
|
760
|
+
query(_filter: LogFilter$1): Promise<readonly LogEntry$1[]>;
|
|
761
|
+
shutdown(): Promise<void>;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
declare class WinstonLoggingAddon implements ICamstackAddon, IConfigurable {
|
|
765
|
+
readonly manifest: AddonManifest;
|
|
766
|
+
private destination;
|
|
767
|
+
private currentConfig;
|
|
768
|
+
initialize(context: AddonContext): Promise<void>;
|
|
769
|
+
shutdown(): Promise<void>;
|
|
770
|
+
getDestination(): WinstonDestination;
|
|
771
|
+
getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
|
|
772
|
+
getConfigSchema(): ConfigUISchema;
|
|
773
|
+
getConfig(): Record<string, unknown>;
|
|
774
|
+
onConfigChange(config: Record<string, unknown>): Promise<void>;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
interface BackupManifest {
|
|
778
|
+
readonly id: string;
|
|
779
|
+
readonly timestamp: number;
|
|
780
|
+
readonly label?: string;
|
|
781
|
+
readonly locations: readonly string[];
|
|
782
|
+
readonly sizeMB: number;
|
|
783
|
+
readonly path: string;
|
|
784
|
+
}
|
|
785
|
+
interface BackupConfig {
|
|
786
|
+
readonly backupDir: string;
|
|
787
|
+
readonly retentionCount: number;
|
|
788
|
+
}
|
|
789
|
+
declare class LocalBackupService {
|
|
790
|
+
private readonly config;
|
|
791
|
+
private readonly logger;
|
|
792
|
+
private readonly eventBus;
|
|
793
|
+
private readonly storage;
|
|
794
|
+
private manifests;
|
|
795
|
+
constructor(config: BackupConfig, logger: IScopedLogger, eventBus: IEventBus, storage: IStorageLocation$1);
|
|
796
|
+
/** Create a backup of specified locations */
|
|
797
|
+
backup(options?: {
|
|
798
|
+
locations?: string[];
|
|
799
|
+
label?: string;
|
|
800
|
+
}): Promise<BackupManifest>;
|
|
801
|
+
/** Restore from a backup */
|
|
802
|
+
restore(backupId: string): Promise<void>;
|
|
803
|
+
/** List all backups sorted by timestamp descending */
|
|
804
|
+
list(): readonly BackupManifest[];
|
|
805
|
+
/** Delete a specific backup */
|
|
806
|
+
delete(backupId: string): Promise<void>;
|
|
807
|
+
private pruneOldBackups;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
declare class LocalBackupAddon implements ICamstackAddon, IConfigurable {
|
|
811
|
+
readonly manifest: AddonManifest;
|
|
812
|
+
private service;
|
|
813
|
+
private currentConfig;
|
|
814
|
+
initialize(context: AddonContext): Promise<void>;
|
|
815
|
+
shutdown(): Promise<void>;
|
|
816
|
+
getService(): LocalBackupService;
|
|
817
|
+
getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
|
|
818
|
+
getConfigSchema(): ConfigUISchema;
|
|
819
|
+
getConfig(): Record<string, unknown>;
|
|
820
|
+
onConfigChange(config: Record<string, unknown>): Promise<void>;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
declare class AdminUIAddon implements ICamstackAddon {
|
|
824
|
+
readonly id = "admin-ui";
|
|
825
|
+
readonly manifest: AddonManifest;
|
|
826
|
+
initialize(_ctx: AddonContext): Promise<void>;
|
|
827
|
+
shutdown(): Promise<void>;
|
|
828
|
+
getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
type ElementState = 'stopped' | 'starting' | 'running' | 'stopping' | 'error' | 'disabled';
|
|
832
|
+
interface ElementStatus {
|
|
833
|
+
state: ElementState;
|
|
834
|
+
error?: string;
|
|
835
|
+
startedAt?: number;
|
|
836
|
+
stoppedAt?: number;
|
|
837
|
+
restartCount: number;
|
|
838
|
+
uptime: number;
|
|
839
|
+
}
|
|
840
|
+
declare class LifecycleStateMachine {
|
|
841
|
+
private readonly elementId;
|
|
842
|
+
private readonly elementType;
|
|
843
|
+
private readonly eventBus;
|
|
844
|
+
private readonly logger;
|
|
845
|
+
private _state;
|
|
846
|
+
private _error?;
|
|
847
|
+
private _startedAt?;
|
|
848
|
+
private _stoppedAt?;
|
|
849
|
+
private _restartCount;
|
|
850
|
+
private _hasStartedOnce;
|
|
851
|
+
constructor(elementId: string, elementType: 'provider' | 'device' | 'addon' | 'process', eventBus: IEventBus, logger: IScopedLogger);
|
|
852
|
+
get state(): ElementState;
|
|
853
|
+
getStatus(): ElementStatus;
|
|
854
|
+
transition(to: ElementState, error?: string): boolean;
|
|
855
|
+
incrementRestartCount(): void;
|
|
856
|
+
private isValidTransition;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
interface FeatureManifest {
|
|
860
|
+
streaming: boolean;
|
|
861
|
+
notifications: boolean;
|
|
862
|
+
objectDetection: boolean;
|
|
863
|
+
remoteAccess: boolean;
|
|
864
|
+
agentCluster: boolean;
|
|
865
|
+
smartHome: boolean;
|
|
866
|
+
recordings: boolean;
|
|
867
|
+
backup: boolean;
|
|
868
|
+
repl: boolean;
|
|
869
|
+
}
|
|
870
|
+
type FeatureFlag = keyof FeatureManifest;
|
|
871
|
+
type FeatureConfigReader = {
|
|
872
|
+
readonly features: FeatureManifest;
|
|
873
|
+
};
|
|
874
|
+
declare class FeatureManager {
|
|
875
|
+
private readonly configReader;
|
|
876
|
+
constructor(configReader: FeatureConfigReader);
|
|
877
|
+
isEnabled(flag: FeatureFlag): boolean;
|
|
878
|
+
getManifest(): FeatureManifest;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/** Bootstrap config -- loaded from config.yaml ONLY at startup.
|
|
882
|
+
* All other settings live in SQL (system_settings table). */
|
|
883
|
+
declare const bootstrapSchema: z.ZodObject<{
|
|
884
|
+
/** Server mode: 'hub' (full server) or 'agent' (worker node) */
|
|
885
|
+
mode: z.ZodDefault<z.ZodEnum<["hub", "agent"]>>;
|
|
886
|
+
server: z.ZodDefault<z.ZodObject<{
|
|
887
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
888
|
+
host: z.ZodDefault<z.ZodString>;
|
|
889
|
+
dataPath: z.ZodDefault<z.ZodString>;
|
|
890
|
+
}, "strip", z.ZodTypeAny, {
|
|
891
|
+
port: number;
|
|
892
|
+
host: string;
|
|
893
|
+
dataPath: string;
|
|
894
|
+
}, {
|
|
895
|
+
port?: number | undefined;
|
|
896
|
+
host?: string | undefined;
|
|
897
|
+
dataPath?: string | undefined;
|
|
898
|
+
}>>;
|
|
899
|
+
auth: z.ZodDefault<z.ZodObject<{
|
|
900
|
+
jwtSecret: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
901
|
+
adminUsername: z.ZodDefault<z.ZodString>;
|
|
902
|
+
adminPassword: z.ZodDefault<z.ZodString>;
|
|
903
|
+
}, "strip", z.ZodTypeAny, {
|
|
904
|
+
jwtSecret: string | null;
|
|
905
|
+
adminUsername: string;
|
|
906
|
+
adminPassword: string;
|
|
907
|
+
}, {
|
|
908
|
+
jwtSecret?: string | null | undefined;
|
|
909
|
+
adminUsername?: string | undefined;
|
|
910
|
+
adminPassword?: string | undefined;
|
|
911
|
+
}>>;
|
|
912
|
+
/** Hub connection config — only used when mode='agent' */
|
|
913
|
+
hub: z.ZodDefault<z.ZodObject<{
|
|
914
|
+
url: z.ZodDefault<z.ZodString>;
|
|
915
|
+
token: z.ZodDefault<z.ZodString>;
|
|
916
|
+
}, "strip", z.ZodTypeAny, {
|
|
917
|
+
url: string;
|
|
918
|
+
token: string;
|
|
919
|
+
}, {
|
|
920
|
+
url?: string | undefined;
|
|
921
|
+
token?: string | undefined;
|
|
922
|
+
}>>;
|
|
923
|
+
/** Agent-specific config — only used when mode='agent' */
|
|
924
|
+
agent: z.ZodDefault<z.ZodObject<{
|
|
925
|
+
name: z.ZodDefault<z.ZodString>;
|
|
926
|
+
/** Port for the agent status page (minimal HTML) */
|
|
927
|
+
statusPort: z.ZodDefault<z.ZodNumber>;
|
|
928
|
+
}, "strip", z.ZodTypeAny, {
|
|
929
|
+
name: string;
|
|
930
|
+
statusPort: number;
|
|
931
|
+
}, {
|
|
932
|
+
name?: string | undefined;
|
|
933
|
+
statusPort?: number | undefined;
|
|
934
|
+
}>>;
|
|
935
|
+
}, "strip", z.ZodTypeAny, {
|
|
936
|
+
agent: {
|
|
937
|
+
name: string;
|
|
938
|
+
statusPort: number;
|
|
939
|
+
};
|
|
940
|
+
auth: {
|
|
941
|
+
jwtSecret: string | null;
|
|
942
|
+
adminUsername: string;
|
|
943
|
+
adminPassword: string;
|
|
944
|
+
};
|
|
945
|
+
mode: "agent" | "hub";
|
|
946
|
+
hub: {
|
|
947
|
+
url: string;
|
|
948
|
+
token: string;
|
|
949
|
+
};
|
|
950
|
+
server: {
|
|
951
|
+
port: number;
|
|
952
|
+
host: string;
|
|
953
|
+
dataPath: string;
|
|
954
|
+
};
|
|
955
|
+
}, {
|
|
956
|
+
agent?: {
|
|
957
|
+
name?: string | undefined;
|
|
958
|
+
statusPort?: number | undefined;
|
|
959
|
+
} | undefined;
|
|
960
|
+
auth?: {
|
|
961
|
+
jwtSecret?: string | null | undefined;
|
|
962
|
+
adminUsername?: string | undefined;
|
|
963
|
+
adminPassword?: string | undefined;
|
|
964
|
+
} | undefined;
|
|
965
|
+
mode?: "agent" | "hub" | undefined;
|
|
966
|
+
hub?: {
|
|
967
|
+
url?: string | undefined;
|
|
968
|
+
token?: string | undefined;
|
|
969
|
+
} | undefined;
|
|
970
|
+
server?: {
|
|
971
|
+
port?: number | undefined;
|
|
972
|
+
host?: string | undefined;
|
|
973
|
+
dataPath?: string | undefined;
|
|
974
|
+
} | undefined;
|
|
975
|
+
}>;
|
|
976
|
+
type BootstrapConfig = z.infer<typeof bootstrapSchema>;
|
|
977
|
+
/**
|
|
978
|
+
* Runtime defaults -- used by ConfigManager.get() for backward compatibility
|
|
979
|
+
* until Plan B wires all runtime settings to the system_settings SQL table.
|
|
980
|
+
*/
|
|
981
|
+
declare const RUNTIME_DEFAULTS: Record<string, unknown>;
|
|
982
|
+
type ServerMode = 'hub' | 'agent';
|
|
983
|
+
type AppConfig = BootstrapConfig & {
|
|
984
|
+
features: {
|
|
985
|
+
streaming: boolean;
|
|
986
|
+
notifications: boolean;
|
|
987
|
+
objectDetection: boolean;
|
|
988
|
+
remoteAccess: boolean;
|
|
989
|
+
agentCluster: boolean;
|
|
990
|
+
smartHome: boolean;
|
|
991
|
+
recordings: boolean;
|
|
992
|
+
backup: boolean;
|
|
993
|
+
repl: boolean;
|
|
994
|
+
};
|
|
995
|
+
storage: {
|
|
996
|
+
provider: string;
|
|
997
|
+
locations: Record<string, string>;
|
|
998
|
+
};
|
|
999
|
+
logging: {
|
|
1000
|
+
level: string;
|
|
1001
|
+
retentionDays: number;
|
|
1002
|
+
};
|
|
1003
|
+
eventBus: {
|
|
1004
|
+
ringBufferSize: number;
|
|
1005
|
+
};
|
|
1006
|
+
addons: {
|
|
1007
|
+
enabled: string[];
|
|
1008
|
+
};
|
|
1009
|
+
retention: {
|
|
1010
|
+
detectionEventsDays: number;
|
|
1011
|
+
audioLevelsDays: number;
|
|
1012
|
+
};
|
|
1013
|
+
providers: Array<{
|
|
1014
|
+
id: string;
|
|
1015
|
+
type: string;
|
|
1016
|
+
name: string;
|
|
1017
|
+
url?: string;
|
|
1018
|
+
username?: string;
|
|
1019
|
+
password?: string;
|
|
1020
|
+
mqtt?: {
|
|
1021
|
+
brokerUrl: string;
|
|
1022
|
+
username?: string;
|
|
1023
|
+
password?: string;
|
|
1024
|
+
topicPrefix: string;
|
|
1025
|
+
};
|
|
1026
|
+
}>;
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
interface ISettingsStore {
|
|
1030
|
+
getSystem(key: string): unknown;
|
|
1031
|
+
setSystem(key: string, value: unknown): void;
|
|
1032
|
+
getAllSystem(): Record<string, unknown>;
|
|
1033
|
+
getAllAddon(addonId: string): Record<string, unknown>;
|
|
1034
|
+
setAllAddon(addonId: string, config: Record<string, unknown>): void;
|
|
1035
|
+
getAllProvider(providerId: string): Record<string, unknown>;
|
|
1036
|
+
setProvider(providerId: string, key: string, value: unknown): void;
|
|
1037
|
+
getAllDevice(deviceId: string): Record<string, unknown>;
|
|
1038
|
+
setDevice(deviceId: string, key: string, value: unknown): void;
|
|
1039
|
+
}
|
|
1040
|
+
declare class ConfigManager {
|
|
1041
|
+
private readonly configPath;
|
|
1042
|
+
private bootstrapConfig;
|
|
1043
|
+
private settingsStore;
|
|
1044
|
+
constructor(configPath: string);
|
|
1045
|
+
/** Called by main.ts after the SQLite DB is ready (Phase 2). */
|
|
1046
|
+
setSettingsStore(store: ISettingsStore): void;
|
|
1047
|
+
/**
|
|
1048
|
+
* Get a config value by dot-notation path.
|
|
1049
|
+
* Priority: bootstrap config -> SQL system_settings -> RUNTIME_DEFAULTS fallback.
|
|
1050
|
+
*/
|
|
1051
|
+
get<T>(path: string): T;
|
|
1052
|
+
/**
|
|
1053
|
+
* Write a value to SQL system_settings.
|
|
1054
|
+
* Throws if the settings store is not yet wired.
|
|
1055
|
+
*/
|
|
1056
|
+
set(key: string, value: unknown): void;
|
|
1057
|
+
/**
|
|
1058
|
+
* Bulk-read all system_settings keys that belong to a logical section.
|
|
1059
|
+
* A "section" is the first segment of a dot-notation key (e.g. 'features', 'logging').
|
|
1060
|
+
*/
|
|
1061
|
+
getSection(section: string): Record<string, unknown>;
|
|
1062
|
+
/**
|
|
1063
|
+
* Bulk-write a section of runtime settings to SQL system_settings.
|
|
1064
|
+
* Each entry in `data` is stored as `section.key`.
|
|
1065
|
+
*/
|
|
1066
|
+
setSection(section: string, data: Record<string, unknown>): void;
|
|
1067
|
+
/** Read all config for an addon from addon_settings. */
|
|
1068
|
+
getAddonConfig(addonId: string): Record<string, unknown>;
|
|
1069
|
+
/** Write (bulk-replace) config for an addon to addon_settings. */
|
|
1070
|
+
setAddonConfig(addonId: string, config: Record<string, unknown>): void;
|
|
1071
|
+
/** Read all config for a provider from provider_settings. */
|
|
1072
|
+
getProviderConfig(providerId: string): Record<string, unknown>;
|
|
1073
|
+
/** Write (upsert) a single key for a provider to provider_settings. */
|
|
1074
|
+
setProviderConfig(providerId: string, key: string, value: unknown): void;
|
|
1075
|
+
/** Read all config for a device from device_settings. */
|
|
1076
|
+
getDeviceConfig(deviceId: string): Record<string, unknown>;
|
|
1077
|
+
/** Write (upsert) a single key for a device to device_settings. */
|
|
1078
|
+
setDeviceConfig(deviceId: string, key: string, value: unknown): void;
|
|
1079
|
+
/** Get a value from the parsed bootstrap config */
|
|
1080
|
+
getBootstrap<T>(path: string): T;
|
|
1081
|
+
/** Features accessor -- reads from SQL when available, falls back to RUNTIME_DEFAULTS */
|
|
1082
|
+
get features(): FeatureManifest;
|
|
1083
|
+
/**
|
|
1084
|
+
* Returns a merged view of bootstrap config + runtime defaults for backward compat.
|
|
1085
|
+
*/
|
|
1086
|
+
get raw(): AppConfig;
|
|
1087
|
+
/**
|
|
1088
|
+
* Atomically update one top-level section of config.yaml and sync in-memory.
|
|
1089
|
+
* Only bootstrap sections (server, auth) are persisted. Runtime settings should
|
|
1090
|
+
* go to SQL (Plan B).
|
|
1091
|
+
*/
|
|
1092
|
+
update(section: string, data: Record<string, unknown>): void;
|
|
1093
|
+
/**
|
|
1094
|
+
* Deep-set a value in a nested plain object using a dot-notation path.
|
|
1095
|
+
* Returns a new object (immutable).
|
|
1096
|
+
*/
|
|
1097
|
+
private setNested;
|
|
1098
|
+
/**
|
|
1099
|
+
* Apply env var overrides onto the raw YAML object.
|
|
1100
|
+
* Only bootstrap-level env vars are applied.
|
|
1101
|
+
*/
|
|
1102
|
+
private applyEnvOverrides;
|
|
1103
|
+
private loadYaml;
|
|
1104
|
+
private warnDefaultCredentials;
|
|
1105
|
+
private getFromBootstrap;
|
|
1106
|
+
private getFromRuntimeDefaults;
|
|
1107
|
+
/**
|
|
1108
|
+
* Perform a prefix-based nested lookup against SQL system_settings.
|
|
1109
|
+
* e.g. path='features' matches keys 'features.streaming', 'features.notifications', etc.
|
|
1110
|
+
* Returns an object keyed by the sub-key, or undefined if nothing is found.
|
|
1111
|
+
*/
|
|
1112
|
+
private getNestedFromSystemSettings;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
declare class SystemEventBus implements IEventBus {
|
|
1116
|
+
private readonly ringBuffer;
|
|
1117
|
+
private readonly subscribers;
|
|
1118
|
+
constructor(bufferSize?: number);
|
|
1119
|
+
emit(event: SystemEvent): void;
|
|
1120
|
+
subscribe(filter: EventFilter, handler: (event: SystemEvent) => void): () => void;
|
|
1121
|
+
getRecent(filter?: EventFilter, limit?: number): readonly SystemEvent[];
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
interface ILogDestination {
|
|
1125
|
+
initialize(): Promise<void>;
|
|
1126
|
+
shutdown(): Promise<void>;
|
|
1127
|
+
write(entry: LogEntry): void;
|
|
1128
|
+
query?(filter: LogFilter): Promise<readonly LogEntry[]>;
|
|
1129
|
+
}
|
|
1130
|
+
declare class LogManager {
|
|
1131
|
+
private readonly ringBuffer;
|
|
1132
|
+
private readonly destinations;
|
|
1133
|
+
constructor(bufferSize?: number);
|
|
1134
|
+
createLogger(scope: string): IScopedLogger;
|
|
1135
|
+
addDestination(dest: ILogDestination): void;
|
|
1136
|
+
removeDestination(dest: ILogDestination): void;
|
|
1137
|
+
query(filter: LogFilter): LogEntry[];
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
interface IStorageBackend {
|
|
1141
|
+
/** Backend type identifier */
|
|
1142
|
+
readonly type: string;
|
|
1143
|
+
/** Base path of this backend */
|
|
1144
|
+
readonly basePath: string;
|
|
1145
|
+
/** Resolve a subpath to an absolute path */
|
|
1146
|
+
resolve(subpath: string): string;
|
|
1147
|
+
/** Check if the backend path exists and is writable */
|
|
1148
|
+
isAvailable(): boolean;
|
|
1149
|
+
/** Ensure base directory exists (mkdir -p equivalent) */
|
|
1150
|
+
initialize(): Promise<void>;
|
|
1151
|
+
}
|
|
1152
|
+
declare class FsStorageBackend implements IStorageBackend {
|
|
1153
|
+
readonly type = "local";
|
|
1154
|
+
readonly basePath: string;
|
|
1155
|
+
constructor(basePath: string);
|
|
1156
|
+
resolve(subpath: string): string;
|
|
1157
|
+
isAvailable(): boolean;
|
|
1158
|
+
initialize(): Promise<void>;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
type StorageLocationName = 'data' | 'media' | 'recordings' | 'models' | 'cache' | 'logs';
|
|
1162
|
+
declare class StorageLocationManager {
|
|
1163
|
+
private readonly backends;
|
|
1164
|
+
private readonly dataPath;
|
|
1165
|
+
constructor(dataPath: string);
|
|
1166
|
+
/** Initialize all locations with default paths */
|
|
1167
|
+
initializeDefaults(): Promise<void>;
|
|
1168
|
+
/** Override a specific location's backend path */
|
|
1169
|
+
setLocationPath(name: StorageLocationName, basePath: string): Promise<void>;
|
|
1170
|
+
/** Get the backend for a location */
|
|
1171
|
+
getBackend(name: StorageLocationName): IStorageBackend;
|
|
1172
|
+
/** Resolve a path within a location */
|
|
1173
|
+
resolve(location: StorageLocationName, subpath: string): string;
|
|
1174
|
+
/** Check if all locations are available */
|
|
1175
|
+
getStatus(): Array<{
|
|
1176
|
+
name: StorageLocationName;
|
|
1177
|
+
available: boolean;
|
|
1178
|
+
path: string;
|
|
1179
|
+
}>;
|
|
1180
|
+
/** All location names */
|
|
1181
|
+
getLocationNames(): StorageLocationName[];
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
interface IStorageProvider {
|
|
1185
|
+
initialize(): Promise<void>;
|
|
1186
|
+
shutdown(): Promise<void>;
|
|
1187
|
+
getLocation(name: StorageLocationName): IStorageLocation;
|
|
1188
|
+
export(locationName: StorageLocationName): Promise<Buffer>;
|
|
1189
|
+
import(locationName: StorageLocationName, data: Buffer): Promise<void>;
|
|
1190
|
+
}
|
|
1191
|
+
interface QueryFilter {
|
|
1192
|
+
where?: Record<string, unknown>;
|
|
1193
|
+
whereIn?: Record<string, unknown[]>;
|
|
1194
|
+
whereBetween?: Record<string, [unknown, unknown]>;
|
|
1195
|
+
orderBy?: {
|
|
1196
|
+
field: string;
|
|
1197
|
+
direction: 'asc' | 'desc';
|
|
1198
|
+
};
|
|
1199
|
+
limit?: number;
|
|
1200
|
+
offset?: number;
|
|
1201
|
+
}
|
|
1202
|
+
interface StorageRecord {
|
|
1203
|
+
collection: string;
|
|
1204
|
+
id: string;
|
|
1205
|
+
data: Record<string, unknown>;
|
|
1206
|
+
}
|
|
1207
|
+
interface IStructuredStorage {
|
|
1208
|
+
query(collection: string, filter?: QueryFilter): Promise<readonly StorageRecord[]>;
|
|
1209
|
+
insert(record: StorageRecord): Promise<StorageRecord>;
|
|
1210
|
+
update(collection: string, id: string, data: Record<string, unknown>): Promise<StorageRecord>;
|
|
1211
|
+
delete(collection: string, id: string): Promise<void>;
|
|
1212
|
+
count(collection: string, filter?: QueryFilter): Promise<number>;
|
|
1213
|
+
}
|
|
1214
|
+
interface IFileStorage {
|
|
1215
|
+
readFile(path: string): Promise<Buffer>;
|
|
1216
|
+
writeFile(path: string, data: Buffer): Promise<void>;
|
|
1217
|
+
deleteFile(path: string): Promise<void>;
|
|
1218
|
+
listFiles(prefix?: string): Promise<readonly string[]>;
|
|
1219
|
+
getFileUrl(path: string): Promise<string>;
|
|
1220
|
+
exists(path: string): Promise<boolean>;
|
|
1221
|
+
}
|
|
1222
|
+
interface IStorageLocation {
|
|
1223
|
+
structured?: IStructuredStorage;
|
|
1224
|
+
files?: IFileStorage;
|
|
1225
|
+
}
|
|
1226
|
+
declare class StorageManager {
|
|
1227
|
+
private provider;
|
|
1228
|
+
private locationManager;
|
|
1229
|
+
setProvider(provider: IStorageProvider): void;
|
|
1230
|
+
getProvider(): IStorageProvider;
|
|
1231
|
+
/**
|
|
1232
|
+
* Set the StorageLocationManager (called from main.ts during Phase 2 boot,
|
|
1233
|
+
* before NestJS lifecycle hooks run).
|
|
1234
|
+
*/
|
|
1235
|
+
setLocationManager(manager: StorageLocationManager): void;
|
|
1236
|
+
/**
|
|
1237
|
+
* Get the StorageLocationManager (for file path resolution).
|
|
1238
|
+
* Available after Phase 2 boot (main.ts calls setLocationManager).
|
|
1239
|
+
*/
|
|
1240
|
+
getLocationManager(): StorageLocationManager;
|
|
1241
|
+
/**
|
|
1242
|
+
* Initialize the StorageLocationManager with a dataPath and set it on this service.
|
|
1243
|
+
* Called during 3-phase boot from main.ts (Phase 2).
|
|
1244
|
+
*/
|
|
1245
|
+
initializeLocations(dataPath: string): Promise<void>;
|
|
1246
|
+
/**
|
|
1247
|
+
* Return the base filesystem path for a named storage location.
|
|
1248
|
+
* Convenience wrapper around locationManager.getBackend(name).basePath.
|
|
1249
|
+
* Available after Phase 2 boot (main.ts calls setLocationManager).
|
|
1250
|
+
*/
|
|
1251
|
+
getLocationPath(name: StorageLocationName): string;
|
|
1252
|
+
/**
|
|
1253
|
+
* Get a storage location, optionally namespaced.
|
|
1254
|
+
* Without namespace: returns the raw location (e.g., 'config' -> config DB)
|
|
1255
|
+
* With namespace: returns a scoped view where all collections/paths are prefixed
|
|
1256
|
+
* e.g., getLocation('addon', 'providers/frigate-1') -> collections prefixed with 'providers/frigate-1/'
|
|
1257
|
+
*/
|
|
1258
|
+
getLocation(name: StorageLocationName | string, namespace?: string): IStorageLocation;
|
|
1259
|
+
private createNamespacedLocation;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Thin wrapper over better-sqlite3 that manages the four settings tables:
|
|
1264
|
+
* system_settings, addon_settings, provider_settings, device_settings.
|
|
1265
|
+
*
|
|
1266
|
+
* All values are stored as JSON text and deserialized on read.
|
|
1267
|
+
*/
|
|
1268
|
+
declare class SettingsStore {
|
|
1269
|
+
private readonly db;
|
|
1270
|
+
constructor(dbPath: string);
|
|
1271
|
+
getSystem(key: string): unknown;
|
|
1272
|
+
setSystem(key: string, value: unknown): void;
|
|
1273
|
+
getAllSystem(): Record<string, unknown>;
|
|
1274
|
+
getAddon(addonId: string, key: string): unknown;
|
|
1275
|
+
setAddon(addonId: string, key: string, value: unknown): void;
|
|
1276
|
+
getAllAddon(addonId: string): Record<string, unknown>;
|
|
1277
|
+
/** Bulk-replace all keys for an addon (within a transaction). */
|
|
1278
|
+
setAllAddon(addonId: string, config: Record<string, unknown>): void;
|
|
1279
|
+
getProvider(providerId: string, key: string): unknown;
|
|
1280
|
+
setProvider(providerId: string, key: string, value: unknown): void;
|
|
1281
|
+
getAllProvider(providerId: string): Record<string, unknown>;
|
|
1282
|
+
getDevice(deviceId: string, key: string): unknown;
|
|
1283
|
+
setDevice(deviceId: string, key: string, value: unknown): void;
|
|
1284
|
+
getAllDevice(deviceId: string): Record<string, unknown>;
|
|
1285
|
+
/** Close the SQLite connection (call on shutdown). */
|
|
1286
|
+
close(): void;
|
|
1287
|
+
/** Check if system_settings is empty (used for first-boot seeding). */
|
|
1288
|
+
isSystemSettingsEmpty(): boolean;
|
|
1289
|
+
/** Seed system_settings with RUNTIME_DEFAULTS (only on first boot). */
|
|
1290
|
+
seedDefaults(): void;
|
|
1291
|
+
private initTables;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/** Core table DDL statements -- executed on first boot */
|
|
1295
|
+
declare const CORE_TABLE_DDL: readonly string[];
|
|
1296
|
+
/** Addon table schema declaration */
|
|
1297
|
+
interface AddonTableSchema {
|
|
1298
|
+
readonly name: string;
|
|
1299
|
+
readonly columns: ReadonlyArray<{
|
|
1300
|
+
readonly name: string;
|
|
1301
|
+
readonly type: 'TEXT' | 'INTEGER' | 'REAL' | 'JSON';
|
|
1302
|
+
readonly primaryKey?: boolean;
|
|
1303
|
+
readonly notNull?: boolean;
|
|
1304
|
+
}>;
|
|
1305
|
+
readonly indexes?: ReadonlyArray<{
|
|
1306
|
+
readonly name: string;
|
|
1307
|
+
readonly columns: readonly string[];
|
|
1308
|
+
readonly unique?: boolean;
|
|
1309
|
+
}>;
|
|
1310
|
+
}
|
|
1311
|
+
/** Generate CREATE TABLE DDL from addon schema */
|
|
1312
|
+
declare function addonTableToDdl(schema: AddonTableSchema): string[];
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Manages scoped API tokens with restricted addon/route/capability access.
|
|
1316
|
+
* Framework-agnostic — dependencies injected via constructor.
|
|
1317
|
+
*/
|
|
1318
|
+
declare class ScopedTokenManager {
|
|
1319
|
+
private readonly storage;
|
|
1320
|
+
constructor(storage: IStructuredStorage);
|
|
1321
|
+
/**
|
|
1322
|
+
* Create a new scoped token. Returns the raw token string (shown once)
|
|
1323
|
+
* and the stored record (with hash, not the raw token).
|
|
1324
|
+
*/
|
|
1325
|
+
create(userId: string, name: string, scopes: TokenScope[], expiresAt?: number): Promise<{
|
|
1326
|
+
token: string;
|
|
1327
|
+
record: ScopedToken;
|
|
1328
|
+
}>;
|
|
1329
|
+
/**
|
|
1330
|
+
* Validate a raw token string. Returns the token record if valid, null otherwise.
|
|
1331
|
+
*/
|
|
1332
|
+
validate(rawToken: string): Promise<ScopedToken | null>;
|
|
1333
|
+
/**
|
|
1334
|
+
* Check whether a token's scopes grant access to the given addon, route, or capability.
|
|
1335
|
+
*/
|
|
1336
|
+
matchesScope(token: ScopedToken, addonId?: string, routePath?: string, capability?: string): boolean;
|
|
1337
|
+
/**
|
|
1338
|
+
* Revoke a token by ID.
|
|
1339
|
+
*/
|
|
1340
|
+
revoke(tokenId: string): Promise<void>;
|
|
1341
|
+
/**
|
|
1342
|
+
* List all tokens for a user (without exposing the raw token).
|
|
1343
|
+
*/
|
|
1344
|
+
listForUser(userId: string): Promise<ScopedToken[]>;
|
|
1345
|
+
/**
|
|
1346
|
+
* Update the lastUsedAt timestamp for a token.
|
|
1347
|
+
*/
|
|
1348
|
+
updateLastUsed(tokenId: string): Promise<void>;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
type UserRole = 'super_admin' | 'admin' | 'viewer';
|
|
1352
|
+
interface TokenPayload {
|
|
1353
|
+
type?: 'api_key';
|
|
1354
|
+
keyId?: string;
|
|
1355
|
+
userId?: string;
|
|
1356
|
+
username?: string;
|
|
1357
|
+
role: UserRole;
|
|
1358
|
+
allowedProviders: string[] | '*';
|
|
1359
|
+
allowedDevices: Record<string, string[] | '*'>;
|
|
1360
|
+
iat?: number;
|
|
1361
|
+
exp?: number;
|
|
1362
|
+
}
|
|
1363
|
+
type AuthConfigReader = {
|
|
1364
|
+
get<T>(path: string): T;
|
|
1365
|
+
update(section: string, data: Record<string, unknown>): void;
|
|
1366
|
+
};
|
|
1367
|
+
declare class AuthManager {
|
|
1368
|
+
private readonly config;
|
|
1369
|
+
private readonly jwtSecret;
|
|
1370
|
+
private scopedTokenManager;
|
|
1371
|
+
constructor(config: AuthConfigReader);
|
|
1372
|
+
signToken(payload: Omit<TokenPayload, 'iat' | 'exp'>): string;
|
|
1373
|
+
verifyToken(token: string): TokenPayload;
|
|
1374
|
+
hashPassword(password: string): Promise<string>;
|
|
1375
|
+
comparePassword(password: string, hash: string): Promise<boolean>;
|
|
1376
|
+
generateApiKey(): {
|
|
1377
|
+
token: string;
|
|
1378
|
+
hash: string;
|
|
1379
|
+
prefix: string;
|
|
1380
|
+
};
|
|
1381
|
+
validateApiKey(token: string, storedHash: string): boolean;
|
|
1382
|
+
/**
|
|
1383
|
+
* Set the scoped token manager for the auth chain.
|
|
1384
|
+
*/
|
|
1385
|
+
setScopedTokenManager(manager: ScopedTokenManager): void;
|
|
1386
|
+
/**
|
|
1387
|
+
* Validate a scoped token string.
|
|
1388
|
+
* Returns the token record if valid, null otherwise.
|
|
1389
|
+
*/
|
|
1390
|
+
validateScopedToken(rawToken: string): Promise<ScopedToken | null>;
|
|
1391
|
+
/**
|
|
1392
|
+
* Check whether a scoped token grants access to a given addon/route/capability.
|
|
1393
|
+
*/
|
|
1394
|
+
matchesScopedTokenScope(token: ScopedToken, addonId?: string, routePath?: string, capability?: string): boolean;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
interface ApiKeyRecord {
|
|
1398
|
+
id: string;
|
|
1399
|
+
label: string;
|
|
1400
|
+
role: UserRole;
|
|
1401
|
+
allowedProviders: string[] | '*';
|
|
1402
|
+
allowedDevices: Record<string, string[] | '*'>;
|
|
1403
|
+
tokenHash: string;
|
|
1404
|
+
tokenPrefix: string;
|
|
1405
|
+
createdAt: number;
|
|
1406
|
+
lastUsedAt?: number;
|
|
1407
|
+
}
|
|
1408
|
+
interface CreateApiKeyInput {
|
|
1409
|
+
label: string;
|
|
1410
|
+
role: UserRole;
|
|
1411
|
+
allowedProviders?: string[] | '*';
|
|
1412
|
+
allowedDevices?: Record<string, string[] | '*'>;
|
|
1413
|
+
}
|
|
1414
|
+
type ApiKeyStorageAccess = {
|
|
1415
|
+
getStructuredStorage(): IStructuredStorage;
|
|
1416
|
+
};
|
|
1417
|
+
declare class ApiKeyManager {
|
|
1418
|
+
private readonly storageAccess;
|
|
1419
|
+
private readonly auth;
|
|
1420
|
+
constructor(storageAccess: ApiKeyStorageAccess, auth: AuthManager);
|
|
1421
|
+
private get structured();
|
|
1422
|
+
create(input: CreateApiKeyInput): Promise<{
|
|
1423
|
+
record: ApiKeyRecord;
|
|
1424
|
+
token: string;
|
|
1425
|
+
}>;
|
|
1426
|
+
validateToken(token: string): Promise<ApiKeyRecord | null>;
|
|
1427
|
+
listAll(): Promise<Omit<ApiKeyRecord, 'tokenHash'>[]>;
|
|
1428
|
+
revoke(id: string): Promise<void>;
|
|
1429
|
+
findById(id: string): Promise<ApiKeyRecord | null>;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
interface UserRecord {
|
|
1433
|
+
id: string;
|
|
1434
|
+
username: string;
|
|
1435
|
+
passwordHash: string;
|
|
1436
|
+
role: UserRole;
|
|
1437
|
+
allowedProviders: string[] | '*';
|
|
1438
|
+
allowedDevices: Record<string, string[] | '*'>;
|
|
1439
|
+
createdAt: number;
|
|
1440
|
+
updatedAt: number;
|
|
1441
|
+
}
|
|
1442
|
+
interface CreateUserInput {
|
|
1443
|
+
username: string;
|
|
1444
|
+
password: string;
|
|
1445
|
+
role: UserRole;
|
|
1446
|
+
allowedProviders?: string[] | '*';
|
|
1447
|
+
allowedDevices?: Record<string, string[] | '*'>;
|
|
1448
|
+
}
|
|
1449
|
+
type UpdatableUserFields = Partial<Pick<UserRecord, 'role' | 'allowedProviders' | 'allowedDevices'>>;
|
|
1450
|
+
type UserStorageAccess = {
|
|
1451
|
+
getStructuredStorage(): IStructuredStorage;
|
|
1452
|
+
};
|
|
1453
|
+
type UserConfigReader = {
|
|
1454
|
+
get<T>(path: string): T;
|
|
1455
|
+
};
|
|
1456
|
+
declare class UserManager {
|
|
1457
|
+
private readonly storageAccess;
|
|
1458
|
+
private readonly auth;
|
|
1459
|
+
private readonly config;
|
|
1460
|
+
constructor(storageAccess: UserStorageAccess, auth: AuthManager, config: UserConfigReader);
|
|
1461
|
+
private get structured();
|
|
1462
|
+
create(input: CreateUserInput): Promise<UserRecord>;
|
|
1463
|
+
findByUsername(username: string): Promise<UserRecord | null>;
|
|
1464
|
+
findById(id: string): Promise<UserRecord | null>;
|
|
1465
|
+
validateCredentials(username: string, password: string): Promise<UserRecord | null>;
|
|
1466
|
+
listAll(): Promise<Omit<UserRecord, 'passwordHash'>[]>;
|
|
1467
|
+
update(id: string, data: UpdatableUserFields): Promise<void>;
|
|
1468
|
+
delete(id: string): Promise<void>;
|
|
1469
|
+
resetPassword(id: string, newPassword: string): Promise<void>;
|
|
1470
|
+
ensureAdminExists(): Promise<void>;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* Central notification service that routes notifications to configured outputs.
|
|
1475
|
+
* Framework-agnostic — dependencies injected via constructor.
|
|
1476
|
+
*
|
|
1477
|
+
* Outputs are resolved from the CapabilityRegistry's 'notification-output'
|
|
1478
|
+
* collection on each call (proxy pattern). Falls back to a local map
|
|
1479
|
+
* when no registry is provided (backward compat).
|
|
1480
|
+
*/
|
|
1481
|
+
declare class NotificationService {
|
|
1482
|
+
private readonly logger;
|
|
1483
|
+
private readonly localOutputs;
|
|
1484
|
+
private readonly routing;
|
|
1485
|
+
private readonly rateLimits;
|
|
1486
|
+
private readonly lastSent;
|
|
1487
|
+
private registry;
|
|
1488
|
+
constructor(logger: IScopedLogger);
|
|
1489
|
+
/** Set the registry for live output lookup. Called once during boot. */
|
|
1490
|
+
setRegistry(registry: CapabilityRegistry): void;
|
|
1491
|
+
/** Resolve all outputs — prefers registry, falls back to local map */
|
|
1492
|
+
private get outputs();
|
|
1493
|
+
/** @deprecated Use registry-based resolution. Kept for backward compat only. */
|
|
1494
|
+
addOutput(output: INotificationOutput): void;
|
|
1495
|
+
/** @deprecated Use registry-based resolution. Kept for backward compat only. */
|
|
1496
|
+
removeOutput(id: string): void;
|
|
1497
|
+
setRouting(category: string, outputIds: string[]): void;
|
|
1498
|
+
setRateLimit(category: string, minIntervalMs: number): void;
|
|
1499
|
+
notify(notification: Notification): Promise<void>;
|
|
1500
|
+
getOutputs(): ReadonlyArray<{
|
|
1501
|
+
id: string;
|
|
1502
|
+
name: string;
|
|
1503
|
+
icon: string;
|
|
1504
|
+
}>;
|
|
1505
|
+
getRouting(): ReadonlyMap<string, string[]>;
|
|
1506
|
+
getOutput(id: string): INotificationOutput | undefined;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
type Unsubscribe = () => void;
|
|
1510
|
+
/**
|
|
1511
|
+
* Service for broadcasting toast notifications to connected UI clients.
|
|
1512
|
+
* Framework-agnostic — integrates with tRPC subscriptions via subscribe().
|
|
1513
|
+
*/
|
|
1514
|
+
declare class ToastService {
|
|
1515
|
+
private readonly listeners;
|
|
1516
|
+
/**
|
|
1517
|
+
* Subscribe to toast events for a specific user.
|
|
1518
|
+
* Returns an unsubscribe function.
|
|
1519
|
+
*/
|
|
1520
|
+
subscribe(connectionId: string, userId: string, callback: (toast: Toast) => void): Unsubscribe;
|
|
1521
|
+
/**
|
|
1522
|
+
* Broadcast a toast to all connected clients.
|
|
1523
|
+
*/
|
|
1524
|
+
broadcast(toast: Toast): void;
|
|
1525
|
+
/**
|
|
1526
|
+
* Send a toast to a specific user's connections only.
|
|
1527
|
+
*/
|
|
1528
|
+
sendToUser(userId: string, toast: Toast): void;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
interface RouteMatch {
|
|
1532
|
+
readonly route: IAddonHttpRoute;
|
|
1533
|
+
readonly addonId: string;
|
|
1534
|
+
readonly params: Record<string, string>;
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Registry for dynamic HTTP routes registered by addons.
|
|
1538
|
+
* Framework-agnostic — the server HTTP layer queries this registry to dispatch requests.
|
|
1539
|
+
*/
|
|
1540
|
+
declare class AddonRouteRegistry {
|
|
1541
|
+
private readonly routes;
|
|
1542
|
+
/**
|
|
1543
|
+
* Register all routes from an addon's route provider.
|
|
1544
|
+
*/
|
|
1545
|
+
registerRoutes(addonId: string, provider: IAddonRouteProvider): void;
|
|
1546
|
+
/**
|
|
1547
|
+
* Unregister all routes for an addon.
|
|
1548
|
+
*/
|
|
1549
|
+
unregisterRoutes(addonId: string): void;
|
|
1550
|
+
/**
|
|
1551
|
+
* Match an incoming request method + path to a registered route.
|
|
1552
|
+
* Supports simple path parameters (e.g., /items/:id).
|
|
1553
|
+
*/
|
|
1554
|
+
matchRoute(method: string, path: string): RouteMatch | null;
|
|
1555
|
+
/**
|
|
1556
|
+
* List all registered routes across all addons.
|
|
1557
|
+
*/
|
|
1558
|
+
listRoutes(): ReadonlyArray<{
|
|
1559
|
+
addonId: string;
|
|
1560
|
+
method: string;
|
|
1561
|
+
path: string;
|
|
1562
|
+
access: string;
|
|
1563
|
+
description?: string;
|
|
1564
|
+
}>;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
interface IRegisteredDevice {
|
|
1568
|
+
readonly id: string;
|
|
1569
|
+
readonly name: string;
|
|
1570
|
+
readonly providerId: string;
|
|
1571
|
+
readonly capabilities: readonly string[];
|
|
1572
|
+
}
|
|
1573
|
+
declare class DeviceRegistry<T extends IRegisteredDevice = IRegisteredDevice> {
|
|
1574
|
+
private readonly eventBus;
|
|
1575
|
+
private readonly devices;
|
|
1576
|
+
private readonly logger;
|
|
1577
|
+
constructor(eventBus: IEventBus, loggingService: LoggerFactory);
|
|
1578
|
+
registerDevice(device: T): void;
|
|
1579
|
+
unregisterDevice(id: string): void;
|
|
1580
|
+
getDevice(id: string): T | null;
|
|
1581
|
+
listDevices(): T[];
|
|
1582
|
+
getDevicesByProvider(providerId: string): T[];
|
|
1583
|
+
getDevicesWithCapability(cap: string): T[];
|
|
1584
|
+
registerProviderDevices(providerId: string, devices: readonly T[]): void;
|
|
1585
|
+
unregisterProviderDevices(providerId: string): void;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
interface IDeviceCapability {
|
|
1589
|
+
kind: string;
|
|
1590
|
+
}
|
|
1591
|
+
interface CapabilityBinding {
|
|
1592
|
+
source: 'native' | 'addon' | 'disabled';
|
|
1593
|
+
addonId?: string;
|
|
1594
|
+
config?: Record<string, unknown>;
|
|
1595
|
+
}
|
|
1596
|
+
interface IResolvableDevice {
|
|
1597
|
+
readonly id: string;
|
|
1598
|
+
readonly capabilities: string[];
|
|
1599
|
+
getCapability<T extends IDeviceCapability>(cap: string): T | null;
|
|
1600
|
+
}
|
|
1601
|
+
interface IAddonRegistryAccess {
|
|
1602
|
+
getAddon(addonId: string): unknown | null;
|
|
1603
|
+
}
|
|
1604
|
+
declare class CapabilityResolver {
|
|
1605
|
+
private readonly addonRegistry;
|
|
1606
|
+
private readonly bindings;
|
|
1607
|
+
constructor(addonRegistry: IAddonRegistryAccess);
|
|
1608
|
+
resolve<T extends IDeviceCapability>(device: IResolvableDevice, cap: string): T | null;
|
|
1609
|
+
setBinding(deviceId: string, cap: string, binding: CapabilityBinding): void;
|
|
1610
|
+
removeBinding(deviceId: string, cap: string): void;
|
|
1611
|
+
getBindings(deviceId: string): Partial<Record<string, CapabilityBinding>>;
|
|
1612
|
+
getEffectiveCapabilities(device: IResolvableDevice): string[];
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
/** Minimal device registry interface for provider management */
|
|
1616
|
+
type ProviderDeviceRegistry = {
|
|
1617
|
+
registerProviderDevices(providerId: string, devices: readonly IRegistrableDevice[]): void;
|
|
1618
|
+
unregisterProviderDevices(providerId: string): void;
|
|
1619
|
+
};
|
|
1620
|
+
interface IRegistrableDevice {
|
|
1621
|
+
readonly id: string;
|
|
1622
|
+
readonly name: string;
|
|
1623
|
+
readonly providerId: string;
|
|
1624
|
+
readonly capabilities: readonly string[];
|
|
1625
|
+
}
|
|
1626
|
+
interface ProviderStatus {
|
|
1627
|
+
connected: boolean;
|
|
1628
|
+
error?: string;
|
|
1629
|
+
deviceCount: number;
|
|
1630
|
+
}
|
|
1631
|
+
interface LiveEvent {
|
|
1632
|
+
type: string;
|
|
1633
|
+
camera: string;
|
|
1634
|
+
timestamp: number;
|
|
1635
|
+
data: Record<string, unknown>;
|
|
1636
|
+
}
|
|
1637
|
+
interface IManagedProvider {
|
|
1638
|
+
readonly id: string;
|
|
1639
|
+
readonly type: string;
|
|
1640
|
+
readonly name: string;
|
|
1641
|
+
start(): Promise<void>;
|
|
1642
|
+
stop(): Promise<void>;
|
|
1643
|
+
getStatus(): ProviderStatus;
|
|
1644
|
+
getDevices(): readonly IRegistrableDevice[];
|
|
1645
|
+
subscribeLiveEvents(callback: (event: LiveEvent) => void): () => void;
|
|
1646
|
+
}
|
|
1647
|
+
interface ProviderListItem {
|
|
1648
|
+
id: string;
|
|
1649
|
+
type: string;
|
|
1650
|
+
name: string;
|
|
1651
|
+
status: ProviderStatus;
|
|
1652
|
+
started: boolean;
|
|
1653
|
+
lifecycle: ElementStatus;
|
|
1654
|
+
}
|
|
1655
|
+
declare class ProviderManager<P extends IManagedProvider = IManagedProvider> {
|
|
1656
|
+
private readonly deviceRegistry;
|
|
1657
|
+
private readonly eventBus;
|
|
1658
|
+
private readonly loggingService;
|
|
1659
|
+
private readonly providers;
|
|
1660
|
+
private readonly logger;
|
|
1661
|
+
constructor(deviceRegistry: ProviderDeviceRegistry, eventBus: IEventBus, loggingService: LoggerFactory);
|
|
1662
|
+
registerProvider(provider: P): void;
|
|
1663
|
+
startProvider(id: string): Promise<void>;
|
|
1664
|
+
stopProvider(id: string): Promise<void>;
|
|
1665
|
+
disableProvider(id: string): Promise<void>;
|
|
1666
|
+
enableProvider(id: string): Promise<void>;
|
|
1667
|
+
restartProvider(id: string): Promise<void>;
|
|
1668
|
+
getProvider(id: string): P | null;
|
|
1669
|
+
getProviderStatus(id: string): ElementStatus | null;
|
|
1670
|
+
listProviders(): readonly ProviderListItem[];
|
|
1671
|
+
shutdownAll(): Promise<void>;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
export { AddonEngineManager, AddonInstaller, type AddonInstallerConfig, AddonLoader, AddonRouteRegistry, type AddonTableSchema, AdminUIAddon, AgentClient, type AgentClientConfig, type AgentEntry, type AgentEventEmitter, type AgentInfo, AgentRegistry, type AgentResources, type AgentStatus, type AgentTask, type AgentTaskResult, AgentTaskRunner, ApiKeyManager, type ApiKeyRecord, type ApiKeyStorageAccess, type AppConfig, type AuthConfigReader, AuthManager, BUILTIN_PACKAGES, type BackupConfig, type BackupManifest, type BinaryHandler, type BootstrapConfig, CORE_TABLE_DDL, type CapabilityBinding, CapabilityRegistry, CapabilityResolver, type ClientNetworkStats, ConfigManager, type ConnectionHandler, DecodeTaskHandler, DetectTaskHandler, type DeviceNetworkStats, DeviceRegistry, type ElementState, type ElementStatus, EventBus, type FeatureConfigReader, type FeatureFlag, FeatureManager, type FeatureManifest, FileSystemStorage, FsStorageBackend, type IAddonRegistryAccess, type IStorageProvider as ICoreStorageProvider, type IDeviceCapability, type IFileStorage, type ILogDestination, type IManagedProvider, INFRA_CAPABILITIES, type INetworkQualityTracker, type IRegisteredDevice, type IReplContextProvider, type IReplEngine, type IResolvableDevice, type ISettingsStore, type IStorageBackend, type IStorageLocation, type IStructuredStorage, type InfraCapability, type InstalledPackage, LifecycleStateMachine, LocalBackupAddon, LocalBackupService, type LogEntry, type LogFilter, type LogLevel, LogManager, LogRingBuffer, ManagedProcess, type ManagedProcessStatus, type MessageHandler, NetworkQualityTracker, NotificationService, PipelineRunner, PipelineValidator, type ProcessConfig, type ProcessEventEmitter, type ProcessLoggerFactory, ProcessManager, type ProcessState, type ProcessStats, type ProviderDeviceRegistry, type ProviderListItem, ProviderManager, PythonEnvManager, type QueryFilter, RUNTIME_DEFAULTS, RecordTaskHandler, type RegisteredAddon, ReplEngine, type ReplResult, type ReplScope, type ReplSessionContext, ScopedLogger, ScopedTokenManager, type ServerMode, SettingsStore, SqliteStorageAddon, SqliteStorageProvider, StorageLocationManager, type StorageLocationName, StorageManager, type StorageRecord, type StreamNetworkStats, SystemEventBus, type TaskDispatchOptions, TaskDispatcher, ToastService, type TokenPayload, type Unsubscribe, type UserConfigReader, UserManager, type UserRecord, type UserRole, type UserStorageAccess, type ValidationIssue, type ValidationResult, WinstonDestination, WinstonLoggingAddon, addonTableToDdl, bootstrapSchema, downloadModel, isInfraCapability };
|