@camstack/system 1.2.67 → 1.2.69
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/adoption-job-engine.d.ts +89 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +8 -0
- package/dist/builtins/device-manager/device-manager.addon.js +329 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +329 -2
- package/dist/builtins/device-manager/device-provider-context.d.ts +3 -0
- package/dist/builtins/device-manager/device-queries.d.ts +3 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +339 -2
- package/dist/builtins/snapshot/index.mjs +339 -2
- package/dist/builtins/snapshot/snapshot-link-url.d.ts +66 -0
- package/dist/builtins/snapshot/snapshot.addon.d.ts +79 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-CbS6uoSb.mjs → dist-C-QzFJ8v.mjs} +313 -1
- package/dist/{dist-NRYjb8qK.js → dist-D7wC-S4p.js} +313 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D7wC-S4p.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_fs = require("node:fs");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as addonPagesCapability, ft as errMsg, pt as BaseAddon, zt as EventCategory } from "../../dist-
|
|
1
|
+
import { _ as addonPagesCapability, ft as errMsg, pt as BaseAddon, zt as EventCategory } from "../../dist-C-QzFJ8v.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as fs from "node:fs";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D7wC-S4p.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_fs = require("node:fs");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ft as errMsg, pt as BaseAddon, y as addonWidgetsCapability, zt as EventCategory } from "../../dist-
|
|
1
|
+
import { ft as errMsg, pt as BaseAddon, y as addonWidgetsCapability, zt as EventCategory } from "../../dist-C-QzFJ8v.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as fs from "node:fs";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D7wC-S4p.js");
|
|
7
7
|
//#region src/builtins/alerts/prune-policy.ts
|
|
8
8
|
/**
|
|
9
9
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Et as createEvent, b as alertsCapability, ft as errMsg, n as AlertSchema, pt as BaseAddon, zt as EventCategory } from "../../dist-
|
|
1
|
+
import { Et as createEvent, b as alertsCapability, ft as errMsg, n as AlertSchema, pt as BaseAddon, zt as EventCategory } from "../../dist-C-QzFJ8v.mjs";
|
|
2
2
|
//#region src/builtins/alerts/prune-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D7wC-S4p.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
let node_fs$1 = require_chunk.__toESM(node_fs, 1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as backupCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-
|
|
1
|
+
import { S as backupCapability, pt as BaseAddon, zt as EventCategory } from "../../dist-C-QzFJ8v.mjs";
|
|
2
2
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import Kt from "node:fs";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D7wC-S4p.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pt as BaseAddon, q as logDestinationCapability } from "../../dist-
|
|
1
|
+
import { pt as BaseAddon, q as logDestinationCapability } from "../../dist-C-QzFJ8v.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-D7wC-S4p.js");
|
|
4
4
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_path = require("node:path");
|
|
6
6
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as coreBlocksCapability, E as coreBlockIdFromAddonId, T as coreBlockAddonId, c as CoreBlockSchema, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, pt as BaseAddon, s as CORE_BLOCK_ADDON_PREFIX } from "../../dist-
|
|
1
|
+
import { D as coreBlocksCapability, E as coreBlockIdFromAddonId, T as coreBlockAddonId, c as CoreBlockSchema, l as DeclaredDevices, o as CORE_BLOCKS_ADDON_ID, pt as BaseAddon, s as CORE_BLOCK_ADDON_PREFIX } from "../../dist-C-QzFJ8v.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as path$1 from "node:path";
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { AdoptionJob, IScopedLogger } from '@camstack/types';
|
|
2
|
+
/**
|
|
3
|
+
* The slice of a `device-adoption` provider this engine drives. Structural on
|
|
4
|
+
* purpose: the real provider satisfies it, and a spec can exercise the whole
|
|
5
|
+
* loop without standing up a registry.
|
|
6
|
+
*/
|
|
7
|
+
export interface AdoptionJobProvider {
|
|
8
|
+
listCandidates(input: {
|
|
9
|
+
integrationId: string;
|
|
10
|
+
page: number;
|
|
11
|
+
pageSize: number;
|
|
12
|
+
filter?: string;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
readonly candidates: readonly {
|
|
15
|
+
readonly childNativeId: string;
|
|
16
|
+
readonly alreadyAdopted?: boolean;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
19
|
+
adopt(input: {
|
|
20
|
+
integrationId: string;
|
|
21
|
+
childNativeIds: readonly string[];
|
|
22
|
+
filter?: string;
|
|
23
|
+
importLocations?: boolean;
|
|
24
|
+
perCandidate?: Record<string, {
|
|
25
|
+
name?: string;
|
|
26
|
+
hiddenChildIds?: string[];
|
|
27
|
+
}>;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
readonly adopted: readonly {
|
|
30
|
+
readonly childNativeId: string;
|
|
31
|
+
readonly parentDeviceId: number;
|
|
32
|
+
readonly accessoryDeviceIds: readonly number[];
|
|
33
|
+
}[];
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export interface AdoptionJobEngineDeps {
|
|
37
|
+
/** Resolve the integration's adoption provider for an `addonId` pin. */
|
|
38
|
+
resolveProvider(addonId: string): Promise<AdoptionJobProvider>;
|
|
39
|
+
readonly logger: IScopedLogger;
|
|
40
|
+
readonly now?: () => number;
|
|
41
|
+
readonly newId?: () => string;
|
|
42
|
+
}
|
|
43
|
+
export interface StartAdoptionJobInput {
|
|
44
|
+
readonly addonId: string;
|
|
45
|
+
readonly integrationId: string;
|
|
46
|
+
readonly childNativeIds: readonly string[];
|
|
47
|
+
readonly filter?: string;
|
|
48
|
+
readonly importLocations?: boolean;
|
|
49
|
+
readonly perCandidate?: Record<string, {
|
|
50
|
+
name?: string;
|
|
51
|
+
hiddenChildIds?: string[];
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
export declare class AdoptionJobEngine {
|
|
55
|
+
private readonly deps;
|
|
56
|
+
private readonly jobs;
|
|
57
|
+
private seq;
|
|
58
|
+
constructor(deps: AdoptionJobEngineDeps);
|
|
59
|
+
private now;
|
|
60
|
+
private newId;
|
|
61
|
+
/**
|
|
62
|
+
* Accept the batch and return immediately. The provider is resolved BEFORE
|
|
63
|
+
* returning so an unroutable `addonId` is refused at the call the operator
|
|
64
|
+
* made, not buried in a job they have to go looking for.
|
|
65
|
+
*/
|
|
66
|
+
start(input: StartAdoptionJobInput): Promise<{
|
|
67
|
+
jobId: string;
|
|
68
|
+
}>;
|
|
69
|
+
get(jobId: string): AdoptionJob | null;
|
|
70
|
+
list(filter: {
|
|
71
|
+
addonId?: string;
|
|
72
|
+
integrationId?: string;
|
|
73
|
+
}): readonly AdoptionJob[];
|
|
74
|
+
/** Cooperative cancel — the in-flight candidate finishes, the rest do not start. */
|
|
75
|
+
cancel(jobId: string): boolean;
|
|
76
|
+
/** Test-only: await the detached loop. Never called by production code. */
|
|
77
|
+
waitForTest(jobId: string): Promise<void>;
|
|
78
|
+
private prune;
|
|
79
|
+
private run;
|
|
80
|
+
/**
|
|
81
|
+
* One `listCandidates` snapshot at job start. A read that FAILS must not be
|
|
82
|
+
* read as "nothing is adopted" — that would send every candidate to the
|
|
83
|
+
* provider and turn a safe retry back into a wall of duplicate-stableId
|
|
84
|
+
* errors — so a failure yields an empty set AND a warning, and the
|
|
85
|
+
* per-candidate failure path stays the backstop.
|
|
86
|
+
*/
|
|
87
|
+
private alreadyAdopted;
|
|
88
|
+
private adoptOne;
|
|
89
|
+
}
|
|
@@ -48,6 +48,8 @@ export declare class DeviceManagerAddon extends BaseAddon {
|
|
|
48
48
|
* the D3 re-handshake after device restore is the reliable replacement.
|
|
49
49
|
*/
|
|
50
50
|
private remoteNativeCaps;
|
|
51
|
+
/** Background adoption jobs — lazily constructed (needs `ctx.logger`). */
|
|
52
|
+
private adoptionJobEngine;
|
|
51
53
|
/** The runtime-state mirror, asserted present (constructed in onInitialize). */
|
|
52
54
|
private get stateMirror();
|
|
53
55
|
/** Build the `ProviderHost` the extracted provider-method modules
|
|
@@ -57,6 +59,12 @@ export declare class DeviceManagerAddon extends BaseAddon {
|
|
|
57
59
|
* `remoteNativeCaps` is mutated in place (never reassigned) so a direct
|
|
58
60
|
* reference preserves the live semantics. */
|
|
59
61
|
private get providerHost();
|
|
62
|
+
/**
|
|
63
|
+
* Background adoption jobs. In-RAM and process-lifetime: a restart forgets
|
|
64
|
+
* them, which is safe because adoption is keyed by a stable id — re-running a
|
|
65
|
+
* job re-adopts nothing (see `AdoptionJobEngine`).
|
|
66
|
+
*/
|
|
67
|
+
private get adoptionJobsImpl();
|
|
60
68
|
/** Wait for a device-provider by addonId, returning null on timeout. */
|
|
61
69
|
private waitDeviceProvider;
|
|
62
70
|
/** Require a device-provider by addonId — throws if not found. */
|
|
@@ -3,9 +3,297 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-D7wC-S4p.js");
|
|
7
7
|
let _camstack_types_node = require("@camstack/types/node");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
|
+
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
10
|
+
/** How many finished jobs are kept per (addonId, integrationId). */
|
|
11
|
+
var JOB_HISTORY = 20;
|
|
12
|
+
function snapshot(j) {
|
|
13
|
+
let adopted = 0;
|
|
14
|
+
let alreadyAdopted = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
let processed = 0;
|
|
17
|
+
let accessoriesCreated = 0;
|
|
18
|
+
for (const r of j.results) {
|
|
19
|
+
if (r.outcome === "cancelled") continue;
|
|
20
|
+
processed += 1;
|
|
21
|
+
accessoriesCreated += r.accessoryCount;
|
|
22
|
+
if (r.outcome === "adopted") adopted += 1;
|
|
23
|
+
else if (r.outcome === "already-adopted") alreadyAdopted += 1;
|
|
24
|
+
else if (r.outcome === "failed") failed += 1;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
jobId: j.jobId,
|
|
28
|
+
addonId: j.addonId,
|
|
29
|
+
integrationId: j.integrationId,
|
|
30
|
+
state: j.state,
|
|
31
|
+
total: j.total,
|
|
32
|
+
processed,
|
|
33
|
+
adopted,
|
|
34
|
+
alreadyAdopted,
|
|
35
|
+
failed,
|
|
36
|
+
accessoriesCreated,
|
|
37
|
+
currentChildNativeId: j.currentChildNativeId,
|
|
38
|
+
results: j.results.map((r) => ({
|
|
39
|
+
childNativeId: r.childNativeId,
|
|
40
|
+
outcome: r.outcome,
|
|
41
|
+
parentDeviceId: r.parentDeviceId,
|
|
42
|
+
accessoryCount: r.accessoryCount,
|
|
43
|
+
error: r.error
|
|
44
|
+
})),
|
|
45
|
+
startedAt: j.startedAt,
|
|
46
|
+
finishedAt: j.finishedAt,
|
|
47
|
+
error: j.error
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
var AdoptionJobEngine = class {
|
|
51
|
+
deps;
|
|
52
|
+
jobs = /* @__PURE__ */ new Map();
|
|
53
|
+
seq = 0;
|
|
54
|
+
constructor(deps) {
|
|
55
|
+
this.deps = deps;
|
|
56
|
+
}
|
|
57
|
+
now() {
|
|
58
|
+
return this.deps.now?.() ?? Date.now();
|
|
59
|
+
}
|
|
60
|
+
newId() {
|
|
61
|
+
if (this.deps.newId) return this.deps.newId();
|
|
62
|
+
this.seq += 1;
|
|
63
|
+
return `adopt-${Date.now().toString(36)}-${this.seq}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Accept the batch and return immediately. The provider is resolved BEFORE
|
|
67
|
+
* returning so an unroutable `addonId` is refused at the call the operator
|
|
68
|
+
* made, not buried in a job they have to go looking for.
|
|
69
|
+
*/
|
|
70
|
+
async start(input) {
|
|
71
|
+
const provider = await this.deps.resolveProvider(input.addonId);
|
|
72
|
+
const job = {
|
|
73
|
+
jobId: this.newId(),
|
|
74
|
+
addonId: input.addonId,
|
|
75
|
+
integrationId: input.integrationId,
|
|
76
|
+
state: "running",
|
|
77
|
+
total: input.childNativeIds.length,
|
|
78
|
+
currentChildNativeId: null,
|
|
79
|
+
results: [],
|
|
80
|
+
startedAt: this.now(),
|
|
81
|
+
finishedAt: null,
|
|
82
|
+
error: null,
|
|
83
|
+
cancelRequested: false,
|
|
84
|
+
done: Promise.resolve()
|
|
85
|
+
};
|
|
86
|
+
this.jobs.set(job.jobId, job);
|
|
87
|
+
this.prune(input.addonId, input.integrationId);
|
|
88
|
+
this.deps.logger.info("adoption job started", {
|
|
89
|
+
tags: { addonId: input.addonId },
|
|
90
|
+
meta: {
|
|
91
|
+
jobId: job.jobId,
|
|
92
|
+
integrationId: input.integrationId,
|
|
93
|
+
total: job.total
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
job.done = this.run(job, provider, input);
|
|
97
|
+
return { jobId: job.jobId };
|
|
98
|
+
}
|
|
99
|
+
get(jobId) {
|
|
100
|
+
const j = this.jobs.get(jobId);
|
|
101
|
+
return j ? snapshot(j) : null;
|
|
102
|
+
}
|
|
103
|
+
list(filter) {
|
|
104
|
+
return [...this.jobs.values()].filter((j) => (filter.addonId === void 0 || j.addonId === filter.addonId) && (filter.integrationId === void 0 || j.integrationId === filter.integrationId)).sort((a, b) => b.startedAt - a.startedAt || b.jobId.localeCompare(a.jobId)).map(snapshot);
|
|
105
|
+
}
|
|
106
|
+
/** Cooperative cancel — the in-flight candidate finishes, the rest do not start. */
|
|
107
|
+
cancel(jobId) {
|
|
108
|
+
const job = this.jobs.get(jobId);
|
|
109
|
+
if (!job || job.state !== "running") return false;
|
|
110
|
+
job.cancelRequested = true;
|
|
111
|
+
this.deps.logger.info("adoption job cancel requested", {
|
|
112
|
+
tags: { addonId: job.addonId },
|
|
113
|
+
meta: {
|
|
114
|
+
jobId,
|
|
115
|
+
integrationId: job.integrationId
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
/** Test-only: await the detached loop. Never called by production code. */
|
|
121
|
+
async waitForTest(jobId) {
|
|
122
|
+
await this.jobs.get(jobId)?.done;
|
|
123
|
+
}
|
|
124
|
+
prune(addonId, integrationId) {
|
|
125
|
+
const finished = [...this.jobs.values()].filter((j) => j.addonId === addonId && j.integrationId === integrationId && j.state !== "running").sort((a, b) => b.startedAt - a.startedAt);
|
|
126
|
+
for (const j of finished.slice(JOB_HISTORY)) this.jobs.delete(j.jobId);
|
|
127
|
+
}
|
|
128
|
+
async run(job, provider, input) {
|
|
129
|
+
try {
|
|
130
|
+
const already = await this.alreadyAdopted(provider, input, job);
|
|
131
|
+
for (const childNativeId of input.childNativeIds) {
|
|
132
|
+
if (job.cancelRequested) {
|
|
133
|
+
job.results.push({
|
|
134
|
+
childNativeId,
|
|
135
|
+
outcome: "cancelled",
|
|
136
|
+
parentDeviceId: null,
|
|
137
|
+
accessoryCount: 0,
|
|
138
|
+
error: null
|
|
139
|
+
});
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (already.has(childNativeId)) {
|
|
143
|
+
job.results.push({
|
|
144
|
+
childNativeId,
|
|
145
|
+
outcome: "already-adopted",
|
|
146
|
+
parentDeviceId: null,
|
|
147
|
+
accessoryCount: 0,
|
|
148
|
+
error: null
|
|
149
|
+
});
|
|
150
|
+
this.deps.logger.info("adoption job: candidate already adopted, skipped", {
|
|
151
|
+
tags: { addonId: job.addonId },
|
|
152
|
+
meta: {
|
|
153
|
+
jobId: job.jobId,
|
|
154
|
+
integrationId: job.integrationId,
|
|
155
|
+
childNativeId
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
job.currentChildNativeId = childNativeId;
|
|
161
|
+
await this.adoptOne(job, provider, input, childNativeId);
|
|
162
|
+
job.currentChildNativeId = null;
|
|
163
|
+
}
|
|
164
|
+
job.state = job.cancelRequested ? "cancelled" : "done";
|
|
165
|
+
} catch (err) {
|
|
166
|
+
job.state = "failed";
|
|
167
|
+
job.error = require_dist.errMsg(err);
|
|
168
|
+
this.deps.logger.error("adoption job failed", {
|
|
169
|
+
tags: { addonId: job.addonId },
|
|
170
|
+
meta: {
|
|
171
|
+
jobId: job.jobId,
|
|
172
|
+
integrationId: job.integrationId,
|
|
173
|
+
error: job.error
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
} finally {
|
|
177
|
+
job.currentChildNativeId = null;
|
|
178
|
+
job.finishedAt = this.now();
|
|
179
|
+
const s = snapshot(job);
|
|
180
|
+
this.deps.logger.info("adoption job finished", {
|
|
181
|
+
tags: { addonId: job.addonId },
|
|
182
|
+
meta: {
|
|
183
|
+
jobId: job.jobId,
|
|
184
|
+
integrationId: job.integrationId,
|
|
185
|
+
state: s.state,
|
|
186
|
+
total: s.total,
|
|
187
|
+
adopted: s.adopted,
|
|
188
|
+
alreadyAdopted: s.alreadyAdopted,
|
|
189
|
+
failed: s.failed,
|
|
190
|
+
accessoriesCreated: s.accessoriesCreated,
|
|
191
|
+
durationMs: (s.finishedAt ?? 0) - s.startedAt
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* One `listCandidates` snapshot at job start. A read that FAILS must not be
|
|
198
|
+
* read as "nothing is adopted" — that would send every candidate to the
|
|
199
|
+
* provider and turn a safe retry back into a wall of duplicate-stableId
|
|
200
|
+
* errors — so a failure yields an empty set AND a warning, and the
|
|
201
|
+
* per-candidate failure path stays the backstop.
|
|
202
|
+
*/
|
|
203
|
+
async alreadyAdopted(provider, input, job) {
|
|
204
|
+
try {
|
|
205
|
+
const page = await provider.listCandidates({
|
|
206
|
+
integrationId: input.integrationId,
|
|
207
|
+
page: 1,
|
|
208
|
+
pageSize: 2e4,
|
|
209
|
+
...input.filter !== void 0 ? { filter: input.filter } : {}
|
|
210
|
+
});
|
|
211
|
+
const out = /* @__PURE__ */ new Set();
|
|
212
|
+
for (const c of page.candidates) if (c.alreadyAdopted === true) out.add(c.childNativeId);
|
|
213
|
+
return out;
|
|
214
|
+
} catch (err) {
|
|
215
|
+
this.deps.logger.warn("adoption job: pre-flight listCandidates failed — every candidate will be attempted", {
|
|
216
|
+
tags: { addonId: job.addonId },
|
|
217
|
+
meta: {
|
|
218
|
+
jobId: job.jobId,
|
|
219
|
+
integrationId: job.integrationId,
|
|
220
|
+
error: require_dist.errMsg(err)
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
return /* @__PURE__ */ new Set();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async adoptOne(job, provider, input, childNativeId) {
|
|
227
|
+
const startedAt = this.now();
|
|
228
|
+
try {
|
|
229
|
+
const per = input.perCandidate?.[childNativeId];
|
|
230
|
+
const entry = (await provider.adopt({
|
|
231
|
+
integrationId: input.integrationId,
|
|
232
|
+
childNativeIds: [childNativeId],
|
|
233
|
+
...input.filter !== void 0 ? { filter: input.filter } : {},
|
|
234
|
+
...input.importLocations !== void 0 ? { importLocations: input.importLocations } : {},
|
|
235
|
+
...per !== void 0 ? { perCandidate: { [childNativeId]: per } } : {}
|
|
236
|
+
})).adopted[0];
|
|
237
|
+
if (!entry) {
|
|
238
|
+
job.results.push({
|
|
239
|
+
childNativeId,
|
|
240
|
+
outcome: "failed",
|
|
241
|
+
parentDeviceId: null,
|
|
242
|
+
accessoryCount: 0,
|
|
243
|
+
error: "provider returned no adopted entry"
|
|
244
|
+
});
|
|
245
|
+
this.deps.logger.warn("adoption job: provider adopted nothing for candidate", {
|
|
246
|
+
tags: { addonId: job.addonId },
|
|
247
|
+
meta: {
|
|
248
|
+
jobId: job.jobId,
|
|
249
|
+
integrationId: job.integrationId,
|
|
250
|
+
childNativeId
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
job.results.push({
|
|
256
|
+
childNativeId,
|
|
257
|
+
outcome: "adopted",
|
|
258
|
+
parentDeviceId: entry.parentDeviceId,
|
|
259
|
+
accessoryCount: entry.accessoryDeviceIds.length,
|
|
260
|
+
error: null
|
|
261
|
+
});
|
|
262
|
+
this.deps.logger.info("adoption job: candidate adopted", {
|
|
263
|
+
tags: {
|
|
264
|
+
deviceId: entry.parentDeviceId,
|
|
265
|
+
addonId: job.addonId
|
|
266
|
+
},
|
|
267
|
+
meta: {
|
|
268
|
+
jobId: job.jobId,
|
|
269
|
+
integrationId: job.integrationId,
|
|
270
|
+
childNativeId,
|
|
271
|
+
accessories: entry.accessoryDeviceIds.length,
|
|
272
|
+
durationMs: this.now() - startedAt
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
} catch (err) {
|
|
276
|
+
job.results.push({
|
|
277
|
+
childNativeId,
|
|
278
|
+
outcome: "failed",
|
|
279
|
+
parentDeviceId: null,
|
|
280
|
+
accessoryCount: 0,
|
|
281
|
+
error: require_dist.errMsg(err)
|
|
282
|
+
});
|
|
283
|
+
this.deps.logger.warn("adoption job: candidate failed", {
|
|
284
|
+
tags: { addonId: job.addonId },
|
|
285
|
+
meta: {
|
|
286
|
+
jobId: job.jobId,
|
|
287
|
+
integrationId: job.integrationId,
|
|
288
|
+
childNativeId,
|
|
289
|
+
durationMs: this.now() - startedAt,
|
|
290
|
+
error: require_dist.errMsg(err)
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
//#endregion
|
|
9
297
|
//#region src/builtins/device-manager/day-night-config-schema.ts
|
|
10
298
|
var MODE_LABELS = {
|
|
11
299
|
auto: "Auto",
|
|
@@ -1128,6 +1416,27 @@ async function adoptionRelease(pctx, input) {
|
|
|
1128
1416
|
const { addonId, ...rest } = input;
|
|
1129
1417
|
return (await pctx.host.requireDeviceAdoptionProvider(addonId)).release(rest);
|
|
1130
1418
|
}
|
|
1419
|
+
async function adoptionStartJob(pctx, input) {
|
|
1420
|
+
const { addonId, integrationId, childNativeIds, filter, importLocations, perCandidate } = input;
|
|
1421
|
+
return pctx.host.adoptionJobs.start({
|
|
1422
|
+
addonId,
|
|
1423
|
+
integrationId,
|
|
1424
|
+
childNativeIds,
|
|
1425
|
+
...filter !== void 0 ? { filter } : {},
|
|
1426
|
+
...importLocations !== void 0 ? { importLocations } : {},
|
|
1427
|
+
...perCandidate !== void 0 ? { perCandidate } : {}
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
async function adoptionListJobs(pctx, input) {
|
|
1431
|
+
const { addonId, integrationId } = input;
|
|
1432
|
+
return pctx.host.adoptionJobs.list({
|
|
1433
|
+
addonId,
|
|
1434
|
+
...integrationId !== void 0 ? { integrationId } : {}
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
async function adoptionCancelJob(pctx, input) {
|
|
1438
|
+
return { cancelled: pctx.host.adoptionJobs.cancel(input.jobId) };
|
|
1439
|
+
}
|
|
1131
1440
|
async function adoptionResync(pctx, input) {
|
|
1132
1441
|
const { camDeviceId, resetToSource } = input;
|
|
1133
1442
|
let owningAddonId = pctx.registry?.getAddonId(camDeviceId) ?? null;
|
|
@@ -3929,6 +4238,8 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
3929
4238
|
* the D3 re-handshake after device restore is the reliable replacement.
|
|
3930
4239
|
*/
|
|
3931
4240
|
remoteNativeCaps = /* @__PURE__ */ new Map();
|
|
4241
|
+
/** Background adoption jobs — lazily constructed (needs `ctx.logger`). */
|
|
4242
|
+
adoptionJobEngine = null;
|
|
3932
4243
|
/** The runtime-state mirror, asserted present (constructed in onInitialize). */
|
|
3933
4244
|
get stateMirror() {
|
|
3934
4245
|
if (!this.stateMirrorImpl) throw new Error("DeviceManagerAddon: state mirror accessed before onInitialize");
|
|
@@ -3954,9 +4265,22 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
3954
4265
|
resolveDeviceProbed: (deviceId) => this.stateMirror.resolveDeviceProbed(deviceId),
|
|
3955
4266
|
waitDeviceProvider: (addonId, timeoutMs) => this.waitDeviceProvider(addonId, timeoutMs),
|
|
3956
4267
|
requireDeviceProvider: (addonId) => this.requireDeviceProvider(addonId),
|
|
3957
|
-
requireDeviceAdoptionProvider: (addonId) => this.requireDeviceAdoptionProvider(addonId)
|
|
4268
|
+
requireDeviceAdoptionProvider: (addonId) => this.requireDeviceAdoptionProvider(addonId),
|
|
4269
|
+
adoptionJobs: this.adoptionJobsImpl
|
|
3958
4270
|
};
|
|
3959
4271
|
}
|
|
4272
|
+
/**
|
|
4273
|
+
* Background adoption jobs. In-RAM and process-lifetime: a restart forgets
|
|
4274
|
+
* them, which is safe because adoption is keyed by a stable id — re-running a
|
|
4275
|
+
* job re-adopts nothing (see `AdoptionJobEngine`).
|
|
4276
|
+
*/
|
|
4277
|
+
get adoptionJobsImpl() {
|
|
4278
|
+
if (!this.adoptionJobEngine) this.adoptionJobEngine = new AdoptionJobEngine({
|
|
4279
|
+
resolveProvider: async (addonId) => this.requireDeviceAdoptionProvider(addonId),
|
|
4280
|
+
logger: this.ctx.logger
|
|
4281
|
+
});
|
|
4282
|
+
return this.adoptionJobEngine;
|
|
4283
|
+
}
|
|
3960
4284
|
/** Wait for a device-provider by addonId, returning null on timeout. */
|
|
3961
4285
|
async waitDeviceProvider(addonId, timeoutMs = 5e3) {
|
|
3962
4286
|
const provider = await this.capabilityRegistry?.waitForProvider("device-provider", addonId, timeoutMs);
|
|
@@ -4136,6 +4460,9 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
4136
4460
|
adoptionRefresh: (input) => adoptionRefresh(pctx, input),
|
|
4137
4461
|
adoptionAdopt: (input) => adoptionAdopt(pctx, input),
|
|
4138
4462
|
adoptionRelease: (input) => adoptionRelease(pctx, input),
|
|
4463
|
+
adoptionStartJob: (input) => adoptionStartJob(pctx, input),
|
|
4464
|
+
adoptionListJobs: (input) => adoptionListJobs(pctx, input),
|
|
4465
|
+
adoptionCancelJob: (input) => adoptionCancelJob(pctx, input),
|
|
4139
4466
|
adoptionResync: (input) => adoptionResync(pctx, input),
|
|
4140
4467
|
discoveryProviders: async () => {
|
|
4141
4468
|
return { providers: (await this.listDiscoveryCapableProviderIds()).map((addonId) => ({
|