@sellable/install 0.1.608-wip.phase188.20260812.2 → 0.1.608

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.
@@ -3,13 +3,13 @@ FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
5
5
 
6
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.608-wip.phase188.20260812.2
7
- ARG MCP_PACKAGE=@sellable/mcp@0.1.864
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.608
7
+ ARG MCP_PACKAGE=@sellable/mcp@0.1.865
8
8
  ARG INSTALLER_INTEGRITY
9
- ARG MCP_INTEGRITY=sha512-UevVd71zQL2FgnqYaeHBkTi73iUV0Lg5BchM5JNhUbvaFxAFQ2M1n9uSS18qVuM9Km0aGZ7a0k8M9e2AVPboAw==
9
+ ARG MCP_INTEGRITY=sha512-282pkYFADFrrIXyQcdUpQwakXiwe/AFUDpDZkTh29CFcGHnN7dvC0MCvaU4LffXA+ZuKCpuf02uxCLGiIEOjbw==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.608-wip.phase188.20260812.2" \
12
- && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.864" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.608" \
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.608-wip.phase188.20260812.2' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.864') throw new Error('package identity rejected');" \
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.608-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.608-wip.phase188.20260812.2', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.864', 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}" \
28
+ && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.608', 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
@@ -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.608-wip.phase188.20260812.2` is
6
+ `@sellable/install@0.1.608` 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.608-wip.phase188.20260812.2-wip.hermes-desktop.20260811160530 .
12
+ --tag sellable-agent-host:0.1.608-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.864";
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.864
8
- ARG MCP_INTEGRITY=sha512-UevVd71zQL2FgnqYaeHBkTi73iUV0Lg5BchM5JNhUbvaFxAFQ2M1n9uSS18qVuM9Km0aGZ7a0k8M9e2AVPboAw==
9
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.608-wip.phase188.20260812.2
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.608
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.608";
101
100
  const MCP_PACKAGE =
102
- process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.864";
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
- nativeGatewayRuntimeStatusReason(status, {
2219
- expectedPid,
2220
- expectedHome,
2221
- }) === null
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
- export async function proveNativeGatewayServing({
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
- fetchImpl(`${origin}/v1/capabilities`, {
2190
+ fetch(`${origin}/v1/capabilities`, {
2260
2191
  headers: authorization ? { authorization } : {},
2261
- signal: requestSignal(),
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
- return await proveNativeGatewayServing({
2362
- desired,
2363
- apiServerKey,
2364
- gateway,
2365
- runtimeConfig,
2366
- signal,
2367
- });
2368
- } catch (error) {
2369
- throwIfAborted(signal);
2370
- if (nativeGatewayServingFailureReason(error) === "GATEWAY_STOPPED") {
2371
- const gatewayReadback = gateway?.readback();
2372
- throw new Error(
2373
- `customer_native_gateway_start_rejected:${redactRuntimeDiagnostic(
2374
- gatewayReadback?.output ?? "candidate exited before readiness"
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, signal });
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
- void servingSupervisor?.poll();
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,
@@ -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.608-wip.phase188.20260812.2";
42
- const MCP_PACKAGE = "@sellable/mcp@0.1.864";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.608";
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
- "@sellable/install@0.1.608-wip.phase188.20260812.2" ||
1551
- pinned.mcpPackage !== "@sellable/mcp@0.1.864" ||
1549
+ pinned.installerPackage !== "@sellable/install@0.1.608" ||
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
- "@sellable/install@0.1.608-wip.phase188.20260812.2" ||
283
- desired.mcpPackage !== "@sellable/mcp@0.1.864" ||
281
+ desired.installerPackage !== "@sellable/install@0.1.608" ||
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
- "@sellable/install@0.1.608-wip.phase188.20260812.2";
30
- export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.864";
28
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.608";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.608-wip.phase188.20260812.2",
3
+ "version": "0.1.608",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {
@@ -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`,