@sellable/install 0.1.608-wip.phase188.20260812.2 → 0.1.609
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/container/Dockerfile +7 -7
- package/container/README.md +2 -2
- package/lib/sellable-agent/admin-runtime-reconciler.mjs +0 -24
- package/lib/sellable-agent/external-runtime-builder.mjs +1 -1
- package/lib/sellable-agent/fly-customer-image/Dockerfile +3 -3
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +96 -455
- package/lib/sellable-agent/fly-runtime-identity.mjs +1 -10
- package/lib/sellable-agent/hermes-memory-snapshot.mjs +16 -5
- package/lib/sellable-agent/host-bootstrap.mjs +2 -2
- package/lib/sellable-agent/host-worker.mjs +2 -3
- package/lib/sellable-agent/profile-materializer.mjs +2 -3
- package/lib/sellable-agent/provisioning-adapter.mjs +2 -3
- package/package.json +1 -1
- package/skill-templates/refill-sends.md +15 -0
package/container/Dockerfile
CHANGED
|
@@ -3,13 +3,13 @@ FROM ${HERMES_IMAGE}
|
|
|
3
3
|
|
|
4
4
|
USER root
|
|
5
5
|
|
|
6
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
7
|
-
ARG MCP_PACKAGE=@sellable/mcp@0.1.
|
|
6
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.609
|
|
7
|
+
ARG MCP_PACKAGE=@sellable/mcp@0.1.865
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
|
-
ARG MCP_INTEGRITY=sha512-
|
|
9
|
+
ARG MCP_INTEGRITY=sha512-282pkYFADFrrIXyQcdUpQwakXiwe/AFUDpDZkTh29CFcGHnN7dvC0MCvaU4LffXA+ZuKCpuf02uxCLGiIEOjbw==
|
|
10
10
|
|
|
11
|
-
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.
|
|
12
|
-
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.
|
|
11
|
+
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.609" \
|
|
12
|
+
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.865" \
|
|
13
13
|
&& test -n "${INSTALLER_INTEGRITY}" \
|
|
14
14
|
&& test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
|
|
15
15
|
&& test "$(npm view "${MCP_PACKAGE}" dist.integrity)" = "${MCP_INTEGRITY}" \
|
|
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.608-wip.phase188.202608
|
|
|
23
23
|
&& mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
|
|
24
24
|
&& npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
|
|
25
25
|
&& npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
|
|
26
|
-
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.
|
|
26
|
+
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.609-wip.hermes-desktop.20260811160530' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.865') throw new Error('package identity rejected');" \
|
|
27
27
|
&& node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
|
|
28
|
-
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.
|
|
28
|
+
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.609', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.865', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
|
|
29
29
|
&& npm cache clean --force
|
|
30
30
|
|
|
31
31
|
COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
|
package/container/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
|
|
4
4
|
|
|
5
5
|
Build from this directory after
|
|
6
|
-
`@sellable/install@0.1.
|
|
6
|
+
`@sellable/install@0.1.609` is
|
|
7
7
|
published:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
docker build --platform linux/amd64 \
|
|
11
11
|
--build-arg INSTALLER_INTEGRITY='sha512-…' \
|
|
12
|
-
--tag sellable-agent-host:0.1.
|
|
12
|
+
--tag sellable-agent-host:0.1.609-wip.hermes-desktop.20260811160530 .
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Create a private `bootstrap/` directory owned by root with mode `0700`. It must contain `bootstrap.json`, `provider-auth`, and `slack-app`, each owned by root with mode `0600`. `bootstrap.json` contains only non-secret identities, hashes, package integrities, and the control-plane URL. The other two files contain the provider JSON and global Slack app token whose hashes are pinned by `bootstrap.json`.
|
|
@@ -80,30 +80,6 @@ export function validateAdminRuntimeTuple(tuple) {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
if (tuple.profileContractTemplate !== undefined) {
|
|
84
|
-
const templatePackages = tuple.profileContractTemplate?.packages;
|
|
85
|
-
const profilePackageByTupleName = {
|
|
86
|
-
"@sellable/mcp": templatePackages?.sellable?.mcp,
|
|
87
|
-
"@sellable/install": templatePackages?.sellable?.install,
|
|
88
|
-
"@sellable/admin-mcp": templatePackages?.["sellable-admin"]?.mcp,
|
|
89
|
-
"@sellable/admin-install": templatePackages?.["sellable-admin"]?.install,
|
|
90
|
-
};
|
|
91
|
-
for (const name of ADMIN_RUNTIME_TUPLE_MEMBERS) {
|
|
92
|
-
const tuplePackage = tuple.packages[name];
|
|
93
|
-
const profilePackage = profilePackageByTupleName[name];
|
|
94
|
-
if (
|
|
95
|
-
profilePackage?.name !== tuplePackage.name ||
|
|
96
|
-
profilePackage?.version !== tuplePackage.version ||
|
|
97
|
-
profilePackage?.integrity !== tuplePackage.integrity
|
|
98
|
-
) {
|
|
99
|
-
return {
|
|
100
|
-
ok: false,
|
|
101
|
-
code: "admin_runtime_tuple_profile_package_mismatch",
|
|
102
|
-
packageName: name,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
83
|
if (
|
|
108
84
|
!SHA256.test(tuple.lockedTreeDigest ?? "") ||
|
|
109
85
|
!SHA256.test(tuple.artifactDigest ?? "")
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
|
|
29
29
|
const MANIFEST_VERSION = "sellable-agent-external-runtime-closure/v1";
|
|
30
30
|
const MCP_PACKAGE = "@sellable/mcp";
|
|
31
|
-
const MCP_VERSION = "0.1.
|
|
31
|
+
const MCP_VERSION = "0.1.865";
|
|
32
32
|
const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
|
|
33
33
|
const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
|
|
34
34
|
|
|
@@ -4,9 +4,9 @@ FROM ${HERMES_IMAGE}
|
|
|
4
4
|
|
|
5
5
|
USER root
|
|
6
6
|
|
|
7
|
-
ARG MCP_PACKAGE=@sellable/mcp@0.1.
|
|
8
|
-
ARG MCP_INTEGRITY=sha512-
|
|
9
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
7
|
+
ARG MCP_PACKAGE=@sellable/mcp@0.1.865
|
|
8
|
+
ARG MCP_INTEGRITY=sha512-282pkYFADFrrIXyQcdUpQwakXiwe/AFUDpDZkTh29CFcGHnN7dvC0MCvaU4LffXA+ZuKCpuf02uxCLGiIEOjbw==
|
|
9
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.609
|
|
10
10
|
ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.186-wip.phase158.20260802034727
|
|
11
11
|
ARG ADMIN_MCP_INTEGRITY=sha512-qYNew5wd2IfgVcp1UYOG9QvpF/8Mx19YJ5HQtMKzvSfUf3evePjtaKsoXLf2C/GUJ5QlLlHJo84VWOI3V01+9A==
|
|
12
12
|
ARG TUS_JS_CLIENT_VERSION=4.3.1
|
|
@@ -96,10 +96,9 @@ const RUNTIME_GENERATION = Number(
|
|
|
96
96
|
const BOOT_SESSION_ID = randomUUID();
|
|
97
97
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
|
|
98
98
|
const INSTALLER_PACKAGE =
|
|
99
|
-
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ??
|
|
100
|
-
"@sellable/install@0.1.608-wip.phase188.20260812.2";
|
|
99
|
+
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.609";
|
|
101
100
|
const MCP_PACKAGE =
|
|
102
|
-
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.
|
|
101
|
+
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.865";
|
|
103
102
|
|
|
104
103
|
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
105
104
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -260,10 +259,6 @@ function compileDesired(claim, trustRootPem) {
|
|
|
260
259
|
if (
|
|
261
260
|
!operation ||
|
|
262
261
|
operation.targetRevisionId !== claim?.revision?.id ||
|
|
263
|
-
!Number.isSafeInteger(operation.configGeneration) ||
|
|
264
|
-
operation.configGeneration < 1 ||
|
|
265
|
-
!Number.isSafeInteger(operation.lifecycleGeneration) ||
|
|
266
|
-
operation.lifecycleGeneration < 0 ||
|
|
267
262
|
!slack ||
|
|
268
263
|
!service ||
|
|
269
264
|
!model ||
|
|
@@ -2142,83 +2137,26 @@ async function startNativeGateway({ desired, runtimeConfig, secrets }) {
|
|
|
2142
2137
|
|
|
2143
2138
|
export const API_SERVER_READINESS_DEADLINE_MS = 240_000;
|
|
2144
2139
|
const API_SERVER_READINESS_RETRY_MS = 1_000;
|
|
2145
|
-
export const NATIVE_GATEWAY_SERVING_PROOF_INTERVAL_MS = 30_000;
|
|
2146
|
-
export const NATIVE_GATEWAY_RECOVERY_COOLDOWN_MS = 60_000;
|
|
2147
|
-
export const NATIVE_GATEWAY_RECOVERY_MAX_ATTEMPTS = 3;
|
|
2148
|
-
const NATIVE_GATEWAY_RECOVERY_BUDGET_MS = 30_000;
|
|
2149
|
-
const NATIVE_GATEWAY_RECOVERY_BACKOFF_MS = 500;
|
|
2150
|
-
const NATIVE_GATEWAY_RECOVERY_BACKOFF_CAP_MS = 2_000;
|
|
2151
|
-
|
|
2152
|
-
const NATIVE_GATEWAY_SERVING_REASONS = new Set([
|
|
2153
|
-
"GATEWAY_STOPPED",
|
|
2154
|
-
"SLACK_DISCONNECTED",
|
|
2155
|
-
"API_UNAVAILABLE",
|
|
2156
|
-
"AUTH_UNAVAILABLE",
|
|
2157
|
-
"CAPABILITIES_UNAVAILABLE",
|
|
2158
|
-
"IDENTITY_MISMATCH",
|
|
2159
|
-
"RECOVERY_FAILED",
|
|
2160
|
-
"RECOVERY_IN_PROGRESS",
|
|
2161
|
-
]);
|
|
2162
|
-
|
|
2163
|
-
function nativeGatewayServingFailure(reasonCode) {
|
|
2164
|
-
const error = new Error(
|
|
2165
|
-
`customer_native_gateway_serving_rejected:${reasonCode}`
|
|
2166
|
-
);
|
|
2167
|
-
error.reasonCode = reasonCode;
|
|
2168
|
-
return error;
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
function nativeGatewayServingFailureReason(error) {
|
|
2172
|
-
return NATIVE_GATEWAY_SERVING_REASONS.has(error?.reasonCode)
|
|
2173
|
-
? error.reasonCode
|
|
2174
|
-
: "RECOVERY_FAILED";
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
function throwIfAborted(signal) {
|
|
2178
|
-
if (!signal?.aborted) return;
|
|
2179
|
-
throw signal.reason instanceof Error
|
|
2180
|
-
? signal.reason
|
|
2181
|
-
: new DOMException("Aborted", "AbortError");
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
function nativeGatewayRuntimeStatusReason(
|
|
2185
|
-
status,
|
|
2186
|
-
{ expectedPid, expectedHome }
|
|
2187
|
-
) {
|
|
2188
|
-
if (!status) return "API_UNAVAILABLE";
|
|
2189
|
-
if (
|
|
2190
|
-
!Number.isSafeInteger(expectedPid) ||
|
|
2191
|
-
expectedPid <= 1 ||
|
|
2192
|
-
status.pid !== expectedPid ||
|
|
2193
|
-
status.kind !== "hermes-gateway" ||
|
|
2194
|
-
!Array.isArray(status.argv) ||
|
|
2195
|
-
!status.argv.includes("gateway") ||
|
|
2196
|
-
!status.argv.includes("run") ||
|
|
2197
|
-
!Number.isSafeInteger(status.start_time) ||
|
|
2198
|
-
status.start_time <= 0 ||
|
|
2199
|
-
status.hermes_home !== expectedHome
|
|
2200
|
-
) {
|
|
2201
|
-
return "IDENTITY_MISMATCH";
|
|
2202
|
-
}
|
|
2203
|
-
if (status.gateway_state !== "running") return "GATEWAY_STOPPED";
|
|
2204
|
-
if (status.platforms?.slack?.state !== "connected") {
|
|
2205
|
-
return "SLACK_DISCONNECTED";
|
|
2206
|
-
}
|
|
2207
|
-
if (status.platforms?.api_server?.state !== "connected") {
|
|
2208
|
-
return "API_UNAVAILABLE";
|
|
2209
|
-
}
|
|
2210
|
-
return null;
|
|
2211
|
-
}
|
|
2212
2140
|
|
|
2213
2141
|
export function nativeGatewayRuntimeStatusAccepted(
|
|
2214
2142
|
status,
|
|
2215
2143
|
{ expectedPid, expectedHome }
|
|
2216
2144
|
) {
|
|
2217
|
-
return (
|
|
2218
|
-
|
|
2219
|
-
expectedPid
|
|
2220
|
-
|
|
2221
|
-
|
|
2145
|
+
return Boolean(
|
|
2146
|
+
status &&
|
|
2147
|
+
Number.isSafeInteger(expectedPid) &&
|
|
2148
|
+
expectedPid > 1 &&
|
|
2149
|
+
status.pid === expectedPid &&
|
|
2150
|
+
status.kind === "hermes-gateway" &&
|
|
2151
|
+
Array.isArray(status.argv) &&
|
|
2152
|
+
status.argv.includes("gateway") &&
|
|
2153
|
+
status.argv.includes("run") &&
|
|
2154
|
+
Number.isSafeInteger(status.start_time) &&
|
|
2155
|
+
status.start_time > 0 &&
|
|
2156
|
+
status.hermes_home === expectedHome &&
|
|
2157
|
+
status.gateway_state === "running" &&
|
|
2158
|
+
status.platforms?.slack?.state === "connected" &&
|
|
2159
|
+
status.platforms?.api_server?.state === "connected"
|
|
2222
2160
|
);
|
|
2223
2161
|
}
|
|
2224
2162
|
|
|
@@ -2241,140 +2179,86 @@ function readNativeGatewayRuntimeStatus(runtimeHome) {
|
|
|
2241
2179
|
}
|
|
2242
2180
|
}
|
|
2243
2181
|
|
|
2244
|
-
|
|
2182
|
+
async function proveApiServerReadiness({
|
|
2245
2183
|
desired,
|
|
2246
2184
|
apiServerKey,
|
|
2247
2185
|
gateway,
|
|
2248
2186
|
runtimeConfig,
|
|
2249
|
-
signal,
|
|
2250
|
-
fetchImpl = fetch,
|
|
2251
|
-
readStatus = readNativeGatewayRuntimeStatus,
|
|
2252
2187
|
}) {
|
|
2253
2188
|
const origin = "http://127.0.0.1:8642";
|
|
2254
|
-
const requestSignal = () =>
|
|
2255
|
-
signal
|
|
2256
|
-
? AbortSignal.any([signal, AbortSignal.timeout(5_000)])
|
|
2257
|
-
: AbortSignal.timeout(5_000);
|
|
2258
2189
|
const request = (authorization) =>
|
|
2259
|
-
|
|
2190
|
+
fetch(`${origin}/v1/capabilities`, {
|
|
2260
2191
|
headers: authorization ? { authorization } : {},
|
|
2261
|
-
signal:
|
|
2192
|
+
signal: AbortSignal.timeout(5_000),
|
|
2262
2193
|
});
|
|
2263
|
-
throwIfAborted(signal);
|
|
2264
|
-
const gatewayReadback = gateway?.readback();
|
|
2265
|
-
if (
|
|
2266
|
-
!gatewayReadback?.running ||
|
|
2267
|
-
!Number.isSafeInteger(gateway?.child?.pid) ||
|
|
2268
|
-
gateway.child.pid <= 1
|
|
2269
|
-
) {
|
|
2270
|
-
throw nativeGatewayServingFailure("GATEWAY_STOPPED");
|
|
2271
|
-
}
|
|
2272
|
-
const statusReason = nativeGatewayRuntimeStatusReason(
|
|
2273
|
-
readStatus(runtimeConfig.runtimeHome),
|
|
2274
|
-
{
|
|
2275
|
-
expectedPid: gateway.child.pid,
|
|
2276
|
-
expectedHome: runtimeConfig.runtimeHome,
|
|
2277
|
-
}
|
|
2278
|
-
);
|
|
2279
|
-
if (statusReason) throw nativeGatewayServingFailure(statusReason);
|
|
2280
|
-
let health;
|
|
2281
|
-
let missing;
|
|
2282
|
-
let wrong;
|
|
2283
|
-
let correct;
|
|
2284
|
-
try {
|
|
2285
|
-
[health, missing, wrong, correct] = await Promise.all([
|
|
2286
|
-
fetchImpl(`${origin}/health`, { signal: requestSignal() }),
|
|
2287
|
-
request(null),
|
|
2288
|
-
request("Bearer phase166-readiness-intentionally-wrong"),
|
|
2289
|
-
request(`Bearer ${apiServerKey}`),
|
|
2290
|
-
]);
|
|
2291
|
-
} catch (error) {
|
|
2292
|
-
throwIfAborted(signal);
|
|
2293
|
-
throw nativeGatewayServingFailure("API_UNAVAILABLE");
|
|
2294
|
-
}
|
|
2295
|
-
throwIfAborted(signal);
|
|
2296
|
-
if (health.status !== 200) {
|
|
2297
|
-
throw nativeGatewayServingFailure("API_UNAVAILABLE");
|
|
2298
|
-
}
|
|
2299
|
-
if (
|
|
2300
|
-
missing.status !== 401 ||
|
|
2301
|
-
wrong.status !== 401 ||
|
|
2302
|
-
correct.status !== 200
|
|
2303
|
-
) {
|
|
2304
|
-
throw nativeGatewayServingFailure("AUTH_UNAVAILABLE");
|
|
2305
|
-
}
|
|
2306
|
-
let capabilities;
|
|
2307
|
-
try {
|
|
2308
|
-
capabilities = await correct.json();
|
|
2309
|
-
} catch {
|
|
2310
|
-
throw nativeGatewayServingFailure("CAPABILITIES_UNAVAILABLE");
|
|
2311
|
-
}
|
|
2312
|
-
const finalStatusReason = nativeGatewayRuntimeStatusReason(
|
|
2313
|
-
readStatus(runtimeConfig.runtimeHome),
|
|
2314
|
-
{
|
|
2315
|
-
expectedPid: gateway.child.pid,
|
|
2316
|
-
expectedHome: runtimeConfig.runtimeHome,
|
|
2317
|
-
}
|
|
2318
|
-
);
|
|
2319
|
-
if (finalStatusReason) {
|
|
2320
|
-
throw nativeGatewayServingFailure(finalStatusReason);
|
|
2321
|
-
}
|
|
2322
|
-
if (
|
|
2323
|
-
capabilities?.auth?.required !== true ||
|
|
2324
|
-
capabilities?.features?.run_submission !== true ||
|
|
2325
|
-
capabilities?.features?.run_events_sse !== true
|
|
2326
|
-
) {
|
|
2327
|
-
throw nativeGatewayServingFailure("CAPABILITIES_UNAVAILABLE");
|
|
2328
|
-
}
|
|
2329
|
-
return Object.freeze({
|
|
2330
|
-
host: "127.0.0.1",
|
|
2331
|
-
port: 8642,
|
|
2332
|
-
cors: false,
|
|
2333
|
-
keyGeneration: desired.apiServerKeyGeneration,
|
|
2334
|
-
keyFingerprint: desired.apiServerKeyFingerprint,
|
|
2335
|
-
authStatusDigest: sha256(
|
|
2336
|
-
JSON.stringify({
|
|
2337
|
-
health: health.status,
|
|
2338
|
-
missing: missing.status,
|
|
2339
|
-
wrong: wrong.status,
|
|
2340
|
-
correct: correct.status,
|
|
2341
|
-
})
|
|
2342
|
-
),
|
|
2343
|
-
capabilitiesDigest: sha256(JSON.stringify(capabilities)),
|
|
2344
|
-
fixtureDigest: HERMES_RUNS_FIXTURE_IDENTITY.fixtureDigest,
|
|
2345
|
-
relayCapability: "fixture_bound_runs_relay_v1",
|
|
2346
|
-
runtimeGeneration: desired.runtimeGeneration,
|
|
2347
|
-
gatewayPid: gateway.child.pid,
|
|
2348
|
-
});
|
|
2349
|
-
}
|
|
2350
|
-
|
|
2351
|
-
async function proveApiServerReadiness({
|
|
2352
|
-
desired,
|
|
2353
|
-
apiServerKey,
|
|
2354
|
-
gateway,
|
|
2355
|
-
runtimeConfig,
|
|
2356
|
-
signal,
|
|
2357
|
-
}) {
|
|
2358
2194
|
const deadlineAt = Date.now() + API_SERVER_READINESS_DEADLINE_MS;
|
|
2359
2195
|
while (Date.now() < deadlineAt) {
|
|
2196
|
+
const gatewayReadback = gateway?.readback();
|
|
2197
|
+
if (!gatewayReadback?.running) {
|
|
2198
|
+
throw new Error(
|
|
2199
|
+
`customer_native_gateway_start_rejected:${redactRuntimeDiagnostic(
|
|
2200
|
+
gatewayReadback?.output ?? "candidate exited before readiness"
|
|
2201
|
+
)}`
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
const candidateStatus = readNativeGatewayRuntimeStatus(
|
|
2205
|
+
runtimeConfig.runtimeHome
|
|
2206
|
+
);
|
|
2207
|
+
if (
|
|
2208
|
+
!nativeGatewayRuntimeStatusAccepted(candidateStatus, {
|
|
2209
|
+
expectedPid: gateway.child.pid,
|
|
2210
|
+
expectedHome: runtimeConfig.runtimeHome,
|
|
2211
|
+
})
|
|
2212
|
+
) {
|
|
2213
|
+
await sleep(API_SERVER_READINESS_RETRY_MS);
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2360
2216
|
try {
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2217
|
+
const [health, missing, wrong, correct] = await Promise.all([
|
|
2218
|
+
fetch(`${origin}/health`, { signal: AbortSignal.timeout(5_000) }),
|
|
2219
|
+
request(null),
|
|
2220
|
+
request("Bearer phase166-readiness-intentionally-wrong"),
|
|
2221
|
+
request(`Bearer ${apiServerKey}`),
|
|
2222
|
+
]);
|
|
2223
|
+
const capabilities = correct.ok ? await correct.json() : null;
|
|
2224
|
+
if (
|
|
2225
|
+
health.status === 200 &&
|
|
2226
|
+
missing.status === 401 &&
|
|
2227
|
+
wrong.status === 401 &&
|
|
2228
|
+
correct.status === 200 &&
|
|
2229
|
+
nativeGatewayRuntimeStatusAccepted(
|
|
2230
|
+
readNativeGatewayRuntimeStatus(runtimeConfig.runtimeHome),
|
|
2231
|
+
{
|
|
2232
|
+
expectedPid: gateway.child.pid,
|
|
2233
|
+
expectedHome: runtimeConfig.runtimeHome,
|
|
2234
|
+
}
|
|
2235
|
+
) &&
|
|
2236
|
+
capabilities?.auth?.required === true &&
|
|
2237
|
+
capabilities?.features?.run_submission === true &&
|
|
2238
|
+
capabilities?.features?.run_events_sse === true
|
|
2239
|
+
) {
|
|
2240
|
+
return Object.freeze({
|
|
2241
|
+
host: "127.0.0.1",
|
|
2242
|
+
port: 8642,
|
|
2243
|
+
cors: false,
|
|
2244
|
+
keyGeneration: desired.apiServerKeyGeneration,
|
|
2245
|
+
keyFingerprint: desired.apiServerKeyFingerprint,
|
|
2246
|
+
authStatusDigest: sha256(
|
|
2247
|
+
JSON.stringify({
|
|
2248
|
+
health: health.status,
|
|
2249
|
+
missing: missing.status,
|
|
2250
|
+
wrong: wrong.status,
|
|
2251
|
+
correct: correct.status,
|
|
2252
|
+
})
|
|
2253
|
+
),
|
|
2254
|
+
capabilitiesDigest: sha256(JSON.stringify(capabilities)),
|
|
2255
|
+
fixtureDigest: HERMES_RUNS_FIXTURE_IDENTITY.fixtureDigest,
|
|
2256
|
+
relayCapability: "fixture_bound_runs_relay_v1",
|
|
2257
|
+
runtimeGeneration: desired.runtimeGeneration,
|
|
2258
|
+
gatewayPid: gateway.child.pid,
|
|
2259
|
+
});
|
|
2377
2260
|
}
|
|
2261
|
+
} catch {
|
|
2378
2262
|
// Hermes can hold its singleton lock before the local HTTP listener is
|
|
2379
2263
|
// accepting connections. Keep the lifecycle claim fenced while the
|
|
2380
2264
|
// bounded readiness window converges instead of replacing it again.
|
|
@@ -2391,12 +2275,10 @@ export async function startProvenNativeGateway({
|
|
|
2391
2275
|
startGateway = startNativeGateway,
|
|
2392
2276
|
proveReadiness = proveApiServerReadiness,
|
|
2393
2277
|
stopGateway = stopNativeGateway,
|
|
2394
|
-
signal,
|
|
2395
2278
|
}) {
|
|
2396
2279
|
let gateway = null;
|
|
2397
2280
|
try {
|
|
2398
|
-
gateway = await startGateway({ desired, runtimeConfig, secrets
|
|
2399
|
-
throwIfAborted(signal);
|
|
2281
|
+
gateway = await startGateway({ desired, runtimeConfig, secrets });
|
|
2400
2282
|
const gatewayReadback = gateway.readback();
|
|
2401
2283
|
if (!gatewayReadback.running) {
|
|
2402
2284
|
throw new Error("customer_native_gateway_readback_rejected");
|
|
@@ -2406,7 +2288,6 @@ export async function startProvenNativeGateway({
|
|
|
2406
2288
|
apiServerKey: secrets.hermesApiServer,
|
|
2407
2289
|
gateway,
|
|
2408
2290
|
runtimeConfig,
|
|
2409
|
-
signal,
|
|
2410
2291
|
});
|
|
2411
2292
|
return { gateway, gatewayReadback, apiServerReadiness };
|
|
2412
2293
|
} catch (error) {
|
|
@@ -2429,230 +2310,6 @@ export async function startProvenNativeGateway({
|
|
|
2429
2310
|
}
|
|
2430
2311
|
}
|
|
2431
2312
|
|
|
2432
|
-
function nativeGatewayServingObservation({
|
|
2433
|
-
runtime,
|
|
2434
|
-
bootSessionId,
|
|
2435
|
-
flyGeneration,
|
|
2436
|
-
state,
|
|
2437
|
-
reasonCode,
|
|
2438
|
-
checkedAt,
|
|
2439
|
-
}) {
|
|
2440
|
-
const gatewayPid = runtime.gateway?.child?.pid;
|
|
2441
|
-
const desired = runtime.desired;
|
|
2442
|
-
const authority = runtime.servingAuthority;
|
|
2443
|
-
if (
|
|
2444
|
-
!Number.isSafeInteger(gatewayPid) ||
|
|
2445
|
-
gatewayPid <= 1 ||
|
|
2446
|
-
authority?.runtimeId !== desired.runtimeId ||
|
|
2447
|
-
authority?.profileId !== desired.profileId ||
|
|
2448
|
-
!Number.isSafeInteger(authority?.runtimeGeneration) ||
|
|
2449
|
-
authority.runtimeGeneration < 1 ||
|
|
2450
|
-
!Number.isSafeInteger(authority?.configGeneration) ||
|
|
2451
|
-
authority.configGeneration < 1 ||
|
|
2452
|
-
!Number.isSafeInteger(authority?.lifecycleGeneration) ||
|
|
2453
|
-
authority.lifecycleGeneration < 0
|
|
2454
|
-
) {
|
|
2455
|
-
throw new Error("customer_native_gateway_observation_rejected");
|
|
2456
|
-
}
|
|
2457
|
-
return Object.freeze({
|
|
2458
|
-
version: 1,
|
|
2459
|
-
runtimeId: authority.runtimeId,
|
|
2460
|
-
flyGeneration,
|
|
2461
|
-
runtimeGeneration: authority.runtimeGeneration,
|
|
2462
|
-
configGeneration: authority.configGeneration,
|
|
2463
|
-
lifecycleGeneration: authority.lifecycleGeneration,
|
|
2464
|
-
bootSessionId,
|
|
2465
|
-
profileId: desired.profileId,
|
|
2466
|
-
hermesHomeId: flyRuntimeDigest({
|
|
2467
|
-
schemaVersion: "sellable-agent-hermes-home/v1",
|
|
2468
|
-
root: DATA_ROOT,
|
|
2469
|
-
profileId: desired.profileId,
|
|
2470
|
-
}),
|
|
2471
|
-
gatewayPid,
|
|
2472
|
-
checkedAt: new Date(checkedAt).toISOString(),
|
|
2473
|
-
state,
|
|
2474
|
-
reasonCode,
|
|
2475
|
-
});
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
/**
|
|
2479
|
-
* One in-process, single-flight owner beside the existing signed claim loop.
|
|
2480
|
-
* It creates no timer: the lifecycle loop decides when to call poll().
|
|
2481
|
-
*/
|
|
2482
|
-
export function createNativeGatewayServingSupervisor({
|
|
2483
|
-
runtime,
|
|
2484
|
-
getActiveRuntime,
|
|
2485
|
-
bootSessionId,
|
|
2486
|
-
flyGeneration,
|
|
2487
|
-
now = Date.now,
|
|
2488
|
-
random = Math.random,
|
|
2489
|
-
pause = (ms, signal) => abortablePause(ms, signal, sleep),
|
|
2490
|
-
proveServing = proveNativeGatewayServing,
|
|
2491
|
-
startProvenGateway = startProvenNativeGateway,
|
|
2492
|
-
stopGateway = stopNativeGateway,
|
|
2493
|
-
}) {
|
|
2494
|
-
let stopped = false;
|
|
2495
|
-
let episode = null;
|
|
2496
|
-
let episodeController = null;
|
|
2497
|
-
let nextEligibleAt = now() + NATIVE_GATEWAY_SERVING_PROOF_INTERVAL_MS;
|
|
2498
|
-
let current = null;
|
|
2499
|
-
const outgoing = [];
|
|
2500
|
-
|
|
2501
|
-
const authorityCurrent = () =>
|
|
2502
|
-
!stopped &&
|
|
2503
|
-
getActiveRuntime() === runtime &&
|
|
2504
|
-
runtime.servingAuthority?.flyGeneration === flyGeneration;
|
|
2505
|
-
|
|
2506
|
-
const publish = (state, reasonCode) => {
|
|
2507
|
-
if (!authorityCurrent()) return null;
|
|
2508
|
-
const observation = nativeGatewayServingObservation({
|
|
2509
|
-
runtime,
|
|
2510
|
-
bootSessionId,
|
|
2511
|
-
flyGeneration,
|
|
2512
|
-
state,
|
|
2513
|
-
reasonCode,
|
|
2514
|
-
checkedAt: now(),
|
|
2515
|
-
});
|
|
2516
|
-
current = observation;
|
|
2517
|
-
if (state !== "READY") {
|
|
2518
|
-
while (outgoing[0]?.state === "READY") outgoing.shift();
|
|
2519
|
-
}
|
|
2520
|
-
const previous = outgoing.at(-1);
|
|
2521
|
-
if (
|
|
2522
|
-
previous?.state === observation.state &&
|
|
2523
|
-
previous?.reasonCode === observation.reasonCode &&
|
|
2524
|
-
previous?.gatewayPid === observation.gatewayPid
|
|
2525
|
-
) {
|
|
2526
|
-
outgoing[outgoing.length - 1] = observation;
|
|
2527
|
-
} else {
|
|
2528
|
-
outgoing.push(observation);
|
|
2529
|
-
}
|
|
2530
|
-
return observation;
|
|
2531
|
-
};
|
|
2532
|
-
|
|
2533
|
-
publish("READY", null);
|
|
2534
|
-
|
|
2535
|
-
const runEpisode = async (signal) => {
|
|
2536
|
-
let failureReason = "RECOVERY_FAILED";
|
|
2537
|
-
try {
|
|
2538
|
-
await proveServing({
|
|
2539
|
-
desired: runtime.desired,
|
|
2540
|
-
apiServerKey: runtime.secrets.hermesApiServer,
|
|
2541
|
-
gateway: runtime.gateway,
|
|
2542
|
-
runtimeConfig: runtime.runtimeConfig,
|
|
2543
|
-
signal,
|
|
2544
|
-
});
|
|
2545
|
-
if (!authorityCurrent()) return;
|
|
2546
|
-
publish("READY", null);
|
|
2547
|
-
nextEligibleAt = now() + NATIVE_GATEWAY_SERVING_PROOF_INTERVAL_MS;
|
|
2548
|
-
return;
|
|
2549
|
-
} catch (error) {
|
|
2550
|
-
throwIfAborted(signal);
|
|
2551
|
-
failureReason = nativeGatewayServingFailureReason(error);
|
|
2552
|
-
}
|
|
2553
|
-
|
|
2554
|
-
publish("RECOVERING", "RECOVERY_IN_PROGRESS");
|
|
2555
|
-
const deadlineAt = now() + NATIVE_GATEWAY_RECOVERY_BUDGET_MS;
|
|
2556
|
-
for (
|
|
2557
|
-
let attempt = 1;
|
|
2558
|
-
attempt <= NATIVE_GATEWAY_RECOVERY_MAX_ATTEMPTS;
|
|
2559
|
-
attempt += 1
|
|
2560
|
-
) {
|
|
2561
|
-
const backoff = Math.round(
|
|
2562
|
-
Math.min(
|
|
2563
|
-
NATIVE_GATEWAY_RECOVERY_BACKOFF_CAP_MS,
|
|
2564
|
-
NATIVE_GATEWAY_RECOVERY_BACKOFF_MS * 2 ** (attempt - 1)
|
|
2565
|
-
) *
|
|
2566
|
-
(0.75 + random() * 0.5)
|
|
2567
|
-
);
|
|
2568
|
-
await pause(backoff, signal);
|
|
2569
|
-
throwIfAborted(signal);
|
|
2570
|
-
if (!authorityCurrent()) return;
|
|
2571
|
-
if (now() >= deadlineAt) {
|
|
2572
|
-
failureReason = "RECOVERY_FAILED";
|
|
2573
|
-
break;
|
|
2574
|
-
}
|
|
2575
|
-
try {
|
|
2576
|
-
await stopGateway(runtime.gateway);
|
|
2577
|
-
throwIfAborted(signal);
|
|
2578
|
-
if (!authorityCurrent()) return;
|
|
2579
|
-
if (now() >= deadlineAt) {
|
|
2580
|
-
failureReason = "RECOVERY_FAILED";
|
|
2581
|
-
break;
|
|
2582
|
-
}
|
|
2583
|
-
const proven = await startProvenGateway({
|
|
2584
|
-
desired: runtime.desired,
|
|
2585
|
-
runtimeConfig: runtime.runtimeConfig,
|
|
2586
|
-
secrets: runtime.secrets,
|
|
2587
|
-
signal,
|
|
2588
|
-
proveReadiness: (input) => proveServing({ ...input, signal }),
|
|
2589
|
-
});
|
|
2590
|
-
throwIfAborted(signal);
|
|
2591
|
-
if (!authorityCurrent()) {
|
|
2592
|
-
await stopGateway(proven.gateway);
|
|
2593
|
-
return;
|
|
2594
|
-
}
|
|
2595
|
-
if (now() >= deadlineAt) {
|
|
2596
|
-
await stopGateway(proven.gateway);
|
|
2597
|
-
failureReason = "RECOVERY_FAILED";
|
|
2598
|
-
break;
|
|
2599
|
-
}
|
|
2600
|
-
runtime.gateway = proven.gateway;
|
|
2601
|
-
publish("READY", null);
|
|
2602
|
-
nextEligibleAt = now() + NATIVE_GATEWAY_SERVING_PROOF_INTERVAL_MS;
|
|
2603
|
-
return;
|
|
2604
|
-
} catch (error) {
|
|
2605
|
-
throwIfAborted(signal);
|
|
2606
|
-
failureReason = nativeGatewayServingFailureReason(error);
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
if (!authorityCurrent()) return;
|
|
2610
|
-
publish("DEGRADED", failureReason);
|
|
2611
|
-
nextEligibleAt = now() + NATIVE_GATEWAY_RECOVERY_COOLDOWN_MS;
|
|
2612
|
-
};
|
|
2613
|
-
|
|
2614
|
-
return Object.freeze({
|
|
2615
|
-
pendingObservation() {
|
|
2616
|
-
return outgoing[0] ?? current;
|
|
2617
|
-
},
|
|
2618
|
-
observationSent(observation) {
|
|
2619
|
-
if (outgoing[0] === observation) outgoing.shift();
|
|
2620
|
-
},
|
|
2621
|
-
poll({ force = false } = {}) {
|
|
2622
|
-
if (
|
|
2623
|
-
stopped ||
|
|
2624
|
-
episode ||
|
|
2625
|
-
!authorityCurrent() ||
|
|
2626
|
-
(!force && now() < nextEligibleAt)
|
|
2627
|
-
) {
|
|
2628
|
-
return episode;
|
|
2629
|
-
}
|
|
2630
|
-
episodeController = new AbortController();
|
|
2631
|
-
const activeEpisode = runEpisode(episodeController.signal)
|
|
2632
|
-
.catch((error) => {
|
|
2633
|
-
if (error?.name !== "AbortError") throw error;
|
|
2634
|
-
})
|
|
2635
|
-
.finally(() => {
|
|
2636
|
-
if (episode === activeEpisode) {
|
|
2637
|
-
episode = null;
|
|
2638
|
-
episodeController = null;
|
|
2639
|
-
}
|
|
2640
|
-
});
|
|
2641
|
-
episode = activeEpisode;
|
|
2642
|
-
return activeEpisode;
|
|
2643
|
-
},
|
|
2644
|
-
async stop() {
|
|
2645
|
-
stopped = true;
|
|
2646
|
-
episodeController?.abort();
|
|
2647
|
-
try {
|
|
2648
|
-
await episode;
|
|
2649
|
-
} catch (error) {
|
|
2650
|
-
if (error?.name !== "AbortError") throw error;
|
|
2651
|
-
}
|
|
2652
|
-
},
|
|
2653
|
-
});
|
|
2654
|
-
}
|
|
2655
|
-
|
|
2656
2313
|
async function proveProvisionedApplication({
|
|
2657
2314
|
desired,
|
|
2658
2315
|
runtimeConfig,
|
|
@@ -3159,14 +2816,6 @@ async function processOperation({ claim, exchange, client, identity }) {
|
|
|
3159
2816
|
runtimeConfig,
|
|
3160
2817
|
trustRootPem,
|
|
3161
2818
|
action: claim.operation.action,
|
|
3162
|
-
servingAuthority: Object.freeze({
|
|
3163
|
-
runtimeId: RUNTIME_ID,
|
|
3164
|
-
flyGeneration: RUNTIME_GENERATION,
|
|
3165
|
-
runtimeGeneration: RUNTIME_GENERATION,
|
|
3166
|
-
configGeneration: claim.operation.configGeneration,
|
|
3167
|
-
lifecycleGeneration: claim.operation.lifecycleGeneration,
|
|
3168
|
-
profileId: desired.profileId,
|
|
3169
|
-
}),
|
|
3170
2819
|
gateway: proof.gateway ?? null,
|
|
3171
2820
|
modelHealthReporter,
|
|
3172
2821
|
};
|
|
@@ -3216,7 +2865,6 @@ async function main() {
|
|
|
3216
2865
|
});
|
|
3217
2866
|
const controller = new AbortController();
|
|
3218
2867
|
let activeRuntime = null;
|
|
3219
|
-
let servingSupervisor = null;
|
|
3220
2868
|
let shutdownMemorySnapshot = null;
|
|
3221
2869
|
const endpointRelay = createCustomerEndpointRelay({
|
|
3222
2870
|
getControlClient: () => client,
|
|
@@ -3230,8 +2878,6 @@ async function main() {
|
|
|
3230
2878
|
const runtime = activeRuntime;
|
|
3231
2879
|
shutdownMemorySnapshot = (async () => {
|
|
3232
2880
|
try {
|
|
3233
|
-
await servingSupervisor?.stop();
|
|
3234
|
-
servingSupervisor = null;
|
|
3235
2881
|
await endpointRelay.fenceActive("RUNTIME_SHUTDOWN");
|
|
3236
2882
|
await stopNativeGateway(runtime.gateway);
|
|
3237
2883
|
runtime.gateway = null;
|
|
@@ -3273,10 +2919,6 @@ async function main() {
|
|
|
3273
2919
|
callerProfileId: activeRuntime.desired.profileId,
|
|
3274
2920
|
}),
|
|
3275
2921
|
leaseSeconds: 60,
|
|
3276
|
-
servingObservation:
|
|
3277
|
-
servingSupervisor?.pendingObservation() ?? undefined,
|
|
3278
|
-
onServingObservationSent: (observation) =>
|
|
3279
|
-
servingSupervisor?.observationSent(observation),
|
|
3280
2922
|
})
|
|
3281
2923
|
: await client.claim(60);
|
|
3282
2924
|
} catch (error) {
|
|
@@ -3321,12 +2963,20 @@ async function main() {
|
|
|
3321
2963
|
),
|
|
3322
2964
|
});
|
|
3323
2965
|
}
|
|
3324
|
-
|
|
2966
|
+
if (
|
|
2967
|
+
activeRuntime?.action !== "INSTANTIATE" &&
|
|
2968
|
+
activeRuntime?.gateway &&
|
|
2969
|
+
!activeRuntime.gateway.readback().running
|
|
2970
|
+
) {
|
|
2971
|
+
activeRuntime.gateway = await startNativeGateway({
|
|
2972
|
+
desired: activeRuntime.desired,
|
|
2973
|
+
runtimeConfig: activeRuntime.runtimeConfig,
|
|
2974
|
+
secrets: activeRuntime.secrets,
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
3325
2977
|
await sleep(1_000);
|
|
3326
2978
|
continue;
|
|
3327
2979
|
}
|
|
3328
|
-
await servingSupervisor?.stop();
|
|
3329
|
-
servingSupervisor = null;
|
|
3330
2980
|
operationHeartbeat = startOperationHeartbeat({
|
|
3331
2981
|
claim,
|
|
3332
2982
|
exchange,
|
|
@@ -3341,15 +2991,6 @@ async function main() {
|
|
|
3341
2991
|
client,
|
|
3342
2992
|
identity,
|
|
3343
2993
|
});
|
|
3344
|
-
servingSupervisor =
|
|
3345
|
-
activeRuntime.action === "INSTANTIATE"
|
|
3346
|
-
? null
|
|
3347
|
-
: createNativeGatewayServingSupervisor({
|
|
3348
|
-
runtime: activeRuntime,
|
|
3349
|
-
getActiveRuntime: () => activeRuntime,
|
|
3350
|
-
bootSessionId: BOOT_SESSION_ID,
|
|
3351
|
-
flyGeneration: RUNTIME_GENERATION,
|
|
3352
|
-
});
|
|
3353
2994
|
soulClaimTracker = createSoulClaimObservationTracker({
|
|
3354
2995
|
runtimeId: RUNTIME_ID,
|
|
3355
2996
|
profileId: activeRuntime.desired.profileId,
|
|
@@ -784,8 +784,6 @@ export async function claimWithSoulObservation({
|
|
|
784
784
|
tracker,
|
|
785
785
|
readSoul,
|
|
786
786
|
leaseSeconds = 60,
|
|
787
|
-
servingObservation,
|
|
788
|
-
onServingObservationSent,
|
|
789
787
|
}) {
|
|
790
788
|
// Deliver lifecycle work before any potentially slow local loader proof.
|
|
791
789
|
// Reconciliation uses the same cadence only after the server confirms that
|
|
@@ -793,9 +791,7 @@ export async function claimWithSoulObservation({
|
|
|
793
791
|
const tick = await client.claimTick({
|
|
794
792
|
leaseSeconds,
|
|
795
793
|
soulObservation: tracker.pending(),
|
|
796
|
-
...(servingObservation ? { servingObservation } : {}),
|
|
797
794
|
});
|
|
798
|
-
if (servingObservation) onServingObservationSent?.(servingObservation);
|
|
799
795
|
if (tick?.claim) return tick.claim;
|
|
800
796
|
|
|
801
797
|
const acknowledgement = tick?.soulAcknowledgement;
|
|
@@ -1078,15 +1074,10 @@ export function createFlyCustomerControlClient({
|
|
|
1078
1074
|
}
|
|
1079
1075
|
return { ...result, signedUrl: undefined, bytes };
|
|
1080
1076
|
};
|
|
1081
|
-
const claimTick = async ({
|
|
1082
|
-
leaseSeconds = 60,
|
|
1083
|
-
soulObservation,
|
|
1084
|
-
servingObservation,
|
|
1085
|
-
} = {}) =>
|
|
1077
|
+
const claimTick = async ({ leaseSeconds = 60, soulObservation } = {}) =>
|
|
1086
1078
|
post("claim", "/api/v3/sellable-agent/worker/claim", {
|
|
1087
1079
|
leaseSeconds,
|
|
1088
1080
|
...(soulObservation ? { soulObservation } : {}),
|
|
1089
|
-
...(servingObservation ? { servingObservation } : {}),
|
|
1090
1081
|
});
|
|
1091
1082
|
return Object.freeze({
|
|
1092
1083
|
claimTick,
|
|
@@ -70,6 +70,7 @@ const MAX_BYTES = 128 * 1024 * 1024;
|
|
|
70
70
|
// Slack gateway for a full ten minutes.
|
|
71
71
|
const NATIVE_COMMAND_TIMEOUT_MS = 120_000;
|
|
72
72
|
const TRANSIENT_PROFILE_ROOTS = new Set([".cache"]);
|
|
73
|
+
const TRANSIENT_PROFILE_PATHS = new Set([".sellable-admin/.venvs"]);
|
|
73
74
|
// The encrypted Fly volume remains the authoritative full profile. The
|
|
74
75
|
// portable disaster-recovery archive also carries state.db and sessions so a
|
|
75
76
|
// replacement volume retains chat history and Slack thread routing. Caches,
|
|
@@ -220,13 +221,23 @@ export function mergeHermesState(sourceRoot, targetRoot) {
|
|
|
220
221
|
) {
|
|
221
222
|
throw new Error("hermes_snapshot_merge_root_rejected");
|
|
222
223
|
}
|
|
223
|
-
const merge = (source, target,
|
|
224
|
+
const merge = (source, target, relativeRoot = "") => {
|
|
224
225
|
for (const name of readdirSync(source)) {
|
|
225
226
|
// UV deliberately materializes wheel-cache entries as symlinks. The
|
|
226
227
|
// cache is disposable and must not become durable profile state; skip
|
|
227
228
|
// only the top-level cache while preserving the closed symlink reject
|
|
228
|
-
// for sessions, skills, memory, and every other Hermes-owned path.
|
|
229
|
-
|
|
229
|
+
// for sessions, skills, memory, and every other Hermes-owned path. Old
|
|
230
|
+
// Admin installers also put a symlink-bearing Lightfield virtualenv in
|
|
231
|
+
// `.sellable-admin/.venvs`; current images materialize it under the
|
|
232
|
+
// runtime root, so it is disposable legacy state rather than profile
|
|
233
|
+
// content.
|
|
234
|
+
const relativePath = relativeRoot ? `${relativeRoot}/${name}` : name;
|
|
235
|
+
if (
|
|
236
|
+
(!relativeRoot && TRANSIENT_PROFILE_ROOTS.has(name)) ||
|
|
237
|
+
TRANSIENT_PROFILE_PATHS.has(relativePath)
|
|
238
|
+
) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
230
241
|
const from = join(source, name);
|
|
231
242
|
const to = join(target, name);
|
|
232
243
|
const stat = lstatSync(from);
|
|
@@ -243,7 +254,7 @@ export function mergeHermesState(sourceRoot, targetRoot) {
|
|
|
243
254
|
}
|
|
244
255
|
if (stat.isDirectory()) {
|
|
245
256
|
mkdirSync(to, { mode: stat.mode & 0o777 });
|
|
246
|
-
merge(from, to);
|
|
257
|
+
merge(from, to, relativePath);
|
|
247
258
|
} else if (stat.isFile()) {
|
|
248
259
|
copyFileSync(from, to);
|
|
249
260
|
chmodSync(to, stat.mode & 0o777);
|
|
@@ -252,7 +263,7 @@ export function mergeHermesState(sourceRoot, targetRoot) {
|
|
|
252
263
|
}
|
|
253
264
|
}
|
|
254
265
|
};
|
|
255
|
-
merge(sourceRoot, targetRoot
|
|
266
|
+
merge(sourceRoot, targetRoot);
|
|
256
267
|
}
|
|
257
268
|
|
|
258
269
|
function cronJobs(profileRoot) {
|
|
@@ -38,8 +38,8 @@ import { installAgentHostServices } from "./service-installer.mjs";
|
|
|
38
38
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
39
39
|
const VERSION = "sellable-agent-host-bootstrap/v1";
|
|
40
40
|
const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
|
|
41
|
-
const INSTALLER_PACKAGE = "@sellable/install@0.1.
|
|
42
|
-
const MCP_PACKAGE = "@sellable/mcp@0.1.
|
|
41
|
+
const INSTALLER_PACKAGE = "@sellable/install@0.1.609";
|
|
42
|
+
const MCP_PACKAGE = "@sellable/mcp@0.1.865";
|
|
43
43
|
const HERMES_VERSION = "0.18.0";
|
|
44
44
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
45
45
|
const SHA256_16 = /^[a-f0-9]{16}$/;
|
|
@@ -1546,9 +1546,8 @@ export function compileClaimToProfileDesired(activeClaim, config) {
|
|
|
1546
1546
|
]) ||
|
|
1547
1547
|
pinned.hermesCli !== "hermes" ||
|
|
1548
1548
|
pinned.hermesVersion !== "0.18.0" ||
|
|
1549
|
-
pinned.installerPackage !==
|
|
1550
|
-
|
|
1551
|
-
pinned.mcpPackage !== "@sellable/mcp@0.1.864" ||
|
|
1549
|
+
pinned.installerPackage !== "@sellable/install@0.1.609" ||
|
|
1550
|
+
pinned.mcpPackage !== "@sellable/mcp@0.1.865" ||
|
|
1552
1551
|
!Array.isArray(policy.toolInclude) ||
|
|
1553
1552
|
policy.toolInclude.length === 0 ||
|
|
1554
1553
|
!Number.isSafeInteger(slack.generation) ||
|
|
@@ -278,9 +278,8 @@ function validateDesired(desired) {
|
|
|
278
278
|
desired.serviceCredentialGeneration < 1 ||
|
|
279
279
|
desired.hermesCli !== "hermes" ||
|
|
280
280
|
desired.hermesVersion !== "0.18.0" ||
|
|
281
|
-
desired.installerPackage !==
|
|
282
|
-
|
|
283
|
-
desired.mcpPackage !== "@sellable/mcp@0.1.864" ||
|
|
281
|
+
desired.installerPackage !== "@sellable/install@0.1.609" ||
|
|
282
|
+
desired.mcpPackage !== "@sellable/mcp@0.1.865" ||
|
|
284
283
|
!Array.isArray(desired.toolInclude) ||
|
|
285
284
|
desired.toolInclude.length === 0 ||
|
|
286
285
|
desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
|
|
@@ -25,9 +25,8 @@ import { fileURLToPath } from "node:url";
|
|
|
25
25
|
import { deriveContainedProfileId } from "./profile-materializer.mjs";
|
|
26
26
|
|
|
27
27
|
export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
|
|
28
|
-
export const PINNED_INSTALL_PACKAGE =
|
|
29
|
-
|
|
30
|
-
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.864";
|
|
28
|
+
export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.609";
|
|
29
|
+
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.865";
|
|
31
30
|
const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
|
|
32
31
|
PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
|
|
33
32
|
);
|
package/package.json
CHANGED
|
@@ -5,7 +5,9 @@ visibility: public
|
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- mcp__sellable__refill_sends
|
|
7
7
|
- mcp__sellable__refill_v3_advance
|
|
8
|
+
- mcp__sellable__refill_v3_world_state
|
|
8
9
|
- mcp__sellable__refill_v3_continue
|
|
10
|
+
- mcp__sellable__get_campaigns
|
|
9
11
|
- mcp__sellable__get_refill_target_plan
|
|
10
12
|
- mcp__sellable__get_scheduler_fill_capacity
|
|
11
13
|
- mcp__sellable__get_subskill_asset
|
|
@@ -604,6 +606,19 @@ Synchronous source, configuration, rubric, and exhaustion authorities already
|
|
|
604
606
|
return after their own exact reread, so they return `settlement.ready:true` from
|
|
605
607
|
that bounded authority receipt and never wait on unrelated campaign-wide
|
|
606
608
|
processing cells.
|
|
609
|
+
|
|
610
|
+
When a non-yolo request names a campaign, resolve that visible name with one
|
|
611
|
+
authorized bounded `get_campaigns` call carrying an explicit `limit`. Require
|
|
612
|
+
exactly one case-insensitive exact match; if none or more than one match, stop
|
|
613
|
+
without preview or mutation. Retain only the returned stable campaign ID. Call
|
|
614
|
+
`refill_v3_world_state({workspaceId, scope, campaignId})`, render that exact
|
|
615
|
+
campaign's two-lane readiness and its explicit zero product side effects, then
|
|
616
|
+
stop at the product-native question with exactly Accept and Decline. Do not call
|
|
617
|
+
`refill_v3_advance` before Accept. Accept calls
|
|
618
|
+
`refill_v3_advance({workspaceId, scope, campaignId})`; Decline stops. Carry the
|
|
619
|
+
same campaignId through every advance, continue, replay, partial-wait, and
|
|
620
|
+
post-wait repeat. Display-name changes never alter this retained identity, and
|
|
621
|
+
no sibling campaign may replace it.
|
|
607
622
|
Call the next advance only when `settlement.ready` is true. When it is false,
|
|
608
623
|
call `wait_for_campaign_processing` with `settlement.resumeInput` **unchanged**;
|
|
609
624
|
that carries the exact `preparationJobId`, `requirePreparationTerminal:true`,
|