@camstack/addon-provider-homeassistant 1.2.146 → 1.2.148
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.js +18 -15
- package/dist/addon.mjs +18 -15
- package/dist/{dist-D2jDnKLv.mjs → dist-DmoYnWid.mjs} +360 -213
- package/dist/{dist-BTJhdlL4.js → dist-nvulD3Mi.js} +360 -213
- package/dist/ha-export/ha-export.addon.js +1 -1
- package/dist/ha-export/ha-export.addon.mjs +1 -1
- package/package.json +1 -1
|
@@ -5369,7 +5369,7 @@ var ZodIssueCode = {
|
|
|
5369
5369
|
var ZodFirstPartyTypeKind;
|
|
5370
5370
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5371
5371
|
//#endregion
|
|
5372
|
-
//#region ../types/dist/sleep-
|
|
5372
|
+
//#region ../types/dist/sleep-C-v96WpE.mjs
|
|
5373
5373
|
/**
|
|
5374
5374
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5375
5375
|
* window to float samples (D455).
|
|
@@ -6984,6 +6984,10 @@ function toNodeId(idOrRunnerId) {
|
|
|
6984
6984
|
const head = idOrRunnerId.split("/")[0];
|
|
6985
6985
|
return head === void 0 || head.length === 0 ? idOrRunnerId : head;
|
|
6986
6986
|
}
|
|
6987
|
+
object({
|
|
6988
|
+
field: string().min(1),
|
|
6989
|
+
on: literal("advance")
|
|
6990
|
+
});
|
|
6987
6991
|
/**
|
|
6988
6992
|
* Output schema shared by the contribution + live methods.
|
|
6989
6993
|
*
|
|
@@ -7467,7 +7471,45 @@ function errMsg(err) {
|
|
|
7467
7471
|
return String(err);
|
|
7468
7472
|
}
|
|
7469
7473
|
//#endregion
|
|
7470
|
-
//#region ../types/dist/composition-
|
|
7474
|
+
//#region ../types/dist/composition-BK-GTFYb.mjs
|
|
7475
|
+
/**
|
|
7476
|
+
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
7477
|
+
* valutatore puro del suo FRONTE.
|
|
7478
|
+
*
|
|
7479
|
+
* Viveva dentro il builtin virtual-doorbell
|
|
7480
|
+
* (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
|
|
7481
|
+
* predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
|
|
7482
|
+
* `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
|
|
7483
|
+
* tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
|
|
7484
|
+
* fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
|
|
7485
|
+
* SPOSTA qui e il doorbell la ri-esporta.
|
|
7486
|
+
*
|
|
7487
|
+
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
7488
|
+
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
7489
|
+
* regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
|
|
7490
|
+
* booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
|
|
7491
|
+
*/
|
|
7492
|
+
/**
|
|
7493
|
+
* Known binary / switch source caps → the boolean slice field whose
|
|
7494
|
+
* false→true rise counts as ACTIVE. Every entry is "fire on active".
|
|
7495
|
+
* Sensors whose "active" reading is not a plain boolean (presence's string
|
|
7496
|
+
* state, connectivity's connected flag) are deliberately excluded — a
|
|
7497
|
+
* reconnect is not a doorbell press, and it is not a recording either.
|
|
7498
|
+
*/
|
|
7499
|
+
var SOURCE_CAP_ACTIVE_FIELD = {
|
|
7500
|
+
contact: "entryOpen",
|
|
7501
|
+
binary: "on",
|
|
7502
|
+
switch: "on",
|
|
7503
|
+
motion: "detected",
|
|
7504
|
+
flood: "flooded",
|
|
7505
|
+
gas: "detected",
|
|
7506
|
+
smoke: "detected",
|
|
7507
|
+
"carbon-monoxide": "detected",
|
|
7508
|
+
vibration: "detected",
|
|
7509
|
+
tamper: "tampered"
|
|
7510
|
+
};
|
|
7511
|
+
/** Cap names whose presence in a device's bindings qualify it as a source. */
|
|
7512
|
+
var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
|
|
7471
7513
|
/**
|
|
7472
7514
|
* Error types for the safe expression engine. Two distinct classes so callers
|
|
7473
7515
|
* can tell a compile-time (grammar) failure from a runtime (evaluation)
|
|
@@ -7665,6 +7707,20 @@ var table = {
|
|
|
7665
7707
|
const present = args.flatMap((a, i) => a === null ? [] : [asFiniteNumber(a, "latest", i)]);
|
|
7666
7708
|
return present.length === 0 ? null : finiteResult(Math.max(...present), "latest");
|
|
7667
7709
|
}
|
|
7710
|
+
},
|
|
7711
|
+
rose: {
|
|
7712
|
+
minArgs: 2,
|
|
7713
|
+
maxArgs: 3,
|
|
7714
|
+
apply: () => {
|
|
7715
|
+
throw new ExpressionEvalError("rose: stateful evaluation context is required");
|
|
7716
|
+
}
|
|
7717
|
+
},
|
|
7718
|
+
count: {
|
|
7719
|
+
minArgs: 1,
|
|
7720
|
+
maxArgs: 1,
|
|
7721
|
+
apply: () => {
|
|
7722
|
+
throw new ExpressionEvalError("count: stateful evaluation context is required");
|
|
7723
|
+
}
|
|
7668
7724
|
}
|
|
7669
7725
|
};
|
|
7670
7726
|
Object.freeze(Object.assign(Object.create(null), table));
|
|
@@ -8179,6 +8235,7 @@ var CompositionItemEntrySchema = object({
|
|
|
8179
8235
|
var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
8180
8236
|
kind: literal("fields"),
|
|
8181
8237
|
cap: string().min(1),
|
|
8238
|
+
enabled: boolean().optional(),
|
|
8182
8239
|
fields: record(string().min(1), CompositionFieldSourceSchema).refine((fields) => Object.keys(fields).length <= 32, { message: `at most 32 fields per capability` }),
|
|
8183
8240
|
/**
|
|
8184
8241
|
* Items of the cap's item array, keyed by item key (D663). Naming `items`
|
|
@@ -8190,6 +8247,7 @@ var CompositionFeatureSchema = discriminatedUnion("kind", [object({
|
|
|
8190
8247
|
}), object({
|
|
8191
8248
|
kind: literal("passthrough"),
|
|
8192
8249
|
cap: string().min(1),
|
|
8250
|
+
enabled: boolean().optional(),
|
|
8193
8251
|
source: CompositionSourceRefSchema
|
|
8194
8252
|
})]);
|
|
8195
8253
|
var CompositionSchema = object({
|
|
@@ -8219,7 +8277,6 @@ var CompositionSchema = object({
|
|
|
8219
8277
|
});
|
|
8220
8278
|
new Set([
|
|
8221
8279
|
"track",
|
|
8222
|
-
"summary",
|
|
8223
8280
|
"face",
|
|
8224
8281
|
"identity",
|
|
8225
8282
|
"plate",
|
|
@@ -13137,6 +13194,15 @@ var cameraStreamsCapability = {
|
|
|
13137
13194
|
*/
|
|
13138
13195
|
durability: "session"
|
|
13139
13196
|
};
|
|
13197
|
+
EventCategory.DoorbellOnPressed;
|
|
13198
|
+
var CompositionEventPlanSchema = object({
|
|
13199
|
+
featureIndex: number().int().nonnegative(),
|
|
13200
|
+
cap: string(),
|
|
13201
|
+
eventName: string(),
|
|
13202
|
+
category: _enum(EventCategory),
|
|
13203
|
+
stateField: string(),
|
|
13204
|
+
fieldKey: string()
|
|
13205
|
+
});
|
|
13140
13206
|
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
|
|
13141
13207
|
var CompositionProblemSchema = object({
|
|
13142
13208
|
code: _enum([
|
|
@@ -13186,12 +13252,14 @@ var CompositionUnavailableModeSchema = _enum([
|
|
|
13186
13252
|
]);
|
|
13187
13253
|
/**
|
|
13188
13254
|
* `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
|
|
13189
|
-
* field of its cap changes value · `constant` reads nothing, computed once
|
|
13255
|
+
* field of its cap changes value · `constant` reads nothing, computed once ·
|
|
13256
|
+
* `accumulator` advances from the target's seed/current output.
|
|
13190
13257
|
*/
|
|
13191
13258
|
var CompositionFieldRoleSchema = _enum([
|
|
13192
13259
|
"derived",
|
|
13193
13260
|
"clock",
|
|
13194
|
-
"constant"
|
|
13261
|
+
"constant",
|
|
13262
|
+
"accumulator"
|
|
13195
13263
|
]);
|
|
13196
13264
|
/** The item a planned field belongs to; its `path` is then the dotted path INSIDE the item. */
|
|
13197
13265
|
var CompositionFieldItemSchema = object({
|
|
@@ -13227,6 +13295,7 @@ var CompositionFeatureModeSchema = _enum([
|
|
|
13227
13295
|
"replace"
|
|
13228
13296
|
]);
|
|
13229
13297
|
var CompositionFeaturePlanSchema = object({
|
|
13298
|
+
/** Index in the stored feature array; disabled features are omitted rather than re-indexed. */
|
|
13230
13299
|
featureIndex: number().int().nonnegative(),
|
|
13231
13300
|
cap: string(),
|
|
13232
13301
|
mode: CompositionFeatureModeSchema
|
|
@@ -13235,7 +13304,8 @@ var CompositionValidationSchema = object({
|
|
|
13235
13304
|
ok: boolean(),
|
|
13236
13305
|
problems: array(CompositionProblemSchema),
|
|
13237
13306
|
fields: array(CompositionFieldPlanSchema),
|
|
13238
|
-
features: array(CompositionFeaturePlanSchema)
|
|
13307
|
+
features: array(CompositionFeaturePlanSchema),
|
|
13308
|
+
events: array(CompositionEventPlanSchema)
|
|
13239
13309
|
});
|
|
13240
13310
|
/**
|
|
13241
13311
|
* Mirrors `CoreBlockStatus`, pinned by `core-blocks-source.spec.ts`. `degraded`:
|
|
@@ -15252,6 +15322,34 @@ var DeviceBindingsForDeviceSchema = object({
|
|
|
15252
15322
|
nativeAddonId: string()
|
|
15253
15323
|
}))
|
|
15254
15324
|
});
|
|
15325
|
+
/**
|
|
15326
|
+
* The STORED wrapper activation for one `(device, capability)` pair — the row
|
|
15327
|
+
* `setWrapperActive` writes, read back three-way (D675):
|
|
15328
|
+
*
|
|
15329
|
+
* - `value` — the store answered and holds an activation: the bound wrapper's
|
|
15330
|
+
* addon id, or `null`, the operator's explicit "off" marker (D62);
|
|
15331
|
+
* - `missing` — the store answered, its activation map exists, and it holds
|
|
15332
|
+
* nothing for this pair: the operator never activated or deactivated a
|
|
15333
|
+
* wrapper here;
|
|
15334
|
+
* - `failed` — the store could not be read, holds no activation map at all
|
|
15335
|
+
* (an empty or unready snapshot, D44), or holds something that is not an
|
|
15336
|
+
* activation. It carries NO information: a consumer must never read it as
|
|
15337
|
+
* `missing` (D315, D393).
|
|
15338
|
+
*
|
|
15339
|
+
* This is the persisted INPUT, not the derived `getBindings` view — no registry
|
|
15340
|
+
* resolution, no `defaultActive` auto-binding, no cluster native-cap folding.
|
|
15341
|
+
*/
|
|
15342
|
+
var WrapperActivationReadSchema = discriminatedUnion("kind", [
|
|
15343
|
+
object({
|
|
15344
|
+
kind: literal("value"),
|
|
15345
|
+
wrapperAddonId: string().nullable()
|
|
15346
|
+
}),
|
|
15347
|
+
object({ kind: literal("missing") }),
|
|
15348
|
+
object({
|
|
15349
|
+
kind: literal("failed"),
|
|
15350
|
+
error: string()
|
|
15351
|
+
})
|
|
15352
|
+
]);
|
|
15255
15353
|
var SavedDeviceRowSchema = object({
|
|
15256
15354
|
/** Numeric id reserved at allocateDeviceId time. */
|
|
15257
15355
|
id: number(),
|
|
@@ -15602,7 +15700,10 @@ method(object({
|
|
|
15602
15700
|
}), _void(), {
|
|
15603
15701
|
kind: "mutation",
|
|
15604
15702
|
auth: "admin"
|
|
15605
|
-
}), method(object({
|
|
15703
|
+
}), method(object({
|
|
15704
|
+
deviceId: number(),
|
|
15705
|
+
capName: string().min(1)
|
|
15706
|
+
}), WrapperActivationReadSchema), method(object({ capName: string() }), array(string())), method(object({ deviceType: string() }), array(object({
|
|
15606
15707
|
capName: string(),
|
|
15607
15708
|
wrappers: array(string())
|
|
15608
15709
|
}))), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), SettingsSchemaWithValuesSchema.nullable()), method(object({ deviceId: number() }), object({
|
|
@@ -20781,6 +20882,102 @@ var oauthIntegrationCapability = {
|
|
|
20781
20882
|
getDescriptor: method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" }) }
|
|
20782
20883
|
};
|
|
20783
20884
|
/**
|
|
20885
|
+
* What started a camera's onboard motion (D672). `camera` is the camera's own
|
|
20886
|
+
* firmware; `sensor` is a device the operator ATTACHED to it. Absent on the
|
|
20887
|
+
* wire means the camera: only the camera-sensors bridge ever says `sensor`.
|
|
20888
|
+
*/
|
|
20889
|
+
var MotionTriggeredBySchema = discriminatedUnion("kind", [object({ kind: literal("camera") }), object({
|
|
20890
|
+
kind: literal("sensor"),
|
|
20891
|
+
deviceId: number().int().positive()
|
|
20892
|
+
})]);
|
|
20893
|
+
Object.freeze({ kind: "camera" });
|
|
20894
|
+
var CameraSensorRefSchema = object({
|
|
20895
|
+
addonId: string().min(1),
|
|
20896
|
+
stableId: string().min(1)
|
|
20897
|
+
});
|
|
20898
|
+
function cameraSensorRefKey(ref) {
|
|
20899
|
+
return `${ref.addonId}/${ref.stableId}`;
|
|
20900
|
+
}
|
|
20901
|
+
/** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
|
|
20902
|
+
var CameraSensorsModeSchema = _enum(["in-addition"]);
|
|
20903
|
+
object({
|
|
20904
|
+
mode: CameraSensorsModeSchema,
|
|
20905
|
+
sensors: array(CameraSensorRefSchema).min(1).max(8)
|
|
20906
|
+
}).superRefine((value, ctx) => {
|
|
20907
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20908
|
+
value.sensors.forEach((ref, index) => {
|
|
20909
|
+
const key = cameraSensorRefKey(ref);
|
|
20910
|
+
if (seen.has(key)) ctx.addIssue({
|
|
20911
|
+
code: "custom",
|
|
20912
|
+
path: ["sensors", index],
|
|
20913
|
+
message: `duplicate camera sensor ${key}`
|
|
20914
|
+
});
|
|
20915
|
+
seen.add(key);
|
|
20916
|
+
});
|
|
20917
|
+
});
|
|
20918
|
+
var RefFields = {
|
|
20919
|
+
addonId: string(),
|
|
20920
|
+
stableId: string()
|
|
20921
|
+
};
|
|
20922
|
+
var CameraSensorAttachmentSchema = discriminatedUnion("state", [
|
|
20923
|
+
object({
|
|
20924
|
+
...RefFields,
|
|
20925
|
+
state: literal("ready"),
|
|
20926
|
+
deviceId: number(),
|
|
20927
|
+
name: string(),
|
|
20928
|
+
caps: array(string()).readonly()
|
|
20929
|
+
}),
|
|
20930
|
+
object({
|
|
20931
|
+
...RefFields,
|
|
20932
|
+
state: literal("offline"),
|
|
20933
|
+
deviceId: number(),
|
|
20934
|
+
name: string(),
|
|
20935
|
+
reason: _enum(["offline", "disabled"])
|
|
20936
|
+
}),
|
|
20937
|
+
object({
|
|
20938
|
+
...RefFields,
|
|
20939
|
+
state: literal("missing"),
|
|
20940
|
+
reason: string()
|
|
20941
|
+
}),
|
|
20942
|
+
object({
|
|
20943
|
+
...RefFields,
|
|
20944
|
+
state: literal("unsupported"),
|
|
20945
|
+
deviceId: number(),
|
|
20946
|
+
name: string(),
|
|
20947
|
+
reason: string()
|
|
20948
|
+
}),
|
|
20949
|
+
object({
|
|
20950
|
+
...RefFields,
|
|
20951
|
+
state: literal("unreadable"),
|
|
20952
|
+
reason: string()
|
|
20953
|
+
})
|
|
20954
|
+
]);
|
|
20955
|
+
/**
|
|
20956
|
+
* Why a configured camera forwards NOTHING from its sensors, however ready they
|
|
20957
|
+
* are (D672): it does not list `onboard` in `motionSources` (attaching a sensor
|
|
20958
|
+
* does not add it), or it has no active detection config at all (the bridge
|
|
20959
|
+
* reads the active config). Absent = forwarded, or an older node that does not
|
|
20960
|
+
* say.
|
|
20961
|
+
*/
|
|
20962
|
+
var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
|
|
20963
|
+
var CameraSensorsStatusSchema = discriminatedUnion("state", [
|
|
20964
|
+
object({ state: literal("not-configured") }),
|
|
20965
|
+
object({
|
|
20966
|
+
state: literal("unreadable"),
|
|
20967
|
+
reason: string()
|
|
20968
|
+
}),
|
|
20969
|
+
object({
|
|
20970
|
+
state: literal("configured"),
|
|
20971
|
+
mode: CameraSensorsModeSchema,
|
|
20972
|
+
sensors: array(CameraSensorAttachmentSchema).readonly(),
|
|
20973
|
+
notForwarded: CameraSensorsNotForwardedSchema.optional()
|
|
20974
|
+
})
|
|
20975
|
+
]);
|
|
20976
|
+
Object.freeze({
|
|
20977
|
+
state: "unreadable",
|
|
20978
|
+
reason: "camera sensor mappings not loaded"
|
|
20979
|
+
});
|
|
20980
|
+
/**
|
|
20784
20981
|
* A model choice has a SCOPE, and a scope change has a workflow.
|
|
20785
20982
|
*
|
|
20786
20983
|
* ## The two scopes
|
|
@@ -21529,6 +21726,12 @@ var MotionEventSchema = object({
|
|
|
21529
21726
|
/** Populated by B5 (recording playback URL for this event). */
|
|
21530
21727
|
mediaUrl: string().optional(),
|
|
21531
21728
|
/**
|
|
21729
|
+
* The FIRST trigger of an `onboard` episode, written once at open (D672).
|
|
21730
|
+
* Absent = not recorded (a row written before D672, or an analyzer /
|
|
21731
|
+
* device-activity episode) — never read as "the camera" (D393).
|
|
21732
|
+
*/
|
|
21733
|
+
triggeredBy: MotionTriggeredBySchema.optional(),
|
|
21734
|
+
/**
|
|
21532
21735
|
* One row per motion EPISODE, not one per push (D475). `null` while the
|
|
21533
21736
|
* episode is still open — a further rising edge extends it in place rather
|
|
21534
21737
|
* than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
|
|
@@ -21687,6 +21890,21 @@ var ObjectEventSchema = object({
|
|
|
21687
21890
|
keyFrameMediaId: number().int().optional(),
|
|
21688
21891
|
/** Populated by B5 (recording playback URL for this event). */
|
|
21689
21892
|
mediaUrl: string().optional(),
|
|
21893
|
+
/**
|
|
21894
|
+
* When the change this row records HAPPENED, epoch ms, when that is earlier
|
|
21895
|
+
* than {@link timestamp} and the producer could date it (D684).
|
|
21896
|
+
*
|
|
21897
|
+
* Set on a package PICK-UP row only: its `timestamp` is when the departure
|
|
21898
|
+
* was CONCLUDED — five minutes of unseen observed time after the parcel went
|
|
21899
|
+
* — and a notification's footage has to be cut around the pick-up, not the
|
|
21900
|
+
* empty doormat after it (D682). Stored so every path that rebuilds a
|
|
21901
|
+
* notification subject from the row (the boot reconcile, a dry-run) anchors
|
|
21902
|
+
* on the same instant the live hook did.
|
|
21903
|
+
*
|
|
21904
|
+
* `timestamp` keeps its meaning. Absent on every other row and on every
|
|
21905
|
+
* pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
|
|
21906
|
+
*/
|
|
21907
|
+
observedAt: number().optional(),
|
|
21690
21908
|
/** The parent track's key-event importance [0,1], propagated to every object
|
|
21691
21909
|
* event of the track (so an event row can be sorted by importance without a
|
|
21692
21910
|
* track join). Absent on legacy rows / before the track was scored. */
|
|
@@ -22022,131 +22240,6 @@ var RecentTracksQueryInput = object({
|
|
|
22022
22240
|
/** See {@link ExcludeSourcesDoc}. */
|
|
22023
22241
|
excludeSources: array(TrackSourceSchema).optional()
|
|
22024
22242
|
});
|
|
22025
|
-
/**
|
|
22026
|
-
* A Summary (D359): the per-camera session envelope that references tracks.
|
|
22027
|
-
* `groupShotMediaId` is the media ROW id of its group shot (`ownerKind:
|
|
22028
|
-
* 'summary'`, served by the event-media plane at `/m/<id>`), null until the
|
|
22029
|
-
* first frame with a member visible has been materialised (D482 — it was the
|
|
22030
|
-
* row's composite key, which embedded the owner and could name another table's
|
|
22031
|
-
* row once event ids became per-table rowids).
|
|
22032
|
-
*/
|
|
22033
|
-
var SummarySchema = object({
|
|
22034
|
-
id: string(),
|
|
22035
|
-
deviceId: number(),
|
|
22036
|
-
openedAt: number(),
|
|
22037
|
-
lastActiveAt: number(),
|
|
22038
|
-
sealedAt: number().nullable(),
|
|
22039
|
-
memberCount: number().int().nonnegative(),
|
|
22040
|
-
groupShotMediaId: number().int().nullable()
|
|
22041
|
-
});
|
|
22042
|
-
var SummariesQueryInput = object({
|
|
22043
|
-
deviceIds: array(number()).min(1),
|
|
22044
|
-
since: number().optional(),
|
|
22045
|
-
until: number().optional(),
|
|
22046
|
-
limit: number().int().min(1).max(200).default(50),
|
|
22047
|
-
/** The same member-wise filter {@link listRecentSummaries} takes — one
|
|
22048
|
-
* meaning of "dog" on every summary surface. */
|
|
22049
|
-
classes: array(string().min(1).max(60)).max(40).optional()
|
|
22050
|
-
});
|
|
22051
|
-
var SummaryDetailSchema = object({
|
|
22052
|
-
summary: SummarySchema,
|
|
22053
|
-
/** Members in absorption order, oldest join first. */
|
|
22054
|
-
members: array(object({
|
|
22055
|
-
trackId: string(),
|
|
22056
|
-
/**
|
|
22057
|
-
* The track's class at the time it JOINED, or `null` when it was unresolved
|
|
22058
|
-
* and the track no longer exists to fill it.
|
|
22059
|
-
*
|
|
22060
|
-
* The join, not the latest opinion: class voting can re-decide a track's
|
|
22061
|
-
* class afterwards, and a group's composition is the history of who came
|
|
22062
|
-
* together. A consumer counts these to badge a thumbnail — "two people and a
|
|
22063
|
-
* dog" — rather than being handed a second, denormalised tally that would
|
|
22064
|
-
* drift from the members it claims to describe.
|
|
22065
|
-
*/
|
|
22066
|
-
className: string().nullable(),
|
|
22067
|
-
/** `primary` is the one member that carries the group's identity. */
|
|
22068
|
-
role: _enum([
|
|
22069
|
-
"primary",
|
|
22070
|
-
"sibling",
|
|
22071
|
-
"child"
|
|
22072
|
-
]).nullable(),
|
|
22073
|
-
joinedAt: number(),
|
|
22074
|
-
/** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
|
|
22075
|
-
* at read time. Absent when nothing resolved, never an empty string. */
|
|
22076
|
-
label: string().optional(),
|
|
22077
|
-
/** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
|
|
22078
|
-
* recognised nobody, and absent for a member whose track has been pruned —
|
|
22079
|
-
* a thumbnail that names the wrong person is worse than one naming nobody. */
|
|
22080
|
-
subLabel: string().optional()
|
|
22081
|
-
})).readonly()
|
|
22082
|
-
});
|
|
22083
|
-
/**
|
|
22084
|
-
* What a LIST of groups carries: the envelope plus what it is MADE OF.
|
|
22085
|
-
*
|
|
22086
|
-
* Coarse on purpose — the operator's rule is "only the rough data on the list,
|
|
22087
|
-
* we extend when we notice we need more". Enough to draw the card ("two people
|
|
22088
|
-
* and a dog") and no more. The MEMBERS, their names and the per-member links
|
|
22089
|
-
* are on {@link getSummary}: shipping every member of every group so a client
|
|
22090
|
-
* can count them turns a page of two hundred groups into a thousand records to
|
|
22091
|
-
* draw two hundred badges.
|
|
22092
|
-
*/
|
|
22093
|
-
var SummaryListItemSchema = object({
|
|
22094
|
-
summary: SummarySchema,
|
|
22095
|
-
/** Class counts, most numerous first; `className: null` is a member whose
|
|
22096
|
-
* class never resolved, counted so the counts add up to `memberCount`. */
|
|
22097
|
-
classes: array(object({
|
|
22098
|
-
className: string().nullable(),
|
|
22099
|
-
count: number().int().positive()
|
|
22100
|
-
})).readonly()
|
|
22101
|
-
});
|
|
22102
|
-
var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
|
|
22103
|
-
/**
|
|
22104
|
-
* The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
|
|
22105
|
-
*
|
|
22106
|
-
* The reel on the cameras page and the Events gallery both ride a cursor, so a
|
|
22107
|
-
* summaries feed that could only answer "the last N for these cameras"
|
|
22108
|
-
* (`listSummaries`) would make groups available in the timeline and nowhere
|
|
22109
|
-
* else. Two feeds with different ordering contracts would be two ideas of what
|
|
22110
|
-
* "newest" means, on one screen.
|
|
22111
|
-
*/
|
|
22112
|
-
var RecentSummariesQueryInput = object({
|
|
22113
|
-
/** Devices to merge. An empty array yields an empty page. */
|
|
22114
|
-
deviceIds: array(number()),
|
|
22115
|
-
/** Window lower bound on `lastActiveAt` (inclusive). */
|
|
22116
|
-
since: number().optional(),
|
|
22117
|
-
/** Window upper bound on `lastActiveAt` (inclusive). */
|
|
22118
|
-
until: number().optional(),
|
|
22119
|
-
limit: number().int().min(1).max(500).default(100),
|
|
22120
|
-
/** Opaque continuation cursor from a previous page's `nextCursor`. */
|
|
22121
|
-
cursor: string().optional(),
|
|
22122
|
-
/**
|
|
22123
|
-
* Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
|
|
22124
|
-
* deliberately the same name: the events filter tree emits one list of ticked
|
|
22125
|
-
* taxonomy nodes, and a summaries feed that called it something else would
|
|
22126
|
-
* make the operator's one filter component produce two shapes.
|
|
22127
|
-
*
|
|
22128
|
-
* A question about the MEMBERS: a group matches when ANY of them does. "Show
|
|
22129
|
-
* me the arrivals with a dog" has to return the group of four people who had
|
|
22130
|
-
* one with them; a filter on the group's own fields could only ever answer
|
|
22131
|
-
* about its primary.
|
|
22132
|
-
*
|
|
22133
|
-
* Matched against all THREE tiers of a member — the class (`animal`), the
|
|
22134
|
-
* sub-class (`dog`) and the instance (a name, a plate) — because which tier a
|
|
22135
|
-
* ticked node lives on is the taxonomy's business, not the caller's. `dog` is
|
|
22136
|
-
* an event kind in the tree and a tier-1 label on an `animal` track; a filter
|
|
22137
|
-
* that read only the class would answer "animal" and never "dog".
|
|
22138
|
-
*
|
|
22139
|
-
* ABSENT or EMPTY means no filter — absent is not a selection of none.
|
|
22140
|
-
*/
|
|
22141
|
-
classes: array(string().min(1).max(60)).max(40).optional()
|
|
22142
|
-
});
|
|
22143
|
-
var RecentSummariesPageSchema = object({
|
|
22144
|
-
/** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
|
|
22145
|
-
* {@link listSummaries} — one list shape, so a card renders the same
|
|
22146
|
-
* whichever feed drew it. */
|
|
22147
|
-
summaries: array(SummaryListItemSchema).readonly(),
|
|
22148
|
-
nextCursor: string().nullable()
|
|
22149
|
-
});
|
|
22150
22243
|
var RecentTracksPageSchema = object({
|
|
22151
22244
|
/** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
|
|
22152
22245
|
tracks: array(TrackSchema).readonly(),
|
|
@@ -22658,7 +22751,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
22658
22751
|
classes: array(string()).optional(),
|
|
22659
22752
|
/** See {@link ExcludeSourcesDoc}. */
|
|
22660
22753
|
excludeSources: array(TrackSourceSchema).optional()
|
|
22661
|
-
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(
|
|
22754
|
+
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
22662
22755
|
kind: "mutation",
|
|
22663
22756
|
auth: "admin"
|
|
22664
22757
|
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
|
|
@@ -23878,6 +23971,23 @@ var MotionSourceEnum = _enum([
|
|
|
23878
23971
|
*/
|
|
23879
23972
|
var MotionSourcesSchema = array(MotionSourceEnum);
|
|
23880
23973
|
/**
|
|
23974
|
+
* The operator-facing name of each MOTION source (D672: `onboard` = the
|
|
23975
|
+
* camera's sensors — its own motion plus any sensor attached to it). The wire
|
|
23976
|
+
* value stays `onboard`; only the label changed. `detectionSources: 'onboard'`
|
|
23977
|
+
* ("Camera onboard AI") and the notification source filter keep their labels.
|
|
23978
|
+
*/
|
|
23979
|
+
var MOTION_SOURCE_LABELS = {
|
|
23980
|
+
analyzer: "Frame-diff Analyzer (motion addon)",
|
|
23981
|
+
onboard: "Camera sensors",
|
|
23982
|
+
"device-activity": "Device activity (the device says it is working)"
|
|
23983
|
+
};
|
|
23984
|
+
/** The order the motion-source picker offers them in (not the enum's order). */
|
|
23985
|
+
var MOTION_SOURCE_PICKER_ORDER = [
|
|
23986
|
+
"analyzer",
|
|
23987
|
+
"onboard",
|
|
23988
|
+
"device-activity"
|
|
23989
|
+
];
|
|
23990
|
+
/**
|
|
23881
23991
|
* Which root detector a camera runs — EXACTLY ONE.
|
|
23882
23992
|
*
|
|
23883
23993
|
* Deliberately the SAME vocabulary post-analysis already tags every detection
|
|
@@ -24224,7 +24334,14 @@ var RunnerCameraConfigSchema = object({
|
|
|
24224
24334
|
*/
|
|
24225
24335
|
inferenceDevices: array(RunnerInferenceDeviceSchema).readonly().optional()
|
|
24226
24336
|
});
|
|
24227
|
-
|
|
24337
|
+
MOTION_SOURCE_PICKER_ORDER.map((value) => value === "onboard" ? {
|
|
24338
|
+
value,
|
|
24339
|
+
label: MOTION_SOURCE_LABELS[value],
|
|
24340
|
+
description: "The camera's own motion, plus any sensor attached to it"
|
|
24341
|
+
} : {
|
|
24342
|
+
value,
|
|
24343
|
+
label: MOTION_SOURCE_LABELS[value]
|
|
24344
|
+
}), motionFpsField.min, motionFpsField.max, motionFpsField.step, motionFpsField.default, detectionFpsField.min, detectionFpsField.max, detectionFpsField.step, detectionFpsField.default, motionCooldownMsField.min, motionCooldownMsField.max, motionCooldownMsField.step, motionCooldownMsField.default, maxSessionHoldMsField.min, maxSessionHoldMsField.max, maxSessionHoldMsField.step, maxSessionHoldMsField.default, audioMotionWindowMsField.min, audioMotionWindowMsField.max, audioMotionWindowMsField.step, audioMotionWindowMsField.default, preRollInferenceFramesField.min, preRollInferenceFramesField.max, preRollInferenceFramesField.step, preRollInferenceFramesField.default, occupancyRecheckSecField.min, occupancyRecheckSecField.max, occupancyRecheckSecField.step, occupancyRecheckSecField.default, occupancyRecheckFramesField.min, occupancyRecheckFramesField.max, occupancyRecheckFramesField.step, occupancyRecheckFramesField.default;
|
|
24228
24345
|
/**
|
|
24229
24346
|
* Runtime load summary returned by `getLocalLoad`. Used by the orchestrator's
|
|
24230
24347
|
* load-balancing levels (L2 capacity-based, L3 hardware-aware) to decide
|
|
@@ -24798,6 +24915,11 @@ var CameraStatusSchema = object({
|
|
|
24798
24915
|
*/
|
|
24799
24916
|
switchedOff: array(CameraSwitchIdSchema).readonly(),
|
|
24800
24917
|
/**
|
|
24918
|
+
* The camera's attached sensors (D672). Optional only for a status composed
|
|
24919
|
+
* by an orchestrator older than D672; this orchestrator always sets it.
|
|
24920
|
+
*/
|
|
24921
|
+
cameraSensors: CameraSensorsStatusSchema.optional(),
|
|
24922
|
+
/**
|
|
24801
24923
|
* Stages of the bounded fan-out that were CUT SHORT — a timeout or a
|
|
24802
24924
|
* rejection — and whose block is therefore `null` because we could not
|
|
24803
24925
|
* READ it, not because there is nothing there.
|
|
@@ -27250,44 +27372,6 @@ DeviceType.Camera, method(object({
|
|
|
27250
27372
|
auth: "protected"
|
|
27251
27373
|
});
|
|
27252
27374
|
/**
|
|
27253
|
-
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
27254
|
-
* valutatore puro del suo FRONTE.
|
|
27255
|
-
*
|
|
27256
|
-
* Viveva dentro il builtin virtual-doorbell
|
|
27257
|
-
* (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
|
|
27258
|
-
* predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
|
|
27259
|
-
* `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
|
|
27260
|
-
* tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
|
|
27261
|
-
* fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
|
|
27262
|
-
* SPOSTA qui e il doorbell la ri-esporta.
|
|
27263
|
-
*
|
|
27264
|
-
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
27265
|
-
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
27266
|
-
* regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
|
|
27267
|
-
* booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
|
|
27268
|
-
*/
|
|
27269
|
-
/**
|
|
27270
|
-
* Known binary / switch source caps → the boolean slice field whose
|
|
27271
|
-
* false→true rise counts as ACTIVE. Every entry is "fire on active".
|
|
27272
|
-
* Sensors whose "active" reading is not a plain boolean (presence's string
|
|
27273
|
-
* state, connectivity's connected flag) are deliberately excluded — a
|
|
27274
|
-
* reconnect is not a doorbell press, and it is not a recording either.
|
|
27275
|
-
*/
|
|
27276
|
-
var SOURCE_CAP_ACTIVE_FIELD = {
|
|
27277
|
-
contact: "entryOpen",
|
|
27278
|
-
binary: "on",
|
|
27279
|
-
switch: "on",
|
|
27280
|
-
motion: "detected",
|
|
27281
|
-
flood: "flooded",
|
|
27282
|
-
gas: "detected",
|
|
27283
|
-
smoke: "detected",
|
|
27284
|
-
"carbon-monoxide": "detected",
|
|
27285
|
-
vibration: "detected",
|
|
27286
|
-
tamper: "tampered"
|
|
27287
|
-
};
|
|
27288
|
-
/** Cap names whose presence in a device's bindings qualify it as a source. */
|
|
27289
|
-
var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
|
|
27290
|
-
/**
|
|
27291
27375
|
* Doorbell button cap. Two kinds of providers coexist behind this cap
|
|
27292
27376
|
* name (same pattern as `snapshot`):
|
|
27293
27377
|
*
|
|
@@ -27360,7 +27444,13 @@ var doorbellCapability = {
|
|
|
27360
27444
|
* push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
|
|
27361
27445
|
* release/duration — it's a pulse.
|
|
27362
27446
|
*/
|
|
27363
|
-
onPressed: {
|
|
27447
|
+
onPressed: {
|
|
27448
|
+
data: DoorbellPressEventSchema,
|
|
27449
|
+
fromState: {
|
|
27450
|
+
field: "lastPressedAt",
|
|
27451
|
+
on: "advance"
|
|
27452
|
+
}
|
|
27453
|
+
} },
|
|
27364
27454
|
status: {
|
|
27365
27455
|
schema: DoorbellStatusSchema,
|
|
27366
27456
|
kind: "push"
|
|
@@ -32257,7 +32347,9 @@ var MotionOnMotionChangedDataSchema = object({
|
|
|
32257
32347
|
detected: boolean(),
|
|
32258
32348
|
timestamp: number(),
|
|
32259
32349
|
source: MotionSourceEnum,
|
|
32260
|
-
regions: array(MotionRegionSchema).readonly().optional()
|
|
32350
|
+
regions: array(MotionRegionSchema).readonly().optional(),
|
|
32351
|
+
/** Who started it (D672). Absent = the camera itself. Only `onboard` carries `sensor`. */
|
|
32352
|
+
triggeredBy: MotionTriggeredBySchema.optional()
|
|
32261
32353
|
});
|
|
32262
32354
|
var motionCapability = {
|
|
32263
32355
|
name: "motion",
|
|
@@ -35247,12 +35339,21 @@ var RecordingStorageUsageSchema = object({
|
|
|
35247
35339
|
*/
|
|
35248
35340
|
devicesRead: _enum(["archive", "unavailable"]).optional(),
|
|
35249
35341
|
/**
|
|
35342
|
+
* Is the `archive` answer WHOLE (D674): the hour ledger loaded without
|
|
35343
|
+
* hitting its row cap, and at least one location resolved? Only then may a
|
|
35344
|
+
* camera ABSENT from `devices[]` be read as "no footage" — and only then is a
|
|
35345
|
+
* row's `oldestMs` the archive's oldest rather than the oldest of a fraction.
|
|
35346
|
+
* OPTIONAL: a recorder predating D674 omits it, and a consumer must read the
|
|
35347
|
+
* absence as `false`. A retention floor is a delete; it never guesses.
|
|
35348
|
+
*/
|
|
35349
|
+
complete: boolean().optional(),
|
|
35350
|
+
/**
|
|
35250
35351
|
* Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
|
|
35251
|
-
* of the payload without it:
|
|
35252
|
-
*
|
|
35253
|
-
*
|
|
35254
|
-
*
|
|
35255
|
-
*
|
|
35352
|
+
* of the payload without it: a recorder that could not build it omits it. A
|
|
35353
|
+
* UI that dereferences it unconditionally crashes the whole Storage page over
|
|
35354
|
+
* a missing disk bar. (The hub's cap routers pass output through unvalidated
|
|
35355
|
+
* since 2026-07-11 — `cap-router-builder.ts` — so a newer recorder's fields
|
|
35356
|
+
* are not stripped by an older hub.)
|
|
35256
35357
|
*/
|
|
35257
35358
|
locations: array(RecordingLocationUsageSchema).optional()
|
|
35258
35359
|
});
|
|
@@ -38771,7 +38872,12 @@ Object.freeze({
|
|
|
38771
38872
|
number: {
|
|
38772
38873
|
kind: "fixed",
|
|
38773
38874
|
result: ["number", "null"]
|
|
38774
|
-
}
|
|
38875
|
+
},
|
|
38876
|
+
rose: {
|
|
38877
|
+
kind: "fixed",
|
|
38878
|
+
result: ["number", "null"]
|
|
38879
|
+
},
|
|
38880
|
+
count: NUMBER
|
|
38775
38881
|
});
|
|
38776
38882
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
38777
38883
|
/**
|
|
@@ -39812,6 +39918,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
39812
39918
|
}];
|
|
39813
39919
|
}
|
|
39814
39920
|
async onShutdown() {
|
|
39921
|
+
this._restoreShuttingDown = true;
|
|
39815
39922
|
this.cancelRestoreRetries();
|
|
39816
39923
|
const devices = await this.ctx.kernel.devices?.getAll() ?? [];
|
|
39817
39924
|
for (const device of devices) try {
|
|
@@ -39929,6 +40036,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
39929
40036
|
};
|
|
39930
40037
|
}
|
|
39931
40038
|
async restoreDevices(savedDevices) {
|
|
40039
|
+
this._restoreShuttingDown = false;
|
|
39932
40040
|
const report = await this.onRestoreDevices(savedDevices);
|
|
39933
40041
|
if (savedDevices.length === 0) return;
|
|
39934
40042
|
if (report && report.failedCount > 0) {
|
|
@@ -39943,8 +40051,13 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
39943
40051
|
/** Retry lane width. See `device-restore-retry.ts` for why retries
|
|
39944
40052
|
* never re-stampede full-width while the initial pass does (D167). */
|
|
39945
40053
|
restoreRetryConcurrency = 4;
|
|
39946
|
-
|
|
40054
|
+
/** Every bounded retry this process started — the default pass's, and each
|
|
40055
|
+
* `restoreWithBoundedRetry` of a provider's own rows. Cancelled together. */
|
|
40056
|
+
_restoreRetrySchedulers = [];
|
|
39947
40057
|
_restoreRetryCompletion = null;
|
|
40058
|
+
/** Set by `onShutdown` BEFORE the retries are cancelled: a restore pass still
|
|
40059
|
+
* in flight must not start a scheduler after the cancel ran. */
|
|
40060
|
+
_restoreShuttingDown = false;
|
|
39948
40061
|
_permanentRestoreFailures = /* @__PURE__ */ new Map();
|
|
39949
40062
|
/** Settles when the background retry rounds finish (or `null` when
|
|
39950
40063
|
* nothing failed). Exposed for tests and subclass diagnostics —
|
|
@@ -40013,8 +40126,8 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
40013
40126
|
});
|
|
40014
40127
|
}
|
|
40015
40128
|
cancelRestoreRetries() {
|
|
40016
|
-
this.
|
|
40017
|
-
this.
|
|
40129
|
+
for (const scheduler of this._restoreRetrySchedulers) scheduler.cancel();
|
|
40130
|
+
this._restoreRetrySchedulers = [];
|
|
40018
40131
|
}
|
|
40019
40132
|
recordPermanentRestoreFailure(failure) {
|
|
40020
40133
|
this._permanentRestoreFailures.set(failure.deviceId, failure);
|
|
@@ -40031,6 +40144,13 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
40031
40144
|
});
|
|
40032
40145
|
}
|
|
40033
40146
|
scheduleRestoreRetries(failures, attempt) {
|
|
40147
|
+
if (this._restoreShuttingDown) {
|
|
40148
|
+
this.ctx.logger.info("Restore retries not scheduled — the provider is shutting down", { meta: {
|
|
40149
|
+
rows: failures.length,
|
|
40150
|
+
deviceIds: failures.map((f) => f.saved.id)
|
|
40151
|
+
} });
|
|
40152
|
+
return;
|
|
40153
|
+
}
|
|
40034
40154
|
const scheduler = new DeviceRestoreRetryScheduler({
|
|
40035
40155
|
logger: this.ctx.logger,
|
|
40036
40156
|
delaysMs: this.restoreRetryDelaysMs,
|
|
@@ -40038,10 +40158,54 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
40038
40158
|
attempt,
|
|
40039
40159
|
onPermanentFailure: (failure) => this.recordPermanentRestoreFailure(failure)
|
|
40040
40160
|
});
|
|
40041
|
-
this.
|
|
40042
|
-
|
|
40161
|
+
this._restoreRetrySchedulers = [...this._restoreRetrySchedulers, scheduler];
|
|
40162
|
+
const run = scheduler.run(failures).then(() => void 0).catch((err) => {
|
|
40043
40163
|
this.ctx.logger.error("Restore retry scheduler crashed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
40044
40164
|
});
|
|
40165
|
+
const prior = this._restoreRetryCompletion;
|
|
40166
|
+
this._restoreRetryCompletion = prior === null ? run : Promise.all([prior, run]).then(() => void 0);
|
|
40167
|
+
}
|
|
40168
|
+
/**
|
|
40169
|
+
* Restore rows the default pass does not handle (a provider's own restore
|
|
40170
|
+
* loop) under the SAME bounded retry (D347): one attempt each, in order; a
|
|
40171
|
+
* row that throws is logged with `tags.deviceId` and retried in the
|
|
40172
|
+
* background rounds, then marked permanently failed like any other.
|
|
40173
|
+
*
|
|
40174
|
+
* A restore is a device build, and a build whose runtime-state load did not
|
|
40175
|
+
* answer is REFUSED rather than started on cold defaults (D677) — so a
|
|
40176
|
+
* transient hub read must cost a retry, never the device until the next
|
|
40177
|
+
* runner restart. An ANSWERED `{}` is the device's true state and never
|
|
40178
|
+
* refuses.
|
|
40179
|
+
*/
|
|
40180
|
+
async restoreWithBoundedRetry(rows, attempt) {
|
|
40181
|
+
const failures = [];
|
|
40182
|
+
for (const saved of rows) {
|
|
40183
|
+
if (this.isUnrestorableShell(saved)) {
|
|
40184
|
+
this.quarantineShell(saved);
|
|
40185
|
+
continue;
|
|
40186
|
+
}
|
|
40187
|
+
try {
|
|
40188
|
+
await attempt(saved);
|
|
40189
|
+
} catch (err) {
|
|
40190
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
40191
|
+
this.ctx.logger.warn("Failed to restore device — bounded retry scheduled", {
|
|
40192
|
+
tags: {
|
|
40193
|
+
deviceId: saved.id,
|
|
40194
|
+
stableId: saved.stableId
|
|
40195
|
+
},
|
|
40196
|
+
meta: {
|
|
40197
|
+
type: saved.type,
|
|
40198
|
+
attempt: 1,
|
|
40199
|
+
error
|
|
40200
|
+
}
|
|
40201
|
+
});
|
|
40202
|
+
failures.push({
|
|
40203
|
+
saved,
|
|
40204
|
+
error
|
|
40205
|
+
});
|
|
40206
|
+
}
|
|
40207
|
+
}
|
|
40208
|
+
if (failures.length > 0) this.scheduleRestoreRetries(failures, attempt);
|
|
40045
40209
|
}
|
|
40046
40210
|
/**
|
|
40047
40211
|
* Tear down and reconstruct ONE device from its persisted rows — the
|
|
@@ -43351,6 +43515,12 @@ Object.freeze({
|
|
|
43351
43515
|
addonId: null,
|
|
43352
43516
|
access: "view"
|
|
43353
43517
|
},
|
|
43518
|
+
"deviceManager.getWrapperActivation": {
|
|
43519
|
+
capName: "device-manager",
|
|
43520
|
+
capScope: "system",
|
|
43521
|
+
addonId: null,
|
|
43522
|
+
access: "view"
|
|
43523
|
+
},
|
|
43354
43524
|
"deviceManager.listAll": {
|
|
43355
43525
|
capName: "device-manager",
|
|
43356
43526
|
capScope: "system",
|
|
@@ -45625,12 +45795,6 @@ Object.freeze({
|
|
|
45625
45795
|
addonId: null,
|
|
45626
45796
|
access: "view"
|
|
45627
45797
|
},
|
|
45628
|
-
"pipelineAnalytics.getSummary": {
|
|
45629
|
-
capName: "pipeline-analytics",
|
|
45630
|
-
capScope: "device",
|
|
45631
|
-
addonId: null,
|
|
45632
|
-
access: "view"
|
|
45633
|
-
},
|
|
45634
45798
|
"pipelineAnalytics.getTrack": {
|
|
45635
45799
|
capName: "pipeline-analytics",
|
|
45636
45800
|
capScope: "device",
|
|
@@ -45691,12 +45855,6 @@ Object.freeze({
|
|
|
45691
45855
|
addonId: null,
|
|
45692
45856
|
access: "view"
|
|
45693
45857
|
},
|
|
45694
|
-
"pipelineAnalytics.listRecentSummaries": {
|
|
45695
|
-
capName: "pipeline-analytics",
|
|
45696
|
-
capScope: "device",
|
|
45697
|
-
addonId: null,
|
|
45698
|
-
access: "view"
|
|
45699
|
-
},
|
|
45700
45858
|
"pipelineAnalytics.listRecentTracks": {
|
|
45701
45859
|
capName: "pipeline-analytics",
|
|
45702
45860
|
capScope: "device",
|
|
@@ -45727,12 +45885,6 @@ Object.freeze({
|
|
|
45727
45885
|
addonId: null,
|
|
45728
45886
|
access: "view"
|
|
45729
45887
|
},
|
|
45730
|
-
"pipelineAnalytics.listSummaries": {
|
|
45731
|
-
capName: "pipeline-analytics",
|
|
45732
|
-
capScope: "device",
|
|
45733
|
-
addonId: null,
|
|
45734
|
-
access: "view"
|
|
45735
|
-
},
|
|
45736
45888
|
"pipelineAnalytics.listTrackMedia": {
|
|
45737
45889
|
capName: "pipeline-analytics",
|
|
45738
45890
|
capScope: "device",
|
|
@@ -49160,6 +49312,11 @@ Object.freeze({
|
|
|
49160
49312
|
form: "single",
|
|
49161
49313
|
optional: false
|
|
49162
49314
|
}],
|
|
49315
|
+
"deviceManager.getWrapperActivation": [{
|
|
49316
|
+
name: "deviceId",
|
|
49317
|
+
form: "single",
|
|
49318
|
+
optional: false
|
|
49319
|
+
}],
|
|
49163
49320
|
"deviceManager.listAll": [{
|
|
49164
49321
|
name: "deviceIds",
|
|
49165
49322
|
form: "array",
|
|
@@ -49897,11 +50054,6 @@ Object.freeze({
|
|
|
49897
50054
|
form: "single",
|
|
49898
50055
|
optional: true
|
|
49899
50056
|
}],
|
|
49900
|
-
"pipelineAnalytics.listRecentSummaries": [{
|
|
49901
|
-
name: "deviceIds",
|
|
49902
|
-
form: "array",
|
|
49903
|
-
optional: false
|
|
49904
|
-
}],
|
|
49905
50057
|
"pipelineAnalytics.listRecentTracks": [{
|
|
49906
50058
|
name: "deviceIds",
|
|
49907
50059
|
form: "array",
|
|
@@ -49912,11 +50064,6 @@ Object.freeze({
|
|
|
49912
50064
|
form: "array",
|
|
49913
50065
|
optional: true
|
|
49914
50066
|
}],
|
|
49915
|
-
"pipelineAnalytics.listSummaries": [{
|
|
49916
|
-
name: "deviceIds",
|
|
49917
|
-
form: "array",
|
|
49918
|
-
optional: false
|
|
49919
|
-
}],
|
|
49920
50067
|
"pipelineAnalytics.listTrackMedia": [{
|
|
49921
50068
|
name: "deviceId",
|
|
49922
50069
|
form: "single",
|