@sellable/install 0.1.810 → 0.1.813

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.
@@ -1,5 +1,5 @@
1
- // Shared by the control plane and the runtime. A custom Runs API is only a
2
- // requested destination here; the runtime must also match a trusted connection.
1
+ // Shared by the control plane and the runtime. A custom Runs API step always
2
+ // names the workspace model connection that serves it.
3
3
  export const ENDPOINT_MODEL_SELECTION_ALLOWLIST = Object.freeze({
4
4
  openrouter: Object.freeze([
5
5
  // Retained for existing saved selections; the UI shortlist excludes Opus.
@@ -49,25 +49,22 @@ export function normalizeRunsApiBaseUrl(value) {
49
49
  }
50
50
  }
51
51
 
52
- // One model: provider, model, effort and, for a custom Runs API, its URL.
53
- // A custom step may also name the workspace's own model connection
54
- // (modelConnectionId), which is always https. Skew: an older runtime refuses
55
- // that unknown key as "invalid", so Verify & save refuses a connection step
56
- // on a machine that cannot fetch its key per attempt.
52
+ // One model: provider, model, effort and, for a custom Runs API, its URL and
53
+ // the workspace's own model connection (modelConnectionId), always https.
54
+ // Skew: an older runtime refuses that unknown key as "invalid", so Verify &
55
+ // save refuses a connection step on a machine that cannot fetch its key per
56
+ // attempt.
57
57
  function compileEndpointModelStep(value, extraKeys = []) {
58
58
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
59
59
  const custom = Object.hasOwn(value, "runsApiBaseUrl");
60
- const connection = Object.hasOwn(value, "modelConnectionId");
61
60
  if (
62
61
  Object.keys(value)
63
62
  .filter((key) => !extraKeys.includes(key))
64
63
  .sort()
65
64
  .join(",") !==
66
- (connection
65
+ (custom
67
66
  ? "model,modelConnectionId,provider,reasoningEffort,runsApiBaseUrl"
68
- : custom
69
- ? "model,provider,reasoningEffort,runsApiBaseUrl"
70
- : "model,provider,reasoningEffort")
67
+ : "model,provider,reasoningEffort")
71
68
  )
72
69
  return null;
73
70
  const { provider, model, reasoningEffort } = value;
@@ -95,7 +92,7 @@ function compileEndpointModelStep(value, extraKeys = []) {
95
92
  return null;
96
93
  const { modelConnectionId } = value;
97
94
  if (
98
- connection &&
95
+ custom &&
99
96
  (typeof modelConnectionId !== "string" ||
100
97
  !/^[A-Za-z0-9_-]{1,128}$/.test(modelConnectionId) ||
101
98
  !runsApiBaseUrl.startsWith("https://"))
@@ -105,8 +102,7 @@ function compileEndpointModelStep(value, extraKeys = []) {
105
102
  provider,
106
103
  model,
107
104
  reasoningEffort,
108
- ...(custom ? { runsApiBaseUrl } : {}),
109
- ...(connection ? { modelConnectionId } : {}),
105
+ ...(custom ? { runsApiBaseUrl, modelConnectionId } : {}),
110
106
  });
111
107
  }
112
108
 
@@ -1,36 +1,12 @@
1
- export const RUNS_API_CONNECTIONS_ENV: "SELLABLE_AGENT_RUNS_API_CONNECTIONS_JSON";
1
+ /** The connection fields the OpenAI-protocol Runs adapter reads. */
2
2
  export type RunsApiConnection = Readonly<{
3
- runtimeId: string;
4
- workspaceId: string;
5
- agentId: string;
6
3
  baseUrl: string;
7
- apiKeyFile: string;
8
- /** Absent: serves every endpoint of its agent. */
9
- endpointIds?: string[];
10
- workKinds: Array<"STANDARD" | "OPENER" | "REPLY" | "FOLLOW_UP">;
11
4
  models: Array<{
12
5
  provider: string;
13
6
  model: string;
14
7
  reasoningEfforts: string[];
15
8
  }>;
16
- localRoot?: string;
17
- toolEndpointIds?: string[];
18
- name?: string;
19
9
  protocol?: "runs-api" | "openai-chat" | "openai-responses";
20
10
  structuredOutputs?: boolean;
21
11
  }>;
22
- export function runsApiConnectionId(connection: RunsApiConnection): string;
23
- export function runsApiConnectionServesEndpoint(
24
- connection: RunsApiConnection,
25
- endpointId: string
26
- ): boolean;
27
- export function parseRunsApiConnections(raw: unknown): RunsApiConnection[];
28
- export function runsApiConnectionsForRuntime(
29
- raw: unknown,
30
- runtime: {
31
- runtimeId: string;
32
- workspaceId: string;
33
- agentId: string;
34
- }
35
- ): RunsApiConnection[];
36
12
  export function createConfiguredRunsClient(options: any): Promise<any>;
@@ -1,17 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import {
3
- compileEndpointModelSelection,
4
- normalizeRunsApiBaseUrl,
5
- } from "./endpoint-model-selection.mjs";
2
+ import { compileEndpointModelSelection } from "./endpoint-model-selection.mjs";
6
3
  import { createHermesRunsClient } from "./hermes-runs-client.mjs";
7
- import { createOpenAiRunsClient } from "./openai-runs-client.mjs";
8
4
  import { createPinnedFetch } from "./public-endpoint-guard.mjs";
9
5
  import { createWorkspaceConnectionRunsClient } from "./workspace-connection-client.mjs";
10
6
 
11
- export const RUNS_API_CONNECTIONS_ENV =
12
- "SELLABLE_AGENT_RUNS_API_CONNECTIONS_JSON";
13
- const id = (value) =>
14
- typeof value === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(value);
15
7
  const record = (value) =>
16
8
  value && typeof value === "object" && !Array.isArray(value);
17
9
  const stable = (value) =>
@@ -23,187 +15,6 @@ const stable = (value) =>
23
15
  .map((key) => `${JSON.stringify(key)}:${stable(value[key])}`)
24
16
  .join(",")}}`
25
17
  : JSON.stringify(value);
26
- const fail = () => {
27
- throw new Error("runs_api_connection_configuration_rejected");
28
- };
29
-
30
- // Only the control plane's machine configuration supplies this registry. It
31
- // contains credential file references, never credentials or request input.
32
- export function parseRunsApiConnections(raw) {
33
- if (raw === undefined || raw === "") return [];
34
- if (typeof raw !== "string" || Buffer.byteLength(raw) > 64 * 1024) fail();
35
- let entries;
36
- try {
37
- entries = JSON.parse(raw);
38
- } catch {
39
- fail();
40
- }
41
- if (!Array.isArray(entries) || entries.length > 32) fail();
42
- const seen = new Set();
43
- return entries.map((entry) => {
44
- if (
45
- !record(entry) ||
46
- Object.keys(entry).some(
47
- (key) =>
48
- ![
49
- "runtimeId",
50
- "workspaceId",
51
- "agentId",
52
- "baseUrl",
53
- "apiKeyFile",
54
- "endpointIds",
55
- "workKinds",
56
- "models",
57
- "localRoot",
58
- "toolEndpointIds",
59
- "name",
60
- "protocol",
61
- "structuredOutputs",
62
- ].includes(key)
63
- ) ||
64
- ![entry.runtimeId, entry.workspaceId, entry.agentId].every(id) ||
65
- (entry.protocol !== undefined &&
66
- !["runs-api", "openai-chat", "openai-responses"].includes(
67
- entry.protocol
68
- )) ||
69
- (entry.structuredOutputs !== undefined &&
70
- typeof entry.structuredOutputs !== "boolean") ||
71
- (entry.name !== undefined &&
72
- (typeof entry.name !== "string" ||
73
- !/^[\p{L}\p{N} .()_-]{1,80}$/u.test(entry.name))) ||
74
- typeof entry.apiKeyFile !== "string" ||
75
- !/^\/(?:opt\/data|run\/secrets)\/[A-Za-z0-9_./-]{1,250}$/.test(
76
- entry.apiKeyFile
77
- ) ||
78
- entry.apiKeyFile
79
- .split("/")
80
- .some((part) => part === "." || part === "..") ||
81
- // Absent: the connection serves every endpoint of its agent.
82
- (entry.endpointIds !== undefined &&
83
- (!Array.isArray(entry.endpointIds) ||
84
- !entry.endpointIds.length ||
85
- entry.endpointIds.length > 64 ||
86
- !entry.endpointIds.every(id))) ||
87
- !Array.isArray(entry.workKinds) ||
88
- !entry.workKinds.length ||
89
- !entry.workKinds.every((kind) =>
90
- ["STANDARD", "OPENER", "REPLY", "FOLLOW_UP"].includes(kind)
91
- ) ||
92
- !Array.isArray(entry.models) ||
93
- !entry.models.length ||
94
- entry.models.length > 16
95
- )
96
- fail();
97
- const baseUrl = normalizeRunsApiBaseUrl(entry.baseUrl);
98
- if (!baseUrl) fail();
99
- const identity = `${entry.runtimeId}:${baseUrl}`;
100
- if (seen.has(identity)) fail();
101
- seen.add(identity);
102
- const models = entry.models.map((model) => {
103
- if (
104
- !record(model) ||
105
- Object.keys(model).sort().join(",") !==
106
- "model,provider,reasoningEfforts" ||
107
- !Array.isArray(model.reasoningEfforts) ||
108
- !model.reasoningEfforts.length ||
109
- model.reasoningEfforts.some(
110
- (reasoningEffort) =>
111
- compileEndpointModelSelection({
112
- provider: model.provider,
113
- model: model.model,
114
- reasoningEffort,
115
- runsApiBaseUrl: baseUrl,
116
- }) === "invalid"
117
- )
118
- )
119
- fail();
120
- return { ...model, reasoningEfforts: [...model.reasoningEfforts] };
121
- });
122
- if (entry.localRoot !== undefined) {
123
- const url = new URL(baseUrl);
124
- if (
125
- (entry.protocol !== undefined && entry.protocol !== "runs-api") ||
126
- typeof entry.localRoot !== "string" ||
127
- !/^\/opt\/data\/[A-Za-z0-9_-]{1,80}$/.test(entry.localRoot) ||
128
- entry.apiKeyFile !== `${entry.localRoot}/api-key` ||
129
- url.hostname !== "127.0.0.1" ||
130
- url.protocol !== "http:" ||
131
- url.pathname !== "/" ||
132
- !url.port ||
133
- Number(url.port) < 1024 ||
134
- models.some(
135
- (model) =>
136
- model.provider !== "claude-code" ||
137
- !/^claude-[a-z0-9-]{1,100}$/.test(model.model) ||
138
- model.reasoningEfforts.some(
139
- (effort) => !["low", "medium", "high"].includes(effort)
140
- )
141
- )
142
- )
143
- fail();
144
- }
145
- // When present, only runs on these endpoints get their own allowed tools
146
- // from the image-owned service. When absent, a run gets the tools its own
147
- // signed turn descriptor allows, and a run allowed none stays tool-free.
148
- if (
149
- entry.toolEndpointIds !== undefined &&
150
- (entry.localRoot === undefined ||
151
- !Array.isArray(entry.toolEndpointIds) ||
152
- !entry.toolEndpointIds.length ||
153
- !entry.toolEndpointIds.every(id) ||
154
- new Set(entry.toolEndpointIds).size !== entry.toolEndpointIds.length ||
155
- (entry.endpointIds !== undefined &&
156
- entry.toolEndpointIds.some(
157
- (value) => !entry.endpointIds.includes(value)
158
- )))
159
- )
160
- fail();
161
- return Object.freeze({
162
- ...entry,
163
- baseUrl,
164
- models,
165
- ...(entry.endpointIds === undefined
166
- ? {}
167
- : { endpointIds: [...entry.endpointIds] }),
168
- workKinds: [...entry.workKinds],
169
- ...(entry.toolEndpointIds === undefined
170
- ? {}
171
- : { toolEndpointIds: [...entry.toolEndpointIds] }),
172
- });
173
- });
174
- }
175
-
176
- /** Whether a trusted connection may serve runs of this endpoint. */
177
- export function runsApiConnectionServesEndpoint(connection, endpointId) {
178
- return (
179
- connection.endpointIds === undefined ||
180
- connection.endpointIds.includes(endpointId)
181
- );
182
- }
183
-
184
- export function runsApiConnectionId(connection) {
185
- return `runs_${createHash("sha256")
186
- .update(
187
- JSON.stringify([
188
- connection.runtimeId,
189
- connection.workspaceId,
190
- connection.agentId,
191
- connection.baseUrl,
192
- ])
193
- )
194
- .digest("hex")
195
- .slice(0, 24)}`;
196
- }
197
-
198
- export function runsApiConnectionsForRuntime(raw, runtime) {
199
- return parseRunsApiConnections(raw).filter(
200
- (connection) =>
201
- connection.runtimeId === runtime.runtimeId &&
202
- connection.workspaceId === runtime.workspaceId &&
203
- connection.agentId === runtime.agentId
204
- );
205
- }
206
-
207
18
  // Exactly the members the endpoint-connection-key verb returns. The key follows
208
19
  // the saved key's rule (16-1024 characters), never the key-file rule; the
209
20
  // remote tool's values are checked where the create body is built.
@@ -260,17 +71,15 @@ function customRunsClient(client, claim, remoteTools) {
260
71
  export async function createConfiguredRunsClient({
261
72
  apiKeyFile,
262
73
  claim,
263
- env = process.env,
264
74
  clientFactory = createHermesRunsClient,
265
75
  fetchConnectionKey,
266
76
  chainStep = 0,
267
77
  createFetch = createPinnedFetch,
268
78
  }) {
269
79
  const selection = claim?.run?.modelSelection;
270
- // A workspace connection never uses the registry, even on a matching URL:
271
- // its key is fetched for this attempt and lives only in these closures, and
272
- // every request goes through the address guard. URL and model come from
273
- // the signed claim, never from the key response.
80
+ // A workspace connection's key is fetched for this attempt and lives only in
81
+ // these closures, and every request goes through the address guard. URL and
82
+ // model come from the signed claim, never from the key response.
274
83
  if (selection?.modelConnectionId) {
275
84
  const step = compileEndpointModelSelection(selection);
276
85
  if (!step || step === "invalid") throw new Error("runs_api_model_rejected");
@@ -305,46 +114,9 @@ export async function createConfiguredRunsClient({
305
114
  }
306
115
  );
307
116
  }
308
- if (!selection?.runsApiBaseUrl) return clientFactory({ apiKeyFile });
309
- const model = compileEndpointModelSelection(selection);
310
- if (!model || model === "invalid") throw new Error("runs_api_model_rejected");
311
- const connection = runsApiConnectionsForRuntime(
312
- env[RUNS_API_CONNECTIONS_ENV],
313
- {
314
- runtimeId: claim.runtime?.runtimeId,
315
- workspaceId: claim.attempt?.workspaceId,
316
- agentId: claim.attempt?.agentId,
317
- }
318
- ).find((value) => value.baseUrl === model.runsApiBaseUrl);
319
- const workKind = claim.run.productExecution?.workKind ?? "STANDARD";
320
- if (
321
- !connection ||
322
- env.SELLABLE_RUNTIME_ID !== connection.runtimeId ||
323
- !runsApiConnectionServesEndpoint(connection, claim.attempt.endpointId) ||
324
- !connection.workKinds.includes(workKind) ||
325
- !connection.models.some(
326
- (allowed) =>
327
- allowed.provider === model.provider &&
328
- allowed.model === model.model &&
329
- allowed.reasoningEfforts.includes(model.reasoningEffort)
330
- )
331
- )
332
- throw new Error("runs_api_connection_not_authorized");
333
- // This client instance retains one destination and one credential for the
334
- // entire attempt, including stream/status/stop, even if settings later change.
335
- const client = connection.protocol?.startsWith("openai-")
336
- ? createOpenAiRunsClient({
337
- connection,
338
- responseSchema: claim.revision?.outputSchema,
339
- modelSelection: model,
340
- })
341
- : clientFactory({
342
- baseUrl: connection.baseUrl,
343
- apiKeyFile: connection.apiKeyFile,
344
- scopeHeaders: {
345
- "x-sellable-workspace-id": connection.workspaceId,
346
- "x-sellable-agent-id": connection.agentId,
347
- },
348
- });
349
- return customRunsClient(client, claim);
117
+ // A custom URL is served only through a workspace connection: no other
118
+ // client, credential or socket is opened for it.
119
+ if (selection?.runsApiBaseUrl)
120
+ throw new Error("runs_api_connection_unavailable");
121
+ return clientFactory({ apiKeyFile });
350
122
  }
@@ -1,4 +1,3 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { pathToFileURL } from "node:url";
3
2
  import {
4
3
  compileEndpointModelSelection,
@@ -6,124 +5,23 @@ import {
6
5
  endpointModelSelectionSource,
7
6
  } from "./endpoint-model-selection.mjs";
8
7
  import { createHermesRunsClient } from "./hermes-runs-client.mjs";
9
- import {
10
- createConfiguredRunsClient,
11
- RUNS_API_CONNECTIONS_ENV,
12
- runsApiConnectionId,
13
- runsApiConnectionServesEndpoint,
14
- runsApiConnectionsForRuntime,
15
- } from "./runs-api-connections.mjs";
16
8
  import { probeRunsClient } from "./workspace-connection-client.mjs";
17
9
 
18
10
  // Fixed read-only machine command. Callers supply identities, never a URL,
19
- // credential path, shell command, or prompt. The installed registry authorizes
20
- // the destination again on the machine before a credential is read.
21
- export async function checkRunsApiConnection(
22
- request,
23
- {
24
- env = process.env,
25
- fetchImpl = fetch,
26
- readToken = (path) => readFile(path, "utf8"),
27
- } = {}
28
- ) {
29
- const checkedAt = new Date().toISOString();
30
- const result = (state) => ({
11
+ // credential path, shell command, or prompt. The machine holds no connection
12
+ // registry, so no id names a connection it can check: a workspace connection
13
+ // is probed by the server and shape-checked here by VERIFY.
14
+ export async function checkRunsApiConnection(request) {
15
+ return {
31
16
  connectionId: request.connectionId,
32
- state,
33
- checkedAt,
34
- });
35
- if (request.runtimeId !== env.SELLABLE_RUNTIME_ID)
36
- return result("NOT_CONFIGURED");
37
- const connection = runsApiConnectionsForRuntime(
38
- env[RUNS_API_CONNECTIONS_ENV],
39
- request
40
- ).find(
41
- (candidate) =>
42
- runsApiConnectionId(candidate) === request.connectionId &&
43
- runsApiConnectionServesEndpoint(candidate, request.endpointId)
44
- );
45
- if (!connection) return result("NOT_CONFIGURED");
46
- try {
47
- const token = (await readToken(connection.apiKeyFile)).trim();
48
- if (!token || token.length > 8192 || /[\r\n]/.test(token))
49
- return result("UNAVAILABLE");
50
- const response = await fetchImpl(
51
- `${connection.baseUrl}/${connection.protocol?.startsWith("openai-") ? "models" : "health"}`,
52
- {
53
- headers: {
54
- authorization: `Bearer ${token}`,
55
- ...(connection.protocol?.startsWith("openai-")
56
- ? {}
57
- : {
58
- "x-sellable-workspace-id": connection.workspaceId,
59
- "x-sellable-agent-id": connection.agentId,
60
- }),
61
- },
62
- redirect: "error",
63
- signal: AbortSignal.timeout(12_000),
64
- }
65
- );
66
- if (response.status === 401 || response.status === 403)
67
- return result("ACCESS_DENIED");
68
- if (!response.ok) return result("UNAVAILABLE");
69
- const reader = response.body?.getReader();
70
- if (!reader) return result("UNAVAILABLE");
71
- let text = "";
72
- let size = 0;
73
- try {
74
- const decoder = new TextDecoder();
75
- while (true) {
76
- const chunk = await reader.read();
77
- if (chunk.done) break;
78
- size += chunk.value.byteLength;
79
- if (size > 16_384) {
80
- await reader.cancel();
81
- return result("UNAVAILABLE");
82
- }
83
- text += decoder.decode(chunk.value, { stream: true });
84
- }
85
- text += decoder.decode();
86
- } finally {
87
- reader.releaseLock();
88
- }
89
- const health = JSON.parse(text);
90
- if (connection.localRoot) {
91
- if (
92
- health.provider !== "claude-code" ||
93
- health.scope?.workspaceId !== connection.workspaceId ||
94
- health.scope?.agentId !== connection.agentId
95
- )
96
- return result("ACCESS_DENIED");
97
- if (health.authentication === "SIGN_IN_REQUIRED")
98
- return result("SIGN_IN_REQUIRED");
99
- return result(
100
- health.authentication === "READY" && health.ok === true
101
- ? "READY"
102
- : "UNAVAILABLE"
103
- );
104
- }
105
- return result(
106
- (
107
- connection.protocol?.startsWith("openai-")
108
- ? Array.isArray(health.data)
109
- : health.ok === true
110
- )
111
- ? "REACHABLE"
112
- : "UNAVAILABLE"
113
- );
114
- } catch {
115
- return result("UNAVAILABLE");
116
- }
17
+ state: "NOT_CONFIGURED",
18
+ checkedAt: new Date().toISOString(),
19
+ };
117
20
  }
118
21
 
119
22
  export async function verifyRunsApiSelection(
120
23
  request,
121
- {
122
- env = process.env,
123
- makeDefaultClient = createHermesRunsClient,
124
- makeConfiguredClient = createConfiguredRunsClient,
125
- health = checkRunsApiConnection,
126
- } = {}
24
+ { env = process.env, makeDefaultClient = createHermesRunsClient } = {}
127
25
  ) {
128
26
  const result = (state, servedRuntime) => ({
129
27
  connectionId: request.connectionId,
@@ -146,29 +44,7 @@ export async function verifyRunsApiSelection(
146
44
  // machine holds none to probe with. This is a shape check only; the server
147
45
  // performs the connectivity probe (228-05).
148
46
  if (selection?.modelConnectionId) return result("REACHABLE");
149
- let verificationWorkKind = "STANDARD";
150
- if (selection?.runsApiBaseUrl) {
151
- const connection = runsApiConnectionsForRuntime(
152
- env[RUNS_API_CONNECTIONS_ENV],
153
- request
154
- ).find(
155
- (item) =>
156
- runsApiConnectionId(item) === request.connectionId &&
157
- item.baseUrl === selection.runsApiBaseUrl
158
- );
159
- if (
160
- !connection ||
161
- !runsApiConnectionServesEndpoint(connection, request.endpointId)
162
- )
163
- return result("NOT_CONFIGURED");
164
- verificationWorkKind = connection.workKinds.includes("STANDARD")
165
- ? "STANDARD"
166
- : connection.workKinds[0];
167
- if (connection.localRoot) {
168
- const readiness = await health(request, { env });
169
- if (readiness.state !== "READY") return readiness;
170
- }
171
- } else if (request.connectionId !== endpointModelSelectionSource(selection))
47
+ if (request.connectionId !== endpointModelSelectionSource(selection))
172
48
  return result("NOT_CONFIGURED");
173
49
  const apiKeyFile =
174
50
  env.SELLABLE_AGENT_SERVER_AUTHENTICATED_KIND === "ADMIN"
@@ -176,30 +52,13 @@ export async function verifyRunsApiSelection(
176
52
  : `/run/sellable-agent/${request.profileId}/api-server-key`;
177
53
  let client;
178
54
  try {
179
- client = selection?.runsApiBaseUrl
180
- ? await makeConfiguredClient({
181
- apiKeyFile,
182
- env,
183
- claim: {
184
- runtime: { runtimeId: request.runtimeId },
185
- attempt: {
186
- workspaceId: request.workspaceId,
187
- agentId: request.agentId,
188
- endpointId: request.endpointId,
189
- },
190
- run: {
191
- modelSelection: selection,
192
- productExecution: { workKind: verificationWorkKind },
193
- },
194
- },
195
- })
196
- : makeDefaultClient({ apiKeyFile });
55
+ client = makeDefaultClient({ apiKeyFile });
197
56
  } catch {
198
57
  return result("UNAVAILABLE");
199
58
  }
200
59
  const probe = await probeRunsClient(client, selection, {
201
60
  signal: AbortSignal.timeout(70_000),
202
- submitVerification: !selection?.runsApiBaseUrl,
61
+ submitVerification: true,
203
62
  });
204
63
  return result(probe.state, probe.servedRuntime);
205
64
  }
@@ -530,8 +530,11 @@ const RETRYABLE_WORKER_CONTROL_STATUSES = new Set([
530
530
  408, 425, 429, 500, 502, 503, 504,
531
531
  ]);
532
532
 
533
- class WorkerControlResponseError extends Error {
534
- constructor(code, { retryable = false, status = null } = {}) {
533
+ export class WorkerControlResponseError extends Error {
534
+ constructor(
535
+ code,
536
+ { retryable = false, status = /** @type {number | null} */ (null) } = {}
537
+ ) {
535
538
  super(code);
536
539
  this.name = "WorkerControlResponseError";
537
540
  this.retryable = retryable;
@@ -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.810";
42
- const MCP_PACKAGE = "@sellable/mcp@0.1.958";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.813";
42
+ const MCP_PACKAGE = "@sellable/mcp@0.1.959";
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.810" ||
1550
- pinned.mcpPackage !== "@sellable/mcp@0.1.958" ||
1549
+ pinned.installerPackage !== "@sellable/install@0.1.813" ||
1550
+ pinned.mcpPackage !== "@sellable/mcp@0.1.959" ||
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.810" ||
316
- desired.mcpPackage !== "@sellable/mcp@0.1.958" ||
315
+ desired.installerPackage !== "@sellable/install@0.1.813" ||
316
+ desired.mcpPackage !== "@sellable/mcp@0.1.959" ||
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.810";
29
- export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.958";
28
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.813";
29
+ export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.959";
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.810",
3
+ "version": "0.1.813",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- export function tagEndpointSession(line: string, sessionId: string): string;