@thyn-ai/sqai 0.1.2 → 0.1.3
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/index.cjs +165 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +166 -37
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/LICENSE +0 -202
package/dist/index.d.cts
CHANGED
|
@@ -140,12 +140,29 @@ interface RuntimeStatus {
|
|
|
140
140
|
platform: string;
|
|
141
141
|
managed: boolean;
|
|
142
142
|
}
|
|
143
|
+
/** A runtime bundle resolved for a specific module filter (build-on-provision). */
|
|
144
|
+
interface FilteredBundle {
|
|
145
|
+
url: string;
|
|
146
|
+
sha256: string;
|
|
147
|
+
version: string;
|
|
148
|
+
cacheKey: string;
|
|
149
|
+
}
|
|
143
150
|
interface ProvisionerOptions {
|
|
144
151
|
contract: CapabilityContract;
|
|
145
152
|
autoInstall: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Build-on-provision: the developer's module filter. All 4,762 functions are
|
|
155
|
+
* available in the contract; only the selected modules are compiled + installed.
|
|
156
|
+
* Empty/undefined → the default pinned bundle (contract.runtime_bundle).
|
|
157
|
+
*/
|
|
158
|
+
runtimeModules?: string[];
|
|
159
|
+
/** Build service that compiles a bundle for an exact filter (POST /v1/runtime/build). */
|
|
160
|
+
buildServiceUrl?: string;
|
|
146
161
|
/** Injection points for tests. */
|
|
147
162
|
runtimeFactory?: (config?: MojoRuntimeConfig) => MojoRuntime;
|
|
148
163
|
downloadBundle?: (url: string, destination: string) => Promise<void>;
|
|
164
|
+
/** Resolve a filtered bundle (default: POST to buildServiceUrl). Test injection. */
|
|
165
|
+
resolveFilteredBundle?: (modules: string[], platformKey: string) => Promise<FilteredBundle>;
|
|
149
166
|
/** Bundle cache directory (default: OS user cache — runtimeCacheDir()). */
|
|
150
167
|
cacheDir?: string;
|
|
151
168
|
/** Trust-root overrides (default: the embedded trust root). */
|
|
@@ -171,6 +188,27 @@ declare class RuntimeProvisioner {
|
|
|
171
188
|
stop(): Promise<void>;
|
|
172
189
|
private newRuntime;
|
|
173
190
|
private cacheDir;
|
|
191
|
+
/** Normalized module filter (sorted, unique, non-empty) or null for the default bundle. */
|
|
192
|
+
private filterModules;
|
|
193
|
+
/** Ask the build service to compile + sign a bundle for exactly these modules. */
|
|
194
|
+
private resolveFilteredBundle;
|
|
195
|
+
/**
|
|
196
|
+
* The filter's cache key, computed IDENTICALLY to the build service
|
|
197
|
+
* (sha256("mods|platform|version")[:20], mods = sorted+unique, comma-joined).
|
|
198
|
+
* Lets the reuse fast-path find an already-running filtered daemon without a
|
|
199
|
+
* build-service round-trip. The build service's returned key is authoritative
|
|
200
|
+
* for install; this only has to match for the reuse optimization to hit.
|
|
201
|
+
*/
|
|
202
|
+
private localCacheKey;
|
|
203
|
+
/**
|
|
204
|
+
* Deterministic per-filter daemon endpoint so a filtered runtime never collides
|
|
205
|
+
* with the default (or another filter's) daemon on the shared default socket.
|
|
206
|
+
*/
|
|
207
|
+
private filterEndpoint;
|
|
208
|
+
private static readonly DAEMON_ENDPOINT_ENV;
|
|
209
|
+
/** Point the about-to-be-spawned daemon at a filter-specific socket/port/dir. */
|
|
210
|
+
private applyDaemonEndpointEnv;
|
|
211
|
+
private restoreEnv;
|
|
174
212
|
private doEnsure;
|
|
175
213
|
private provisionError;
|
|
176
214
|
private wrapVerificationError;
|
|
@@ -179,7 +217,7 @@ declare class RuntimeProvisioner {
|
|
|
179
217
|
private verifiedInstallOrNull;
|
|
180
218
|
private acquireLock;
|
|
181
219
|
private lockIsStale;
|
|
182
|
-
/** Serialize installers on `<cacheDir>/<
|
|
220
|
+
/** Serialize installers on `<cacheDir>/<installKey>.lock`; returns the verified install. */
|
|
183
221
|
private ensureInstalled;
|
|
184
222
|
/** Holder-of-the-lock path: download → verify → extract → atomic rename. */
|
|
185
223
|
private downloadVerifyExtract;
|
|
@@ -384,6 +422,15 @@ interface SqaiConfig {
|
|
|
384
422
|
timeout?: number;
|
|
385
423
|
maxRetries?: number;
|
|
386
424
|
resultStore?: ResultStoreConfig;
|
|
425
|
+
/**
|
|
426
|
+
* Build-on-provision module filter. All 4,762 functions are available in the
|
|
427
|
+
* contract; set this to install only the modules you use. The build service
|
|
428
|
+
* compiles + signs a bundle for exactly this set (cached by filter-hash).
|
|
429
|
+
* Omit for the default pinned bundle. Env: SQAI_RUNTIME_MODULES (comma-separated).
|
|
430
|
+
*/
|
|
431
|
+
runtimeModules?: string[];
|
|
432
|
+
/** Build service endpoint for filtered bundles. Env: SQAI_BUILD_SERVICE_URL. */
|
|
433
|
+
buildServiceUrl?: string;
|
|
387
434
|
/** BYO substrate (tests). */
|
|
388
435
|
runtime?: Runtime;
|
|
389
436
|
/** BYO computation dispatch target (tests / custom self-hosted engines). */
|
package/dist/index.d.ts
CHANGED
|
@@ -140,12 +140,29 @@ interface RuntimeStatus {
|
|
|
140
140
|
platform: string;
|
|
141
141
|
managed: boolean;
|
|
142
142
|
}
|
|
143
|
+
/** A runtime bundle resolved for a specific module filter (build-on-provision). */
|
|
144
|
+
interface FilteredBundle {
|
|
145
|
+
url: string;
|
|
146
|
+
sha256: string;
|
|
147
|
+
version: string;
|
|
148
|
+
cacheKey: string;
|
|
149
|
+
}
|
|
143
150
|
interface ProvisionerOptions {
|
|
144
151
|
contract: CapabilityContract;
|
|
145
152
|
autoInstall: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Build-on-provision: the developer's module filter. All 4,762 functions are
|
|
155
|
+
* available in the contract; only the selected modules are compiled + installed.
|
|
156
|
+
* Empty/undefined → the default pinned bundle (contract.runtime_bundle).
|
|
157
|
+
*/
|
|
158
|
+
runtimeModules?: string[];
|
|
159
|
+
/** Build service that compiles a bundle for an exact filter (POST /v1/runtime/build). */
|
|
160
|
+
buildServiceUrl?: string;
|
|
146
161
|
/** Injection points for tests. */
|
|
147
162
|
runtimeFactory?: (config?: MojoRuntimeConfig) => MojoRuntime;
|
|
148
163
|
downloadBundle?: (url: string, destination: string) => Promise<void>;
|
|
164
|
+
/** Resolve a filtered bundle (default: POST to buildServiceUrl). Test injection. */
|
|
165
|
+
resolveFilteredBundle?: (modules: string[], platformKey: string) => Promise<FilteredBundle>;
|
|
149
166
|
/** Bundle cache directory (default: OS user cache — runtimeCacheDir()). */
|
|
150
167
|
cacheDir?: string;
|
|
151
168
|
/** Trust-root overrides (default: the embedded trust root). */
|
|
@@ -171,6 +188,27 @@ declare class RuntimeProvisioner {
|
|
|
171
188
|
stop(): Promise<void>;
|
|
172
189
|
private newRuntime;
|
|
173
190
|
private cacheDir;
|
|
191
|
+
/** Normalized module filter (sorted, unique, non-empty) or null for the default bundle. */
|
|
192
|
+
private filterModules;
|
|
193
|
+
/** Ask the build service to compile + sign a bundle for exactly these modules. */
|
|
194
|
+
private resolveFilteredBundle;
|
|
195
|
+
/**
|
|
196
|
+
* The filter's cache key, computed IDENTICALLY to the build service
|
|
197
|
+
* (sha256("mods|platform|version")[:20], mods = sorted+unique, comma-joined).
|
|
198
|
+
* Lets the reuse fast-path find an already-running filtered daemon without a
|
|
199
|
+
* build-service round-trip. The build service's returned key is authoritative
|
|
200
|
+
* for install; this only has to match for the reuse optimization to hit.
|
|
201
|
+
*/
|
|
202
|
+
private localCacheKey;
|
|
203
|
+
/**
|
|
204
|
+
* Deterministic per-filter daemon endpoint so a filtered runtime never collides
|
|
205
|
+
* with the default (or another filter's) daemon on the shared default socket.
|
|
206
|
+
*/
|
|
207
|
+
private filterEndpoint;
|
|
208
|
+
private static readonly DAEMON_ENDPOINT_ENV;
|
|
209
|
+
/** Point the about-to-be-spawned daemon at a filter-specific socket/port/dir. */
|
|
210
|
+
private applyDaemonEndpointEnv;
|
|
211
|
+
private restoreEnv;
|
|
174
212
|
private doEnsure;
|
|
175
213
|
private provisionError;
|
|
176
214
|
private wrapVerificationError;
|
|
@@ -179,7 +217,7 @@ declare class RuntimeProvisioner {
|
|
|
179
217
|
private verifiedInstallOrNull;
|
|
180
218
|
private acquireLock;
|
|
181
219
|
private lockIsStale;
|
|
182
|
-
/** Serialize installers on `<cacheDir>/<
|
|
220
|
+
/** Serialize installers on `<cacheDir>/<installKey>.lock`; returns the verified install. */
|
|
183
221
|
private ensureInstalled;
|
|
184
222
|
/** Holder-of-the-lock path: download → verify → extract → atomic rename. */
|
|
185
223
|
private downloadVerifyExtract;
|
|
@@ -384,6 +422,15 @@ interface SqaiConfig {
|
|
|
384
422
|
timeout?: number;
|
|
385
423
|
maxRetries?: number;
|
|
386
424
|
resultStore?: ResultStoreConfig;
|
|
425
|
+
/**
|
|
426
|
+
* Build-on-provision module filter. All 4,762 functions are available in the
|
|
427
|
+
* contract; set this to install only the modules you use. The build service
|
|
428
|
+
* compiles + signs a bundle for exactly this set (cached by filter-hash).
|
|
429
|
+
* Omit for the default pinned bundle. Env: SQAI_RUNTIME_MODULES (comma-separated).
|
|
430
|
+
*/
|
|
431
|
+
runtimeModules?: string[];
|
|
432
|
+
/** Build service endpoint for filtered bundles. Env: SQAI_BUILD_SERVICE_URL. */
|
|
433
|
+
buildServiceUrl?: string;
|
|
387
434
|
/** BYO substrate (tests). */
|
|
388
435
|
runtime?: Runtime;
|
|
389
436
|
/** BYO computation dispatch target (tests / custom self-hosted engines). */
|
package/dist/index.js
CHANGED
|
@@ -134,11 +134,15 @@ function fromAlgentaError(error) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// src/env.ts
|
|
137
|
+
var DEFAULT_BUILD_SERVICE_URL = "https://sqai-buildservice.fly.dev";
|
|
137
138
|
function readEnv(env = process.env) {
|
|
138
139
|
const apiKey = (env.SQAI_API_KEY ?? "").trim() || void 0;
|
|
139
140
|
const engineUrl = (env.SQAI_ENGINE_URL ?? "").trim() || void 0;
|
|
140
141
|
const autoInstall = (env.SQAI_RUNTIME_AUTO_INSTALL ?? "").trim() !== "0";
|
|
141
|
-
|
|
142
|
+
const modulesRaw = (env.SQAI_RUNTIME_MODULES ?? "").split(",").map((m) => m.trim()).filter((m) => m.length > 0);
|
|
143
|
+
const runtimeModules = modulesRaw.length > 0 ? modulesRaw : void 0;
|
|
144
|
+
const buildServiceUrl = (env.SQAI_BUILD_SERVICE_URL ?? "").trim() || DEFAULT_BUILD_SERVICE_URL;
|
|
145
|
+
return { apiKey, engineUrl, autoInstall, runtimeModules, buildServiceUrl };
|
|
142
146
|
}
|
|
143
147
|
function inferMode(explicit, resolved) {
|
|
144
148
|
if (explicit) {
|
|
@@ -314,7 +318,7 @@ var ResultStore = class {
|
|
|
314
318
|
import { homedir, platform as osPlatform, arch as osArch } from "os";
|
|
315
319
|
import { join as join2 } from "path";
|
|
316
320
|
import { createWriteStream, promises as fs2 } from "fs";
|
|
317
|
-
import { randomBytes as randomBytes2 } from "crypto";
|
|
321
|
+
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
318
322
|
import { Readable } from "stream";
|
|
319
323
|
import { pipeline } from "stream/promises";
|
|
320
324
|
import { MojoRuntime } from "algenta-sdk";
|
|
@@ -990,7 +994,7 @@ async function removeQuietly(path) {
|
|
|
990
994
|
} catch {
|
|
991
995
|
}
|
|
992
996
|
}
|
|
993
|
-
var RuntimeProvisioner = class {
|
|
997
|
+
var RuntimeProvisioner = class _RuntimeProvisioner {
|
|
994
998
|
options;
|
|
995
999
|
trustRoot;
|
|
996
1000
|
runtime = null;
|
|
@@ -1050,8 +1054,110 @@ var RuntimeProvisioner = class {
|
|
|
1050
1054
|
cacheDir() {
|
|
1051
1055
|
return this.options.cacheDir ?? runtimeCacheDir();
|
|
1052
1056
|
}
|
|
1057
|
+
/** Normalized module filter (sorted, unique, non-empty) or null for the default bundle. */
|
|
1058
|
+
filterModules() {
|
|
1059
|
+
const mods = (this.options.runtimeModules ?? []).map((m) => m.trim()).filter((m) => m.length > 0);
|
|
1060
|
+
if (mods.length === 0) {
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
return Array.from(new Set(mods)).sort();
|
|
1064
|
+
}
|
|
1065
|
+
/** Ask the build service to compile + sign a bundle for exactly these modules. */
|
|
1066
|
+
async resolveFilteredBundle(modules, platformKey) {
|
|
1067
|
+
if (this.options.resolveFilteredBundle) {
|
|
1068
|
+
return this.options.resolveFilteredBundle(modules, platformKey);
|
|
1069
|
+
}
|
|
1070
|
+
const base = this.options.buildServiceUrl;
|
|
1071
|
+
if (!base) {
|
|
1072
|
+
throw new SqaiError(
|
|
1073
|
+
"runtime_provision_failed",
|
|
1074
|
+
"runtimeModules was set but no build service is configured. Set SQAI_BUILD_SERVICE_URL to a build-on-provision endpoint, or omit runtimeModules to use the default bundle.",
|
|
1075
|
+
{ details: { reason: "no_build_service", modules } }
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
let response;
|
|
1079
|
+
try {
|
|
1080
|
+
response = await fetch(`${base.replace(/\/$/, "")}/v1/runtime/build`, {
|
|
1081
|
+
method: "POST",
|
|
1082
|
+
headers: { "content-type": "application/json" },
|
|
1083
|
+
body: JSON.stringify({ modules, platform: platformKey })
|
|
1084
|
+
});
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1087
|
+
throw new SqaiError(
|
|
1088
|
+
"runtime_provision_failed",
|
|
1089
|
+
`Build service request failed: ${message}`,
|
|
1090
|
+
{ retryable: true, details: { reason: "build_service_unreachable", modules } }
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
if (!response.ok) {
|
|
1094
|
+
throw new SqaiError(
|
|
1095
|
+
"runtime_provision_failed",
|
|
1096
|
+
`Build service returned HTTP ${response.status} for the requested filter.`,
|
|
1097
|
+
{ retryable: response.status >= 500, details: { reason: "build_failed", modules } }
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
const body = await response.json();
|
|
1101
|
+
return {
|
|
1102
|
+
url: body.url,
|
|
1103
|
+
sha256: body.sha256,
|
|
1104
|
+
version: body.version ?? this.options.contract.runtime_bundle.version,
|
|
1105
|
+
cacheKey: body.cache_key
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* The filter's cache key, computed IDENTICALLY to the build service
|
|
1110
|
+
* (sha256("mods|platform|version")[:20], mods = sorted+unique, comma-joined).
|
|
1111
|
+
* Lets the reuse fast-path find an already-running filtered daemon without a
|
|
1112
|
+
* build-service round-trip. The build service's returned key is authoritative
|
|
1113
|
+
* for install; this only has to match for the reuse optimization to hit.
|
|
1114
|
+
*/
|
|
1115
|
+
localCacheKey(modules, platformKey) {
|
|
1116
|
+
const canonical = modules.join(",");
|
|
1117
|
+
const version = this.options.contract.runtime_bundle.version;
|
|
1118
|
+
return createHash3("sha256").update(`${canonical}|${platformKey}|${version}`).digest("hex").slice(0, 20);
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Deterministic per-filter daemon endpoint so a filtered runtime never collides
|
|
1122
|
+
* with the default (or another filter's) daemon on the shared default socket.
|
|
1123
|
+
*/
|
|
1124
|
+
filterEndpoint(cacheKey) {
|
|
1125
|
+
const seed = parseInt(cacheKey.slice(0, 6), 16) || 0;
|
|
1126
|
+
const port = 50100 + seed % 800;
|
|
1127
|
+
return { sock: join2(this.cacheDir(), `${cacheKey}.sock`), tcp: `127.0.0.1:${port}` };
|
|
1128
|
+
}
|
|
1129
|
+
static DAEMON_ENDPOINT_ENV = [
|
|
1130
|
+
"ALGENTA_DAEMON_SOCK",
|
|
1131
|
+
"ALGENTA_DAEMON_TCP",
|
|
1132
|
+
"ALGENTA_MOJO_SOCK",
|
|
1133
|
+
"ALGENTA_RUNTIME_DIR"
|
|
1134
|
+
];
|
|
1135
|
+
/** Point the about-to-be-spawned daemon at a filter-specific socket/port/dir. */
|
|
1136
|
+
applyDaemonEndpointEnv(endpoint, installDir) {
|
|
1137
|
+
const saved = {};
|
|
1138
|
+
for (const key of _RuntimeProvisioner.DAEMON_ENDPOINT_ENV) {
|
|
1139
|
+
saved[key] = process.env[key];
|
|
1140
|
+
}
|
|
1141
|
+
process.env.ALGENTA_DAEMON_SOCK = endpoint.sock;
|
|
1142
|
+
process.env.ALGENTA_DAEMON_TCP = endpoint.tcp;
|
|
1143
|
+
process.env.ALGENTA_MOJO_SOCK = `${endpoint.sock}.worker`;
|
|
1144
|
+
process.env.ALGENTA_RUNTIME_DIR = join2(installDir, ".runtime");
|
|
1145
|
+
return saved;
|
|
1146
|
+
}
|
|
1147
|
+
restoreEnv(saved) {
|
|
1148
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
1149
|
+
if (value === void 0) {
|
|
1150
|
+
delete process.env[key];
|
|
1151
|
+
} else {
|
|
1152
|
+
process.env[key] = value;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1053
1156
|
async doEnsure() {
|
|
1054
|
-
const
|
|
1157
|
+
const platformKey = currentPlatformKey();
|
|
1158
|
+
const filter = this.filterModules();
|
|
1159
|
+
const probeEndpoint = filter ? this.filterEndpoint(this.localCacheKey(filter, platformKey)) : void 0;
|
|
1160
|
+
const runtime = this.newRuntime(probeEndpoint ? { tcpAddress: probeEndpoint.tcp } : void 0);
|
|
1055
1161
|
try {
|
|
1056
1162
|
const health = await runtime.health();
|
|
1057
1163
|
if (health.runtime_available) {
|
|
@@ -1060,40 +1166,54 @@ var RuntimeProvisioner = class {
|
|
|
1060
1166
|
}
|
|
1061
1167
|
} catch {
|
|
1062
1168
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
);
|
|
1169
|
+
let version;
|
|
1170
|
+
let installKey;
|
|
1171
|
+
let platformBundle;
|
|
1172
|
+
let endpoint;
|
|
1173
|
+
if (filter) {
|
|
1174
|
+
const resolved = await this.resolveFilteredBundle(filter, platformKey);
|
|
1175
|
+
version = resolved.version;
|
|
1176
|
+
installKey = `filtered-${resolved.cacheKey}`;
|
|
1177
|
+
platformBundle = { url: resolved.url, sha256: resolved.sha256 };
|
|
1178
|
+
endpoint = this.filterEndpoint(resolved.cacheKey);
|
|
1179
|
+
} else {
|
|
1180
|
+
const bundle = this.options.contract.runtime_bundle;
|
|
1181
|
+
const pinned = bundle.platforms[platformKey];
|
|
1182
|
+
if (!pinned) {
|
|
1183
|
+
const supported = Object.keys(bundle.platforms);
|
|
1184
|
+
throw new SqaiError(
|
|
1185
|
+
"unsupported_platform",
|
|
1186
|
+
supported.length === 0 ? "No managed runtime bundle is published yet for any platform. Start a local Algenta runtime daemon, or set SQAI_ENGINE_URL / SQAI_API_KEY to use a hosted engine." : `No managed runtime bundle is published for '${platformKey}'.`,
|
|
1187
|
+
{ details: { platform: platformKey, supported } }
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
version = bundle.version;
|
|
1191
|
+
installKey = version;
|
|
1192
|
+
platformBundle = pinned;
|
|
1073
1193
|
}
|
|
1074
1194
|
if (!this.options.autoInstall) {
|
|
1075
1195
|
throw new SqaiError(
|
|
1076
1196
|
"runtime_provision_failed",
|
|
1077
1197
|
"Automatic runtime installation is disabled (SQAI_RUNTIME_AUTO_INSTALL=0). Install manually with `sqai runtime install` or `sqai runtime install --from-file`.",
|
|
1078
|
-
{ details: { platform: platformKey, version
|
|
1198
|
+
{ details: { platform: platformKey, version } }
|
|
1079
1199
|
);
|
|
1080
1200
|
}
|
|
1081
|
-
if (compareRuntimeVersions(
|
|
1201
|
+
if (compareRuntimeVersions(version, this.trustRoot.minAcceptedRuntimeVersion) < 0) {
|
|
1082
1202
|
throw new SqaiError(
|
|
1083
1203
|
"runtime_provision_failed",
|
|
1084
|
-
`Managed runtime ${
|
|
1204
|
+
`Managed runtime ${version} is below the minimum accepted runtime version ${this.trustRoot.minAcceptedRuntimeVersion} (rollback protection).`,
|
|
1085
1205
|
{
|
|
1086
1206
|
details: {
|
|
1087
1207
|
reason: "rollback_protected",
|
|
1088
1208
|
platform: platformKey,
|
|
1089
|
-
version
|
|
1209
|
+
version,
|
|
1090
1210
|
min_accepted_version: this.trustRoot.minAcceptedRuntimeVersion
|
|
1091
1211
|
}
|
|
1092
1212
|
}
|
|
1093
1213
|
);
|
|
1094
1214
|
}
|
|
1095
|
-
const installed = await this.ensureInstalled(platformKey,
|
|
1096
|
-
return this.spawnInstalled(platformKey, installed);
|
|
1215
|
+
const installed = await this.ensureInstalled(platformKey, version, platformBundle, installKey);
|
|
1216
|
+
return this.spawnInstalled(platformKey, installed, endpoint);
|
|
1097
1217
|
}
|
|
1098
1218
|
provisionError(reason, message, platformKey, version, extra = {}, retryable = false) {
|
|
1099
1219
|
return new SqaiError("runtime_provision_failed", message, {
|
|
@@ -1135,8 +1255,8 @@ var RuntimeProvisioner = class {
|
|
|
1135
1255
|
};
|
|
1136
1256
|
}
|
|
1137
1257
|
/** Verify an existing install if present; returns null when absent/invalid. */
|
|
1138
|
-
async verifiedInstallOrNull(platformKey, version, removeOnFailure) {
|
|
1139
|
-
const installDir = join2(this.cacheDir(),
|
|
1258
|
+
async verifiedInstallOrNull(platformKey, version, removeOnFailure, installKey = version) {
|
|
1259
|
+
const installDir = join2(this.cacheDir(), installKey);
|
|
1140
1260
|
try {
|
|
1141
1261
|
await fs2.access(join2(installDir, MANIFEST_NAME));
|
|
1142
1262
|
} catch {
|
|
@@ -1187,15 +1307,15 @@ var RuntimeProvisioner = class {
|
|
|
1187
1307
|
return true;
|
|
1188
1308
|
}
|
|
1189
1309
|
}
|
|
1190
|
-
/** Serialize installers on `<cacheDir>/<
|
|
1191
|
-
async ensureInstalled(platformKey, version, platformBundle) {
|
|
1310
|
+
/** Serialize installers on `<cacheDir>/<installKey>.lock`; returns the verified install. */
|
|
1311
|
+
async ensureInstalled(platformKey, version, platformBundle, installKey = version) {
|
|
1192
1312
|
const cacheDir = this.cacheDir();
|
|
1193
1313
|
await fs2.mkdir(cacheDir, { recursive: true });
|
|
1194
|
-
const existing = await this.verifiedInstallOrNull(platformKey, version, true);
|
|
1314
|
+
const existing = await this.verifiedInstallOrNull(platformKey, version, true, installKey);
|
|
1195
1315
|
if (existing) {
|
|
1196
1316
|
return existing;
|
|
1197
1317
|
}
|
|
1198
|
-
const lockPath = join2(cacheDir, `${
|
|
1318
|
+
const lockPath = join2(cacheDir, `${installKey}.lock`);
|
|
1199
1319
|
const waitTimeoutMs = this.options.lockWaitTimeoutMs ?? LOCK_WAIT_TIMEOUT_MS;
|
|
1200
1320
|
const pollIntervalMs = this.options.lockPollIntervalMs ?? LOCK_POLL_INTERVAL_MS;
|
|
1201
1321
|
const deadline = Date.now() + waitTimeoutMs;
|
|
@@ -1207,12 +1327,12 @@ var RuntimeProvisioner = class {
|
|
|
1207
1327
|
await removeQuietly(lockPath);
|
|
1208
1328
|
continue;
|
|
1209
1329
|
}
|
|
1210
|
-
const finished = await this.verifiedInstallOrNull(platformKey, version, false);
|
|
1330
|
+
const finished = await this.verifiedInstallOrNull(platformKey, version, false, installKey);
|
|
1211
1331
|
if (finished) {
|
|
1212
1332
|
return finished;
|
|
1213
1333
|
}
|
|
1214
1334
|
if (Date.now() >= deadline) {
|
|
1215
|
-
const lastChance = await this.verifiedInstallOrNull(platformKey, version, false);
|
|
1335
|
+
const lastChance = await this.verifiedInstallOrNull(platformKey, version, false, installKey);
|
|
1216
1336
|
if (lastChance) {
|
|
1217
1337
|
return lastChance;
|
|
1218
1338
|
}
|
|
@@ -1221,29 +1341,29 @@ var RuntimeProvisioner = class {
|
|
|
1221
1341
|
`Another installer holds the runtime install lock for ${version} and did not finish within ${Math.round(waitTimeoutMs / 1e3)}s. Retry, or remove the stale lock from the runtime cache directory.`,
|
|
1222
1342
|
platformKey,
|
|
1223
1343
|
version,
|
|
1224
|
-
{ lock_file: `${
|
|
1344
|
+
{ lock_file: `${installKey}.lock` },
|
|
1225
1345
|
true
|
|
1226
1346
|
);
|
|
1227
1347
|
}
|
|
1228
1348
|
await sleep(pollIntervalMs);
|
|
1229
1349
|
}
|
|
1230
1350
|
try {
|
|
1231
|
-
const raced = await this.verifiedInstallOrNull(platformKey, version, false);
|
|
1351
|
+
const raced = await this.verifiedInstallOrNull(platformKey, version, false, installKey);
|
|
1232
1352
|
if (raced) {
|
|
1233
1353
|
return raced;
|
|
1234
1354
|
}
|
|
1235
|
-
return await this.downloadVerifyExtract(platformKey, version, platformBundle);
|
|
1355
|
+
return await this.downloadVerifyExtract(platformKey, version, platformBundle, installKey);
|
|
1236
1356
|
} finally {
|
|
1237
1357
|
await removeQuietly(lockPath);
|
|
1238
1358
|
}
|
|
1239
1359
|
}
|
|
1240
1360
|
/** Holder-of-the-lock path: download → verify → extract → atomic rename. */
|
|
1241
|
-
async downloadVerifyExtract(platformKey, version, platformBundle) {
|
|
1361
|
+
async downloadVerifyExtract(platformKey, version, platformBundle, installKey = version) {
|
|
1242
1362
|
const cacheDir = this.cacheDir();
|
|
1243
1363
|
const token = randomBytes2(6).toString("hex");
|
|
1244
1364
|
const tarballPath = join2(cacheDir, `tmp-${token}.tgz`);
|
|
1245
1365
|
const extractDir = join2(cacheDir, `tmp-${token}`);
|
|
1246
|
-
const installDir = join2(cacheDir,
|
|
1366
|
+
const installDir = join2(cacheDir, installKey);
|
|
1247
1367
|
const url = resolveBundleUrl(platformBundle.url, version, platformKey);
|
|
1248
1368
|
try {
|
|
1249
1369
|
const download = this.options.downloadBundle ?? defaultDownloadBundle;
|
|
@@ -1290,7 +1410,7 @@ var RuntimeProvisioner = class {
|
|
|
1290
1410
|
try {
|
|
1291
1411
|
await fs2.rename(extractDir, installDir);
|
|
1292
1412
|
} catch (error) {
|
|
1293
|
-
const raced = await this.verifiedInstallOrNull(platformKey, version, false);
|
|
1413
|
+
const raced = await this.verifiedInstallOrNull(platformKey, version, false, installKey);
|
|
1294
1414
|
if (raced) {
|
|
1295
1415
|
return raced;
|
|
1296
1416
|
}
|
|
@@ -1309,7 +1429,7 @@ var RuntimeProvisioner = class {
|
|
|
1309
1429
|
}
|
|
1310
1430
|
}
|
|
1311
1431
|
/** Spawn the installed bundle's daemon through MojoRuntime autoStart. */
|
|
1312
|
-
async spawnInstalled(platformKey, installed) {
|
|
1432
|
+
async spawnInstalled(platformKey, installed, endpoint) {
|
|
1313
1433
|
const version = installed.manifest.runtime_bundle_version;
|
|
1314
1434
|
let entryRelative;
|
|
1315
1435
|
try {
|
|
@@ -1322,7 +1442,10 @@ var RuntimeProvisioner = class {
|
|
|
1322
1442
|
"start",
|
|
1323
1443
|
"--foreground"
|
|
1324
1444
|
];
|
|
1325
|
-
const managed = this.newRuntime(
|
|
1445
|
+
const managed = this.newRuntime(
|
|
1446
|
+
endpoint ? { autoStart: true, daemonCommand, tcpAddress: endpoint.tcp } : { autoStart: true, daemonCommand }
|
|
1447
|
+
);
|
|
1448
|
+
const savedEnv = endpoint ? this.applyDaemonEndpointEnv(endpoint, installed.installDir) : null;
|
|
1326
1449
|
try {
|
|
1327
1450
|
const health = await managed.health();
|
|
1328
1451
|
if (!health.runtime_available) {
|
|
@@ -1346,6 +1469,10 @@ var RuntimeProvisioner = class {
|
|
|
1346
1469
|
version,
|
|
1347
1470
|
{ daemon_entry: entryRelative }
|
|
1348
1471
|
);
|
|
1472
|
+
} finally {
|
|
1473
|
+
if (savedEnv) {
|
|
1474
|
+
this.restoreEnv(savedEnv);
|
|
1475
|
+
}
|
|
1349
1476
|
}
|
|
1350
1477
|
try {
|
|
1351
1478
|
await fs2.writeFile(
|
|
@@ -1612,7 +1739,9 @@ var SQAI = class {
|
|
|
1612
1739
|
this.resultStore = new ResultStore(config.resultStore);
|
|
1613
1740
|
this.provisioner = new RuntimeProvisioner({
|
|
1614
1741
|
contract: this.contractIndex.contract,
|
|
1615
|
-
autoInstall: env.autoInstall
|
|
1742
|
+
autoInstall: env.autoInstall,
|
|
1743
|
+
runtimeModules: config.runtimeModules ?? env.runtimeModules,
|
|
1744
|
+
buildServiceUrl: config.buildServiceUrl ?? env.buildServiceUrl
|
|
1616
1745
|
});
|
|
1617
1746
|
if (config.runtime) {
|
|
1618
1747
|
this.rawRuntime = config.runtime;
|