@sellable/install 0.1.789 → 0.1.791

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.789
7
- ARG MCP_PACKAGE=@sellable/mcp@0.1.955
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.791
7
+ ARG MCP_PACKAGE=@sellable/mcp@0.1.956
8
8
  ARG INSTALLER_INTEGRITY
9
- ARG MCP_INTEGRITY=sha512-OsyazlSe75wU6O1jAqOutTTt3K+rP5epwQKZEDWyl+qz0wxstVwTbq3FFEGniwr+svVOX3GWET8koIqeDyanow==
9
+ ARG MCP_INTEGRITY=sha512-62iUZKLKer7LbUcFLliBsXEJ6UhCqLDcoHTk9seN/Pp6qdaIFC1qHj8aitfazhGerb4wSVpEqOQuPDJCxuZa9g==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.789" \
12
- && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.955" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.791" \
12
+ && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.956" \
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.789" \
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.789' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.955') 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.791' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.956') 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.789', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.955', 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.791', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.956', 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,7 +3,7 @@
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.789` is
6
+ `@sellable/install@0.1.791` is
7
7
  published:
8
8
 
9
9
  ```sh
@@ -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.955";
31
+ const MCP_VERSION = "0.1.956";
32
32
  const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
33
33
  const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
34
34
 
@@ -6,9 +6,9 @@ USER root
6
6
 
7
7
  ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
8
8
 
9
- ARG MCP_PACKAGE=@sellable/mcp@0.1.955
10
- ARG MCP_INTEGRITY=sha512-OsyazlSe75wU6O1jAqOutTTt3K+rP5epwQKZEDWyl+qz0wxstVwTbq3FFEGniwr+svVOX3GWET8koIqeDyanow==
11
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.789
9
+ ARG MCP_PACKAGE=@sellable/mcp@0.1.956
10
+ ARG MCP_INTEGRITY=sha512-62iUZKLKer7LbUcFLliBsXEJ6UhCqLDcoHTk9seN/Pp6qdaIFC1qHj8aitfazhGerb4wSVpEqOQuPDJCxuZa9g==
11
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.791
12
12
  ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.202
13
13
  ARG ADMIN_MCP_INTEGRITY=sha512-ym3aye/0vQ8tfULJlKz9JFOxvIQsl1G+n3np+TTgK/tecwNblBo/j3AkTnRqH9iomKzPbZzP+n4tEQsvsU8ByA==
14
14
  ARG TUS_JS_CLIENT_VERSION=4.3.1
@@ -95,6 +95,7 @@ import {
95
95
  import {
96
96
  createHermesRunsClient,
97
97
  HERMES_RUNS_FIXTURE_IDENTITY,
98
+ validateEndpointModelSelection,
98
99
  validateOpenerOutputContract,
99
100
  } from "./hermes-runs-client.mjs";
100
101
  import {
@@ -123,9 +124,9 @@ const RUNTIME_GENERATION = Number(
123
124
  const BOOT_SESSION_ID = randomUUID();
124
125
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.21.5";
125
126
  const INSTALLER_PACKAGE =
126
- process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.789";
127
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.791";
127
128
  const MCP_PACKAGE =
128
- process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.955";
129
+ process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.956";
129
130
  export const ENDPOINT_MAX_CONCURRENT_RUNS = 10;
130
131
  const DEFAULT_PROFILE_BUNDLE_ROOT =
131
132
  "/usr/local/share/sellable-agent/default-profile-bundle";
@@ -1071,6 +1072,16 @@ function endpointEventEnvelope(event, ordinal) {
1071
1072
  outputDigest: event.outputDigest ?? null,
1072
1073
  errorDigest: event.errorDigest ?? null,
1073
1074
  evidenceDigest: event.evidence?.digest ?? null,
1075
+ ...(event.servedRuntime || event.usage
1076
+ ? {
1077
+ servedProvider: event.servedRuntime?.provider ?? null,
1078
+ servedModel: event.servedRuntime?.model ?? null,
1079
+ inputTokens: event.usage?.inputTokens ?? null,
1080
+ outputTokens: event.usage?.outputTokens ?? null,
1081
+ cacheReadTokens: event.usage?.cacheReadTokens ?? null,
1082
+ cacheWriteTokens: event.usage?.cacheWriteTokens ?? null,
1083
+ }
1084
+ : {}),
1074
1085
  ...(/^writer-[123]$|^editor$/.test(event.stage ?? "")
1075
1086
  ? {
1076
1087
  stage: event.stage,
@@ -1887,6 +1898,11 @@ export function createCustomerEndpointRelay(options = {}) {
1887
1898
 
1888
1899
  const runClaim = async (claim, outerSignal) => {
1889
1900
  const identity = endpointClaimIdentity(claim);
1901
+ // A caller-named model rides on the run; absent means the machine default.
1902
+ const modelSelection =
1903
+ claim?.run?.modelSelection === undefined
1904
+ ? null
1905
+ : validateEndpointModelSelection(claim.run.modelSelection);
1890
1906
  const activeRuntime = getActiveRuntime();
1891
1907
  if (!endpointRuntimeCurrent(claim, activeRuntime, runtimeGeneration)) {
1892
1908
  return { ok: false, code: "customer_endpoint_generation_fenced" };
@@ -2031,6 +2047,15 @@ export function createCustomerEndpointRelay(options = {}) {
2031
2047
  held.committedSequence = sequences.at(-1);
2032
2048
  };
2033
2049
  try {
2050
+ if (usesOpenerDraftStrategy(claim) && modelSelection) {
2051
+ // The three-draft stages are pinned to the Codex output-format path in
2052
+ // the Hermes bridge; refuse rather than silently serve the default.
2053
+ await complete(claim, identity, {
2054
+ attemptStatus: "FAILED",
2055
+ status: "FAILED",
2056
+ });
2057
+ return { ok: false, code: "OPENER_STAGE_MODEL_SELECTION_UNSUPPORTED" };
2058
+ }
2034
2059
  if (usesOpenerDraftStrategy(claim)) {
2035
2060
  // Phase 206: writers are private native sessions within this one fenced
2036
2061
  // claim. Only the editor's validated result reaches durable completion.
@@ -2126,6 +2151,7 @@ export function createCustomerEndpointRelay(options = {}) {
2126
2151
  claim.run.conversationHistory
2127
2152
  ),
2128
2153
  correlation: `${claim.attempt.conversationId}:${claim.attempt.runId}:${claim.attempt.id}`,
2154
+ ...(modelSelection ? { modelSelection } : {}),
2129
2155
  };
2130
2156
  while (!submitted) {
2131
2157
  try {
@@ -200,6 +200,39 @@ function canonicalHistory(value) {
200
200
  return result;
201
201
  }
202
202
 
203
+ // The models a caller may name for one endpoint run. The server checks the
204
+ // same list before it hands the run over; the runtime checks it again here so
205
+ // a run can never reach Hermes with a model that was not reviewed.
206
+ export const ENDPOINT_MODEL_SELECTION_ALLOWLIST = Object.freeze({
207
+ openrouter: Object.freeze([
208
+ "anthropic/claude-opus-5.5",
209
+ "anthropic/claude-sonnet-5.5",
210
+ ]),
211
+ });
212
+ const ENDPOINT_MODEL_REASONING_EFFORTS = Object.freeze([
213
+ "low",
214
+ "medium",
215
+ "high",
216
+ "xhigh",
217
+ ]);
218
+
219
+ export function validateEndpointModelSelection(value) {
220
+ if (
221
+ !exactObject(value) ||
222
+ Object.keys(value).sort().join(",") !== "model,provider,reasoningEffort" ||
223
+ !Object.hasOwn(ENDPOINT_MODEL_SELECTION_ALLOWLIST, value.provider) ||
224
+ !ENDPOINT_MODEL_SELECTION_ALLOWLIST[value.provider].includes(value.model) ||
225
+ !ENDPOINT_MODEL_REASONING_EFFORTS.includes(value.reasoningEffort)
226
+ ) {
227
+ throw new Error("hermes_runs_model_selection_rejected");
228
+ }
229
+ return Object.freeze({
230
+ provider: value.provider,
231
+ model: value.model,
232
+ reasoningEffort: value.reasoningEffort,
233
+ });
234
+ }
235
+
203
236
  export function validateOpenerOutputContract(value) {
204
237
  const schema = value?.schema;
205
238
  if (
@@ -232,7 +265,7 @@ function compileCreateBody(input, openerStage = false) {
232
265
  "input",
233
266
  "instructions",
234
267
  "correlation",
235
- ...(openerStage ? ["outputContract"] : []),
268
+ ...(openerStage ? ["outputContract"] : ["modelSelection"]),
236
269
  ];
237
270
  if (Object.keys(input).some((key) => !allowed.includes(key))) {
238
271
  throw new Error("hermes_runs_submit_rejected");
@@ -254,11 +287,24 @@ function compileCreateBody(input, openerStage = false) {
254
287
  if (openerStage && history.length !== 0) {
255
288
  throw new Error("hermes_opener_stage_history_rejected");
256
289
  }
290
+ const modelSelection =
291
+ input.modelSelection === undefined
292
+ ? null
293
+ : validateEndpointModelSelection(input.modelSelection);
257
294
  return Object.freeze({
258
295
  input: compiledInput,
259
296
  instructions,
260
297
  conversation_history: history,
261
298
  session_id: `${openerStage ? "sellable_opener_stage" : "sellable_endpoint"}_${sha256(correlation).slice(0, 32)}`,
299
+ // Hermes 0.21.5 honours a per-run provider and model on /v1/runs and
300
+ // resolves the provider's credential from the machine's own pool.
301
+ ...(modelSelection === null
302
+ ? {}
303
+ : {
304
+ provider: modelSelection.provider,
305
+ model: modelSelection.model,
306
+ model_options: { reasoning_effort: modelSelection.reasoningEffort },
307
+ }),
262
308
  ...(input.outputContract === undefined
263
309
  ? {}
264
310
  : {
@@ -287,6 +333,30 @@ async function readBounded(response, maximum = MAX_RESPONSE_BYTES) {
287
333
  }
288
334
  }
289
335
 
336
+ const SERVED_RUNTIME_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
337
+
338
+ // The provider and model that actually served a run (a fallback can differ
339
+ // from the request) and its token counts, so cost and model are auditable.
340
+ function servedRuntime(value) {
341
+ return exactObject(value) &&
342
+ SERVED_RUNTIME_ID.test(value.provider ?? "") &&
343
+ SERVED_RUNTIME_ID.test(value.model ?? "")
344
+ ? Object.freeze({ provider: value.provider, model: value.model })
345
+ : null;
346
+ }
347
+
348
+ function runUsage(value) {
349
+ if (!exactObject(value)) return null;
350
+ const count = (key) =>
351
+ Number.isSafeInteger(value[key]) && value[key] >= 0 ? value[key] : null;
352
+ return Object.freeze({
353
+ inputTokens: count("input_tokens"),
354
+ outputTokens: count("output_tokens"),
355
+ cacheReadTokens: count("cache_read_tokens"),
356
+ cacheWriteTokens: count("cache_write_tokens"),
357
+ });
358
+ }
359
+
290
360
  function ensureRunId(value) {
291
361
  if (!RUN_ID.test(value ?? "")) throw new Error("hermes_runs_run_id_rejected");
292
362
  return value;
@@ -340,6 +410,8 @@ function nativeStatus(value, expectedRunId) {
340
410
  classification: "COMPLETED",
341
411
  output,
342
412
  outputDigest: sha256(stableJson(output)),
413
+ servedRuntime: servedRuntime(value.runtime),
414
+ usage: runUsage(value.usage),
343
415
  });
344
416
  }
345
417
  if (status === "failed") {
@@ -439,6 +511,8 @@ export function sanitizeHermesRunEvent(value, options = {}) {
439
511
  output: value.output,
440
512
  outputDigest: sha256(stableJson(value.output)),
441
513
  usageDigest: sha256(stableJson(value.usage)),
514
+ servedRuntime: servedRuntime(value.runtime),
515
+ usage: runUsage(value.usage),
442
516
  });
443
517
  }
444
518
  if (value.event === "run.failed") {
@@ -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.789";
42
- const MCP_PACKAGE = "@sellable/mcp@0.1.955";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.791";
42
+ const MCP_PACKAGE = "@sellable/mcp@0.1.956";
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,8 +1546,8 @@ export function compileClaimToProfileDesired(activeClaim, config) {
1546
1546
  ]) ||
1547
1547
  pinned.hermesCli !== "hermes" ||
1548
1548
  pinned.hermesVersion !== "0.18.0" ||
1549
- pinned.installerPackage !== "@sellable/install@0.1.789" ||
1550
- pinned.mcpPackage !== "@sellable/mcp@0.1.955" ||
1549
+ pinned.installerPackage !== "@sellable/install@0.1.791" ||
1550
+ pinned.mcpPackage !== "@sellable/mcp@0.1.956" ||
1551
1551
  !Array.isArray(policy.toolInclude) ||
1552
1552
  policy.toolInclude.length === 0 ||
1553
1553
  !Number.isSafeInteger(slack.generation) ||
@@ -312,8 +312,8 @@ function validateDesired(desired) {
312
312
  desired.serviceCredentialGeneration < 1 ||
313
313
  desired.hermesCli !== "hermes" ||
314
314
  desired.hermesVersion !== "0.18.0" ||
315
- desired.installerPackage !== "@sellable/install@0.1.789" ||
316
- desired.mcpPackage !== "@sellable/mcp@0.1.955" ||
315
+ desired.installerPackage !== "@sellable/install@0.1.791" ||
316
+ desired.mcpPackage !== "@sellable/mcp@0.1.956" ||
317
317
  !Array.isArray(desired.toolInclude) ||
318
318
  desired.toolInclude.length === 0 ||
319
319
  desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
@@ -25,8 +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 = "@sellable/install@0.1.789";
29
- export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.955";
28
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.791";
29
+ export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.956";
30
30
  const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
31
31
  PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
32
32
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.789",
3
+ "version": "0.1.791",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {