@camstack/system 1.0.7 → 1.1.0
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-runner.js +3 -3
- package/dist/addon-runner.mjs +3 -3
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +8 -8
- package/dist/builtins/device-manager/device-manager.addon.mjs +8 -8
- package/dist/builtins/platform-probe/index.js +27 -139
- package/dist/builtins/platform-probe/index.mjs +28 -140
- package/dist/builtins/platform-probe/platform-scorer.d.ts +17 -10
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
- package/dist/index.js +153 -78
- package/dist/index.mjs +153 -79
- package/dist/kernel/addon-installer.d.ts +20 -0
- package/dist/kernel/config-manager.d.ts +4 -4
- package/dist/kernel/fs-utils.d.ts +16 -6
- package/dist/kernel/index.d.ts +2 -1
- package/dist/kernel/moleculer/addon-deploy-source.d.ts +16 -0
- package/dist/kernel/transport/child-cap-protocol.d.ts +10 -0
- package/dist/{manifest-python-deps-eBDj5HEY.js → manifest-python-deps-BWURo7dc.js} +34 -17
- package/dist/{manifest-python-deps-CoJXeb9u.mjs → manifest-python-deps-BcrTzHH_.mjs} +54 -37
- package/dist/{model-download-service-JtVQtbb6.js → model-download-service-1eEOkNeS.js} +35 -5
- package/dist/{model-download-service-C7AjBsX9.mjs → model-download-service-RxAOiYvX.mjs} +35 -5
- package/package.json +1 -1
|
@@ -23,6 +23,14 @@ export interface CapCallOutMessage {
|
|
|
23
23
|
readonly method: string;
|
|
24
24
|
readonly args: unknown;
|
|
25
25
|
readonly deviceId?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Optional per-call node pin (out-of-band; NOT part of the validated method
|
|
28
|
+
* args). When set, the parent routes this singleton/in-process cap call to
|
|
29
|
+
* the named node's provider instead of the default (hub) resolution — used to
|
|
30
|
+
* query a remote agent's in-process singleton (e.g. the agent's own
|
|
31
|
+
* `platform-probe` hardware). Set via `ctx.api`'s `onNode(nodeId)` modifier.
|
|
32
|
+
*/
|
|
33
|
+
readonly nodeId?: string;
|
|
26
34
|
}
|
|
27
35
|
/** Child → parent: a system event produced by the child to be forwarded to the hub event bus. */
|
|
28
36
|
export interface ChildEventMessage {
|
|
@@ -59,6 +67,8 @@ export interface CapCallMessage {
|
|
|
59
67
|
readonly method: string;
|
|
60
68
|
readonly args: unknown;
|
|
61
69
|
readonly deviceId?: number;
|
|
70
|
+
/** Optional per-call node pin (out-of-band routing hint; see {@link CapCallOutMessage.nodeId}). */
|
|
71
|
+
readonly nodeId?: string;
|
|
62
72
|
}
|
|
63
73
|
/** Parent → child: a system event delivered by the hub for the child to handle locally. */
|
|
64
74
|
export interface ParentEventMessage {
|
|
@@ -5,6 +5,7 @@ let node_fs = require("node:fs");
|
|
|
5
5
|
node_fs = require_chunk.__toESM(node_fs);
|
|
6
6
|
let node_path = require("node:path");
|
|
7
7
|
node_path = require_chunk.__toESM(node_path);
|
|
8
|
+
let _camstack_types = require("@camstack/types");
|
|
8
9
|
let node_crypto = require("node:crypto");
|
|
9
10
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
10
11
|
let node_child_process = require("node:child_process");
|
|
@@ -3647,7 +3648,8 @@ var LocalChildRegistry = class {
|
|
|
3647
3648
|
capName: out.capName,
|
|
3648
3649
|
method: out.method,
|
|
3649
3650
|
args: out.args,
|
|
3650
|
-
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {}
|
|
3651
|
+
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {},
|
|
3652
|
+
...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {}
|
|
3651
3653
|
};
|
|
3652
3654
|
if (this.resolveChildId(out.capName, out.deviceId) !== null) {
|
|
3653
3655
|
if (!this.egressRoutedCaps.has(out.capName)) {
|
|
@@ -3892,7 +3894,8 @@ var LocalChildClient = class {
|
|
|
3892
3894
|
capName: input.capName,
|
|
3893
3895
|
method: input.method,
|
|
3894
3896
|
args: input.args,
|
|
3895
|
-
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {}
|
|
3897
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
3898
|
+
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
|
|
3896
3899
|
};
|
|
3897
3900
|
return this.channel.request(msg);
|
|
3898
3901
|
}
|
|
@@ -4586,7 +4589,10 @@ function createParentUnownedCallHandler(deps) {
|
|
|
4586
4589
|
const deviceId = input.deviceId ?? extractDeviceId(input.args);
|
|
4587
4590
|
const resolver = deps.getResolver();
|
|
4588
4591
|
if (resolver !== null) try {
|
|
4589
|
-
const route = resolver.resolveCapRoute(input.capName,
|
|
4592
|
+
const route = resolver.resolveCapRoute(input.capName, {
|
|
4593
|
+
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {},
|
|
4594
|
+
...deviceId !== void 0 ? { deviceId } : {}
|
|
4595
|
+
});
|
|
4590
4596
|
return await resolver.dispatch(route, input.method, input.args);
|
|
4591
4597
|
} catch (err) {
|
|
4592
4598
|
if (!(err instanceof CapRouteError) || err.reason !== "no-provider") throw err;
|
|
@@ -4623,7 +4629,7 @@ function createParentUnownedCallHandler(deps) {
|
|
|
4623
4629
|
return await brokerCallForCap(deps.broker, input.capName, input.method, input.args);
|
|
4624
4630
|
} catch (cause) {
|
|
4625
4631
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
4626
|
-
throw new Error(`parent could not route child unowned cap call "${input.capName}.${input.method}": ${reason}`,
|
|
4632
|
+
throw new Error(`parent could not route child unowned cap call "${input.capName}.${input.method}": ${reason}`, { cause });
|
|
4627
4633
|
}
|
|
4628
4634
|
};
|
|
4629
4635
|
}
|
|
@@ -5031,11 +5037,13 @@ function ipcParentLink(getCallOut) {
|
|
|
5031
5037
|
const input = op.input;
|
|
5032
5038
|
const inputObj = input && typeof input === "object" && !Array.isArray(input) ? input : null;
|
|
5033
5039
|
const deviceId = inputObj && typeof Reflect.get(inputObj, "deviceId") === "number" ? Reflect.get(inputObj, "deviceId") : void 0;
|
|
5040
|
+
const pinnedNodeId = (0, _camstack_types.readNodePin)(op.context);
|
|
5034
5041
|
const capCallInput = {
|
|
5035
5042
|
capName: parsed.capName,
|
|
5036
5043
|
method: parsed.method,
|
|
5037
5044
|
args: op.input,
|
|
5038
|
-
...deviceId !== void 0 ? { deviceId } : {}
|
|
5045
|
+
...deviceId !== void 0 ? { deviceId } : {},
|
|
5046
|
+
...pinnedNodeId !== void 0 ? { nodeId: pinnedNodeId } : {}
|
|
5039
5047
|
};
|
|
5040
5048
|
return observable((observer) => {
|
|
5041
5049
|
callOut(capCallInput).then((data) => {
|
|
@@ -5100,7 +5108,7 @@ var CapUsageRegistry = class {
|
|
|
5100
5108
|
let window = byCap.get(rec.capName);
|
|
5101
5109
|
if (!window) {
|
|
5102
5110
|
window = {
|
|
5103
|
-
buckets:
|
|
5111
|
+
buckets: Array.from({ length: this.retentionSeconds }, () => 0),
|
|
5104
5112
|
lastCallAtMs: 0
|
|
5105
5113
|
};
|
|
5106
5114
|
byCap.set(rec.capName, window);
|
|
@@ -6277,16 +6285,25 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
|
|
|
6277
6285
|
};
|
|
6278
6286
|
const writeBlob = async (coll, key, value, namespace) => {
|
|
6279
6287
|
if (!settingsApi) return;
|
|
6280
|
-
|
|
6281
|
-
namespace
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6288
|
+
try {
|
|
6289
|
+
await settingsApi.set.mutate(namespace !== void 0 ? {
|
|
6290
|
+
namespace,
|
|
6291
|
+
collection: coll,
|
|
6292
|
+
key,
|
|
6293
|
+
value
|
|
6294
|
+
} : {
|
|
6295
|
+
collection: coll,
|
|
6296
|
+
key,
|
|
6297
|
+
value
|
|
6298
|
+
});
|
|
6299
|
+
} catch (err) {
|
|
6300
|
+
const isTrpcError = err instanceof _trpc_client.TRPCClientError;
|
|
6301
|
+
scopedLogger.warn(`settings write failed (${coll}/${key}) — ${isTrpcError ? "transport error" : "store error"}`, { meta: {
|
|
6302
|
+
collection: coll,
|
|
6303
|
+
key,
|
|
6304
|
+
error: (0, _camstack_types_addon.errMsg)(err)
|
|
6305
|
+
} });
|
|
6306
|
+
}
|
|
6290
6307
|
};
|
|
6291
6308
|
const rmwChains = /* @__PURE__ */ new Map();
|
|
6292
6309
|
const serializeRmw = (coll, key, op) => {
|
|
@@ -6295,7 +6312,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
|
|
|
6295
6312
|
rmwChains.set(chainKey, next);
|
|
6296
6313
|
next.finally(() => {
|
|
6297
6314
|
if (rmwChains.get(chainKey) === next) rmwChains.delete(chainKey);
|
|
6298
|
-
});
|
|
6315
|
+
}).catch(() => void 0);
|
|
6299
6316
|
return next;
|
|
6300
6317
|
};
|
|
6301
6318
|
const settingsView = {
|
|
@@ -4,6 +4,7 @@ import { createServer } from "node:http";
|
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path$1 from "node:path";
|
|
6
6
|
import { isAbsolute, join } from "node:path";
|
|
7
|
+
import { readNodePin } from "@camstack/types";
|
|
7
8
|
import * as crypto$1 from "node:crypto";
|
|
8
9
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
9
10
|
import { execFile } from "node:child_process";
|
|
@@ -11,7 +12,7 @@ import { promisify } from "node:util";
|
|
|
11
12
|
import * as os from "node:os";
|
|
12
13
|
import { tmpdir } from "node:os";
|
|
13
14
|
import { unlink } from "node:fs/promises";
|
|
14
|
-
import { DATAPLANE_SECRET_HEADER, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, scopeKey, sleep } from "@camstack/types/addon";
|
|
15
|
+
import { DATAPLANE_SECRET_HEADER as DATAPLANE_SECRET_HEADER$1, DeviceType as DeviceType$1, DisposerChain, EventCategory as EventCategory$1, ReadinessRegistry, asJsonObject as asJsonObject$1, asString as asString$1, createDeviceProxy, deviceOpsCapability, emitReadiness, errMsg as errMsg$1, expandCapMethods, scopeKey, sleep as sleep$1 } from "@camstack/types/addon";
|
|
15
16
|
import { TRPCClientError, createTRPCClient } from "@trpc/client";
|
|
16
17
|
import { connect, createServer as createServer$1 } from "node:net";
|
|
17
18
|
//#region src/kernel/addon-class-resolver.ts
|
|
@@ -102,7 +103,7 @@ async function installManifestNativeDeps(addonDir, pkgRaw, logger, registry) {
|
|
|
102
103
|
} catch (err) {
|
|
103
104
|
logger.warn("Failed to write native deps marker", { meta: {
|
|
104
105
|
markerFile,
|
|
105
|
-
error: errMsg(err)
|
|
106
|
+
error: errMsg$1(err)
|
|
106
107
|
} });
|
|
107
108
|
}
|
|
108
109
|
return;
|
|
@@ -129,7 +130,7 @@ async function installManifestNativeDeps(addonDir, pkgRaw, logger, registry) {
|
|
|
129
130
|
timeout: 3e5
|
|
130
131
|
});
|
|
131
132
|
} catch (err) {
|
|
132
|
-
throw new Error(`npm install of native deps failed for ${addonDir}: ${errMsg(err)}`, { cause: err });
|
|
133
|
+
throw new Error(`npm install of native deps failed for ${addonDir}: ${errMsg$1(err)}`, { cause: err });
|
|
133
134
|
}
|
|
134
135
|
await rebuildNativeDeps(addonDir, pending.map(([name]) => name), logger);
|
|
135
136
|
try {
|
|
@@ -137,7 +138,7 @@ async function installManifestNativeDeps(addonDir, pkgRaw, logger, registry) {
|
|
|
137
138
|
} catch (err) {
|
|
138
139
|
logger.warn("Failed to write native deps marker", { meta: {
|
|
139
140
|
markerFile,
|
|
140
|
-
error: errMsg(err)
|
|
141
|
+
error: errMsg$1(err)
|
|
141
142
|
} });
|
|
142
143
|
}
|
|
143
144
|
}
|
|
@@ -174,7 +175,7 @@ function copyPrebuiltNativeDep(name, addonDir, logger) {
|
|
|
174
175
|
logger.warn("Prebuilt native dep copy failed — will try npm install", { meta: {
|
|
175
176
|
name,
|
|
176
177
|
source,
|
|
177
|
-
error: errMsg(err)
|
|
178
|
+
error: errMsg$1(err)
|
|
178
179
|
} });
|
|
179
180
|
}
|
|
180
181
|
}
|
|
@@ -207,13 +208,13 @@ function candidateHoistedDirs(name, addonDir) {
|
|
|
207
208
|
}
|
|
208
209
|
/** Read & validate `camstack.nativeDependencies`. Returns null when absent. */
|
|
209
210
|
function readNativeDeps(pkgRaw) {
|
|
210
|
-
const camstack = asJsonObject(pkgRaw["camstack"]);
|
|
211
|
+
const camstack = asJsonObject$1(pkgRaw["camstack"]);
|
|
211
212
|
if (!camstack) return null;
|
|
212
|
-
const native = asJsonObject(camstack["nativeDependencies"]);
|
|
213
|
+
const native = asJsonObject$1(camstack["nativeDependencies"]);
|
|
213
214
|
if (!native) return null;
|
|
214
215
|
const out = {};
|
|
215
216
|
for (const [k, v] of Object.entries(native)) {
|
|
216
|
-
const range = asString(v);
|
|
217
|
+
const range = asString$1(v);
|
|
217
218
|
if (range) out[k] = range;
|
|
218
219
|
}
|
|
219
220
|
return Object.keys(out).length > 0 ? out : null;
|
|
@@ -280,7 +281,7 @@ async function rebuildNativeDeps(addonDir, packageNames, logger) {
|
|
|
280
281
|
} catch (err) {
|
|
281
282
|
logger.warn("Electron rebuild failed (continuing — prebuilt binary may be present)", { meta: {
|
|
282
283
|
addonDir,
|
|
283
|
-
error: errMsg(err)
|
|
284
|
+
error: errMsg$1(err)
|
|
284
285
|
} });
|
|
285
286
|
}
|
|
286
287
|
return;
|
|
@@ -298,7 +299,7 @@ async function rebuildNativeDeps(addonDir, packageNames, logger) {
|
|
|
298
299
|
} catch (err) {
|
|
299
300
|
logger.warn("npm rebuild failed (continuing — prebuilt binary may be present)", { meta: {
|
|
300
301
|
addonDir,
|
|
301
|
-
error: errMsg(err)
|
|
302
|
+
error: errMsg$1(err)
|
|
302
303
|
} });
|
|
303
304
|
}
|
|
304
305
|
}
|
|
@@ -1031,7 +1032,7 @@ async function callWithServiceDiscoveryRetry(broker, actionName, input) {
|
|
|
1031
1032
|
if (!(err instanceof Error ? err.message : String(err)).includes("is not found")) throw err;
|
|
1032
1033
|
attempt++;
|
|
1033
1034
|
if (attempt === 1) broker.logger.warn(`native-cap-bridge: service not found for "${actionName}", retrying with exponential backoff…`);
|
|
1034
|
-
await sleep(delay);
|
|
1035
|
+
await sleep$1(delay);
|
|
1035
1036
|
delay = Math.min(delay * 2, BACKOFF_MAX_MS);
|
|
1036
1037
|
}
|
|
1037
1038
|
}
|
|
@@ -1136,7 +1137,7 @@ function createBrokerDeviceManagerApi(opts) {
|
|
|
1136
1137
|
parentDeviceId,
|
|
1137
1138
|
logger: (() => {
|
|
1138
1139
|
const base = opts.logger.child(stableId);
|
|
1139
|
-
const containerDeviceId = parentDeviceId ?? (deviceMeta?.type === DeviceType.Container ? id : null);
|
|
1140
|
+
const containerDeviceId = parentDeviceId ?? (deviceMeta?.type === DeviceType$1.Container ? id : null);
|
|
1140
1141
|
return base.withTags(containerDeviceId !== null ? {
|
|
1141
1142
|
deviceId: id,
|
|
1142
1143
|
containerDeviceId
|
|
@@ -1162,7 +1163,7 @@ function createBrokerDeviceManagerApi(opts) {
|
|
|
1162
1163
|
id,
|
|
1163
1164
|
stableId,
|
|
1164
1165
|
addonId,
|
|
1165
|
-
type: DeviceType.Camera,
|
|
1166
|
+
type: DeviceType$1.Camera,
|
|
1166
1167
|
name: stableId,
|
|
1167
1168
|
location: null,
|
|
1168
1169
|
disabled: false,
|
|
@@ -1228,7 +1229,7 @@ function createBrokerDeviceManagerApi(opts) {
|
|
|
1228
1229
|
type: "device",
|
|
1229
1230
|
id
|
|
1230
1231
|
},
|
|
1231
|
-
category: EventCategory.DeviceBindingsChanged,
|
|
1232
|
+
category: EventCategory$1.DeviceBindingsChanged,
|
|
1232
1233
|
data: {
|
|
1233
1234
|
deviceId: id,
|
|
1234
1235
|
capName: cap.name,
|
|
@@ -1612,7 +1613,7 @@ function createBrokerDeviceManagerApi(opts) {
|
|
|
1612
1613
|
type: "device",
|
|
1613
1614
|
id: device.id
|
|
1614
1615
|
},
|
|
1615
|
-
category: EventCategory.DeviceReady,
|
|
1616
|
+
category: EventCategory$1.DeviceReady,
|
|
1616
1617
|
data: { deviceId: device.id }
|
|
1617
1618
|
});
|
|
1618
1619
|
};
|
|
@@ -1751,7 +1752,7 @@ function createBrokerDeviceManagerApi(opts) {
|
|
|
1751
1752
|
type: "device",
|
|
1752
1753
|
id: deviceId
|
|
1753
1754
|
},
|
|
1754
|
-
category: EventCategory.DeviceBindingsChanged,
|
|
1755
|
+
category: EventCategory$1.DeviceBindingsChanged,
|
|
1755
1756
|
data: {
|
|
1756
1757
|
deviceId,
|
|
1757
1758
|
capName,
|
|
@@ -3646,7 +3647,8 @@ var LocalChildRegistry = class {
|
|
|
3646
3647
|
capName: out.capName,
|
|
3647
3648
|
method: out.method,
|
|
3648
3649
|
args: out.args,
|
|
3649
|
-
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {}
|
|
3650
|
+
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {},
|
|
3651
|
+
...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {}
|
|
3650
3652
|
};
|
|
3651
3653
|
if (this.resolveChildId(out.capName, out.deviceId) !== null) {
|
|
3652
3654
|
if (!this.egressRoutedCaps.has(out.capName)) {
|
|
@@ -3891,7 +3893,8 @@ var LocalChildClient = class {
|
|
|
3891
3893
|
capName: input.capName,
|
|
3892
3894
|
method: input.method,
|
|
3893
3895
|
args: input.args,
|
|
3894
|
-
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {}
|
|
3896
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
3897
|
+
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
|
|
3895
3898
|
};
|
|
3896
3899
|
return this.channel.request(msg);
|
|
3897
3900
|
}
|
|
@@ -4585,7 +4588,10 @@ function createParentUnownedCallHandler(deps) {
|
|
|
4585
4588
|
const deviceId = input.deviceId ?? extractDeviceId(input.args);
|
|
4586
4589
|
const resolver = deps.getResolver();
|
|
4587
4590
|
if (resolver !== null) try {
|
|
4588
|
-
const route = resolver.resolveCapRoute(input.capName,
|
|
4591
|
+
const route = resolver.resolveCapRoute(input.capName, {
|
|
4592
|
+
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {},
|
|
4593
|
+
...deviceId !== void 0 ? { deviceId } : {}
|
|
4594
|
+
});
|
|
4589
4595
|
return await resolver.dispatch(route, input.method, input.args);
|
|
4590
4596
|
} catch (err) {
|
|
4591
4597
|
if (!(err instanceof CapRouteError) || err.reason !== "no-provider") throw err;
|
|
@@ -4622,7 +4628,7 @@ function createParentUnownedCallHandler(deps) {
|
|
|
4622
4628
|
return await brokerCallForCap(deps.broker, input.capName, input.method, input.args);
|
|
4623
4629
|
} catch (cause) {
|
|
4624
4630
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
4625
|
-
throw new Error(`parent could not route child unowned cap call "${input.capName}.${input.method}": ${reason}`,
|
|
4631
|
+
throw new Error(`parent could not route child unowned cap call "${input.capName}.${input.method}": ${reason}`, { cause });
|
|
4626
4632
|
}
|
|
4627
4633
|
};
|
|
4628
4634
|
}
|
|
@@ -5030,11 +5036,13 @@ function ipcParentLink(getCallOut) {
|
|
|
5030
5036
|
const input = op.input;
|
|
5031
5037
|
const inputObj = input && typeof input === "object" && !Array.isArray(input) ? input : null;
|
|
5032
5038
|
const deviceId = inputObj && typeof Reflect.get(inputObj, "deviceId") === "number" ? Reflect.get(inputObj, "deviceId") : void 0;
|
|
5039
|
+
const pinnedNodeId = readNodePin(op.context);
|
|
5033
5040
|
const capCallInput = {
|
|
5034
5041
|
capName: parsed.capName,
|
|
5035
5042
|
method: parsed.method,
|
|
5036
5043
|
args: op.input,
|
|
5037
|
-
...deviceId !== void 0 ? { deviceId } : {}
|
|
5044
|
+
...deviceId !== void 0 ? { deviceId } : {},
|
|
5045
|
+
...pinnedNodeId !== void 0 ? { nodeId: pinnedNodeId } : {}
|
|
5038
5046
|
};
|
|
5039
5047
|
return observable((observer) => {
|
|
5040
5048
|
callOut(capCallInput).then((data) => {
|
|
@@ -5099,7 +5107,7 @@ var CapUsageRegistry = class {
|
|
|
5099
5107
|
let window = byCap.get(rec.capName);
|
|
5100
5108
|
if (!window) {
|
|
5101
5109
|
window = {
|
|
5102
|
-
buckets:
|
|
5110
|
+
buckets: Array.from({ length: this.retentionSeconds }, () => 0),
|
|
5103
5111
|
lastCallAtMs: 0
|
|
5104
5112
|
};
|
|
5105
5113
|
byCap.set(rec.capName, window);
|
|
@@ -5319,7 +5327,7 @@ function createAddonDataPlaneFacility(args) {
|
|
|
5319
5327
|
let server = null;
|
|
5320
5328
|
let baseUrl = "";
|
|
5321
5329
|
const route = (req, res) => {
|
|
5322
|
-
if (req.headers[DATAPLANE_SECRET_HEADER] !== secret) {
|
|
5330
|
+
if (req.headers[DATAPLANE_SECRET_HEADER$1] !== secret) {
|
|
5323
5331
|
res.writeHead(403).end();
|
|
5324
5332
|
return;
|
|
5325
5333
|
}
|
|
@@ -5438,7 +5446,7 @@ function resolveNodeRoot() {
|
|
|
5438
5446
|
var inFlight = /* @__PURE__ */ new Map();
|
|
5439
5447
|
var LOCK_STALE_MS = 10 * 6e4;
|
|
5440
5448
|
var LOCK_POLL_MS = 500;
|
|
5441
|
-
function sleep$
|
|
5449
|
+
function sleep$2(ms) {
|
|
5442
5450
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5443
5451
|
}
|
|
5444
5452
|
/**
|
|
@@ -5457,7 +5465,7 @@ async function withFileLock(lockPath, fn) {
|
|
|
5457
5465
|
} catch {
|
|
5458
5466
|
try {
|
|
5459
5467
|
if (Date.now() - fs.statSync(lockPath).mtimeMs > LOCK_STALE_MS) fs.rmSync(lockPath, { force: true });
|
|
5460
|
-
else await sleep$
|
|
5468
|
+
else await sleep$2(LOCK_POLL_MS);
|
|
5461
5469
|
} catch {}
|
|
5462
5470
|
}
|
|
5463
5471
|
try {
|
|
@@ -6249,7 +6257,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
|
|
|
6249
6257
|
} });
|
|
6250
6258
|
registerWorkerDisposerChain(nodeId, addonId, workerDisposerChain);
|
|
6251
6259
|
const bindingCache = /* @__PURE__ */ new Map();
|
|
6252
|
-
scopedEventBus.subscribe({ category: EventCategory.DeviceBindingsChanged }, (e) => {
|
|
6260
|
+
scopedEventBus.subscribe({ category: EventCategory$1.DeviceBindingsChanged }, (e) => {
|
|
6253
6261
|
const data = e.data ?? {};
|
|
6254
6262
|
if (typeof data.deviceId === "number") {
|
|
6255
6263
|
bindingCache.delete(data.deviceId);
|
|
@@ -6276,16 +6284,25 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
|
|
|
6276
6284
|
};
|
|
6277
6285
|
const writeBlob = async (coll, key, value, namespace) => {
|
|
6278
6286
|
if (!settingsApi) return;
|
|
6279
|
-
|
|
6280
|
-
namespace
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6287
|
+
try {
|
|
6288
|
+
await settingsApi.set.mutate(namespace !== void 0 ? {
|
|
6289
|
+
namespace,
|
|
6290
|
+
collection: coll,
|
|
6291
|
+
key,
|
|
6292
|
+
value
|
|
6293
|
+
} : {
|
|
6294
|
+
collection: coll,
|
|
6295
|
+
key,
|
|
6296
|
+
value
|
|
6297
|
+
});
|
|
6298
|
+
} catch (err) {
|
|
6299
|
+
const isTrpcError = err instanceof TRPCClientError;
|
|
6300
|
+
scopedLogger.warn(`settings write failed (${coll}/${key}) — ${isTrpcError ? "transport error" : "store error"}`, { meta: {
|
|
6301
|
+
collection: coll,
|
|
6302
|
+
key,
|
|
6303
|
+
error: errMsg$1(err)
|
|
6304
|
+
} });
|
|
6305
|
+
}
|
|
6289
6306
|
};
|
|
6290
6307
|
const rmwChains = /* @__PURE__ */ new Map();
|
|
6291
6308
|
const serializeRmw = (coll, key, op) => {
|
|
@@ -6294,7 +6311,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
|
|
|
6294
6311
|
rmwChains.set(chainKey, next);
|
|
6295
6312
|
next.finally(() => {
|
|
6296
6313
|
if (rmwChains.get(chainKey) === next) rmwChains.delete(chainKey);
|
|
6297
|
-
});
|
|
6314
|
+
}).catch(() => void 0);
|
|
6298
6315
|
return next;
|
|
6299
6316
|
};
|
|
6300
6317
|
const settingsView = {
|
|
@@ -301,6 +301,24 @@ function createFileDataPlaneHandler(opts) {
|
|
|
301
301
|
}
|
|
302
302
|
//#endregion
|
|
303
303
|
//#region src/download/model-downloader.ts
|
|
304
|
+
function isNonEmptyFile(filePath) {
|
|
305
|
+
return node_fs.existsSync(filePath) && node_fs.statSync(filePath).size > 0;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Sibling files of a single-file (non-directory) format — extra files the
|
|
309
|
+
* format needs, fetched from the same remote directory as `url` and stored
|
|
310
|
+
* flat alongside the main file in `modelsDir`. Catalog-declared via
|
|
311
|
+
* `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
|
|
312
|
+
* `.xml`. The downloader stays format-agnostic; the convention lives in the
|
|
313
|
+
* catalog data (like `MLPACKAGE_FILES` for the directory case).
|
|
314
|
+
*/
|
|
315
|
+
function siblingFilesFor(formatEntry) {
|
|
316
|
+
return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
|
|
317
|
+
}
|
|
318
|
+
/** Resolve a sibling's remote URL relative to the main file's directory. */
|
|
319
|
+
function siblingUrl(mainUrl, sibling) {
|
|
320
|
+
return mainUrl.replace(/[^/]+$/, sibling);
|
|
321
|
+
}
|
|
304
322
|
/** Build fetch headers, including HF auth token for huggingface.co URLs */
|
|
305
323
|
function buildHeaders(url) {
|
|
306
324
|
const headers = { "User-Agent": "CamStack/1.0" };
|
|
@@ -466,14 +484,18 @@ async function ensureModel(modelsDir, entry, format, onProgress) {
|
|
|
466
484
|
if (entry.extraFiles) for (const extra of entry.extraFiles) await downloadFile(extra.url, node_path.join(modelsDir, extra.filename));
|
|
467
485
|
const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
|
|
468
486
|
const modelPath = node_path.join(modelsDir, filename);
|
|
487
|
+
const siblings = siblingFilesFor(formatEntry);
|
|
469
488
|
if (node_fs.existsSync(modelPath)) if (formatEntry.isDirectory && !node_fs.existsSync(node_path.join(modelPath, "Manifest.json"))) node_fs.rmSync(modelPath, {
|
|
470
489
|
recursive: true,
|
|
471
490
|
force: true
|
|
472
491
|
});
|
|
473
|
-
else return modelPath;
|
|
492
|
+
else if (siblings.some((f) => !isNonEmptyFile(node_path.join(modelsDir, f)))) {} else return modelPath;
|
|
474
493
|
node_fs.mkdirSync(modelsDir, { recursive: true });
|
|
475
494
|
if (formatEntry.isDirectory) await downloadDirectory(formatEntry.url, modelPath, formatEntry.files, onProgress);
|
|
476
|
-
else
|
|
495
|
+
else {
|
|
496
|
+
await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
|
|
497
|
+
for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), node_path.join(modelsDir, sibling));
|
|
498
|
+
}
|
|
477
499
|
return modelPath;
|
|
478
500
|
}
|
|
479
501
|
/** Compute the on-disk path for a given model + format, even when not yet downloaded. */
|
|
@@ -490,17 +512,25 @@ function isModelDownloaded(modelsDir, entry, format) {
|
|
|
490
512
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
491
513
|
if (!modelPath || !node_fs.existsSync(modelPath)) return false;
|
|
492
514
|
if (formatEntry.isDirectory) return node_fs.existsSync(node_path.join(modelPath, "Manifest.json"));
|
|
493
|
-
|
|
515
|
+
if (node_fs.statSync(modelPath).size <= 0) return false;
|
|
516
|
+
return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(node_path.join(modelsDir, f)));
|
|
494
517
|
}
|
|
495
518
|
/** Remove the on-disk model file/directory. Returns true if something was deleted. */
|
|
496
519
|
function deleteModelFromDisk(modelsDir, entry, format) {
|
|
497
520
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
498
521
|
if (!modelPath || !node_fs.existsSync(modelPath)) return false;
|
|
499
|
-
|
|
522
|
+
const formatEntry = entry.formats[format];
|
|
523
|
+
if (formatEntry?.isDirectory) node_fs.rmSync(modelPath, {
|
|
500
524
|
recursive: true,
|
|
501
525
|
force: true
|
|
502
526
|
});
|
|
503
|
-
else
|
|
527
|
+
else {
|
|
528
|
+
node_fs.unlinkSync(modelPath);
|
|
529
|
+
if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
|
|
530
|
+
const sibPath = node_path.join(modelsDir, sibling);
|
|
531
|
+
if (node_fs.existsSync(sibPath)) node_fs.unlinkSync(sibPath);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
504
534
|
return true;
|
|
505
535
|
}
|
|
506
536
|
//#endregion
|
|
@@ -300,6 +300,24 @@ function createFileDataPlaneHandler(opts) {
|
|
|
300
300
|
}
|
|
301
301
|
//#endregion
|
|
302
302
|
//#region src/download/model-downloader.ts
|
|
303
|
+
function isNonEmptyFile(filePath) {
|
|
304
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Sibling files of a single-file (non-directory) format — extra files the
|
|
308
|
+
* format needs, fetched from the same remote directory as `url` and stored
|
|
309
|
+
* flat alongside the main file in `modelsDir`. Catalog-declared via
|
|
310
|
+
* `formatEntry.files`, e.g. OpenVINO IR lists its `.bin` weights next to the
|
|
311
|
+
* `.xml`. The downloader stays format-agnostic; the convention lives in the
|
|
312
|
+
* catalog data (like `MLPACKAGE_FILES` for the directory case).
|
|
313
|
+
*/
|
|
314
|
+
function siblingFilesFor(formatEntry) {
|
|
315
|
+
return formatEntry.isDirectory ? [] : formatEntry.files ?? [];
|
|
316
|
+
}
|
|
317
|
+
/** Resolve a sibling's remote URL relative to the main file's directory. */
|
|
318
|
+
function siblingUrl(mainUrl, sibling) {
|
|
319
|
+
return mainUrl.replace(/[^/]+$/, sibling);
|
|
320
|
+
}
|
|
303
321
|
/** Build fetch headers, including HF auth token for huggingface.co URLs */
|
|
304
322
|
function buildHeaders(url) {
|
|
305
323
|
const headers = { "User-Agent": "CamStack/1.0" };
|
|
@@ -465,14 +483,18 @@ async function ensureModel(modelsDir, entry, format, onProgress) {
|
|
|
465
483
|
if (entry.extraFiles) for (const extra of entry.extraFiles) await downloadFile(extra.url, path$1.join(modelsDir, extra.filename));
|
|
466
484
|
const filename = formatEntry.url.split("/").pop() ?? `${entry.id}.${format}`;
|
|
467
485
|
const modelPath = path$1.join(modelsDir, filename);
|
|
486
|
+
const siblings = siblingFilesFor(formatEntry);
|
|
468
487
|
if (fs.existsSync(modelPath)) if (formatEntry.isDirectory && !fs.existsSync(path$1.join(modelPath, "Manifest.json"))) fs.rmSync(modelPath, {
|
|
469
488
|
recursive: true,
|
|
470
489
|
force: true
|
|
471
490
|
});
|
|
472
|
-
else return modelPath;
|
|
491
|
+
else if (siblings.some((f) => !isNonEmptyFile(path$1.join(modelsDir, f)))) {} else return modelPath;
|
|
473
492
|
fs.mkdirSync(modelsDir, { recursive: true });
|
|
474
493
|
if (formatEntry.isDirectory) await downloadDirectory(formatEntry.url, modelPath, formatEntry.files, onProgress);
|
|
475
|
-
else
|
|
494
|
+
else {
|
|
495
|
+
await downloadFile(formatEntry.url, modelPath, (downloaded, total) => onProgress?.(downloaded, total === 0 ? void 0 : total));
|
|
496
|
+
for (const sibling of siblings) await downloadFile(siblingUrl(formatEntry.url, sibling), path$1.join(modelsDir, sibling));
|
|
497
|
+
}
|
|
476
498
|
return modelPath;
|
|
477
499
|
}
|
|
478
500
|
/** Compute the on-disk path for a given model + format, even when not yet downloaded. */
|
|
@@ -489,17 +511,25 @@ function isModelDownloaded(modelsDir, entry, format) {
|
|
|
489
511
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
490
512
|
if (!modelPath || !fs.existsSync(modelPath)) return false;
|
|
491
513
|
if (formatEntry.isDirectory) return fs.existsSync(path$1.join(modelPath, "Manifest.json"));
|
|
492
|
-
|
|
514
|
+
if (fs.statSync(modelPath).size <= 0) return false;
|
|
515
|
+
return siblingFilesFor(formatEntry).every((f) => isNonEmptyFile(path$1.join(modelsDir, f)));
|
|
493
516
|
}
|
|
494
517
|
/** Remove the on-disk model file/directory. Returns true if something was deleted. */
|
|
495
518
|
function deleteModelFromDisk(modelsDir, entry, format) {
|
|
496
519
|
const modelPath = getModelFilePath(modelsDir, entry, format);
|
|
497
520
|
if (!modelPath || !fs.existsSync(modelPath)) return false;
|
|
498
|
-
|
|
521
|
+
const formatEntry = entry.formats[format];
|
|
522
|
+
if (formatEntry?.isDirectory) fs.rmSync(modelPath, {
|
|
499
523
|
recursive: true,
|
|
500
524
|
force: true
|
|
501
525
|
});
|
|
502
|
-
else
|
|
526
|
+
else {
|
|
527
|
+
fs.unlinkSync(modelPath);
|
|
528
|
+
if (formatEntry) for (const sibling of siblingFilesFor(formatEntry)) {
|
|
529
|
+
const sibPath = path$1.join(modelsDir, sibling);
|
|
530
|
+
if (fs.existsSync(sibPath)) fs.unlinkSync(sibPath);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
503
533
|
return true;
|
|
504
534
|
}
|
|
505
535
|
//#endregion
|