@sellable/install 0.1.797-wip.runtime-ux.20261004.2 → 0.1.799
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 +4 -4
- package/container/README.md +1 -1
- package/lib/sellable-agent/fly-customer-image/Dockerfile +1 -1
- package/lib/sellable-agent/fly-customer-image/claude-code-runs-api.d.mts +25 -8
- package/lib/sellable-agent/fly-customer-image/claude-code-runs-api.mjs +203 -36
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +1 -1
- package/lib/sellable-agent/host-bootstrap.mjs +1 -1
- package/lib/sellable-agent/host-worker.mjs +1 -1
- package/lib/sellable-agent/profile-materializer.mjs +1 -1
- package/lib/sellable-agent/provisioning-adapter.mjs +1 -1
- package/package.json +1 -1
package/container/Dockerfile
CHANGED
|
@@ -3,12 +3,12 @@ FROM ${HERMES_IMAGE}
|
|
|
3
3
|
|
|
4
4
|
USER root
|
|
5
5
|
|
|
6
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
6
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.799
|
|
7
7
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.957
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
9
|
ARG MCP_INTEGRITY=sha512-tIZuO5IEanT/afamDIWdorUNMdqZA6xnjL9jGAmGqYIlIqkXdHB9slWiwfLfmM44iEp7OT98IBHJlvbpMThk6A==
|
|
10
10
|
|
|
11
|
-
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.
|
|
11
|
+
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.799" \
|
|
12
12
|
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.957" \
|
|
13
13
|
&& test -n "${INSTALLER_INTEGRITY}" \
|
|
14
14
|
&& test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
|
|
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.797-wip.runtime-ux.2026
|
|
|
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.799' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.957') 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.799', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.957', 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,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.
|
|
6
|
+
`@sellable/install@0.1.799` is
|
|
7
7
|
published:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
@@ -8,7 +8,7 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
|
|
|
8
8
|
|
|
9
9
|
ARG MCP_PACKAGE=@sellable/mcp@0.1.957
|
|
10
10
|
ARG MCP_INTEGRITY=sha512-tIZuO5IEanT/afamDIWdorUNMdqZA6xnjL9jGAmGqYIlIqkXdHB9slWiwfLfmM44iEp7OT98IBHJlvbpMThk6A==
|
|
11
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
11
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.799
|
|
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
|
|
@@ -2,6 +2,7 @@ import type { SpawnOptions } from "node:child_process";
|
|
|
2
2
|
import type { Server } from "node:http";
|
|
3
3
|
import type { RunsApiConnection } from "./runs-api-connections.mjs";
|
|
4
4
|
|
|
5
|
+
export const CLAUDE_RUNS_MAX_CONCURRENCY: number;
|
|
5
6
|
export type ClaudeRunsRequest = {
|
|
6
7
|
input: string;
|
|
7
8
|
instructions: string;
|
|
@@ -30,17 +31,25 @@ export type ClaudeRunsServer = Server & { stopActiveRuns(): Promise<void> };
|
|
|
30
31
|
export type ClaudeRunsApiOptions = {
|
|
31
32
|
token: string;
|
|
32
33
|
connection: RunsApiConnection;
|
|
33
|
-
execute?:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
execute?:
|
|
35
|
+
| ((request: ClaudeRunsRequest) => {
|
|
36
|
+
promise: Promise<ClaudeRunsResult>;
|
|
37
|
+
stop(): void;
|
|
38
|
+
})
|
|
39
|
+
| null;
|
|
37
40
|
audit?: (entry: Record<string, unknown>) => void;
|
|
38
41
|
maxConcurrency?: number;
|
|
39
42
|
maxToolConcurrency?: number;
|
|
40
43
|
toolLanes?: ClaudeRunsToolLanes | null;
|
|
41
|
-
checkAuthentication?: (
|
|
42
|
-
|
|
43
|
-
>;
|
|
44
|
+
checkAuthentication?: (
|
|
45
|
+
configDir: string
|
|
46
|
+
) => Promise<"READY" | "SIGN_IN_REQUIRED" | "UNAVAILABLE">;
|
|
47
|
+
accounts?: () => string[];
|
|
48
|
+
startRun?: (
|
|
49
|
+
request: ClaudeRunsRequest,
|
|
50
|
+
options: { root: string; configDir: string }
|
|
51
|
+
) => { promise: Promise<ClaudeRunsResult>; stop(code?: string): void };
|
|
52
|
+
now?: () => number;
|
|
44
53
|
};
|
|
45
54
|
export function validateClaudeRunsRequest(
|
|
46
55
|
body: unknown,
|
|
@@ -54,8 +63,14 @@ export function endpointToolNames(options: {
|
|
|
54
63
|
export function claudeInvocation(
|
|
55
64
|
request: ClaudeRunsRequest,
|
|
56
65
|
root: string,
|
|
57
|
-
mcpSpecPath?: string | null
|
|
66
|
+
mcpSpecPath?: string | null,
|
|
67
|
+
configDir?: string
|
|
58
68
|
): { command: string; args: string[]; options: SpawnOptions };
|
|
69
|
+
export function claudeAccountDirs(root: string): string[];
|
|
70
|
+
export function claudeUsageLimit(
|
|
71
|
+
stdout: string,
|
|
72
|
+
now?: number
|
|
73
|
+
): { retryAt: number } | null;
|
|
59
74
|
export function parseClaudeResult(
|
|
60
75
|
stdout: string,
|
|
61
76
|
request: ClaudeRunsRequest
|
|
@@ -64,6 +79,7 @@ export function startClaudeRun(
|
|
|
64
79
|
request: ClaudeRunsRequest,
|
|
65
80
|
options: {
|
|
66
81
|
root: string;
|
|
82
|
+
configDir?: string;
|
|
67
83
|
spawnImpl?: (...args: any[]) => any;
|
|
68
84
|
timeoutMs?: number;
|
|
69
85
|
}
|
|
@@ -81,6 +97,7 @@ export function startConfiguredClaudeRunsApis(options?: {
|
|
|
81
97
|
|
|
82
98
|
export function readClaudeAuthentication(options: {
|
|
83
99
|
root: string;
|
|
100
|
+
configDir?: string;
|
|
84
101
|
execFileImpl?: any;
|
|
85
102
|
timeoutMs?: number;
|
|
86
103
|
}): Promise<"READY" | "SIGN_IN_REQUIRED" | "UNAVAILABLE">;
|
|
@@ -2,7 +2,9 @@ import { execFile, spawn } from "node:child_process";
|
|
|
2
2
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import {
|
|
4
4
|
appendFileSync,
|
|
5
|
+
existsSync,
|
|
5
6
|
lstatSync,
|
|
7
|
+
readdirSync,
|
|
6
8
|
readFileSync,
|
|
7
9
|
rmSync,
|
|
8
10
|
writeFileSync,
|
|
@@ -20,6 +22,9 @@ import {
|
|
|
20
22
|
RUNS_API_CONNECTIONS_ENV,
|
|
21
23
|
} from "./runs-api-connections.mjs";
|
|
22
24
|
|
|
25
|
+
// Each pool runs as many generations at once as one Hermes endpoint may
|
|
26
|
+
// (ENDPOINT_USER_MAX_CONCURRENCY); the runtime still caps the machine at 10.
|
|
27
|
+
export const CLAUDE_RUNS_MAX_CONCURRENCY = 8;
|
|
23
28
|
const MAX_BODY = 320 * 1024;
|
|
24
29
|
const MAX_OUTPUT = 48 * 1024;
|
|
25
30
|
const MAX_CLI_OUTPUT = 512 * 1024;
|
|
@@ -129,7 +134,12 @@ export function endpointToolNames({
|
|
|
129
134
|
return [...names];
|
|
130
135
|
}
|
|
131
136
|
|
|
132
|
-
export function claudeInvocation(
|
|
137
|
+
export function claudeInvocation(
|
|
138
|
+
request,
|
|
139
|
+
root,
|
|
140
|
+
mcpSpecPath = null,
|
|
141
|
+
configDir = `${root}/claude`
|
|
142
|
+
) {
|
|
133
143
|
return {
|
|
134
144
|
command: `${root}/cli/node_modules/.bin/claude`,
|
|
135
145
|
args: [
|
|
@@ -186,8 +196,8 @@ export function claudeInvocation(request, root, mcpSpecPath = null) {
|
|
|
186
196
|
stdio: ["pipe", "pipe", "pipe"],
|
|
187
197
|
env: {
|
|
188
198
|
PATH: "/usr/local/bin:/usr/bin:/bin",
|
|
189
|
-
HOME:
|
|
190
|
-
CLAUDE_CONFIG_DIR:
|
|
199
|
+
HOME: configDir,
|
|
200
|
+
CLAUDE_CONFIG_DIR: configDir,
|
|
191
201
|
LANG: "C.UTF-8",
|
|
192
202
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
193
203
|
},
|
|
@@ -199,12 +209,15 @@ export function claudeInvocation(request, root, mcpSpecPath = null) {
|
|
|
199
209
|
// this boundary; CLI stdout can contain account identity and is never forwarded.
|
|
200
210
|
export function readClaudeAuthentication({
|
|
201
211
|
root,
|
|
212
|
+
configDir = `${root}/claude`,
|
|
202
213
|
execFileImpl = execFile,
|
|
203
214
|
timeoutMs = 8_000,
|
|
204
215
|
} = {}) {
|
|
205
216
|
const invocation = claudeInvocation(
|
|
206
217
|
{ instructions: "", input: "", model: "claude-opus", effort: "high" },
|
|
207
|
-
root
|
|
218
|
+
root,
|
|
219
|
+
null,
|
|
220
|
+
configDir
|
|
208
221
|
);
|
|
209
222
|
return new Promise((resolve) => {
|
|
210
223
|
execFileImpl(
|
|
@@ -231,6 +244,90 @@ export function readClaudeAuthentication({
|
|
|
231
244
|
});
|
|
232
245
|
}
|
|
233
246
|
|
|
247
|
+
// Each sign-in is its own Claude config folder: the primary `claude`, then
|
|
248
|
+
// `accounts/<name>` in name order. Refresh tokens rotate inside a folder, so a
|
|
249
|
+
// login is never copied between folders.
|
|
250
|
+
export function claudeAccountDirs(root) {
|
|
251
|
+
const dirs = [`${root}/claude`];
|
|
252
|
+
let names = [];
|
|
253
|
+
try {
|
|
254
|
+
names = readdirSync(`${root}/accounts`).sort();
|
|
255
|
+
} catch {
|
|
256
|
+
/* No extra accounts. */
|
|
257
|
+
}
|
|
258
|
+
for (const name of names)
|
|
259
|
+
if (
|
|
260
|
+
/^[a-z0-9][a-z0-9-]{0,62}$/.test(name) &&
|
|
261
|
+
existsSync(`${root}/accounts/${name}/.credentials.json`)
|
|
262
|
+
)
|
|
263
|
+
dirs.push(`${root}/accounts/${name}`);
|
|
264
|
+
return dirs;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const MONTHS = "janfebmaraprmayjunjulaugsepoctnovdec";
|
|
268
|
+
const USAGE_LIMIT_RETRY_MS = 15 * 60_000;
|
|
269
|
+
|
|
270
|
+
// Claude Code reports a used-up plan as an API 429 result, for example
|
|
271
|
+
// "You've hit your weekly limit · resets Oct 7, 10am (UTC)". Returns when that
|
|
272
|
+
// account can be tried again, or null for any other result.
|
|
273
|
+
export function claudeUsageLimit(stdout, now = Date.now()) {
|
|
274
|
+
let value;
|
|
275
|
+
try {
|
|
276
|
+
value = JSON.parse(stdout);
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
if (
|
|
281
|
+
!record(value) ||
|
|
282
|
+
value.is_error !== true ||
|
|
283
|
+
value.api_error_status !== 429
|
|
284
|
+
)
|
|
285
|
+
return null;
|
|
286
|
+
const reset =
|
|
287
|
+
/resets\s+(?:([a-z]{3})[a-z]*\s+(\d{1,2}),?\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*\(UTC\)/i.exec(
|
|
288
|
+
String(value.result ?? "")
|
|
289
|
+
);
|
|
290
|
+
let retryAt = null;
|
|
291
|
+
if (reset) {
|
|
292
|
+
const hour =
|
|
293
|
+
(Number(reset[3]) % 12) + (reset[5].toLowerCase() === "pm" ? 12 : 0);
|
|
294
|
+
const minute = Number(reset[4] ?? 0);
|
|
295
|
+
const today = new Date(now);
|
|
296
|
+
if (reset[1]) {
|
|
297
|
+
const month = MONTHS.indexOf(reset[1].toLowerCase()) / 3;
|
|
298
|
+
if (Number.isInteger(month)) {
|
|
299
|
+
retryAt = Date.UTC(
|
|
300
|
+
today.getUTCFullYear(),
|
|
301
|
+
month,
|
|
302
|
+
Number(reset[2]),
|
|
303
|
+
hour,
|
|
304
|
+
minute
|
|
305
|
+
);
|
|
306
|
+
if (retryAt < now - 86_400_000)
|
|
307
|
+
retryAt = Date.UTC(
|
|
308
|
+
today.getUTCFullYear() + 1,
|
|
309
|
+
month,
|
|
310
|
+
Number(reset[2]),
|
|
311
|
+
hour,
|
|
312
|
+
minute
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
retryAt = Date.UTC(
|
|
317
|
+
today.getUTCFullYear(),
|
|
318
|
+
today.getUTCMonth(),
|
|
319
|
+
today.getUTCDate(),
|
|
320
|
+
hour,
|
|
321
|
+
minute
|
|
322
|
+
);
|
|
323
|
+
if (retryAt <= now) retryAt += 86_400_000;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (retryAt === null || retryAt <= now || retryAt > now + 8 * 86_400_000)
|
|
327
|
+
retryAt = now + USAGE_LIMIT_RETRY_MS;
|
|
328
|
+
return { retryAt };
|
|
329
|
+
}
|
|
330
|
+
|
|
234
331
|
export function parseClaudeResult(stdout, request) {
|
|
235
332
|
const result = JSON.parse(stdout);
|
|
236
333
|
const models = Object.keys(result.modelUsage ?? {});
|
|
@@ -269,7 +366,12 @@ export function parseClaudeResult(stdout, request) {
|
|
|
269
366
|
|
|
270
367
|
export function startClaudeRun(
|
|
271
368
|
request,
|
|
272
|
-
{
|
|
369
|
+
{
|
|
370
|
+
root,
|
|
371
|
+
configDir = `${root}/claude`,
|
|
372
|
+
spawnImpl = spawn,
|
|
373
|
+
timeoutMs = 750_000,
|
|
374
|
+
} = {}
|
|
273
375
|
) {
|
|
274
376
|
// The tool server's environment travels in a private per-run file, never
|
|
275
377
|
// in argv, and is removed when the run ends.
|
|
@@ -285,7 +387,7 @@ export function startClaudeRun(
|
|
|
285
387
|
const removeSpec = () => {
|
|
286
388
|
if (mcpSpecPath) rmSync(mcpSpecPath, { force: true });
|
|
287
389
|
};
|
|
288
|
-
const invocation = claudeInvocation(request, root, mcpSpecPath);
|
|
390
|
+
const invocation = claudeInvocation(request, root, mcpSpecPath, configDir);
|
|
289
391
|
let child;
|
|
290
392
|
try {
|
|
291
393
|
child = spawnImpl(invocation.command, invocation.args, invocation.options);
|
|
@@ -331,12 +433,16 @@ export function startClaudeRun(
|
|
|
331
433
|
clearTimeout(timer);
|
|
332
434
|
clearTimeout(killTimer);
|
|
333
435
|
removeSpec();
|
|
436
|
+
const stdout = Buffer.concat(chunks).toString("utf8");
|
|
437
|
+
const limit = reason ? null : claudeUsageLimit(stdout);
|
|
438
|
+
if (limit)
|
|
439
|
+
return reject(
|
|
440
|
+
Object.assign(new Error("claude_runs_usage_limited"), limit)
|
|
441
|
+
);
|
|
334
442
|
if (reason || code !== 0)
|
|
335
443
|
return reject(new Error(reason ?? "claude_runs_cli_failed"));
|
|
336
444
|
try {
|
|
337
|
-
resolve(
|
|
338
|
-
parseClaudeResult(Buffer.concat(chunks).toString("utf8"), request)
|
|
339
|
-
);
|
|
445
|
+
resolve(parseClaudeResult(stdout, request));
|
|
340
446
|
} catch {
|
|
341
447
|
reject(new Error("claude_runs_output_rejected"));
|
|
342
448
|
}
|
|
@@ -350,39 +456,100 @@ export function startClaudeRun(
|
|
|
350
456
|
export function createClaudeRunsApi({
|
|
351
457
|
token,
|
|
352
458
|
connection,
|
|
353
|
-
execute =
|
|
459
|
+
execute = null,
|
|
460
|
+
audit = () => {},
|
|
461
|
+
maxConcurrency = CLAUDE_RUNS_MAX_CONCURRENCY,
|
|
462
|
+
// Tool lanes (replies, follow-ups) have their own slots so a batch of
|
|
463
|
+
// openers can never crowd them out.
|
|
464
|
+
maxToolConcurrency = CLAUDE_RUNS_MAX_CONCURRENCY,
|
|
465
|
+
toolLanes = null,
|
|
466
|
+
checkAuthentication = (configDir) =>
|
|
467
|
+
readClaudeAuthentication({ root: connection.localRoot, configDir }),
|
|
468
|
+
accounts = () => claudeAccountDirs(connection.localRoot),
|
|
469
|
+
startRun = startClaudeRun,
|
|
470
|
+
now = Date.now,
|
|
471
|
+
} = {}) {
|
|
472
|
+
if (!/^[a-f0-9]{64}$/.test(token ?? ""))
|
|
473
|
+
throw new Error("claude_runs_key_rejected");
|
|
474
|
+
// An account whose plan is used up rests until its reset; runs go to the
|
|
475
|
+
// next signed-in account meanwhile, and back to the first once it resets.
|
|
476
|
+
const usageLimitedUntil = new Map();
|
|
477
|
+
const resting = (configDir) =>
|
|
478
|
+
(usageLimitedUntil.get(configDir) ?? 0) > now();
|
|
479
|
+
const runOnAccounts = (request) => {
|
|
354
480
|
let activeRun;
|
|
355
481
|
let stopped = false;
|
|
356
482
|
return {
|
|
357
483
|
promise: (async () => {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
|
|
484
|
+
let failure = null;
|
|
485
|
+
for (const configDir of accounts()) {
|
|
486
|
+
if (resting(configDir)) {
|
|
487
|
+
failure = "claude_runs_usage_limited";
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const state = await checkAuthentication(configDir);
|
|
491
|
+
if (stopped) throw new Error("claude_runs_cancelled");
|
|
492
|
+
if (state !== "READY") {
|
|
493
|
+
failure ??=
|
|
494
|
+
state === "SIGN_IN_REQUIRED"
|
|
495
|
+
? "claude_runs_sign_in_required"
|
|
496
|
+
: "claude_runs_auth_check_unavailable";
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
activeRun = startRun(request, {
|
|
500
|
+
root: connection.localRoot,
|
|
501
|
+
configDir,
|
|
502
|
+
});
|
|
503
|
+
try {
|
|
504
|
+
return await activeRun.promise;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
if (stopped || error.message !== "claude_runs_usage_limited")
|
|
507
|
+
throw error;
|
|
508
|
+
usageLimitedUntil.set(configDir, error.retryAt);
|
|
509
|
+
failure = "claude_runs_usage_limited";
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
throw new Error(failure ?? "claude_runs_sign_in_required");
|
|
368
513
|
})(),
|
|
369
514
|
stop() {
|
|
370
515
|
stopped = true;
|
|
371
516
|
activeRun?.stop();
|
|
372
517
|
},
|
|
373
518
|
};
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
519
|
+
};
|
|
520
|
+
const executeRun = execute ?? runOnAccounts;
|
|
521
|
+
const readHealth = async () => {
|
|
522
|
+
const dirs = accounts();
|
|
523
|
+
let ready = 0;
|
|
524
|
+
let signedOut = 0;
|
|
525
|
+
const retryAts = [];
|
|
526
|
+
for (const configDir of dirs) {
|
|
527
|
+
if (resting(configDir)) {
|
|
528
|
+
retryAts.push(usageLimitedUntil.get(configDir));
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
const state = await checkAuthentication(configDir).catch(
|
|
532
|
+
() => "UNAVAILABLE"
|
|
533
|
+
);
|
|
534
|
+
if (state === "READY") ready++;
|
|
535
|
+
if (state === "SIGN_IN_REQUIRED") signedOut++;
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
authentication: ready
|
|
539
|
+
? "READY"
|
|
540
|
+
: signedOut === dirs.length
|
|
541
|
+
? "SIGN_IN_REQUIRED"
|
|
542
|
+
: "UNAVAILABLE",
|
|
543
|
+
accounts: {
|
|
544
|
+
total: dirs.length,
|
|
545
|
+
ready,
|
|
546
|
+
usageLimited: retryAts.length,
|
|
547
|
+
retryAt: retryAts.length
|
|
548
|
+
? new Date(Math.min(...retryAts)).toISOString()
|
|
549
|
+
: null,
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
};
|
|
386
553
|
// The run's own tools and the profile's MCP server, or null for a tool-free run.
|
|
387
554
|
const resolveTools = (request) => {
|
|
388
555
|
if (!connection.toolEndpointIds || !toolLanes) return null;
|
|
@@ -462,12 +629,11 @@ export function createClaudeRunsApi({
|
|
|
462
629
|
)
|
|
463
630
|
return json(res, 403, { error: "claude_runs_scope_rejected" });
|
|
464
631
|
if (req.method === "GET" && req.url === "/health") {
|
|
465
|
-
const authentication = await
|
|
466
|
-
() => "UNAVAILABLE"
|
|
467
|
-
);
|
|
632
|
+
const { authentication, accounts: accountHealth } = await readHealth();
|
|
468
633
|
return json(res, 200, {
|
|
469
634
|
ok: authentication === "READY",
|
|
470
635
|
authentication,
|
|
636
|
+
accounts: accountHealth,
|
|
471
637
|
provider: "claude-code",
|
|
472
638
|
active: inFlight.plain + inFlight.tools,
|
|
473
639
|
toolActive: inFlight.tools,
|
|
@@ -518,7 +684,7 @@ export function createClaudeRunsApi({
|
|
|
518
684
|
inFlight[pool]++;
|
|
519
685
|
runs.set(run.run_id, run);
|
|
520
686
|
try {
|
|
521
|
-
run.execution =
|
|
687
|
+
run.execution = executeRun(request);
|
|
522
688
|
run.execution.promise
|
|
523
689
|
.then(
|
|
524
690
|
(result) =>
|
|
@@ -539,6 +705,7 @@ export function createClaudeRunsApi({
|
|
|
539
705
|
error: [
|
|
540
706
|
"claude_runs_sign_in_required",
|
|
541
707
|
"claude_runs_auth_check_unavailable",
|
|
708
|
+
"claude_runs_usage_limited",
|
|
542
709
|
].includes(error.message)
|
|
543
710
|
? error.message
|
|
544
711
|
: "claude_runs_generation_failed",
|
|
@@ -126,7 +126,7 @@ const RUNTIME_GENERATION = Number(
|
|
|
126
126
|
const BOOT_SESSION_ID = randomUUID();
|
|
127
127
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.21.5";
|
|
128
128
|
const INSTALLER_PACKAGE =
|
|
129
|
-
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.
|
|
129
|
+
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.799";
|
|
130
130
|
const MCP_PACKAGE =
|
|
131
131
|
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.957";
|
|
132
132
|
export const ENDPOINT_MAX_CONCURRENT_RUNS = 10;
|
|
@@ -38,7 +38,7 @@ 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.
|
|
41
|
+
const INSTALLER_PACKAGE = "@sellable/install@0.1.799";
|
|
42
42
|
const MCP_PACKAGE = "@sellable/mcp@0.1.957";
|
|
43
43
|
const HERMES_VERSION = "0.18.0";
|
|
44
44
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
@@ -1546,7 +1546,7 @@ 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.
|
|
1549
|
+
pinned.installerPackage !== "@sellable/install@0.1.799" ||
|
|
1550
1550
|
pinned.mcpPackage !== "@sellable/mcp@0.1.957" ||
|
|
1551
1551
|
!Array.isArray(policy.toolInclude) ||
|
|
1552
1552
|
policy.toolInclude.length === 0 ||
|
|
@@ -312,7 +312,7 @@ 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.
|
|
315
|
+
desired.installerPackage !== "@sellable/install@0.1.799" ||
|
|
316
316
|
desired.mcpPackage !== "@sellable/mcp@0.1.957" ||
|
|
317
317
|
!Array.isArray(desired.toolInclude) ||
|
|
318
318
|
desired.toolInclude.length === 0 ||
|
|
@@ -25,7 +25,7 @@ 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.
|
|
28
|
+
export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.799";
|
|
29
29
|
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.957";
|
|
30
30
|
const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
|
|
31
31
|
PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
|