@camstack/addon-pipeline-orchestrator 1.2.181 → 1.2.182
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/assets/viewer/.viewer-current-files.json +5 -5
- package/assets/viewer/.viewer-version +1 -1
- package/assets/viewer/_expo/static/js/web/Clipboard-fffea46d75b5210ec63206a6e71109c9.js +8 -0
- package/assets/viewer/_expo/static/js/web/Haptics-3d0b88cef97052d03a162bf09cceb2f2.js +3 -0
- package/assets/viewer/_expo/static/js/web/{index-e332b95855a7450dc918a234e13f19ea.js → index-0a664153411e8ea701f301321758dcde.js} +1555 -1551
- package/assets/viewer/_expo/static/js/web/node-fs-01fcfc0be4013fb16134783c5c55066c.js +1 -0
- package/assets/viewer/_expo/static/js/web/node-path-cbe5ac061679ae3273222ef45ab6ff8e.js +1 -0
- package/assets/viewer/index.html +1 -1
- package/dist/_stub.js +1 -1
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-CUGRt6hQ.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-B944RKiT.mjs} +3 -3
- package/dist/{_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-bQkIh01P.mjs → _virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-H_CMYslN.mjs} +1 -1
- package/dist/{hostInit-DdMcjaFM.mjs → hostInit-Dp6V7dau.mjs} +3 -3
- package/dist/index.js +295 -57
- package/dist/index.mjs +295 -57
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -17069,6 +17069,7 @@ var NcSystemEventKindSchema = _enum([
|
|
|
17069
17069
|
"addon-crash-loop",
|
|
17070
17070
|
"addon-update-available",
|
|
17071
17071
|
"server-update-available",
|
|
17072
|
+
"wrapper-update-available",
|
|
17072
17073
|
"alarm-triggered",
|
|
17073
17074
|
"alarm-armed",
|
|
17074
17075
|
"alarm-disarmed",
|
|
@@ -23327,6 +23328,53 @@ var ImageContractSchema = object({
|
|
|
23327
23328
|
/** One operator-grade sentence: this node runs image X; the contract says Y. */
|
|
23328
23329
|
message: string()
|
|
23329
23330
|
});
|
|
23331
|
+
/**
|
|
23332
|
+
* The shell verdict (see {@link WrapperKindSchema}) with the proofs that
|
|
23333
|
+
* produced it.
|
|
23334
|
+
*
|
|
23335
|
+
* There is deliberately NO `canSelfUpdate` and no apply action: a wrapper
|
|
23336
|
+
* update is an ANNOUNCEMENT. The notification body carries the exact command
|
|
23337
|
+
* for a `docker` shell and says the app restarts into the new build for
|
|
23338
|
+
* `electron`, and that sentence is chosen from `kind` alone — a boolean
|
|
23339
|
+
* nothing reads is the defect this repo keeps paying for.
|
|
23340
|
+
*/
|
|
23341
|
+
var WrapperIdentitySchema = object({
|
|
23342
|
+
kind: _enum([
|
|
23343
|
+
"docker",
|
|
23344
|
+
"electron",
|
|
23345
|
+
"native",
|
|
23346
|
+
"contradictory",
|
|
23347
|
+
"unknown"
|
|
23348
|
+
]),
|
|
23349
|
+
/** Every probe that ran, in a stable order. Never empty. */
|
|
23350
|
+
evidence: array(object({
|
|
23351
|
+
/** Which probe this row reports. */
|
|
23352
|
+
fact: _enum([
|
|
23353
|
+
"dockerenv-file",
|
|
23354
|
+
"proc-1-cgroup",
|
|
23355
|
+
"platform",
|
|
23356
|
+
"electron-app-version-env"
|
|
23357
|
+
]),
|
|
23358
|
+
/**
|
|
23359
|
+
* `observed` — this process read it itself. `declared` — the shell told the
|
|
23360
|
+
* process and nothing here can check it. Only the Electron app version is
|
|
23361
|
+
* ever `declared`, and only because a child cannot see its parent.
|
|
23362
|
+
*/
|
|
23363
|
+
mode: _enum(["observed", "declared"]),
|
|
23364
|
+
/** Did the probe support the fact it names? A `false` row is still evidence. */
|
|
23365
|
+
holds: boolean(),
|
|
23366
|
+
/** What was actually read, verbatim enough for an operator to argue with. */
|
|
23367
|
+
detail: string()
|
|
23368
|
+
})),
|
|
23369
|
+
/**
|
|
23370
|
+
* The version of the SHELL, when the shell has one it can state: the
|
|
23371
|
+
* Electron app version it declared, or the baked seed closure that
|
|
23372
|
+
* fingerprints a container / app bundle. `null` for `contradictory` and
|
|
23373
|
+
* `unknown` — which version belongs to the shell is precisely what is not
|
|
23374
|
+
* known there.
|
|
23375
|
+
*/
|
|
23376
|
+
currentVersion: string().nullable()
|
|
23377
|
+
});
|
|
23330
23378
|
var ServerRollbackInfoSchema = object({
|
|
23331
23379
|
/** The version that failed (or was manually rolled back). */
|
|
23332
23380
|
fromVersion: string(),
|
|
@@ -23368,7 +23416,13 @@ var ServerPackageStatusSchema = object({
|
|
|
23368
23416
|
* Seed-vs-contract verdict (see {@link ImageContractSchema}). Optional for
|
|
23369
23417
|
* version skew: an older provider's payload simply omits it.
|
|
23370
23418
|
*/
|
|
23371
|
-
imageContract: ImageContractSchema.optional()
|
|
23419
|
+
imageContract: ImageContractSchema.optional(),
|
|
23420
|
+
/**
|
|
23421
|
+
* What this node runs INSIDE (see {@link WrapperIdentitySchema}). Optional
|
|
23422
|
+
* for version skew: an older provider's payload simply omits it, and an
|
|
23423
|
+
* absent wrapper is unknown — never `native`.
|
|
23424
|
+
*/
|
|
23425
|
+
wrapper: WrapperIdentitySchema.optional()
|
|
23372
23426
|
});
|
|
23373
23427
|
var ServerUpdateCheckResultSchema = object({
|
|
23374
23428
|
packageName: string(),
|
|
@@ -42897,6 +42951,52 @@ var LoadShedController = class LoadShedController {
|
|
|
42897
42951
|
}
|
|
42898
42952
|
};
|
|
42899
42953
|
//#endregion
|
|
42954
|
+
//#region src/dispatch-timing.ts
|
|
42955
|
+
/**
|
|
42956
|
+
* How long the placement census waits for ONE runner's `getLocalCameras`
|
|
42957
|
+
* before counting that node as not having reported. A node that did not
|
|
42958
|
+
* report never has its assignments dropped (`reconcilePlacement` says so), so
|
|
42959
|
+
* skipping a slow node costs one pass of orphan cleanup on that node, not
|
|
42960
|
+
* correctness. The census fans out in parallel, so this bounds the whole
|
|
42961
|
+
* census at one budget rather than one budget per node.
|
|
42962
|
+
*/
|
|
42963
|
+
var RECONCILE_NODE_BUDGET_MS = 1500;
|
|
42964
|
+
/**
|
|
42965
|
+
* Wait for `promise`, but never longer than `budgetMs`.
|
|
42966
|
+
*
|
|
42967
|
+
* The promise is NOT cancelled — it keeps running, and its eventual rejection
|
|
42968
|
+
* is observed here so an overrun never becomes an unhandled rejection. The
|
|
42969
|
+
* caller decides what an overrun means; this only says that it happened.
|
|
42970
|
+
*/
|
|
42971
|
+
function raceBudget(promise, budgetMs) {
|
|
42972
|
+
return new Promise((resolve) => {
|
|
42973
|
+
let done = false;
|
|
42974
|
+
const timer = setTimeout(() => {
|
|
42975
|
+
if (done) return;
|
|
42976
|
+
done = true;
|
|
42977
|
+
resolve({ kind: "budget-exceeded" });
|
|
42978
|
+
}, budgetMs);
|
|
42979
|
+
timer.unref?.();
|
|
42980
|
+
promise.then((value) => {
|
|
42981
|
+
if (done) return;
|
|
42982
|
+
done = true;
|
|
42983
|
+
clearTimeout(timer);
|
|
42984
|
+
resolve({
|
|
42985
|
+
kind: "settled",
|
|
42986
|
+
value
|
|
42987
|
+
});
|
|
42988
|
+
}, (error) => {
|
|
42989
|
+
if (done) return;
|
|
42990
|
+
done = true;
|
|
42991
|
+
clearTimeout(timer);
|
|
42992
|
+
resolve({
|
|
42993
|
+
kind: "rejected",
|
|
42994
|
+
error
|
|
42995
|
+
});
|
|
42996
|
+
});
|
|
42997
|
+
});
|
|
42998
|
+
}
|
|
42999
|
+
//#endregion
|
|
42900
43000
|
//#region src/agent-load-service.ts
|
|
42901
43001
|
var AgentLoadService = class AgentLoadService {
|
|
42902
43002
|
deps;
|
|
@@ -42934,28 +43034,69 @@ var AgentLoadService = class AgentLoadService {
|
|
|
42934
43034
|
* collects every known runner, which is what the UI wants.
|
|
42935
43035
|
*/
|
|
42936
43036
|
async collectAgentLoad(options) {
|
|
42937
|
-
const loads = [];
|
|
42938
43037
|
const onlyEnabled = options?.onlyEnabled ?? false;
|
|
42939
|
-
|
|
42940
|
-
if (onlyEnabled
|
|
42941
|
-
|
|
42942
|
-
|
|
42943
|
-
|
|
42944
|
-
|
|
42945
|
-
|
|
42946
|
-
|
|
42947
|
-
|
|
42948
|
-
|
|
42949
|
-
|
|
42950
|
-
|
|
42951
|
-
|
|
42952
|
-
|
|
42953
|
-
|
|
42954
|
-
|
|
43038
|
+
const nodeIds = this.deps.topology.knownRunnerNodeIds().filter((nodeId) => {
|
|
43039
|
+
if (!onlyEnabled || this.deps.topology.isNodeEnabled(nodeId)) return true;
|
|
43040
|
+
this.deps.logger.debug("runner excluded by enabledNodes whitelist", { tags: { nodeId } });
|
|
43041
|
+
return false;
|
|
43042
|
+
});
|
|
43043
|
+
const budgetMs = this.agentLoadBudgetMs();
|
|
43044
|
+
const entries = await Promise.all(nodeIds.map((nodeId) => this.censusNode(nodeId, budgetMs)));
|
|
43045
|
+
const loads = entries.flatMap((e) => e.load === null ? [] : [e.load]);
|
|
43046
|
+
const skipped = entries.filter((e) => e.load === null).map((e) => e.nodeId);
|
|
43047
|
+
if (skipped.length > 0) {
|
|
43048
|
+
const nodes = Object.fromEntries(entries.map((e) => [e.nodeId, {
|
|
43049
|
+
ms: e.ms,
|
|
43050
|
+
outcome: e.outcome,
|
|
43051
|
+
...e.error === void 0 ? {} : { error: e.error }
|
|
43052
|
+
}]));
|
|
43053
|
+
this.deps.logger.warn("agent load census skipped a runner", { meta: {
|
|
43054
|
+
budgetMs,
|
|
43055
|
+
skipped,
|
|
43056
|
+
nodes
|
|
43057
|
+
} });
|
|
42955
43058
|
}
|
|
42956
43059
|
this.refreshAgentLoadCache(loads);
|
|
42957
43060
|
return loads;
|
|
42958
43061
|
}
|
|
43062
|
+
/**
|
|
43063
|
+
* One runner's load under `budgetMs`. A wedged runner (transport up enough
|
|
43064
|
+
* to stay in the registry, but whose call neither resolves nor rejects) is
|
|
43065
|
+
* counted as `budget-exceeded` and skipped exactly like an offline one; the
|
|
43066
|
+
* in-flight call is left to settle on its own, as before.
|
|
43067
|
+
*/
|
|
43068
|
+
async censusNode(nodeId, budgetMs) {
|
|
43069
|
+
const api = this.deps.api();
|
|
43070
|
+
const startedAt = Date.now();
|
|
43071
|
+
if (!api) return {
|
|
43072
|
+
nodeId,
|
|
43073
|
+
ms: 0,
|
|
43074
|
+
outcome: "failed",
|
|
43075
|
+
load: null,
|
|
43076
|
+
error: "addon not initialized"
|
|
43077
|
+
};
|
|
43078
|
+
const outcome = await raceBudget(api.pipelineRunner.getLocalLoad.query({ nodeId }), budgetMs);
|
|
43079
|
+
const ms = Date.now() - startedAt;
|
|
43080
|
+
if (outcome.kind === "settled") return {
|
|
43081
|
+
nodeId,
|
|
43082
|
+
ms,
|
|
43083
|
+
outcome: "reported",
|
|
43084
|
+
load: outcome.value
|
|
43085
|
+
};
|
|
43086
|
+
if (outcome.kind === "rejected") return {
|
|
43087
|
+
nodeId,
|
|
43088
|
+
ms,
|
|
43089
|
+
outcome: "failed",
|
|
43090
|
+
load: null,
|
|
43091
|
+
error: errMsg(outcome.error)
|
|
43092
|
+
};
|
|
43093
|
+
return {
|
|
43094
|
+
nodeId,
|
|
43095
|
+
ms,
|
|
43096
|
+
outcome: "budget-exceeded",
|
|
43097
|
+
load: null
|
|
43098
|
+
};
|
|
43099
|
+
}
|
|
42959
43100
|
refreshAgentLoadCache(loads) {
|
|
42960
43101
|
const next = /* @__PURE__ */ new Map();
|
|
42961
43102
|
for (const load of loads) next.set(load.nodeId, {
|
|
@@ -43002,25 +43143,6 @@ var AgentLoadService = class AgentLoadService {
|
|
|
43002
43143
|
const value = (this.deps.readGlobalSettings() ?? {}).agentLoadTimeoutMs;
|
|
43003
43144
|
return typeof value === "number" && value > 0 ? value : AgentLoadService.DEFAULT_AGENT_LOAD_TIMEOUT_MS;
|
|
43004
43145
|
}
|
|
43005
|
-
/**
|
|
43006
|
-
* `getLocalLoad` for one runner node, bounded by `agentLoadBudgetMs`. On
|
|
43007
|
-
* timeout it rejects so `collectAgentLoad`'s catch treats the node exactly
|
|
43008
|
-
* like an offline one (logged at debug, skipped).
|
|
43009
|
-
*/
|
|
43010
|
-
async queryLocalLoadBounded(nodeId) {
|
|
43011
|
-
const api = this.deps.api();
|
|
43012
|
-
if (!api) throw new Error("queryLocalLoadBounded: addon not initialized");
|
|
43013
|
-
const budgetMs = this.agentLoadBudgetMs();
|
|
43014
|
-
let timer;
|
|
43015
|
-
const timeout = new Promise((_resolve, reject) => {
|
|
43016
|
-
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`getLocalLoad exceeded ${budgetMs}ms budget for ${nodeId}`)), budgetMs);
|
|
43017
|
-
});
|
|
43018
|
-
try {
|
|
43019
|
-
return await Promise.race([api.pipelineRunner.getLocalLoad.query({ nodeId }), timeout]);
|
|
43020
|
-
} finally {
|
|
43021
|
-
if (timer) clearTimeout(timer);
|
|
43022
|
-
}
|
|
43023
|
-
}
|
|
43024
43146
|
/** Mirrors the former `onShutdown`'s `this.cachedAgentLoad.clear()`. */
|
|
43025
43147
|
dispose() {
|
|
43026
43148
|
this.cachedAgentLoad.clear();
|
|
@@ -50591,19 +50713,44 @@ var ReconcileController = class ReconcileController {
|
|
|
50591
50713
|
async runPlacementReconcile() {
|
|
50592
50714
|
const api = this.deps.api();
|
|
50593
50715
|
if (!api) return;
|
|
50716
|
+
const censusStartedAt = Date.now();
|
|
50594
50717
|
const runners = [];
|
|
50595
|
-
|
|
50596
|
-
|
|
50597
|
-
|
|
50718
|
+
const census = [];
|
|
50719
|
+
await Promise.all(this.deps.topology.collectRunnerNodeIds().map(async (nodeId) => {
|
|
50720
|
+
const startedAt = Date.now();
|
|
50721
|
+
const outcome = await raceBudget(api.pipelineRunner.getLocalCameras.query(void 0, nodePin(nodeId)), RECONCILE_NODE_BUDGET_MS);
|
|
50722
|
+
const ms = Date.now() - startedAt;
|
|
50723
|
+
if (outcome.kind === "settled") {
|
|
50724
|
+
runners.push({
|
|
50725
|
+
nodeId,
|
|
50726
|
+
attached: outcome.value
|
|
50727
|
+
});
|
|
50728
|
+
census.push({
|
|
50729
|
+
nodeId,
|
|
50730
|
+
ms,
|
|
50731
|
+
outcome: "reported"
|
|
50732
|
+
});
|
|
50733
|
+
return;
|
|
50734
|
+
}
|
|
50735
|
+
census.push({
|
|
50598
50736
|
nodeId,
|
|
50599
|
-
|
|
50737
|
+
ms,
|
|
50738
|
+
outcome: outcome.kind === "rejected" ? "failed" : "budget-exceeded"
|
|
50600
50739
|
});
|
|
50601
|
-
} catch (err) {
|
|
50602
50740
|
this.deps.logger.debug("placement reconcile skipped node — getLocalCameras failed", {
|
|
50603
50741
|
tags: { nodeId },
|
|
50604
|
-
meta: {
|
|
50742
|
+
meta: {
|
|
50743
|
+
error: outcome.kind === "rejected" ? errMsg(outcome.error) : "budget exceeded",
|
|
50744
|
+
ms
|
|
50745
|
+
}
|
|
50605
50746
|
});
|
|
50606
|
-
}
|
|
50747
|
+
}));
|
|
50748
|
+
const censusMs = Date.now() - censusStartedAt;
|
|
50749
|
+
if (censusMs >= 1e3 || census.some((entry) => entry.outcome !== "reported")) this.deps.logger.info("placement reconcile: runner census was slow", { meta: {
|
|
50750
|
+
censusMs,
|
|
50751
|
+
nodes: census,
|
|
50752
|
+
budgetMs: RECONCILE_NODE_BUDGET_MS
|
|
50753
|
+
} });
|
|
50607
50754
|
const { assignedNodeById, dispatching } = this.deps.ledger.snapshotForReconcile();
|
|
50608
50755
|
const idleSessionDeviceIds = new Set([...this.deps.activeDeviceIds()].filter((deviceId) => this.deps.isIdleSessionCamera(deviceId)));
|
|
50609
50756
|
const motionWatchAttachments = /* @__PURE__ */ new Set();
|
|
@@ -50617,7 +50764,7 @@ var ReconcileController = class ReconcileController {
|
|
|
50617
50764
|
});
|
|
50618
50765
|
if (detach.length > 0) {
|
|
50619
50766
|
this.deps.logger.info("placement reconcile: detaching orphans/duplicates", { meta: { detach } });
|
|
50620
|
-
|
|
50767
|
+
await Promise.all(detach.map(({ nodeId, deviceId }) => this.deps.detachOn(nodeId, deviceId).catch((err) => {
|
|
50621
50768
|
this.deps.logger.warn("placement reconcile: detach failed", {
|
|
50622
50769
|
tags: {
|
|
50623
50770
|
nodeId,
|
|
@@ -50625,7 +50772,7 @@ var ReconcileController = class ReconcileController {
|
|
|
50625
50772
|
},
|
|
50626
50773
|
meta: { error: errMsg(err) }
|
|
50627
50774
|
});
|
|
50628
|
-
});
|
|
50775
|
+
})));
|
|
50629
50776
|
}
|
|
50630
50777
|
if (dropStaleAssignment.length > 0) {
|
|
50631
50778
|
this.deps.logger.info("placement reconcile: dropping stale assignments", { meta: { deviceIds: dropStaleAssignment } });
|
|
@@ -51015,6 +51162,13 @@ var TtlMemo = class {
|
|
|
51015
51162
|
* declared wrong.
|
|
51016
51163
|
*/
|
|
51017
51164
|
generations = /* @__PURE__ */ new Map();
|
|
51165
|
+
/**
|
|
51166
|
+
* The last value ANY read produced for a key, kept across
|
|
51167
|
+
* {@link invalidate}. Never served by {@link get}; it is the bounded fallback
|
|
51168
|
+
* of {@link getWithin} alone (D441) — a caller that would rather balance on a
|
|
51169
|
+
* snapshot a few seconds old than wait seconds for the re-read.
|
|
51170
|
+
*/
|
|
51171
|
+
lastKnown = /* @__PURE__ */ new Map();
|
|
51018
51172
|
ttlMs;
|
|
51019
51173
|
now;
|
|
51020
51174
|
constructor(opts) {
|
|
@@ -51048,6 +51202,50 @@ var TtlMemo = class {
|
|
|
51048
51202
|
}
|
|
51049
51203
|
}
|
|
51050
51204
|
/**
|
|
51205
|
+
* The value for `key` within `budgetMs`: from memory when fresh, from `load`
|
|
51206
|
+
* when the read lands in time, and otherwise the LAST KNOWN value —
|
|
51207
|
+
* invalidated or not — with its age (D441).
|
|
51208
|
+
*
|
|
51209
|
+
* Three properties, each pinned by a test:
|
|
51210
|
+
* - a read that overruns the budget is NOT abandoned: it keeps running and
|
|
51211
|
+
* stores its result for the next caller, so a stale answer is served at
|
|
51212
|
+
* most once per read;
|
|
51213
|
+
* - a COLD key waits for the read past the budget, because a cold cache must
|
|
51214
|
+
* never be the reason a camera fails to dispatch (the `get` contract);
|
|
51215
|
+
* - a read that FAILS with a last-known value in hand serves it as stale.
|
|
51216
|
+
*/
|
|
51217
|
+
async getWithin(key, budgetMs) {
|
|
51218
|
+
const at = this.now();
|
|
51219
|
+
const entry = this.entries.get(key);
|
|
51220
|
+
if (entry) this.entries.set(key, {
|
|
51221
|
+
...entry,
|
|
51222
|
+
usedAt: at
|
|
51223
|
+
});
|
|
51224
|
+
if (entry && at - entry.readAt < this.ttlMs) return {
|
|
51225
|
+
value: entry.value,
|
|
51226
|
+
stale: false,
|
|
51227
|
+
ageMs: at - entry.readAt
|
|
51228
|
+
};
|
|
51229
|
+
const fallback = this.lastKnown.get(key);
|
|
51230
|
+
const pending = this.read(key);
|
|
51231
|
+
if (fallback === void 0) return {
|
|
51232
|
+
value: await pending,
|
|
51233
|
+
stale: false,
|
|
51234
|
+
ageMs: 0
|
|
51235
|
+
};
|
|
51236
|
+
const outcome = await raceBudget(pending, budgetMs);
|
|
51237
|
+
if (outcome.kind === "settled") return {
|
|
51238
|
+
value: outcome.value,
|
|
51239
|
+
stale: false,
|
|
51240
|
+
ageMs: 0
|
|
51241
|
+
};
|
|
51242
|
+
return {
|
|
51243
|
+
value: fallback.value,
|
|
51244
|
+
stale: true,
|
|
51245
|
+
ageMs: this.now() - fallback.readAt
|
|
51246
|
+
};
|
|
51247
|
+
}
|
|
51248
|
+
/**
|
|
51051
51249
|
* Declare the cached answer for `key` WRONG — not merely old.
|
|
51052
51250
|
*
|
|
51053
51251
|
* The caller has just changed the thing being cached (an attach or a detach
|
|
@@ -51092,6 +51290,7 @@ var TtlMemo = class {
|
|
|
51092
51290
|
this.entries.clear();
|
|
51093
51291
|
this.inFlight.clear();
|
|
51094
51292
|
this.generations.clear();
|
|
51293
|
+
this.lastKnown.clear();
|
|
51095
51294
|
}
|
|
51096
51295
|
/** One shared in-flight read per key; stores the result on success. */
|
|
51097
51296
|
read(key) {
|
|
@@ -51099,14 +51298,15 @@ var TtlMemo = class {
|
|
|
51099
51298
|
if (existing) return existing;
|
|
51100
51299
|
const generation = this.generations.get(key) ?? 0;
|
|
51101
51300
|
const pending = this.opts.load(key).then((value) => {
|
|
51102
|
-
if ((this.generations.get(key) ?? 0) !== generation) return value;
|
|
51103
51301
|
const at = this.now();
|
|
51104
|
-
const
|
|
51105
|
-
this.entries.set(key, {
|
|
51302
|
+
const stored = {
|
|
51106
51303
|
value,
|
|
51107
51304
|
readAt: at,
|
|
51108
|
-
usedAt:
|
|
51109
|
-
}
|
|
51305
|
+
usedAt: this.entries.get(key)?.usedAt ?? at
|
|
51306
|
+
};
|
|
51307
|
+
this.lastKnown.set(key, stored);
|
|
51308
|
+
if ((this.generations.get(key) ?? 0) !== generation) return value;
|
|
51309
|
+
this.entries.set(key, stored);
|
|
51110
51310
|
return value;
|
|
51111
51311
|
}).finally(() => {
|
|
51112
51312
|
if (this.inFlight.get(key) === pending) this.inFlight.delete(key);
|
|
@@ -51574,7 +51774,7 @@ var SessionDispatchController = class {
|
|
|
51574
51774
|
return;
|
|
51575
51775
|
}
|
|
51576
51776
|
this.activeRefireCountByDevice.delete(deviceId);
|
|
51577
|
-
await this.dispatchDetectionSession(deviceId, cur, trigger);
|
|
51777
|
+
const timings = await this.dispatchDetectionSession(deviceId, cur, trigger);
|
|
51578
51778
|
if (this.sessionRegistry.has(deviceId)) this.scheduleSessionTeardown(deviceId, cooldownMs);
|
|
51579
51779
|
const doneAt = Date.now();
|
|
51580
51780
|
this.deps.logger.info("session motion → attach latency", {
|
|
@@ -51583,6 +51783,7 @@ var SessionDispatchController = class {
|
|
|
51583
51783
|
...busLagMs !== void 0 ? { busLagMs } : {},
|
|
51584
51784
|
lockWaitMs: lockAcquiredAt - lockRequestedAt,
|
|
51585
51785
|
dispatchMs: doneAt - lockAcquiredAt,
|
|
51786
|
+
...timings,
|
|
51586
51787
|
totalMs: doneAt - receivedAt,
|
|
51587
51788
|
...emittedAt !== void 0 ? { sinceMotionMs: doneAt - emittedAt } : {}
|
|
51588
51789
|
}
|
|
@@ -51615,9 +51816,32 @@ var SessionDispatchController = class {
|
|
|
51615
51816
|
*/
|
|
51616
51817
|
async dispatchDetectionSession(deviceId, config, trigger) {
|
|
51617
51818
|
const log = this.deps.logger.withTags({ deviceId });
|
|
51618
|
-
|
|
51819
|
+
const reconcileStartedAt = Date.now();
|
|
51820
|
+
const census = this.deps.reconcilePlacementFromRunners();
|
|
51821
|
+
const censusOutcome = await raceBudget(census, 500);
|
|
51822
|
+
const reconcileMs = Date.now() - reconcileStartedAt;
|
|
51823
|
+
const reconcileBudgetExceeded = censusOutcome.kind === "budget-exceeded";
|
|
51824
|
+
const dispatchGuard = this.deps.ledger.markDispatching(deviceId);
|
|
51825
|
+
const releaseGuard = () => {
|
|
51826
|
+
if (!reconcileBudgetExceeded) {
|
|
51827
|
+
dispatchGuard.dispose();
|
|
51828
|
+
return;
|
|
51829
|
+
}
|
|
51830
|
+
census.finally(() => dispatchGuard.dispose());
|
|
51831
|
+
};
|
|
51832
|
+
if (reconcileBudgetExceeded) log.warn("detection session: placement census overran its budget — placing without it", { meta: { budgetMs: 500 } });
|
|
51833
|
+
else if (censusOutcome.kind === "rejected") log.warn("detection session: placement census failed — placing on the ledger as it is", { meta: {
|
|
51834
|
+
error: errMsg(censusOutcome.error),
|
|
51835
|
+
reconcileMs
|
|
51836
|
+
} });
|
|
51837
|
+
const balanceStartedAt = Date.now();
|
|
51619
51838
|
const preferredAgent = await this.deps.readPipelinePin(deviceId);
|
|
51620
|
-
const
|
|
51839
|
+
const loadAnswer = await this.agentLoads.getWithin(CLUSTER_KEY, 500);
|
|
51840
|
+
const loads = loadAnswer.value;
|
|
51841
|
+
if (loadAnswer.stale) log.warn("detection session: agent load read overran its budget — balancing on the last known load", { meta: {
|
|
51842
|
+
budgetMs: 500,
|
|
51843
|
+
loadAgeMs: loadAnswer.ageMs
|
|
51844
|
+
} });
|
|
51621
51845
|
const allEligible = this.deps.topology.detectionEligibleNodes(deviceId);
|
|
51622
51846
|
const usableEligible = allEligible.filter((n) => this.deps.isNodeInferenceUsable(n));
|
|
51623
51847
|
const eligible = usableEligible.length > 0 ? usableEligible : allEligible;
|
|
@@ -51631,9 +51855,11 @@ var SessionDispatchController = class {
|
|
|
51631
51855
|
});
|
|
51632
51856
|
if (!decision || decision.kind !== "assigned") {
|
|
51633
51857
|
log.warn("detection session: no eligible node — will retry on next motion", { meta: { reason: decision?.kind === "pending" ? decision.reason : "no-runners-online" } });
|
|
51634
|
-
|
|
51858
|
+
releaseGuard();
|
|
51859
|
+
return null;
|
|
51635
51860
|
}
|
|
51636
51861
|
const targetNodeId = decision.agentNodeId;
|
|
51862
|
+
const balancedAt = Date.now();
|
|
51637
51863
|
const enabledDevices = await this.eligibleInferenceDevices.get(targetNodeId);
|
|
51638
51864
|
const targetLoad = loads.find((n) => n.nodeId === targetNodeId);
|
|
51639
51865
|
const deviceCaps = await this.inferenceDeviceCaps.get(targetNodeId);
|
|
@@ -51663,6 +51889,7 @@ var SessionDispatchController = class {
|
|
|
51663
51889
|
...pinnedDeviceKey ? { pin: pinnedDeviceKey } : {}
|
|
51664
51890
|
}
|
|
51665
51891
|
});
|
|
51892
|
+
const deviceSelectedAt = Date.now();
|
|
51666
51893
|
const pipelineConfig = await this.deps.settingsStore.resolvePipelineForDevice(deviceId, targetNodeId, deviceKey);
|
|
51667
51894
|
const deviceBase = await this.deps.settingsStore.buildNodeInferenceDeviceSteps(targetNodeId, deviceKey ?? "");
|
|
51668
51895
|
const deviceOverride = deviceKey ? await this.deps.settingsStore.readCameraDeviceOverride(deviceId, targetNodeId, deviceKey) : {};
|
|
@@ -51698,9 +51925,11 @@ var SessionDispatchController = class {
|
|
|
51698
51925
|
preRollInferenceEnabled: config.preRollInferenceEnabled,
|
|
51699
51926
|
preRollInferenceFrames: config.preRollInferenceFrames
|
|
51700
51927
|
};
|
|
51701
|
-
const
|
|
51928
|
+
const configuredAt = Date.now();
|
|
51929
|
+
let attachedAt = configuredAt;
|
|
51702
51930
|
try {
|
|
51703
51931
|
await this.deps.attachOn(targetNodeId, sessionConfig);
|
|
51932
|
+
attachedAt = Date.now();
|
|
51704
51933
|
this.deps.recordAssignment(deviceId, targetNodeId, decision.reason, decision.reason === "manual");
|
|
51705
51934
|
this.sessionRegistry.register(deviceId, targetNodeId, Date.now(), deviceKey);
|
|
51706
51935
|
log.info("detection session: dispatched on motion", {
|
|
@@ -51716,9 +51945,18 @@ var SessionDispatchController = class {
|
|
|
51716
51945
|
}
|
|
51717
51946
|
});
|
|
51718
51947
|
} finally {
|
|
51719
|
-
|
|
51948
|
+
releaseGuard();
|
|
51720
51949
|
this.notePlacementChanged();
|
|
51721
51950
|
}
|
|
51951
|
+
return {
|
|
51952
|
+
reconcileMs,
|
|
51953
|
+
reconcileBudgetExceeded,
|
|
51954
|
+
balanceMs: balancedAt - balanceStartedAt,
|
|
51955
|
+
balanceLoadStale: loadAnswer.stale,
|
|
51956
|
+
deviceSelectMs: deviceSelectedAt - balancedAt,
|
|
51957
|
+
configMs: configuredAt - deviceSelectedAt,
|
|
51958
|
+
attachMs: attachedAt - configuredAt
|
|
51959
|
+
};
|
|
51722
51960
|
}
|
|
51723
51961
|
/**
|
|
51724
51962
|
* Schedule a session teardown after `cooldownMs` of no rising-edge motion.
|
package/dist/remoteEntry.js
CHANGED
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-B944RKiT.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline-orchestrator",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.182",
|
|
4
4
|
"description": "Hub-side camera-to-agent load balancer — tracks runner capacity and dispatches attachCamera calls to the optimal pipeline-runner instance",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|