camstack 1.2.48 → 1.2.49

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.
@@ -15560,6 +15560,46 @@ var CameraSwitchGroupSchema = external_exports.object({
15560
15560
  /** Unix ms when the group was composed server-side. */
15561
15561
  fetchedAt: external_exports.number()
15562
15562
  });
15563
+ var LogChannelLevelSchema = external_exports.enum([
15564
+ "info",
15565
+ "warn",
15566
+ "error"
15567
+ ]);
15568
+ var LogChannelDescriptorSchema = external_exports.object({
15569
+ /**
15570
+ * Dotted `area.thing`, unique across the workspace. `area` is conventionally
15571
+ * the addon's short name so an operator reading a channel list can tell who
15572
+ * owns it without a second lookup.
15573
+ */
15574
+ name: external_exports.string().min(3).regex(/^[a-z0-9-]+(\.[a-z0-9-]+)+$/, "a channel name is dotted lower-kebab, e.g. area.thing"),
15575
+ /** One sentence: what the operator will SEE after arming it. */
15576
+ description: external_exports.string().min(1),
15577
+ /** The level its lines are emitted at. Never below `info`. */
15578
+ defaultLevel: LogChannelLevelSchema,
15579
+ /**
15580
+ * Whether this channel can be narrowed to a camera.
15581
+ *
15582
+ * `true` is a PROMISE with two halves, and both must hold: the gate is
15583
+ * consulted with the numeric device id, AND every line the channel admits
15584
+ * carries `tags: { deviceId }` with that same numeric id. The second half is
15585
+ * what makes `| json | deviceId="617"` work in Loki — `loki-payload.ts`
15586
+ * keeps `deviceId` out of the stream labels for cardinality, so the tag in
15587
+ * the body is the only way to filter.
15588
+ *
15589
+ * A channel whose lines carry the device only in `meta` (or not at all) is
15590
+ * declared `false`. Declaring it `true` anyway would be a lie the UI repeats:
15591
+ * the operator narrows to one camera, sees nothing, and concludes the code
15592
+ * path was never taken.
15593
+ */
15594
+ perDevice: external_exports.boolean()
15595
+ });
15596
+ var LogChannelWindowSchema = external_exports.object({
15597
+ channel: external_exports.string().min(1),
15598
+ /** Epoch ms the window closes at. */
15599
+ armedUntilMs: external_exports.number(),
15600
+ /** `null` = every camera. A non-empty list narrows to those numeric ids. */
15601
+ deviceIds: external_exports.array(external_exports.number().int()).readonly().nullable()
15602
+ });
15563
15603
  var OpsLogDomainSchema = external_exports.enum(["recording", "events"]);
15564
15604
  var OpsLogOpSchema = external_exports.enum([
15565
15605
  "prune",
@@ -19047,6 +19087,21 @@ var MutationFilterSchema = external_exports.object({
19047
19087
  whereBetween: external_exports.record(external_exports.string(), external_exports.tuple([external_exports.unknown(), external_exports.unknown()])).optional(),
19048
19088
  whereNot: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
19049
19089
  });
19090
+ var AggregateFieldSchema = external_exports.object({
19091
+ /** Result key. */
19092
+ as: external_exports.string().min(1),
19093
+ /** Column to aggregate. Must be a real column of a declared collection. */
19094
+ field: external_exports.string().min(1),
19095
+ op: external_exports.enum([
19096
+ "sum",
19097
+ "min",
19098
+ "max"
19099
+ ])
19100
+ });
19101
+ var AggregateResultSchema = external_exports.object({
19102
+ count: external_exports.number().int(),
19103
+ values: external_exports.record(external_exports.string(), external_exports.number().nullable())
19104
+ });
19050
19105
  var SettingsRecordSchema = external_exports.object({
19051
19106
  id: external_exports.string(),
19052
19107
  data: external_exports.record(external_exports.string(), external_exports.unknown())
@@ -19174,6 +19229,32 @@ var settingsStoreCapability = {
19174
19229
  collection: external_exports.string(),
19175
19230
  filter: QueryFilterSchema.optional()
19176
19231
  }), external_exports.number()),
19232
+ /**
19233
+ * `COUNT(*)` and one `SUM` / `MIN` / `MAX` per requested field, in ONE
19234
+ * statement, over the rows `filter` selects.
19235
+ *
19236
+ * Exists because "how much is there" was being answered by materialising
19237
+ * "what is there". The recorder's storage-pressure sweep asked its in-RAM
19238
+ * footage index for bytes/count/oldest/newest across a set of storage
19239
+ * locations twice a minute, and the only way to answer that from a map is
19240
+ * to visit every row — 7.1 M of them on the live hub, ~15 M row visits a
19241
+ * minute on the main thread, which is also why the whole archive had to
19242
+ * stay resident to be visited. The question is a sum; nothing needs to be
19243
+ * materialised to answer it.
19244
+ *
19245
+ * **The engine REFUSES a field it cannot serve**, exactly as
19246
+ * `query.columns` does and unlike a PREDICATE, which is skipped when
19247
+ * unresolvable. A dropped predicate over-matches and the caller sees extra
19248
+ * rows; a dropped aggregate returns a NUMBER that is wrong and looks
19249
+ * exactly like a real one. That asymmetry is what this repo has already
19250
+ * paid for once in `count`.
19251
+ */
19252
+ aggregate: method(external_exports.object({
19253
+ namespace: external_exports.string().optional(),
19254
+ collection: external_exports.string(),
19255
+ fields: external_exports.array(AggregateFieldSchema).readonly(),
19256
+ filter: QueryFilterSchema.optional()
19257
+ }), AggregateResultSchema),
19177
19258
  /** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
19178
19259
  histogram: method(external_exports.object({
19179
19260
  namespace: external_exports.string().optional(),
@@ -19332,6 +19413,15 @@ var dataStoreProviderCapability = {
19332
19413
  collection: external_exports.string(),
19333
19414
  filter: QueryFilterSchema.optional()
19334
19415
  }), external_exports.number(), { auth: "admin" }),
19416
+ /** `COUNT(*)` plus one SUM/MIN/MAX per field, in one statement. Mirror of
19417
+ * `settings-store.aggregate` — see it for why an unresolvable field is
19418
+ * refused rather than dropped. */
19419
+ aggregate: method(external_exports.object({
19420
+ namespace: external_exports.string().optional(),
19421
+ collection: external_exports.string(),
19422
+ fields: external_exports.array(AggregateFieldSchema).readonly(),
19423
+ filter: QueryFilterSchema.optional()
19424
+ }), AggregateResultSchema, { auth: "admin" }),
19335
19425
  /** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
19336
19426
  histogram: method(external_exports.object({
19337
19427
  namespace: external_exports.string().optional(),
@@ -20409,6 +20499,39 @@ var deviceManagerCapability = {
20409
20499
  /** List children of a parent device (by parent numeric id). */
20410
20500
  getChildren: method(external_exports.object({ parentDeviceId: external_exports.number() }), external_exports.array(DeviceInfoSchema)),
20411
20501
  /**
20502
+ * `getChildren` for a NAMED SET of parents, in one call.
20503
+ *
20504
+ * The accessory reconcile in `device-cap-proxy.ts` asks this question once
20505
+ * per registered device — every `BaseDevice` inherits a
20506
+ * `getAccessoryChildren()` that returns `[]`, so even a leaf accessory
20507
+ * pays a round-trip to learn it has nothing to prune. Measured on the live
20508
+ * hub 2026-08-27 over a 120-second boot window, fleet of 1 017 devices:
20509
+ * `DeviceRowStore.list < DeviceRowStore.listByParent < getChildren` at
20510
+ * **1 024 calls** returning **919 rows in total** — 1 024 RPCs and 1 024
20511
+ * indexed scans to move less than one row each. `listByParentMany`
20512
+ * collapses the scans; this collapses the RPCs.
20513
+ *
20514
+ * Keyed by parent id as a STRING — a JSON object cannot key by number
20515
+ * (same reason as `getDeviceStatusAggregateBatch`). The per-parent value
20516
+ * is exactly what `getChildren` returns for that parent.
20517
+ *
20518
+ * A parent with no children — or one the fleet does not know — is ABSENT
20519
+ * from the record, never an invented empty row: the same contract as
20520
+ * `DeviceRowStore.getMany`/`listByParentMany`. An EMPTY `parentDeviceIds`
20521
+ * reads nothing at all rather than degrading to "every device".
20522
+ *
20523
+ * `parentDeviceIds` is capped at {@link DEVICE_CHILDREN_BATCH_MAX} — see
20524
+ * that constant for why. A caller with more parents than that sends more
20525
+ * than one call; it never sends one pathological one.
20526
+ *
20527
+ * Version skew: this is a NEW method, not a new field on `getChildren`, so
20528
+ * a hub that predates it answers NOT_FOUND rather than silently stripping
20529
+ * an unknown input key and answering a DIFFERENT question. The kernel-side
20530
+ * loader degrades to per-parent `getChildren` on that error — see
20531
+ * `children-batch-loader.ts`.
20532
+ */
20533
+ getChildrenBatch: method(external_exports.object({ parentDeviceIds: external_exports.array(external_exports.number()).max(256) }), external_exports.record(external_exports.string(), external_exports.array(DeviceInfoSchema))),
20534
+ /**
20412
20535
  * Resolve the devices LINKED to a camera — the single policy authority
20413
20536
  * both consumers call (viewer devices panel + pipeline-analytics event
20414
20537
  * kinds/ingest). Device-tree children are ALWAYS included; mode 'auto'
@@ -21454,6 +21577,38 @@ var llmCapability = {
21454
21577
  })
21455
21578
  }
21456
21579
  };
21580
+ var LogChannelApplyResultSchema = external_exports.object({
21581
+ /** How many declared channels are armed in this process after the call. */
21582
+ armed: external_exports.number().int().min(0),
21583
+ /**
21584
+ * Names the document armed that this process does not declare. Reported
21585
+ * rather than swallowed: a name here is either a typo or an addon that has
21586
+ * not booted, and both deserve a line instead of silence.
21587
+ */
21588
+ unknown: external_exports.array(external_exports.string()).readonly()
21589
+ });
21590
+ var logChannelsCapability = {
21591
+ name: "log-channels",
21592
+ scope: "system",
21593
+ mode: "collection",
21594
+ internal: true,
21595
+ methods: {
21596
+ /** The channels this addon declares. Inert: no value, no state. */
21597
+ list: method(external_exports.void(), external_exports.array(LogChannelDescriptorSchema).readonly()),
21598
+ /**
21599
+ * Refresh this process's mirror from the document's FULL set of armed
21600
+ * windows.
21601
+ *
21602
+ * Full and not incremental on purpose: the document is the authority, so a
21603
+ * channel it does not name is disarmed here. An incremental apply would
21604
+ * let a disarm get lost in transit and leave a channel running that
21605
+ * nobody can see is running.
21606
+ */
21607
+ apply: method(external_exports.object({ windows: external_exports.array(LogChannelWindowSchema).readonly() }), LogChannelApplyResultSchema, { kind: "mutation" })
21608
+ },
21609
+ /** In-process only — enumerated through `addons.listCapabilityProviders`. */
21610
+ mount: { kind: "skip" }
21611
+ };
21457
21612
  var LogLevelSchema = external_exports.enum([
21458
21613
  "debug",
21459
21614
  "info",
@@ -36699,6 +36854,14 @@ var LoggingLevelLayerSchema = external_exports.object({
36699
36854
  scope: LoggingScopeKindSchema,
36700
36855
  /** The node this layer speaks for; `null` on the cluster layer. */
36701
36856
  nodeId: external_exports.string().nullable(),
36857
+ /**
36858
+ * The declared channel this layer speaks for; `null` on every layer but
36859
+ * `component`. Never folded into `nodeId`: a component level is CLUSTER-WIDE
36860
+ * by design — the convention this repo settled on is one orchestrator-wide
36861
+ * setting, never per node (D52) — so a component layer that carried a node
36862
+ * would invite a per-node copy of a value that has no per-node meaning.
36863
+ */
36864
+ component: external_exports.string().nullable(),
36702
36865
  /** Explicitly set here, or `null` when this layer inherits. */
36703
36866
  level: LogLevelSchema$1.nullable()
36704
36867
  });
@@ -36724,6 +36887,38 @@ var DiagnosticWindowPatchSchema = external_exports.object({
36724
36887
  /** Cadence of the diagnostic's aggregated report line. Clamped by the server. */
36725
36888
  reportEveryMs: external_exports.number().int().positive().optional()
36726
36889
  });
36890
+ var LogChannelWindowStateSchema = external_exports.object({
36891
+ channel: external_exports.string(),
36892
+ armed: external_exports.boolean(),
36893
+ /** Epoch ms the window closes at. 0 when disarmed. */
36894
+ armedUntilMs: external_exports.number(),
36895
+ /** Ms left before it expires on its own. 0 when disarmed. */
36896
+ remainingMs: external_exports.number(),
36897
+ /**
36898
+ * The cameras it is narrowed to, or `null` for every camera.
36899
+ *
36900
+ * A channel declared `perDevice: false` can only ever report `null` here:
36901
+ * its lines do not carry `tags: { deviceId }`, so narrowing them would
36902
+ * produce a filter that silently matches nothing. The server REFUSES such a
36903
+ * patch rather than quietly widening it — ignoring the request would teach
36904
+ * the operator that per-camera filtering works on that channel when it does
36905
+ * not.
36906
+ */
36907
+ deviceIds: external_exports.array(external_exports.number().int()).readonly().nullable()
36908
+ });
36909
+ var LogChannelWindowPatchSchema = external_exports.object({
36910
+ channel: external_exports.string().min(1),
36911
+ armMs: external_exports.number().int().min(0),
36912
+ /**
36913
+ * Narrow to these numeric device ids. Absent or `null` = every camera.
36914
+ *
36915
+ * Numeric because the repo's own rule makes it possible: every log line
36916
+ * about a device carries `tags: { deviceId }` with the numeric id. That rule
36917
+ * was paid for with a 22% thumbnail gap and a 3-hour media blackout both
36918
+ * diagnosed by hand, and this is the first thing that collects on it.
36919
+ */
36920
+ deviceIds: external_exports.array(external_exports.number().int()).readonly().nullable().optional()
36921
+ });
36727
36922
  var LoggingSettingsPatchSchema = external_exports.object({
36728
36923
  /**
36729
36924
  * Absent leaves the level untouched. `null` CLEARS the explicit value at the
@@ -36734,19 +36929,50 @@ var LoggingSettingsPatchSchema = external_exports.object({
36734
36929
  * Only the diagnostics NAMED here change. An armed window that is not listed
36735
36930
  * keeps running — a patch is never a full replacement.
36736
36931
  */
36737
- diagnostics: external_exports.array(DiagnosticWindowPatchSchema).readonly().optional()
36932
+ diagnostics: external_exports.array(DiagnosticWindowPatchSchema).readonly().optional(),
36933
+ /**
36934
+ * Only the channels NAMED here change. An armed channel that is not listed
36935
+ * keeps running — same rule as `diagnostics`, because a patch that silently
36936
+ * disarmed the channels it did not mention would make the Levels page and
36937
+ * the Diagnostics page fight over the same value.
36938
+ */
36939
+ channels: external_exports.array(LogChannelWindowPatchSchema).readonly().optional()
36940
+ });
36941
+ var GetLoggingSettingsInputSchema = external_exports.object({
36942
+ scopeNodeId: external_exports.string().optional(),
36943
+ /**
36944
+ * The declared CHANNEL this document is addressed at, when the caller wants
36945
+ * the `component` layer. Absent = the node/cluster hierarchy only.
36946
+ *
36947
+ * Naming it separately rather than overloading `scopeNodeId` keeps the two
36948
+ * axes from collapsing: a component level is cluster-wide, a node level is
36949
+ * not, and one selector for both would make "which of these two did I just
36950
+ * set" unanswerable — the exact ambiguity `explicit` exists to remove.
36951
+ */
36952
+ scopeComponent: external_exports.string().optional()
36738
36953
  });
36739
- var GetLoggingSettingsInputSchema = external_exports.object({ scopeNodeId: external_exports.string().optional() });
36740
36954
  var SetLoggingSettingsInputSchema = external_exports.object({
36741
36955
  scopeNodeId: external_exports.string().optional(),
36956
+ scopeComponent: external_exports.string().optional(),
36742
36957
  patch: LoggingSettingsPatchSchema
36743
36958
  });
36744
36959
  var LoggingSettingsStateSchema = external_exports.object({
36745
36960
  /** The layer this document was read at. `null` = the cluster layer. */
36746
36961
  scopeNodeId: external_exports.string().nullable(),
36962
+ /** The channel this document was read at. `null` = no component layer. */
36963
+ scopeComponent: external_exports.string().nullable(),
36747
36964
  effective: LoggingEffectiveSchema,
36748
36965
  explicit: LoggingExplicitSchema,
36749
36966
  activeWindows: external_exports.array(DiagnosticWindowSchema).readonly(),
36967
+ /**
36968
+ * Every channel the cluster's addons DECLARE, gathered from the
36969
+ * `log-channels` providers. Not stored anywhere: assembled per read, so a
36970
+ * channel added by a redeployed addon appears without anybody editing a
36971
+ * list, and a channel whose addon is gone stops being offered.
36972
+ */
36973
+ channels: external_exports.array(LogChannelDescriptorSchema).readonly(),
36974
+ /** The channels ARMED right now, each with its deadline. */
36975
+ activeChannels: external_exports.array(LogChannelWindowStateSchema).readonly(),
36750
36976
  persisted: external_exports.boolean()
36751
36977
  });
36752
36978
  var systemCapability = {
@@ -38657,6 +38883,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
38657
38883
  addonId: null,
38658
38884
  access: "view"
38659
38885
  },
38886
+ "dataStoreProvider.aggregate": {
38887
+ capName: "data-store-provider",
38888
+ capScope: "system",
38889
+ addonId: null,
38890
+ access: "view"
38891
+ },
38660
38892
  "dataStoreProvider.count": {
38661
38893
  capName: "data-store-provider",
38662
38894
  capScope: "system",
@@ -39071,6 +39303,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
39071
39303
  addonId: null,
39072
39304
  access: "view"
39073
39305
  },
39306
+ "deviceManager.getChildrenBatch": {
39307
+ capName: "device-manager",
39308
+ capScope: "system",
39309
+ addonId: null,
39310
+ access: "view"
39311
+ },
39074
39312
  "deviceManager.getConfigSchema": {
39075
39313
  capName: "device-manager",
39076
39314
  capScope: "system",
@@ -40121,6 +40359,18 @@ var METHOD_ACCESS_MAP = Object.freeze({
40121
40359
  addonId: null,
40122
40360
  access: "create"
40123
40361
  },
40362
+ "logChannels.apply": {
40363
+ capName: "log-channels",
40364
+ capScope: "system",
40365
+ addonId: null,
40366
+ access: "create"
40367
+ },
40368
+ "logChannels.list": {
40369
+ capName: "log-channels",
40370
+ capScope: "system",
40371
+ addonId: null,
40372
+ access: "view"
40373
+ },
40124
40374
  "logDestination.query": {
40125
40375
  capName: "log-destination",
40126
40376
  capScope: "system",
@@ -42275,6 +42525,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
42275
42525
  addonId: null,
42276
42526
  access: "create"
42277
42527
  },
42528
+ "settingsStore.aggregate": {
42529
+ capName: "settings-store",
42530
+ capScope: "system",
42531
+ addonId: null,
42532
+ access: "view"
42533
+ },
42278
42534
  "settingsStore.count": {
42279
42535
  capName: "settings-store",
42280
42536
  capScope: "system",
@@ -43854,6 +44110,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
43854
44110
  form: "single",
43855
44111
  optional: false
43856
44112
  }],
44113
+ "deviceManager.getChildrenBatch": [{
44114
+ name: "parentDeviceIds",
44115
+ form: "array",
44116
+ optional: false
44117
+ }],
43857
44118
  "deviceManager.getConfigSchema": [{
43858
44119
  name: "deviceId",
43859
44120
  form: "single",
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runDiscover
4
- } from "./chunk-4E6GZ4AC.js";
4
+ } from "./chunk-U5U3EPIG.js";
5
5
  import "./chunk-LMMQX4CK.js";
6
6
 
7
7
  // src/cli.ts
@@ -38,7 +38,7 @@ async function runServe(args) {
38
38
  ...typeof values.data === "string" ? { data: values.data } : {}
39
39
  };
40
40
  Object.assign(process.env, buildServeEnv(opts));
41
- await import("./launcher-MNOEQWRN.js");
41
+ await import("./launcher-Y5UOZ2NP.js");
42
42
  }
43
43
 
44
44
  // src/commands/agent.ts
@@ -83,7 +83,7 @@ async function runAgent(args) {
83
83
  ...typeof values.port === "string" ? { port: values.port } : {}
84
84
  };
85
85
  Object.assign(process.env, buildAgentEnv(opts));
86
- await import("./launcher-MNOEQWRN.js");
86
+ await import("./launcher-Y5UOZ2NP.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -1130,7 +1130,7 @@ function isUnknown(_value) {
1130
1130
  return true;
1131
1131
  }
1132
1132
  async function resolveServerInteractive(presetNamespace) {
1133
- const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-LCFU2BSJ.js");
1133
+ const { discoverNodes, resolveHubFromDiscovered, filterHubNodes, DEFAULT_HUB_HTTPS_PORT } = await import("./discover-WF63I2OT.js");
1134
1134
  if (presetNamespace) {
1135
1135
  const spinner4 = clack.spinner();
1136
1136
  spinner4.start(`Discovering hub on LAN (namespace "${presetNamespace}")`);
@@ -5,7 +5,7 @@ import {
5
5
  filterHubNodes,
6
6
  resolveHubFromDiscovered,
7
7
  runDiscover
8
- } from "./chunk-4E6GZ4AC.js";
8
+ } from "./chunk-U5U3EPIG.js";
9
9
  import "./chunk-LMMQX4CK.js";
10
10
  export {
11
11
  DEFAULT_HUB_HTTPS_PORT,