@camstack/kernel 0.1.7 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon-installer-2BW2KLQD.mjs +7 -0
- package/dist/chunk-67QMERMP.mjs +411 -0
- package/dist/chunk-67QMERMP.mjs.map +1 -0
- package/dist/chunk-S5YWNNPK.mjs +135 -0
- package/dist/{chunk-RHK5CCAL.mjs.map → chunk-S5YWNNPK.mjs.map} +1 -1
- package/dist/index.d.mts +645 -0
- package/dist/index.d.ts +645 -0
- package/dist/index.js +1754 -2037
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1261 -1462
- package/dist/index.mjs.map +1 -1
- package/dist/worker/addon-worker-entry.d.mts +2 -0
- package/dist/worker/addon-worker-entry.d.ts +2 -0
- package/dist/worker/addon-worker-entry.js +300 -430
- package/dist/worker/addon-worker-entry.js.map +1 -1
- package/dist/worker/addon-worker-entry.mjs +4 -8
- package/dist/worker/addon-worker-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/addon-installer-WQBOEZQT.mjs +0 -6
- package/dist/chunk-GJ3DKNOD.mjs +0 -494
- package/dist/chunk-GJ3DKNOD.mjs.map +0 -1
- package/dist/chunk-LZOMFHX3.mjs +0 -38
- package/dist/chunk-LZOMFHX3.mjs.map +0 -1
- package/dist/chunk-RHK5CCAL.mjs +0 -154
- /package/dist/{addon-installer-WQBOEZQT.mjs.map → addon-installer-2BW2KLQD.mjs.map} +0 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
import { AddonDeclaration, ICamstackAddon, AddonContext, InstalledPackage, IScopedLogger, CapabilityDeclaration, CapabilityConsumerRegistration, CapabilityMode, CapabilityInfo, WorkerInfo, IAddonProcessManager, WorkerToMainMessage, SubProcessConfig, IManagedSubProcess, SubProcessInfo, WorkerProcessStats } from '@camstack/types';
|
|
2
|
+
export { InstalledPackage } from '@camstack/types';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
interface RegisteredAddon {
|
|
6
|
+
readonly declaration: AddonDeclaration;
|
|
7
|
+
readonly packageName: string;
|
|
8
|
+
readonly packageVersion: string;
|
|
9
|
+
/** Human-readable package name from camstack.displayName in package.json */
|
|
10
|
+
readonly packageDisplayName?: string;
|
|
11
|
+
readonly addonClass: new () => ICamstackAddon;
|
|
12
|
+
}
|
|
13
|
+
declare class AddonLoader {
|
|
14
|
+
private addons;
|
|
15
|
+
/** Scan addons directory and load all addon packages.
|
|
16
|
+
* Supports scoped layout: addons/@scope/package-name/ */
|
|
17
|
+
loadFromDirectory(addonsDir: string): Promise<void>;
|
|
18
|
+
private tryLoadAddon;
|
|
19
|
+
/** Load addon from a specific directory (package.json + dist/) */
|
|
20
|
+
loadFromAddonDir(addonDir: string): Promise<void>;
|
|
21
|
+
/** Load addon from a direct path (for development/testing) */
|
|
22
|
+
loadFromPath(addonId: string, modulePath: string, packageName: string, declaration?: Partial<AddonDeclaration>, packageVersion?: string): Promise<void>;
|
|
23
|
+
/** Get a registered addon by ID */
|
|
24
|
+
getAddon(addonId: string): RegisteredAddon | undefined;
|
|
25
|
+
/** List all registered addons */
|
|
26
|
+
listAddons(): RegisteredAddon[];
|
|
27
|
+
/** Check if an addon is registered */
|
|
28
|
+
hasAddon(addonId: string): boolean;
|
|
29
|
+
/** Create a new instance of an addon (not yet initialized) */
|
|
30
|
+
createInstance(addonId: string): ICamstackAddon;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
declare class AddonEngineManager {
|
|
34
|
+
private readonly loader;
|
|
35
|
+
private readonly baseContext;
|
|
36
|
+
private engines;
|
|
37
|
+
constructor(loader: AddonLoader, baseContext: Partial<Omit<AddonContext, 'addonConfig'>>);
|
|
38
|
+
/**
|
|
39
|
+
* Get or create an addon engine for the given effective config.
|
|
40
|
+
* Cameras with the same addonId + effective config share the same engine.
|
|
41
|
+
*/
|
|
42
|
+
getOrCreateEngine(addonId: string, globalConfig: Record<string, unknown>, cameraOverride?: Record<string, unknown>): Promise<ICamstackAddon>;
|
|
43
|
+
/** Get all active engines */
|
|
44
|
+
getActiveEngines(): Map<string, ICamstackAddon>;
|
|
45
|
+
/** Shutdown a specific engine by its config key */
|
|
46
|
+
shutdownEngine(configKey: string): Promise<void>;
|
|
47
|
+
/** Shutdown all engines */
|
|
48
|
+
shutdownAll(): Promise<void>;
|
|
49
|
+
/** Compute a deterministic config key (visible for tests) */
|
|
50
|
+
computeConfigKey(addonId: string, effectiveConfig: Record<string, unknown>): string;
|
|
51
|
+
private hashConfig;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface AddonInstallerConfig {
|
|
55
|
+
/** Directory where addons are stored (e.g., {dataDir}/addons) — each addon is a subdirectory */
|
|
56
|
+
readonly addonsDir: string;
|
|
57
|
+
/** npm registry URL (default: https://registry.npmjs.org) */
|
|
58
|
+
readonly registry?: string;
|
|
59
|
+
/** Workspace packages directory (e.g., /path/to/camstack-server/packages) — for dev installs */
|
|
60
|
+
readonly workspacePackagesDir?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
declare class AddonInstaller {
|
|
64
|
+
private readonly addonsDir;
|
|
65
|
+
private readonly registry;
|
|
66
|
+
private readonly workspacePackagesDir;
|
|
67
|
+
constructor(config: AddonInstallerConfig);
|
|
68
|
+
/** Required addon packages that must be installed for the server to function */
|
|
69
|
+
static readonly REQUIRED_PACKAGES: readonly ["@camstack/core", "@camstack/addon-stream-broker", "@camstack/addon-recording", "@camstack/addon-vision", "@camstack/addon-admin-ui", "@camstack/addon-webrtc-adaptive", "@camstack/addon-analytics", "@camstack/addon-scene-intelligence", "@camstack/addon-advanced-notifier"];
|
|
70
|
+
/** Ensure the addons directory exists */
|
|
71
|
+
initialize(): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Ensure all required packages are installed in the addons directory.
|
|
74
|
+
* This replaces the standalone first-boot-installer.ts.
|
|
75
|
+
*/
|
|
76
|
+
ensureRequiredPackages(): Promise<void>;
|
|
77
|
+
private findWorkspacePackage;
|
|
78
|
+
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
79
|
+
installFromTgz(tgzPath: string): Promise<{
|
|
80
|
+
name: string;
|
|
81
|
+
version: string;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Install addon — prefers workspace if available, falls back to npm.
|
|
85
|
+
* This ensures dev builds (with vite output, etc.) are used when in workspace mode.
|
|
86
|
+
*/
|
|
87
|
+
install(packageName: string, version?: string): Promise<{
|
|
88
|
+
name: string;
|
|
89
|
+
version: string;
|
|
90
|
+
}>;
|
|
91
|
+
/**
|
|
92
|
+
* Install addon from workspace by copying package.json + dist/ to addons dir.
|
|
93
|
+
* Does NOT build — expects dist/ to already exist (run `npm run build` separately).
|
|
94
|
+
*/
|
|
95
|
+
installFromWorkspace(packageName: string): Promise<{
|
|
96
|
+
name: string;
|
|
97
|
+
version: string;
|
|
98
|
+
}>;
|
|
99
|
+
/** Install addon from npm (download tgz, then extract) */
|
|
100
|
+
installFromNpm(packageName: string, version?: string): Promise<{
|
|
101
|
+
name: string;
|
|
102
|
+
version: string;
|
|
103
|
+
}>;
|
|
104
|
+
/** Uninstall addon (delete directory) */
|
|
105
|
+
uninstall(packageName: string): Promise<void>;
|
|
106
|
+
/** List installed addons (directories with package.json containing camstack.addons) */
|
|
107
|
+
listInstalled(): InstalledPackage[];
|
|
108
|
+
/** Check if an addon is installed */
|
|
109
|
+
isInstalled(packageName: string): boolean;
|
|
110
|
+
/** Get installed package info */
|
|
111
|
+
getInstalledPackage(packageName: string): InstalledPackage | null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Ensure a directory exists (recursive).
|
|
116
|
+
* Single source of truth — replaces scattered mkdirSync calls.
|
|
117
|
+
*/
|
|
118
|
+
declare function ensureDir(dirPath: string): void;
|
|
119
|
+
/**
|
|
120
|
+
* Copy a directory recursively.
|
|
121
|
+
* Single source of truth — extracted from addon-installer + first-boot-installer.
|
|
122
|
+
*/
|
|
123
|
+
declare function copyDirRecursive(src: string, dest: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Strip @camstack/* dependencies and devDependencies from a package.json object.
|
|
126
|
+
* Used when installing addons into the addons directory — @camstack packages
|
|
127
|
+
* are provided by the host runtime, not installed per-addon.
|
|
128
|
+
*
|
|
129
|
+
* Returns a new object (immutable).
|
|
130
|
+
*/
|
|
131
|
+
declare function stripCamstackDeps(pkg: Record<string, unknown>): Record<string, unknown>;
|
|
132
|
+
/**
|
|
133
|
+
* Copy extra file directories declared in package.json "files" field.
|
|
134
|
+
* Copies directories (not individual files) from source to destination.
|
|
135
|
+
* Skips "dist" (already handled) and glob patterns.
|
|
136
|
+
*/
|
|
137
|
+
declare function copyExtraFileDirs(pkgJson: Record<string, unknown>, sourceDir: string, destDir: string): void;
|
|
138
|
+
/**
|
|
139
|
+
* Create symlinks in node_modules so that static `import from '@camstack/core'` works.
|
|
140
|
+
* Links: node_modules/@camstack/{pkg} -> data/addons/{pkg}/
|
|
141
|
+
* (not just dist -- need package.json for Node's exports resolution)
|
|
142
|
+
*/
|
|
143
|
+
declare function symlinkAddonsToNodeModules(addonsDir: string, nodeModulesDir: string): void;
|
|
144
|
+
/**
|
|
145
|
+
* Check if any file in src/ is newer than dist/.
|
|
146
|
+
* Returns true if rebuild is needed.
|
|
147
|
+
*/
|
|
148
|
+
declare function isSourceNewer(packageDir: string): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Ensure a library dependency (not an addon) has a dist/ directory.
|
|
151
|
+
* Does NOT rebuild — in dev mode, `npm run build` should be run separately.
|
|
152
|
+
* Only checks that dist/ exists and has an index file.
|
|
153
|
+
*/
|
|
154
|
+
declare function ensureLibraryBuilt(packageName: string, packagesDir: string): void;
|
|
155
|
+
/**
|
|
156
|
+
* Install a single npm package into a target directory (package.json + dist/).
|
|
157
|
+
* No validation on camstack.addons -- works for any @camstack/* package.
|
|
158
|
+
* Uses synchronous child_process calls (suitable for first-boot and update paths).
|
|
159
|
+
*/
|
|
160
|
+
declare function installPackageFromNpmSync(packageName: string, targetDir: string): void;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Detect whether we're running inside the monorepo workspace.
|
|
164
|
+
* Returns the absolute path to the packages/ directory if found, otherwise null.
|
|
165
|
+
*
|
|
166
|
+
* Single source of truth — replaces identical implementations in:
|
|
167
|
+
* - first-boot-installer.ts
|
|
168
|
+
* - addon-bridge.service.ts
|
|
169
|
+
*/
|
|
170
|
+
declare function detectWorkspacePackagesDir(startDir: string): string | null;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Central registry for all capability providers and consumers.
|
|
174
|
+
* Lives in @camstack/kernel. Mode-aware: singleton (one active) or collection (all active).
|
|
175
|
+
*
|
|
176
|
+
* Uses injected LoggingService, NEVER NestJS static Logger.
|
|
177
|
+
*/
|
|
178
|
+
declare class CapabilityRegistry {
|
|
179
|
+
private readonly logger;
|
|
180
|
+
private readonly configReader;
|
|
181
|
+
private readonly capabilities;
|
|
182
|
+
/** Per-device singleton overrides: deviceId → (capability → addonId) */
|
|
183
|
+
private readonly deviceOverrides;
|
|
184
|
+
/** Per-device collection filters: deviceId → (capability → addonIds[]) */
|
|
185
|
+
private readonly deviceCollectionFilters;
|
|
186
|
+
constructor(logger: IScopedLogger, configReader: (capability: string) => string | undefined);
|
|
187
|
+
/**
|
|
188
|
+
* Declare a capability (typically called when addon manifests are loaded).
|
|
189
|
+
* Must be called before registerProvider/registerConsumer for that capability.
|
|
190
|
+
*/
|
|
191
|
+
declareCapability(declaration: CapabilityDeclaration): void;
|
|
192
|
+
/**
|
|
193
|
+
* Register a capability provider (called by addon loader when addon is enabled).
|
|
194
|
+
* For singleton: auto-activates if user-preferred or first registered.
|
|
195
|
+
* For collection: adds to active set and notifies consumers.
|
|
196
|
+
*/
|
|
197
|
+
registerProvider(capability: string, addonId: string, provider: unknown): void;
|
|
198
|
+
/**
|
|
199
|
+
* Unregister a provider (called when addon is disabled/uninstalled).
|
|
200
|
+
*/
|
|
201
|
+
unregisterProvider(capability: string, addonId: string): void;
|
|
202
|
+
/**
|
|
203
|
+
* Register a consumer that wants to be notified when providers change.
|
|
204
|
+
* If a provider is already active, the consumer is immediately notified.
|
|
205
|
+
* Returns a disposer function for cleanup.
|
|
206
|
+
*/
|
|
207
|
+
registerConsumer<T = unknown>(registration: CapabilityConsumerRegistration<T>): () => void;
|
|
208
|
+
/**
|
|
209
|
+
* Get the active singleton provider for a capability.
|
|
210
|
+
* Returns null if none set.
|
|
211
|
+
*/
|
|
212
|
+
getSingleton<T = unknown>(capability: string): T | null;
|
|
213
|
+
/**
|
|
214
|
+
* Get all active collection providers for a capability.
|
|
215
|
+
*/
|
|
216
|
+
getCollection<T = unknown>(capability: string): readonly T[];
|
|
217
|
+
/**
|
|
218
|
+
* Set which addon should be the active singleton for a capability.
|
|
219
|
+
* Call with `immediate: true` to also swap the runtime provider now
|
|
220
|
+
* (consumers' onSet will be awaited).
|
|
221
|
+
*/
|
|
222
|
+
setActiveSingleton(capability: string, addonId: string, immediate?: boolean): Promise<void>;
|
|
223
|
+
/**
|
|
224
|
+
* Get the mode declared for a capability.
|
|
225
|
+
*/
|
|
226
|
+
getMode(capability: string): CapabilityMode | undefined;
|
|
227
|
+
/**
|
|
228
|
+
* List all registered capabilities with their providers.
|
|
229
|
+
*/
|
|
230
|
+
listCapabilities(): CapabilityInfo[];
|
|
231
|
+
/**
|
|
232
|
+
* Check if all dependencies for a capability are satisfied (have active providers).
|
|
233
|
+
*/
|
|
234
|
+
areDependenciesMet(declaration: CapabilityDeclaration): boolean;
|
|
235
|
+
/**
|
|
236
|
+
* Get the dependency-ordered list of capability names for boot sequencing.
|
|
237
|
+
* Returns capabilities sorted topologically by dependsOn.
|
|
238
|
+
* Throws if a cycle is detected.
|
|
239
|
+
*/
|
|
240
|
+
getBootOrder(): string[];
|
|
241
|
+
/**
|
|
242
|
+
* Set a per-device singleton override. When resolveForDevice is called for
|
|
243
|
+
* this device + capability, the specified addon's provider is returned
|
|
244
|
+
* instead of the global singleton.
|
|
245
|
+
*/
|
|
246
|
+
setDeviceOverride(deviceId: string, capability: string, addonId: string): void;
|
|
247
|
+
/**
|
|
248
|
+
* Clear a per-device singleton override, reverting to the global singleton.
|
|
249
|
+
*/
|
|
250
|
+
clearDeviceOverride(deviceId: string, capability: string): void;
|
|
251
|
+
/**
|
|
252
|
+
* Get all per-device singleton overrides for a device.
|
|
253
|
+
* Returns a Map of capability name to addon ID.
|
|
254
|
+
*/
|
|
255
|
+
getDeviceOverrides(deviceId: string): Map<string, string>;
|
|
256
|
+
/**
|
|
257
|
+
* Resolve a singleton provider for a specific device.
|
|
258
|
+
* 1. Check device override — return that addon's provider
|
|
259
|
+
* 2. Fallback to global singleton
|
|
260
|
+
*/
|
|
261
|
+
resolveForDevice<T = unknown>(capability: string, deviceId: string): T | null;
|
|
262
|
+
/**
|
|
263
|
+
* Set a per-device collection filter. When resolveCollectionForDevice is called
|
|
264
|
+
* for this device + capability, only providers from the specified addon IDs
|
|
265
|
+
* are returned instead of the full collection.
|
|
266
|
+
*/
|
|
267
|
+
setDeviceCollectionFilter(deviceId: string, capability: string, addonIds: string[]): void;
|
|
268
|
+
/**
|
|
269
|
+
* Clear a per-device collection filter, reverting to the full collection.
|
|
270
|
+
*/
|
|
271
|
+
clearDeviceCollectionFilter(deviceId: string, capability: string): void;
|
|
272
|
+
/**
|
|
273
|
+
* Resolve collection providers for a specific device.
|
|
274
|
+
* If a filter exists for the device + capability, only those addon's providers are returned.
|
|
275
|
+
* If no filter exists, the full collection is returned.
|
|
276
|
+
*/
|
|
277
|
+
resolveCollectionForDevice<T = unknown>(capability: string, deviceId: string): readonly T[];
|
|
278
|
+
/**
|
|
279
|
+
* Get a specific addon's provider by addon ID, regardless of whether it's the active singleton.
|
|
280
|
+
* Useful for per-device overrides that need to look up any registered provider.
|
|
281
|
+
*/
|
|
282
|
+
getProviderByAddonId<T = unknown>(capability: string, addonId: string): T | null;
|
|
283
|
+
private activateSingleton;
|
|
284
|
+
private activateSingletonAsync;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
interface InfraCapability {
|
|
288
|
+
/** Capability name */
|
|
289
|
+
readonly name: string;
|
|
290
|
+
/** If true, boot aborts when this capability's addon fails to initialize */
|
|
291
|
+
readonly required: boolean;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Infrastructure capabilities that must boot before all other addons.
|
|
295
|
+
* Enabled in Phase 1 of the boot sequence. Order matters: storage before logging.
|
|
296
|
+
*/
|
|
297
|
+
declare const INFRA_CAPABILITIES: readonly InfraCapability[];
|
|
298
|
+
/** Check if a capability name is an infrastructure capability */
|
|
299
|
+
declare function isInfraCapability(name: string): boolean;
|
|
300
|
+
|
|
301
|
+
/** Default data directory — used when CAMSTACK_DATA env var is not set */
|
|
302
|
+
declare const DEFAULT_DATA_PATH = "camstack-data";
|
|
303
|
+
/** Bootstrap config -- loaded from config.yaml ONLY at startup.
|
|
304
|
+
* All other settings live in SQL (system_settings table). */
|
|
305
|
+
declare const bootstrapSchema: z.ZodObject<{
|
|
306
|
+
/** Server mode: 'hub' (full server) or 'agent' (worker node) */
|
|
307
|
+
mode: z.ZodDefault<z.ZodEnum<["hub", "agent"]>>;
|
|
308
|
+
server: z.ZodDefault<z.ZodObject<{
|
|
309
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
310
|
+
host: z.ZodDefault<z.ZodString>;
|
|
311
|
+
dataPath: z.ZodDefault<z.ZodString>;
|
|
312
|
+
}, "strip", z.ZodTypeAny, {
|
|
313
|
+
port: number;
|
|
314
|
+
host: string;
|
|
315
|
+
dataPath: string;
|
|
316
|
+
}, {
|
|
317
|
+
port?: number | undefined;
|
|
318
|
+
host?: string | undefined;
|
|
319
|
+
dataPath?: string | undefined;
|
|
320
|
+
}>>;
|
|
321
|
+
auth: z.ZodDefault<z.ZodObject<{
|
|
322
|
+
jwtSecret: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
323
|
+
adminUsername: z.ZodDefault<z.ZodString>;
|
|
324
|
+
adminPassword: z.ZodDefault<z.ZodString>;
|
|
325
|
+
}, "strip", z.ZodTypeAny, {
|
|
326
|
+
jwtSecret: string | null;
|
|
327
|
+
adminUsername: string;
|
|
328
|
+
adminPassword: string;
|
|
329
|
+
}, {
|
|
330
|
+
jwtSecret?: string | null | undefined;
|
|
331
|
+
adminUsername?: string | undefined;
|
|
332
|
+
adminPassword?: string | undefined;
|
|
333
|
+
}>>;
|
|
334
|
+
/** Hub connection config — only used when mode='agent' */
|
|
335
|
+
hub: z.ZodDefault<z.ZodObject<{
|
|
336
|
+
url: z.ZodDefault<z.ZodString>;
|
|
337
|
+
token: z.ZodDefault<z.ZodString>;
|
|
338
|
+
}, "strip", z.ZodTypeAny, {
|
|
339
|
+
url: string;
|
|
340
|
+
token: string;
|
|
341
|
+
}, {
|
|
342
|
+
url?: string | undefined;
|
|
343
|
+
token?: string | undefined;
|
|
344
|
+
}>>;
|
|
345
|
+
/** Agent-specific config — only used when mode='agent' */
|
|
346
|
+
agent: z.ZodDefault<z.ZodObject<{
|
|
347
|
+
name: z.ZodDefault<z.ZodString>;
|
|
348
|
+
/** Port for the agent status page (minimal HTML) */
|
|
349
|
+
statusPort: z.ZodDefault<z.ZodNumber>;
|
|
350
|
+
}, "strip", z.ZodTypeAny, {
|
|
351
|
+
name: string;
|
|
352
|
+
statusPort: number;
|
|
353
|
+
}, {
|
|
354
|
+
name?: string | undefined;
|
|
355
|
+
statusPort?: number | undefined;
|
|
356
|
+
}>>;
|
|
357
|
+
/** TLS configuration */
|
|
358
|
+
tls: z.ZodDefault<z.ZodObject<{
|
|
359
|
+
/** Enable HTTPS (default: true) */
|
|
360
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
361
|
+
/** Path to custom cert file (PEM). If not set, auto-generates self-signed. */
|
|
362
|
+
certPath: z.ZodOptional<z.ZodString>;
|
|
363
|
+
/** Path to custom key file (PEM). Required if certPath is set. */
|
|
364
|
+
keyPath: z.ZodOptional<z.ZodString>;
|
|
365
|
+
}, "strip", z.ZodTypeAny, {
|
|
366
|
+
enabled: boolean;
|
|
367
|
+
certPath?: string | undefined;
|
|
368
|
+
keyPath?: string | undefined;
|
|
369
|
+
}, {
|
|
370
|
+
enabled?: boolean | undefined;
|
|
371
|
+
certPath?: string | undefined;
|
|
372
|
+
keyPath?: string | undefined;
|
|
373
|
+
}>>;
|
|
374
|
+
}, "strip", z.ZodTypeAny, {
|
|
375
|
+
hub: {
|
|
376
|
+
url: string;
|
|
377
|
+
token: string;
|
|
378
|
+
};
|
|
379
|
+
agent: {
|
|
380
|
+
name: string;
|
|
381
|
+
statusPort: number;
|
|
382
|
+
};
|
|
383
|
+
mode: "hub" | "agent";
|
|
384
|
+
server: {
|
|
385
|
+
port: number;
|
|
386
|
+
host: string;
|
|
387
|
+
dataPath: string;
|
|
388
|
+
};
|
|
389
|
+
auth: {
|
|
390
|
+
jwtSecret: string | null;
|
|
391
|
+
adminUsername: string;
|
|
392
|
+
adminPassword: string;
|
|
393
|
+
};
|
|
394
|
+
tls: {
|
|
395
|
+
enabled: boolean;
|
|
396
|
+
certPath?: string | undefined;
|
|
397
|
+
keyPath?: string | undefined;
|
|
398
|
+
};
|
|
399
|
+
}, {
|
|
400
|
+
hub?: {
|
|
401
|
+
url?: string | undefined;
|
|
402
|
+
token?: string | undefined;
|
|
403
|
+
} | undefined;
|
|
404
|
+
agent?: {
|
|
405
|
+
name?: string | undefined;
|
|
406
|
+
statusPort?: number | undefined;
|
|
407
|
+
} | undefined;
|
|
408
|
+
mode?: "hub" | "agent" | undefined;
|
|
409
|
+
server?: {
|
|
410
|
+
port?: number | undefined;
|
|
411
|
+
host?: string | undefined;
|
|
412
|
+
dataPath?: string | undefined;
|
|
413
|
+
} | undefined;
|
|
414
|
+
auth?: {
|
|
415
|
+
jwtSecret?: string | null | undefined;
|
|
416
|
+
adminUsername?: string | undefined;
|
|
417
|
+
adminPassword?: string | undefined;
|
|
418
|
+
} | undefined;
|
|
419
|
+
tls?: {
|
|
420
|
+
enabled?: boolean | undefined;
|
|
421
|
+
certPath?: string | undefined;
|
|
422
|
+
keyPath?: string | undefined;
|
|
423
|
+
} | undefined;
|
|
424
|
+
}>;
|
|
425
|
+
type BootstrapConfig = z.infer<typeof bootstrapSchema>;
|
|
426
|
+
/**
|
|
427
|
+
* Runtime defaults -- used by ConfigManager.get() for backward compatibility
|
|
428
|
+
* until Plan B wires all runtime settings to the system_settings SQL table.
|
|
429
|
+
*/
|
|
430
|
+
declare const RUNTIME_DEFAULTS: Record<string, unknown>;
|
|
431
|
+
type ServerMode = 'hub' | 'agent';
|
|
432
|
+
type AppConfig = BootstrapConfig & {
|
|
433
|
+
features: {
|
|
434
|
+
streaming: boolean;
|
|
435
|
+
notifications: boolean;
|
|
436
|
+
objectDetection: boolean;
|
|
437
|
+
remoteAccess: boolean;
|
|
438
|
+
agentCluster: boolean;
|
|
439
|
+
smartHome: boolean;
|
|
440
|
+
recordings: boolean;
|
|
441
|
+
backup: boolean;
|
|
442
|
+
repl: boolean;
|
|
443
|
+
};
|
|
444
|
+
storage: {
|
|
445
|
+
provider: string;
|
|
446
|
+
locations: Record<string, string>;
|
|
447
|
+
};
|
|
448
|
+
logging: {
|
|
449
|
+
level: string;
|
|
450
|
+
retentionDays: number;
|
|
451
|
+
};
|
|
452
|
+
eventBus: {
|
|
453
|
+
ringBufferSize: number;
|
|
454
|
+
};
|
|
455
|
+
retention: {
|
|
456
|
+
detectionEventsDays: number;
|
|
457
|
+
audioLevelsDays: number;
|
|
458
|
+
};
|
|
459
|
+
providers: Array<{
|
|
460
|
+
id: string;
|
|
461
|
+
type: string;
|
|
462
|
+
name: string;
|
|
463
|
+
url?: string;
|
|
464
|
+
username?: string;
|
|
465
|
+
password?: string;
|
|
466
|
+
mqtt?: {
|
|
467
|
+
brokerUrl: string;
|
|
468
|
+
username?: string;
|
|
469
|
+
password?: string;
|
|
470
|
+
topicPrefix: string;
|
|
471
|
+
};
|
|
472
|
+
}>;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
interface ISettingsStore {
|
|
476
|
+
getSystem(key: string): unknown;
|
|
477
|
+
setSystem(key: string, value: unknown): void;
|
|
478
|
+
getAllSystem(): Record<string, unknown>;
|
|
479
|
+
getAllAddon(addonId: string): Record<string, unknown>;
|
|
480
|
+
setAllAddon(addonId: string, config: Record<string, unknown>): void;
|
|
481
|
+
getAllProvider(providerId: string): Record<string, unknown>;
|
|
482
|
+
setProvider(providerId: string, key: string, value: unknown): void;
|
|
483
|
+
getAllDevice(deviceId: string): Record<string, unknown>;
|
|
484
|
+
setDevice(deviceId: string, key: string, value: unknown): void;
|
|
485
|
+
}
|
|
486
|
+
/** Feature flags manifest — inlined to avoid dependency on core's feature module */
|
|
487
|
+
interface FeatureManifest {
|
|
488
|
+
streaming: boolean;
|
|
489
|
+
notifications: boolean;
|
|
490
|
+
objectDetection: boolean;
|
|
491
|
+
remoteAccess: boolean;
|
|
492
|
+
agentCluster: boolean;
|
|
493
|
+
smartHome: boolean;
|
|
494
|
+
recordings: boolean;
|
|
495
|
+
backup: boolean;
|
|
496
|
+
repl: boolean;
|
|
497
|
+
}
|
|
498
|
+
declare class ConfigManager {
|
|
499
|
+
private readonly configPath;
|
|
500
|
+
private bootstrapConfig;
|
|
501
|
+
private settingsStore;
|
|
502
|
+
constructor(configPath: string);
|
|
503
|
+
/** Called by main.ts after the SQLite DB is ready (Phase 2). */
|
|
504
|
+
setSettingsStore(store: ISettingsStore): void;
|
|
505
|
+
/**
|
|
506
|
+
* Get a config value by dot-notation path.
|
|
507
|
+
* Priority: bootstrap config -> SQL system_settings -> RUNTIME_DEFAULTS fallback.
|
|
508
|
+
*/
|
|
509
|
+
get<T>(path: string): T;
|
|
510
|
+
/**
|
|
511
|
+
* Write a value to SQL system_settings.
|
|
512
|
+
* Throws if the settings store is not yet wired.
|
|
513
|
+
*/
|
|
514
|
+
set(key: string, value: unknown): void;
|
|
515
|
+
/**
|
|
516
|
+
* Bulk-read all system_settings keys that belong to a logical section.
|
|
517
|
+
* A "section" is the first segment of a dot-notation key (e.g. 'features', 'logging').
|
|
518
|
+
*/
|
|
519
|
+
getSection(section: string): Record<string, unknown>;
|
|
520
|
+
/**
|
|
521
|
+
* Bulk-write a section of runtime settings to SQL system_settings.
|
|
522
|
+
* Each entry in `data` is stored as `section.key`.
|
|
523
|
+
*/
|
|
524
|
+
setSection(section: string, data: Record<string, unknown>): void;
|
|
525
|
+
/** Read all config for an addon from addon_settings. */
|
|
526
|
+
getAddonConfig(addonId: string): Record<string, unknown>;
|
|
527
|
+
/** Write (bulk-replace) config for an addon to addon_settings. */
|
|
528
|
+
setAddonConfig(addonId: string, config: Record<string, unknown>): void;
|
|
529
|
+
/** Read all config for a provider from provider_settings. */
|
|
530
|
+
getProviderConfig(providerId: string): Record<string, unknown>;
|
|
531
|
+
/** Write (upsert) a single key for a provider to provider_settings. */
|
|
532
|
+
setProviderConfig(providerId: string, key: string, value: unknown): void;
|
|
533
|
+
/** Read all config for a device from device_settings. */
|
|
534
|
+
getDeviceConfig(deviceId: string): Record<string, unknown>;
|
|
535
|
+
/** Write (upsert) a single key for a device to device_settings. */
|
|
536
|
+
setDeviceConfig(deviceId: string, key: string, value: unknown): void;
|
|
537
|
+
/** Get a value from the parsed bootstrap config */
|
|
538
|
+
getBootstrap<T>(path: string): T;
|
|
539
|
+
/** Features accessor -- reads from SQL when available, falls back to RUNTIME_DEFAULTS */
|
|
540
|
+
get features(): FeatureManifest;
|
|
541
|
+
/**
|
|
542
|
+
* Returns a merged view of bootstrap config + runtime defaults for backward compat.
|
|
543
|
+
*/
|
|
544
|
+
get raw(): AppConfig;
|
|
545
|
+
/** Sections that live in config.yaml. Everything else goes to SQL. */
|
|
546
|
+
private static readonly BOOTSTRAP_SECTIONS;
|
|
547
|
+
/**
|
|
548
|
+
* Atomically update one top-level section of config.yaml and sync in-memory.
|
|
549
|
+
* Only bootstrap sections (server, auth, mode) are written to YAML.
|
|
550
|
+
* Runtime settings must use setSection() which writes to SQL.
|
|
551
|
+
*/
|
|
552
|
+
update(section: string, data: Record<string, unknown>): void;
|
|
553
|
+
/**
|
|
554
|
+
* Deep-set a value in a nested plain object using a dot-notation path.
|
|
555
|
+
* Returns a new object (immutable).
|
|
556
|
+
*/
|
|
557
|
+
private setNested;
|
|
558
|
+
/**
|
|
559
|
+
* Apply env var overrides onto the raw YAML object.
|
|
560
|
+
* Only bootstrap-level env vars are applied.
|
|
561
|
+
*/
|
|
562
|
+
private applyEnvOverrides;
|
|
563
|
+
private loadYaml;
|
|
564
|
+
private warnDefaultCredentials;
|
|
565
|
+
private getFromBootstrap;
|
|
566
|
+
private getFromRuntimeDefaults;
|
|
567
|
+
/**
|
|
568
|
+
* Perform a prefix-based nested lookup against SQL system_settings.
|
|
569
|
+
* e.g. path='features' matches keys 'features.streaming', 'features.notifications', etc.
|
|
570
|
+
* Returns an object keyed by the sub-key, or undefined if nothing is found.
|
|
571
|
+
*/
|
|
572
|
+
private getNestedFromSystemSettings;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
interface WorkerHostOptions {
|
|
576
|
+
readonly workerEntryPath: string;
|
|
577
|
+
readonly devMode?: boolean;
|
|
578
|
+
/** Force all addons in-process (emergency fallback) */
|
|
579
|
+
readonly forceInProcess?: boolean;
|
|
580
|
+
readonly heartbeatIntervalMs?: number;
|
|
581
|
+
readonly heartbeatTimeoutMs?: number;
|
|
582
|
+
readonly maxCrashesInWindow?: number;
|
|
583
|
+
readonly crashWindowMs?: number;
|
|
584
|
+
readonly shutdownTimeoutMs?: number;
|
|
585
|
+
/** Log writer function — receives structured log entries from workers */
|
|
586
|
+
readonly onWorkerLog?: (entry: {
|
|
587
|
+
addonId: string;
|
|
588
|
+
level: string;
|
|
589
|
+
message: string;
|
|
590
|
+
scope: readonly string[];
|
|
591
|
+
meta?: Record<string, unknown>;
|
|
592
|
+
}) => void;
|
|
593
|
+
}
|
|
594
|
+
declare class AddonWorkerHost {
|
|
595
|
+
private readonly workers;
|
|
596
|
+
private readonly options;
|
|
597
|
+
private heartbeatTimer;
|
|
598
|
+
/** Set of addons that failed to fork and fell back to in-process */
|
|
599
|
+
private readonly fallbackInProcess;
|
|
600
|
+
constructor(options: WorkerHostOptions);
|
|
601
|
+
/** Check if an addon is infrastructure (must stay in-process) */
|
|
602
|
+
isInfraAddon(addonId: string): boolean;
|
|
603
|
+
/** Check if an addon fell back to in-process after fork failure */
|
|
604
|
+
isFallbackInProcess(addonId: string): boolean;
|
|
605
|
+
/** Mark an addon as fallen back to in-process */
|
|
606
|
+
markFallbackInProcess(addonId: string): void;
|
|
607
|
+
/**
|
|
608
|
+
* Determine if an addon should be forked.
|
|
609
|
+
* Default: fork everything except infra addons.
|
|
610
|
+
* Addons can opt out with `inProcess: true` in declaration.
|
|
611
|
+
* Emergency fallback: CAMSTACK_FORCE_INPROCESS=true disables all forking.
|
|
612
|
+
*/
|
|
613
|
+
shouldFork(addonId: string, declaration?: {
|
|
614
|
+
forkable?: boolean;
|
|
615
|
+
inProcess?: boolean;
|
|
616
|
+
}): boolean;
|
|
617
|
+
/** Fork a worker for an addon */
|
|
618
|
+
forkWorker(addonId: string, addonDir: string, config: Record<string, unknown>, storagePaths: Record<string, string>, dataDir?: string, locationPaths?: Record<string, string>, workerToken?: string): Promise<void>;
|
|
619
|
+
/** List all workers with stats */
|
|
620
|
+
listWorkers(): readonly WorkerInfo[];
|
|
621
|
+
/** Gracefully shutdown a single worker by addon ID */
|
|
622
|
+
shutdownWorkerById(addonId: string): Promise<void>;
|
|
623
|
+
/** Gracefully restart a single worker by addon ID (stop then re-fork with same config) */
|
|
624
|
+
restartWorker(addonId: string): Promise<void>;
|
|
625
|
+
/** Gracefully shutdown all workers */
|
|
626
|
+
shutdownAll(): Promise<void>;
|
|
627
|
+
/** Start heartbeat monitoring — workers now report heartbeat via tRPC, not IPC PING */
|
|
628
|
+
startHeartbeatMonitoring(): void;
|
|
629
|
+
/** Update heartbeat timestamp for a worker (called when agent.heartbeat arrives) */
|
|
630
|
+
updateWorkerHeartbeat(addonId: string, cpuPercent?: number, memoryRss?: number): void;
|
|
631
|
+
private handleWorkerExit;
|
|
632
|
+
private shutdownWorker;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
declare class WorkerProcessManager implements IAddonProcessManager {
|
|
636
|
+
private readonly sendToMain;
|
|
637
|
+
private readonly processes;
|
|
638
|
+
constructor(sendToMain: (msg: WorkerToMainMessage) => void);
|
|
639
|
+
spawn(config: SubProcessConfig): Promise<IManagedSubProcess>;
|
|
640
|
+
listProcesses(): readonly SubProcessInfo[];
|
|
641
|
+
getWorkerStats(): WorkerProcessStats;
|
|
642
|
+
killAll(): Promise<void>;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export { AddonEngineManager, AddonInstaller, type AddonInstallerConfig, AddonLoader, AddonWorkerHost, type AppConfig, type BootstrapConfig, CapabilityRegistry, ConfigManager, DEFAULT_DATA_PATH, INFRA_CAPABILITIES, type ISettingsStore, type InfraCapability, RUNTIME_DEFAULTS, type RegisteredAddon, type ServerMode, type WorkerHostOptions, WorkerProcessManager, bootstrapSchema, copyDirRecursive, copyExtraFileDirs, detectWorkspacePackagesDir, ensureDir, ensureLibraryBuilt, installPackageFromNpmSync, isInfraCapability, isSourceNewer, stripCamstackDeps, symlinkAddonsToNodeModules };
|