@camstack/addon-agent-ui 1.2.333 → 1.2.334

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 CHANGED
@@ -5849,7 +5849,7 @@ var ZodIssueCode = {
5849
5849
  var ZodFirstPartyTypeKind;
5850
5850
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5851
5851
  //#endregion
5852
- //#region ../types/dist/sleep-BX_MqzUe.mjs
5852
+ //#region ../types/dist/sleep-CmjWurK5.mjs
5853
5853
  /**
5854
5854
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5855
5855
  * window to float samples (D455).
@@ -6526,6 +6526,250 @@ async function runAddonMigrations(host, migrations, logger, options = {}) {
6526
6526
  blockOwner
6527
6527
  };
6528
6528
  }
6529
+ /** The record calls the one-switch migration makes, over the migration API. */
6530
+ function switchRecordsOverMigrationApi(api) {
6531
+ return { kind: (definition) => {
6532
+ const handle = api.records.kind(definition);
6533
+ return {
6534
+ get: (id) => handle.get({ id }),
6535
+ put: (input, opts) => handle.put({
6536
+ ...input,
6537
+ ...opts?.ifRev === void 0 ? {} : { ifRev: opts.ifRev }
6538
+ }),
6539
+ patch: (id, partial) => handle.patch({
6540
+ id,
6541
+ partial
6542
+ })
6543
+ };
6544
+ } };
6545
+ }
6546
+ function integrationRowsApiOver(api) {
6547
+ return {
6548
+ list: async () => (await api.integrations.list({})).integrations,
6549
+ readLegacyIntegrationRows: (input) => api.legacyRows.read(input),
6550
+ clearLegacyIntegrationRows: (input) => api.legacyRows.clear(input)
6551
+ };
6552
+ }
6553
+ object({
6554
+ cap: string().min(1),
6555
+ kind: string().min(1).optional()
6556
+ });
6557
+ var GLOBAL_SCOPE = { kind: "global" };
6558
+ /** The scope every addon-wide (not per-node, not per-device) setting lives in. */
6559
+ function globalSecretScope() {
6560
+ return GLOBAL_SCOPE;
6561
+ }
6562
+ function encodeSegment(value) {
6563
+ return encodeURIComponent(value);
6564
+ }
6565
+ function decodeSegment(value) {
6566
+ try {
6567
+ return decodeURIComponent(value);
6568
+ } catch {
6569
+ return null;
6570
+ }
6571
+ }
6572
+ /**
6573
+ * The field path of a value inside an array item, addressed by the item's id:
6574
+ * `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
6575
+ * `providers[authentik].clientSecret`.
6576
+ */
6577
+ function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
6578
+ return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
6579
+ }
6580
+ function parseScope(segments) {
6581
+ switch (segments[0]) {
6582
+ case "global": return {
6583
+ scope: GLOBAL_SCOPE,
6584
+ used: 1
6585
+ };
6586
+ case "node": {
6587
+ const nodeId = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
6588
+ return nodeId === null || nodeId.length === 0 ? null : {
6589
+ scope: {
6590
+ kind: "node",
6591
+ nodeId
6592
+ },
6593
+ used: 2
6594
+ };
6595
+ }
6596
+ case "device": {
6597
+ const raw = segments[1];
6598
+ if (raw === void 0 || !/^\d+$/.test(raw)) return null;
6599
+ return {
6600
+ scope: {
6601
+ kind: "device",
6602
+ deviceId: Number(raw)
6603
+ },
6604
+ used: 2
6605
+ };
6606
+ }
6607
+ case "integration": {
6608
+ const id = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
6609
+ return id === null || id.length === 0 ? null : {
6610
+ scope: {
6611
+ kind: "integration",
6612
+ integrationId: id
6613
+ },
6614
+ used: 2
6615
+ };
6616
+ }
6617
+ case "row": {
6618
+ const collection = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
6619
+ const rowId = segments[2] !== void 0 ? decodeSegment(segments[2]) : null;
6620
+ if (collection === null || rowId === null || collection.length === 0 || rowId.length === 0) return null;
6621
+ return {
6622
+ scope: {
6623
+ kind: "row",
6624
+ collection,
6625
+ rowId
6626
+ },
6627
+ used: 3
6628
+ };
6629
+ }
6630
+ default: return null;
6631
+ }
6632
+ }
6633
+ /** Take a ref apart; `null` when it is not a well-formed ref. */
6634
+ function parseSecretRef(ref) {
6635
+ if (!ref.startsWith("secret://")) return null;
6636
+ const segments = ref.slice(9).split("/");
6637
+ const owner = segments[0] !== void 0 ? decodeSegment(segments[0]) : null;
6638
+ if (owner === null || owner.length === 0) return null;
6639
+ const parsed = parseScope(segments.slice(1));
6640
+ if (parsed === null) return null;
6641
+ const fieldPath = segments.slice(1 + parsed.used).join("/");
6642
+ if (fieldPath.length === 0) return null;
6643
+ return {
6644
+ owner,
6645
+ scope: parsed.scope,
6646
+ fieldPath
6647
+ };
6648
+ }
6649
+ /** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
6650
+ /** The files a kind may live in. `history` is device-history's (D850), never records. */
6651
+ var RECORD_DATABASES = ["settings", "events"];
6652
+ /** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
6653
+ var RECORD_CLASSES = [
6654
+ "config",
6655
+ "ledger",
6656
+ "audit",
6657
+ "mirror",
6658
+ "ephemeral"
6659
+ ];
6660
+ var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
6661
+ var RECORD_MAX_DATA_BYTES = 1024 * 1024;
6662
+ var RECORD_BATCH_MAX = 1e3;
6663
+ var RECORD_QUERY_MAX = 1e3;
6664
+ /** The body key of the switch; only {@link RecordIntegrationInstances.setInstanceEnabled} writes it. */
6665
+ var SWITCH_KEY = "enabled";
6666
+ /**
6667
+ * The one-switch migration (epic #440, step 3). An integration's on/off state used to live in the
6668
+ * registry's `enabled` column for a device addon; it now lives only in the instance record's
6669
+ * body (`enabled`, D1007 superseded). This moves a registry `enabled = false` into the body, once.
6670
+ *
6671
+ * For every registry row of the addon that is OFF:
6672
+ * - no record under its id: the record is created from `initialBody(name)` with `enabled: false`
6673
+ * (a default integration that never had settings, Reolink's among them);
6674
+ * - a record whose body states no `enabled`: `enabled: false` is written into it;
6675
+ * - a record that already states `enabled` (either way) is the newer truth and is left alone.
6676
+ * A row that is ON writes nothing: an absent field reads as on. A rerun finds every off row with
6677
+ * a stated switch and does nothing, so nothing switched off comes back on and nothing switched on
6678
+ * goes off. The registry column itself is neither read afterwards nor written; its drop is step 5.
6679
+ *
6680
+ * It runs in the addon process, BEFORE the addon registers its `integration-instances` provider,
6681
+ * which is also why `integrations.list` still answers the stored column at that point.
6682
+ */
6683
+ function registrySwitchToBodyId(addonId, date) {
6684
+ return `${addonId}/${date}-registry-switch-to-body`;
6685
+ }
6686
+ async function moveRegistrySwitchToBody(ports, spec) {
6687
+ const rows = await ports.listIntegrations(spec.addonId);
6688
+ let created = 0;
6689
+ let switched = 0;
6690
+ let kept = 0;
6691
+ for (const row of rows) {
6692
+ if (row.enabled) continue;
6693
+ const body = await ports.readBody(row.id);
6694
+ if (body === null) {
6695
+ if (await ports.createRecord(row.id, {
6696
+ ...spec.initialBody(row.name),
6697
+ ["enabled"]: false
6698
+ }) === "created") created += 1;
6699
+ else kept += 1;
6700
+ continue;
6701
+ }
6702
+ if ("enabled" in body) {
6703
+ kept += 1;
6704
+ continue;
6705
+ }
6706
+ await ports.switchOff(row.id);
6707
+ switched += 1;
6708
+ }
6709
+ const summary = {
6710
+ integrations: rows.length,
6711
+ off: rows.filter((r) => !r.enabled).length,
6712
+ created,
6713
+ switched,
6714
+ kept
6715
+ };
6716
+ return created === 0 && switched === 0 ? {
6717
+ outcome: "already-done",
6718
+ summary
6719
+ } : {
6720
+ outcome: "done",
6721
+ summary
6722
+ };
6723
+ }
6724
+ /** A migration entry: an addon states its kind and how a new body starts, nothing else. */
6725
+ function registrySwitchToBodyMigration(spec) {
6726
+ return {
6727
+ meta: {
6728
+ id: registrySwitchToBodyId(spec.addonId, spec.date),
6729
+ owner: spec.addonId,
6730
+ level: "addon-state",
6731
+ seq: spec.seq,
6732
+ dependsOn: [],
6733
+ store: "settings",
6734
+ scope: "cluster",
6735
+ onFailure: "block-owner",
6736
+ bodyKind: "procedure",
6737
+ description: "Move the registry enabled flag of every switched-off integration into its instance record body, creating the record when there is none",
6738
+ fixtureMark: "enabled"
6739
+ },
6740
+ procedure: (ports) => moveRegistrySwitchToBody(ports, {
6741
+ addonId: spec.addonId,
6742
+ initialBody: spec.initialBody
6743
+ })
6744
+ };
6745
+ }
6746
+ /** The hub and records sides of {@link registrySwitchToBodyMigration}. */
6747
+ function registrySwitchPortsOver(deps) {
6748
+ const handle = deps.records.kind({
6749
+ name: deps.kind.name,
6750
+ declaration: deps.kind.declaration,
6751
+ schema: record(string(), unknown())
6752
+ });
6753
+ return {
6754
+ listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
6755
+ id: i.id,
6756
+ name: i.name,
6757
+ enabled: i.enabled !== false
6758
+ })),
6759
+ readBody: async (id) => (await handle.get(id))?.data ?? null,
6760
+ createRecord: async (id, body) => {
6761
+ if (await handle.get(id) !== null) return "exists";
6762
+ await handle.put({
6763
+ id,
6764
+ data: body
6765
+ }, { ifRev: 0 });
6766
+ return "created";
6767
+ },
6768
+ switchOff: async (id) => {
6769
+ await handle.patch(id, { [SWITCH_KEY]: false });
6770
+ }
6771
+ };
6772
+ }
6529
6773
  /**
6530
6774
  * Build a {@link DurableState} over a single store key. Transport-agnostic:
6531
6775
  * `read`/`write` are the addon-store or device-store accessors. The whole
@@ -6657,98 +6901,6 @@ var SCOPED_SETTINGS_SCOPES = [
6657
6901
  "device-defaults",
6658
6902
  "node"
6659
6903
  ];
6660
- var GLOBAL_SCOPE = { kind: "global" };
6661
- /** The scope every addon-wide (not per-node, not per-device) setting lives in. */
6662
- function globalSecretScope() {
6663
- return GLOBAL_SCOPE;
6664
- }
6665
- function encodeSegment(value) {
6666
- return encodeURIComponent(value);
6667
- }
6668
- function decodeSegment(value) {
6669
- try {
6670
- return decodeURIComponent(value);
6671
- } catch {
6672
- return null;
6673
- }
6674
- }
6675
- /**
6676
- * The field path of a value inside an array item, addressed by the item's id:
6677
- * `arrayItemFieldPath('providers', 'authentik', 'clientSecret')` →
6678
- * `providers[authentik].clientSecret`.
6679
- */
6680
- function arrayItemFieldPath(arrayKey, itemId, fieldKey) {
6681
- return `${arrayKey}[${encodeSegment(itemId)}].${fieldKey}`;
6682
- }
6683
- function parseScope(segments) {
6684
- switch (segments[0]) {
6685
- case "global": return {
6686
- scope: GLOBAL_SCOPE,
6687
- used: 1
6688
- };
6689
- case "node": {
6690
- const nodeId = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
6691
- return nodeId === null || nodeId.length === 0 ? null : {
6692
- scope: {
6693
- kind: "node",
6694
- nodeId
6695
- },
6696
- used: 2
6697
- };
6698
- }
6699
- case "device": {
6700
- const raw = segments[1];
6701
- if (raw === void 0 || !/^\d+$/.test(raw)) return null;
6702
- return {
6703
- scope: {
6704
- kind: "device",
6705
- deviceId: Number(raw)
6706
- },
6707
- used: 2
6708
- };
6709
- }
6710
- case "integration": {
6711
- const id = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
6712
- return id === null || id.length === 0 ? null : {
6713
- scope: {
6714
- kind: "integration",
6715
- integrationId: id
6716
- },
6717
- used: 2
6718
- };
6719
- }
6720
- case "row": {
6721
- const collection = segments[1] !== void 0 ? decodeSegment(segments[1]) : null;
6722
- const rowId = segments[2] !== void 0 ? decodeSegment(segments[2]) : null;
6723
- if (collection === null || rowId === null || collection.length === 0 || rowId.length === 0) return null;
6724
- return {
6725
- scope: {
6726
- kind: "row",
6727
- collection,
6728
- rowId
6729
- },
6730
- used: 3
6731
- };
6732
- }
6733
- default: return null;
6734
- }
6735
- }
6736
- /** Take a ref apart; `null` when it is not a well-formed ref. */
6737
- function parseSecretRef(ref) {
6738
- if (!ref.startsWith("secret://")) return null;
6739
- const segments = ref.slice(9).split("/");
6740
- const owner = segments[0] !== void 0 ? decodeSegment(segments[0]) : null;
6741
- if (owner === null || owner.length === 0) return null;
6742
- const parsed = parseScope(segments.slice(1));
6743
- if (parsed === null) return null;
6744
- const fieldPath = segments.slice(1 + parsed.used).join("/");
6745
- if (fieldPath.length === 0) return null;
6746
- return {
6747
- owner,
6748
- scope: parsed.scope,
6749
- fieldPath
6750
- };
6751
- }
6752
6904
  /** A write to a secret path could not be stored in the keychain, so it was refused. */
6753
6905
  var KeychainWriteRefusedError = class extends Error {
6754
6906
  fieldPath;
@@ -6759,7 +6911,7 @@ var KeychainWriteRefusedError = class extends Error {
6759
6911
  }
6760
6912
  };
6761
6913
  var KEEP = { kind: "keep" };
6762
- function isRecord$6(value) {
6914
+ function isRecord$4(value) {
6763
6915
  return typeof value === "object" && value !== null && !Array.isArray(value);
6764
6916
  }
6765
6917
  function defaultMintId() {
@@ -6772,7 +6924,7 @@ function collectSecretMarkerRefs(value, out = []) {
6772
6924
  return out;
6773
6925
  }
6774
6926
  if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
6775
- else if (isRecord$6(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6927
+ else if (isRecord$4(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6776
6928
  return out;
6777
6929
  }
6778
6930
  async function storedValueEquals(secrets, marker, candidate) {
@@ -6827,7 +6979,7 @@ function rowId(row, idKey) {
6827
6979
  return null;
6828
6980
  }
6829
6981
  async function writeArray(ctx, node, incoming, stored, fieldPath) {
6830
- const storedRows = Array.isArray(stored) ? stored.filter(isRecord$6) : [];
6982
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord$4) : [];
6831
6983
  if (!Array.isArray(incoming)) {
6832
6984
  ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
6833
6985
  return {
@@ -6843,7 +6995,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
6843
6995
  const kept = /* @__PURE__ */ new Set();
6844
6996
  const rows = [];
6845
6997
  for (const row of incoming) {
6846
- if (!isRecord$6(row)) {
6998
+ if (!isRecord$4(row)) {
6847
6999
  rows.push(row);
6848
7000
  continue;
6849
7001
  }
@@ -6868,7 +7020,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
6868
7020
  case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
6869
7021
  case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
6870
7022
  case "object": {
6871
- if (!isRecord$6(incoming)) {
7023
+ if (!isRecord$4(incoming)) {
6872
7024
  if (incoming === void 0) return KEEP;
6873
7025
  ctx.deletes.push(...collectSecretMarkerRefs(stored));
6874
7026
  return {
@@ -6876,7 +7028,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
6876
7028
  value: incoming
6877
7029
  };
6878
7030
  }
6879
- const storedObj = isRecord$6(stored) ? stored : {};
7031
+ const storedObj = isRecord$4(stored) ? stored : {};
6880
7032
  return {
6881
7033
  kind: "value",
6882
7034
  value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
@@ -6999,7 +7151,7 @@ async function resolveSecretMarkers(value, secrets, options = {}) {
6999
7151
  value: items
7000
7152
  };
7001
7153
  }
7002
- if (isRecord$6(v)) {
7154
+ if (isRecord$4(v)) {
7003
7155
  const out = {};
7004
7156
  for (const [k, child] of Object.entries(v)) {
7005
7157
  const s = substitute(child);
@@ -7031,7 +7183,7 @@ var SecretOverwriteRefusedError = class extends Error {
7031
7183
  this.name = "SecretOverwriteRefusedError";
7032
7184
  }
7033
7185
  };
7034
- function isRecord$5(value) {
7186
+ function isRecord$3(value) {
7035
7187
  return typeof value === "object" && value !== null && !Array.isArray(value);
7036
7188
  }
7037
7189
  function resolvedValue(pass, marker) {
@@ -7060,7 +7212,7 @@ function inArray(pass, incoming, stored) {
7060
7212
  return incoming.map((item, index) => {
7061
7213
  const twin = stored[index];
7062
7214
  if (isSecretMarker(twin)) return typeof item === "string" && item === resolvedValue(pass, twin) ? twin : item;
7063
- if (isRecord$5(item) && isRecord$5(twin)) return inArrayRow(pass, item, twin);
7215
+ if (isRecord$3(item) && isRecord$3(twin)) return inArrayRow(pass, item, twin);
7064
7216
  if (Array.isArray(item) && Array.isArray(twin)) return inArray(pass, item, twin);
7065
7217
  return item;
7066
7218
  });
@@ -7071,7 +7223,7 @@ function inArrayRow(pass, incoming, stored) {
7071
7223
  const twin = stored[key];
7072
7224
  if (isSecretMarker(twin)) {
7073
7225
  if (typeof value === "string" && value === resolvedValue(pass, twin)) out[key] = twin;
7074
- } else if (isRecord$5(value) && isRecord$5(twin)) out[key] = inArrayRow(pass, value, twin);
7226
+ } else if (isRecord$3(value) && isRecord$3(twin)) out[key] = inArrayRow(pass, value, twin);
7075
7227
  else if (Array.isArray(value) && Array.isArray(twin)) out[key] = inArray(pass, value, twin);
7076
7228
  }
7077
7229
  return out;
@@ -7079,7 +7231,7 @@ function inArrayRow(pass, incoming, stored) {
7079
7231
  async function walk(pass, incoming, stored) {
7080
7232
  if (isSecretMarker(stored)) return atMarker(pass, incoming, stored);
7081
7233
  if (Array.isArray(incoming) && Array.isArray(stored)) return inArray(pass, incoming, stored);
7082
- if (!isRecord$5(incoming) || !isRecord$5(stored)) return incoming;
7234
+ if (!isRecord$3(incoming) || !isRecord$3(stored)) return incoming;
7083
7235
  const out = { ...incoming };
7084
7236
  for (const [key, value] of Object.entries(incoming)) out[key] = await walk(pass, value, stored[key]);
7085
7237
  return out;
@@ -7110,7 +7262,7 @@ async function restoreUnchangedSecrets(input) {
7110
7262
  function isJsonEqual(a, b) {
7111
7263
  if (Object.is(a, b)) return true;
7112
7264
  if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((v, i) => isJsonEqual(v, b[i]));
7113
- if (isRecord$5(a) && isRecord$5(b)) {
7265
+ if (isRecord$3(a) && isRecord$3(b)) {
7114
7266
  const keys = Object.keys(a);
7115
7267
  if (keys.length !== Object.keys(b).length) return false;
7116
7268
  return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isJsonEqual(a[k], b[k]));
@@ -7134,7 +7286,7 @@ function markerKeysOf(blob, owner) {
7134
7286
  }
7135
7287
  function nodeFromStored(value, owner) {
7136
7288
  if (isSecretMarker(value)) return isSecretRefOwnedBy(value.$secret, owner) ? { kind: "leaf" } : null;
7137
- if (!isRecord$5(value)) return null;
7289
+ if (!isRecord$3(value)) return null;
7138
7290
  const children = /* @__PURE__ */ new Map();
7139
7291
  for (const [key, child] of Object.entries(value)) {
7140
7292
  const node = nodeFromStored(child, owner);
@@ -7631,13 +7783,48 @@ var BaseAddon = class {
7631
7783
  if (api === void 0) throw new Error(`${this.ctx.id}: the migration API is not available on this context`);
7632
7784
  return this.runBound(sets.flatMap((set) => set.bind(api)), options);
7633
7785
  }
7634
- async runBound(migrations, options) {
7786
+ /**
7787
+ * A device addon states its instance kind once, before its migrations run, and the one-switch
7788
+ * migration (registry `enabled` to the instance body, epic #440 step 3) joins whatever
7789
+ * catalogue the addon runs, in the SAME declaration (the engine replaces an owner's catalogue
7790
+ * on every declare).
7791
+ */
7792
+ declareRegistrySwitch(spec) {
7793
+ this.registrySwitchSpec = spec;
7794
+ }
7795
+ registrySwitchSpec = null;
7796
+ registrySwitchBound() {
7797
+ const spec = this.registrySwitchSpec;
7798
+ if (spec === null) return [];
7799
+ const api = this.ctx.migrationApi;
7800
+ if (api === void 0) {
7801
+ this.ctx.logger.warn("the registry switch migration is skipped: the migration API is not available on this context");
7802
+ return [];
7803
+ }
7804
+ return migrationSet([registrySwitchToBodyMigration({
7805
+ addonId: this.ctx.id,
7806
+ date: "2026-11-06",
7807
+ seq: 1e3,
7808
+ initialBody: spec.initialBody
7809
+ })], (a) => registrySwitchPortsOver({
7810
+ api: integrationRowsApiOver(a),
7811
+ records: switchRecordsOverMigrationApi(a),
7812
+ kind: spec.kind
7813
+ })).bind(api);
7814
+ }
7815
+ async runBound(declared, options) {
7816
+ const added = this.registrySwitchBound();
7817
+ const migrations = [...declared, ...added];
7818
+ const phased = options?.only === void 0 || added.length === 0 ? options : {
7819
+ ...options,
7820
+ only: [...options.only, ...added.map((m) => m.meta.id)]
7821
+ };
7635
7822
  const api = this.ctx.api.migrations;
7636
7823
  const report = await runAddonMigrations({
7637
7824
  declareOwner: (input) => api.declareOwner.mutate(input),
7638
7825
  startMigration: (input) => api.startMigration.mutate(input),
7639
7826
  finishMigration: (input) => api.finishMigration.mutate(input)
7640
- }, migrations, this.ctx.logger, options);
7827
+ }, migrations, this.ctx.logger, phased);
7641
7828
  if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7642
7829
  return report;
7643
7830
  }
@@ -8710,21 +8897,6 @@ function normalizeAddonInitResult(result) {
8710
8897
  if (Array.isArray(result)) return { providers: result };
8711
8898
  return result;
8712
8899
  }
8713
- /** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
8714
- /** The files a kind may live in. `history` is device-history's (D850), never records. */
8715
- var RECORD_DATABASES = ["settings", "events"];
8716
- /** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
8717
- var RECORD_CLASSES = [
8718
- "config",
8719
- "ledger",
8720
- "audit",
8721
- "mirror",
8722
- "ephemeral"
8723
- ];
8724
- var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
8725
- var RECORD_MAX_DATA_BYTES = 1024 * 1024;
8726
- var RECORD_BATCH_MAX = 1e3;
8727
- var RECORD_QUERY_MAX = 1e3;
8728
8900
  /**
8729
8901
  * The share-view scope kinds a share-link token can carry. The hub mints
8730
8902
  * and validates them (`ShareViewGrantSchema` builds its
@@ -8815,20 +8987,6 @@ var RecordKindDeclarationSchema = object({
8815
8987
  }).strict();
8816
8988
  /** The kind names: lower-case, dash separated, short enough for an index name. */
8817
8989
  var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
8818
- /**
8819
- * Entry kinds (D881): every list of user-created entries (notification
8820
- * targets, brokers, LLM profiles) is ONE record kind with an id, a name and a
8821
- * kind-specific body, and the admin UI lists/adds/edits/deletes all of them
8822
- * through the `record-entries` capability with password fields derived from
8823
- * the schema (`.meta({ secret: true })`).
8824
- */
8825
- /** The body of a plain entry: a name and the fields the kind adds. */
8826
- function entryBody(shape) {
8827
- return object({
8828
- name: string().min(1),
8829
- ...shape
8830
- });
8831
- }
8832
8990
  object({
8833
8991
  field: string().min(1),
8834
8992
  on: literal("advance")
@@ -10660,6 +10818,10 @@ Object.freeze({
10660
10818
  "all",
10661
10819
  "one"
10662
10820
  ]
10821
+ },
10822
+ {
10823
+ "path": "positionUpdatedAt",
10824
+ "valueType": "number"
10663
10825
  }
10664
10826
  ],
10665
10827
  "meter": [{
@@ -10914,22 +11076,6 @@ Object.freeze({
10914
11076
  "path": "pumpRunning",
10915
11077
  "valueType": "boolean"
10916
11078
  },
10917
- {
10918
- "path": "lastDrink",
10919
- "valueType": "number"
10920
- },
10921
- {
10922
- "path": "currentHourDrink",
10923
- "valueType": "number"
10924
- },
10925
- {
10926
- "path": "last24hDrink",
10927
- "valueType": "number"
10928
- },
10929
- {
10930
- "path": "todayDrink",
10931
- "valueType": "number"
10932
- },
10933
11079
  {
10934
11080
  "path": "error",
10935
11081
  "valueType": "string"
@@ -11495,6 +11641,60 @@ Object.freeze({
11495
11641
  }
11496
11642
  ]
11497
11643
  });
11644
+ Object.freeze({
11645
+ "actions": { "run": 363e4 },
11646
+ "audio-analyzer": { "classify": 3e4 },
11647
+ "custom-model-registry": { "importModelBundle": 18e5 },
11648
+ "database-snapshot": { "writeSnapshot": 18e5 },
11649
+ "device-manager": {
11650
+ "migrateDevice": 72e4,
11651
+ "runDiscoverySweep": 12e4
11652
+ },
11653
+ "device-provider": { "reloadDevice": 18e4 },
11654
+ "llm": {
11655
+ "generate": 6e5,
11656
+ "generateVision": 6e5,
11657
+ "testProfile": 6e5
11658
+ },
11659
+ "llm-runtime": {
11660
+ "complete": 6e5,
11661
+ "completeApple": 6e5,
11662
+ "ensureStarted": 6e5,
11663
+ "installModel": 36e5
11664
+ },
11665
+ "local-network": { "scanNetwork": 9e4 },
11666
+ "model-convert": { "convert": 6e5 },
11667
+ "pipeline-analytics": {
11668
+ "addRetrainFrameFromRecording": 6e4,
11669
+ "offerRetrainArchive": 12e4,
11670
+ "proposeRetrainAnnotationsWithLlm": 18e4,
11671
+ "resolveRetrainTrainingCorpus": 6e4,
11672
+ "writeRetrainArchive": 36e5
11673
+ },
11674
+ "server-management": { "applyServerUpdate": 96e4 },
11675
+ "terminal-session": { "pullOutput": 15e3 },
11676
+ "training": {
11677
+ "canaryResult": 12e4,
11678
+ "cancelJob": 12e4,
11679
+ "planJob": 6e4,
11680
+ "registerResult": 18e5,
11681
+ "rollbackCanary": 12e4,
11682
+ "startJob": 12e4,
11683
+ "startJobBatch": 12e4
11684
+ },
11685
+ "training-provider": {
11686
+ "cancelJob": 6e4,
11687
+ "cleanupJob": 3e5,
11688
+ "getJob": 6e4,
11689
+ "listJobEvents": 6e4,
11690
+ "listJobs": 12e4,
11691
+ "offerArtifact": 3e5,
11692
+ "readResult": 3e5,
11693
+ "submitJob": 18e5,
11694
+ "uploadDataset": 72e5,
11695
+ "validateCredentials": 3e4
11696
+ }
11697
+ });
11498
11698
  object({
11499
11699
  code: string().regex(/^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/),
11500
11700
  params: record(string(), union([string(), number()])).optional()
@@ -12847,10 +13047,6 @@ var IntegrationInstanceRefSchema = object({
12847
13047
  ]),
12848
13048
  candidateStableId: string().optional()
12849
13049
  });
12850
- object({
12851
- cap: string().min(1),
12852
- kind: string().min(1).optional()
12853
- });
12854
13050
  var EncodeProfileSchema = object({
12855
13051
  video: object({
12856
13052
  codec: _enum([
@@ -19025,31 +19221,21 @@ var APPLIANCE_CAP_VOCABULARY = {
19025
19221
  "pet-fountain": {
19026
19222
  scope: "device",
19027
19223
  noun: t("Pet fountain", "Fontana per animali"),
19028
- actions: [
19029
- {
19030
- id: "petFountain.pumpOn",
19031
- method: "setPump",
19032
- label: t("Pump on", "Pompa accesa"),
19033
- phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
19034
- icon: "play",
19035
- args: { on: true }
19036
- },
19037
- {
19038
- id: "petFountain.pumpOff",
19039
- method: "setPump",
19040
- label: t("Pump off", "Pompa spenta"),
19041
- phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
19042
- icon: "square",
19043
- args: { on: false }
19044
- },
19045
- {
19046
- id: "petFountain.resetDailyConsumption",
19047
- method: "resetDailyConsumption",
19048
- label: t("Clear daily consumption", "Azzera il consumo giornaliero"),
19049
- phrase: t("Clear the daily water consumption of {device}", "Azzera il consumo giornaliero di acqua di {device}"),
19050
- icon: "rotate-ccw"
19051
- }
19052
- ],
19224
+ actions: [{
19225
+ id: "petFountain.pumpOn",
19226
+ method: "setPump",
19227
+ label: t("Pump on", "Pompa accesa"),
19228
+ phrase: t("Start the pump of {device}", "Avvia la pompa di {device}"),
19229
+ icon: "play",
19230
+ args: { on: true }
19231
+ }, {
19232
+ id: "petFountain.pumpOff",
19233
+ method: "setPump",
19234
+ label: t("Pump off", "Pompa spenta"),
19235
+ phrase: t("Stop the pump of {device}", "Ferma la pompa di {device}"),
19236
+ icon: "square",
19237
+ args: { on: false }
19238
+ }],
19053
19239
  state: [{
19054
19240
  kind: "boolean",
19055
19241
  field: "empty",
@@ -42943,8 +43129,15 @@ var ActionInputSchema = object({
42943
43129
  var ActionRunOriginSchema = object({
42944
43130
  /** The automation block whose step started it. */
42945
43131
  blockId: string().min(1).optional() });
42946
- /** `started` = the caller did not ask to wait. */
42947
- var ActionRunOutcomeSchema = union([AutomationRunOutcomeSchema, literal("started")]);
43132
+ /**
43133
+ * `started` = the caller did not ask to wait. `timed-out` = the run reached the
43134
+ * ceiling (`ACTION_RUN_MAX_MS`) and was stopped; it is never a `ran`.
43135
+ */
43136
+ var ActionRunOutcomeSchema = union([
43137
+ AutomationRunOutcomeSchema,
43138
+ literal("started"),
43139
+ literal("timed-out")
43140
+ ]);
42948
43141
  var ActionRunResultSchema = object({
42949
43142
  runId: string().min(1),
42950
43143
  outcome: ActionRunOutcomeSchema,
@@ -43466,6 +43659,8 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
43466
43659
  kind: "mutation",
43467
43660
  auth: "admin"
43468
43661
  });
43662
+ /** `timeoutMs` of `actions.run`: a waited call outlives any legal run. */
43663
+ var ACTION_RUN_TIMEOUT_MS = 363e4;
43469
43664
  /**
43470
43665
  * actions — the ONE surface for runnable actions, for the UI and for
43471
43666
  * automations. Provided by the core-blocks builtin on the hub.
@@ -43484,6 +43679,12 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
43484
43679
  * An admin, and an in-process caller (an automation block), may run any action.
43485
43680
  * Every refusal is typed (`[not-linked] …`) and logged.
43486
43681
  *
43682
+ * **A run lasts at most `ACTION_RUN_MAX_MS` (60 min).** The provider stops a
43683
+ * run that reaches it and answers `timed-out` (never `ran`); an action whose
43684
+ * `wait` steps alone add up to more is refused at create/update
43685
+ * (`[invalid-steps]`). `run` declares a `timeoutMs` above that ceiling, so a
43686
+ * waited call (an automation step) is never cut by the 60 s transport default.
43687
+ *
43487
43688
  * A second `run` of an action that is already running is REFUSED — traced and
43488
43689
  * logged — never queued.
43489
43690
  */
@@ -43510,7 +43711,8 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
43510
43711
  role: "viewer",
43511
43712
  auth: "protected",
43512
43713
  kind: "mutation",
43513
- caller: "required"
43714
+ caller: "required",
43715
+ timeoutMs: ACTION_RUN_TIMEOUT_MS
43514
43716
  }), method(ActionIdInput, _void(), {
43515
43717
  kind: "mutation",
43516
43718
  auth: "admin"
@@ -47527,6 +47729,9 @@ var MediaPlayerStatusSchema = object({
47527
47729
  /** Repeat mode. Null when no repeat surface.
47528
47730
  * Pair with `DeviceFeature.MediaPlayerRepeat`. */
47529
47731
  repeat: MediaPlayerRepeatSchema.nullable(),
47732
+ /** Ms epoch when `position` was read from the device. Null when the driver
47733
+ * does not say; a UI that advances the position while playing counts from it. */
47734
+ positionUpdatedAt: number().nullable().default(null),
47530
47735
  /** Ms epoch when the slice was last updated. */
47531
47736
  lastChangedAt: number()
47532
47737
  });
@@ -53061,6 +53266,7 @@ var IntegrationWithStateSchema = object({
53061
53266
  id: string(),
53062
53267
  addonId: string(),
53063
53268
  name: string(),
53269
+ /** A projection of the instance body's switch (`setInstanceEnabled` is the only writer). */
53064
53270
  enabled: boolean(),
53065
53271
  info: record(string(), unknown()).readonly(),
53066
53272
  /** The discovery candidate this integration was set up from (D984). */
@@ -53073,6 +53279,7 @@ var IntegrationLiteSchema = object({
53073
53279
  id: string(),
53074
53280
  addonId: string(),
53075
53281
  name: string(),
53282
+ /** A projection of the instance body's switch (`setInstanceEnabled` is the only writer). */
53076
53283
  enabled: boolean(),
53077
53284
  info: record(string(), unknown()).readonly(),
53078
53285
  /** The discovery candidate this integration was set up from (D984). */
@@ -53172,7 +53379,6 @@ var TestConnectionResultSchema = object({
53172
53379
  var CreateIntegrationInputSchema = object({
53173
53380
  addonId: string(),
53174
53381
  name: string(),
53175
- enabled: boolean().optional(),
53176
53382
  info: record(string(), unknown()).optional(),
53177
53383
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
53178
53384
  originStableId: string().optional(),
@@ -53181,6 +53387,11 @@ var CreateIntegrationInputSchema = object({
53181
53387
  var UpdateIntegrationInputSchema = object({
53182
53388
  id: string(),
53183
53389
  name: string().optional(),
53390
+ /**
53391
+ * Accepted for a FIXED builtin only (`info.fixed`: alarm panel, camera grid, core blocks,
53392
+ * terminal), which keeps no instance record: the registry flag is its one store. Any other
53393
+ * integration is switched through `integration-instances.setInstanceEnabled`.
53394
+ */
53184
53395
  enabled: boolean().optional(),
53185
53396
  info: record(string(), unknown()).optional()
53186
53397
  });
@@ -53228,7 +53439,7 @@ var IntegrationInstanceIdSchema = string().min(1);
53228
53439
  var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
53229
53440
  /**
53230
53441
  * What the addon's instances ARE for the Integrations page (planning#416). `devices`: each
53231
- * instance is an integration that brings devices (the registry owns its on/off switch);
53442
+ * instance is an integration that brings devices (its record body owns the on/off switch);
53232
53443
  * `services`: a connection configured from the addon's own function page, never listed on
53233
53444
  * Integrations.
53234
53445
  * It is the same vocabulary as the manifest's `integrationRole`, which stays as an override.
@@ -53345,8 +53556,8 @@ var IntegrationInstanceSchema = object({
53345
53556
  configured: boolean(),
53346
53557
  /**
53347
53558
  * The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
53348
- * so a row written before the field existed is on). Absent when the `integrations` registry
53349
- * owns the switch (`describe().role` is `devices`): read it from the registry row.
53559
+ * so a row written before the field existed is on). The same for every role, `devices` included:
53560
+ * the body is the one store, `setInstanceEnabled` the one writer (epic #440, step 3).
53350
53561
  */
53351
53562
  enabled: boolean().optional(),
53352
53563
  /** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
@@ -56182,6 +56393,8 @@ var MigrationIntegrationSchema = object({
56182
56393
  addonId: string(),
56183
56394
  name: string(),
56184
56395
  info: Rows,
56396
+ /** The registry's stored switch: read by the one-switch migration, nothing else. */
56397
+ enabled: boolean().optional(),
56185
56398
  originStableId: string().optional()
56186
56399
  });
56187
56400
  var MigrationDeviceSchema = object({
@@ -65700,14 +65913,6 @@ object({
65700
65913
  empty: boolean().nullable(),
65701
65914
  /** True while the pump is running. Null while unknown. */
65702
65915
  pumpRunning: boolean().nullable(),
65703
- /** Water drunk in the last drink, in litres. */
65704
- lastDrink: number().min(0).nullable().meta(readingMeta("volume")),
65705
- /** Water drunk in the current hour, in litres. */
65706
- currentHourDrink: number().min(0).nullable().meta(readingMeta("volume")),
65707
- /** Water drunk over the last 24 hours (rolling), in litres. */
65708
- last24hDrink: number().min(0).nullable().meta(readingMeta("volume")),
65709
- /** Water drunk since local midnight, in litres. Null when the fountain has no such sensor. */
65710
- todayDrink: number().min(0).nullable().meta(readingMeta("volume")),
65711
65916
  /** Decoded fault message. Null means healthy; a non-null string is an active fault. */
65712
65917
  error: string().nullable(),
65713
65918
  /** Ms epoch when the slice was last refreshed. */
@@ -65720,9 +65925,6 @@ DeviceType.PetFountain, method(object({
65720
65925
  }), _void(), {
65721
65926
  kind: "mutation",
65722
65927
  auth: "protected"
65723
- }), method(object({ deviceId: DeviceIdSchema$1 }), _void(), {
65724
- kind: "mutation",
65725
- auth: "protected"
65726
65928
  });
65727
65929
  /**
65728
65930
  * The wire shapes of the Auto tune plan and apply (D745), used by the
@@ -81096,13 +81298,6 @@ Object.freeze({
81096
81298
  access: "view",
81097
81299
  role: "viewer"
81098
81300
  },
81099
- "petFountain.resetDailyConsumption": {
81100
- capName: "pet-fountain",
81101
- capScope: "device",
81102
- addonId: null,
81103
- access: "delete",
81104
- role: "viewer"
81105
- },
81106
81301
  "petFountain.setPump": {
81107
81302
  capName: "pet-fountain",
81108
81303
  capScope: "device",
@@ -87499,11 +87694,6 @@ Object.freeze({
87499
87694
  form: "single",
87500
87695
  optional: false
87501
87696
  }],
87502
- "petFountain.resetDailyConsumption": [{
87503
- name: "deviceId",
87504
- form: "single",
87505
- optional: false
87506
- }],
87507
87697
  "petFountain.setPump": [{
87508
87698
  name: "deviceId",
87509
87699
  form: "single",
@@ -90247,7 +90437,6 @@ object({
90247
90437
  delay: number().nonnegative().optional(),
90248
90438
  commands: record(string(), unknown())
90249
90439
  });
90250
- entryBody({ brokerId: string() });
90251
90440
  //#endregion
90252
90441
  //#region src/addon.ts
90253
90442
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -90269,7 +90458,7 @@ var AgentUIAddon = class extends BaseAddon {
90269
90458
  capability: adminUiCapability,
90270
90459
  provider: {
90271
90460
  getStaticDir: async () => ({ staticDir: path.resolve(__dirname) }),
90272
- getVersion: async () => ({ version: "1.2.333" })
90461
+ getVersion: async () => ({ version: "1.2.334" })
90273
90462
  }
90274
90463
  }];
90275
90464
  }