@sellable/install 0.1.798 → 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 +24 -8
- package/lib/sellable-agent/fly-customer-image/claude-code-runs-api.mjs +200 -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.798" \
|
|
|
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
|
|
@@ -31,17 +31,25 @@ export type ClaudeRunsServer = Server & { stopActiveRuns(): Promise<void> };
|
|
|
31
31
|
export type ClaudeRunsApiOptions = {
|
|
32
32
|
token: string;
|
|
33
33
|
connection: RunsApiConnection;
|
|
34
|
-
execute?:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
execute?:
|
|
35
|
+
| ((request: ClaudeRunsRequest) => {
|
|
36
|
+
promise: Promise<ClaudeRunsResult>;
|
|
37
|
+
stop(): void;
|
|
38
|
+
})
|
|
39
|
+
| null;
|
|
38
40
|
audit?: (entry: Record<string, unknown>) => void;
|
|
39
41
|
maxConcurrency?: number;
|
|
40
42
|
maxToolConcurrency?: number;
|
|
41
43
|
toolLanes?: ClaudeRunsToolLanes | null;
|
|
42
|
-
checkAuthentication?: (
|
|
43
|
-
|
|
44
|
-
>;
|
|
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;
|
|
45
53
|
};
|
|
46
54
|
export function validateClaudeRunsRequest(
|
|
47
55
|
body: unknown,
|
|
@@ -55,8 +63,14 @@ export function endpointToolNames(options: {
|
|
|
55
63
|
export function claudeInvocation(
|
|
56
64
|
request: ClaudeRunsRequest,
|
|
57
65
|
root: string,
|
|
58
|
-
mcpSpecPath?: string | null
|
|
66
|
+
mcpSpecPath?: string | null,
|
|
67
|
+
configDir?: string
|
|
59
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;
|
|
60
74
|
export function parseClaudeResult(
|
|
61
75
|
stdout: string,
|
|
62
76
|
request: ClaudeRunsRequest
|
|
@@ -65,6 +79,7 @@ export function startClaudeRun(
|
|
|
65
79
|
request: ClaudeRunsRequest,
|
|
66
80
|
options: {
|
|
67
81
|
root: string;
|
|
82
|
+
configDir?: string;
|
|
68
83
|
spawnImpl?: (...args: any[]) => any;
|
|
69
84
|
timeoutMs?: number;
|
|
70
85
|
}
|
|
@@ -82,6 +97,7 @@ export function startConfiguredClaudeRunsApis(options?: {
|
|
|
82
97
|
|
|
83
98
|
export function readClaudeAuthentication(options: {
|
|
84
99
|
root: string;
|
|
100
|
+
configDir?: string;
|
|
85
101
|
execFileImpl?: any;
|
|
86
102
|
timeoutMs?: number;
|
|
87
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,
|
|
@@ -132,7 +134,12 @@ export function endpointToolNames({
|
|
|
132
134
|
return [...names];
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
export function claudeInvocation(
|
|
137
|
+
export function claudeInvocation(
|
|
138
|
+
request,
|
|
139
|
+
root,
|
|
140
|
+
mcpSpecPath = null,
|
|
141
|
+
configDir = `${root}/claude`
|
|
142
|
+
) {
|
|
136
143
|
return {
|
|
137
144
|
command: `${root}/cli/node_modules/.bin/claude`,
|
|
138
145
|
args: [
|
|
@@ -189,8 +196,8 @@ export function claudeInvocation(request, root, mcpSpecPath = null) {
|
|
|
189
196
|
stdio: ["pipe", "pipe", "pipe"],
|
|
190
197
|
env: {
|
|
191
198
|
PATH: "/usr/local/bin:/usr/bin:/bin",
|
|
192
|
-
HOME:
|
|
193
|
-
CLAUDE_CONFIG_DIR:
|
|
199
|
+
HOME: configDir,
|
|
200
|
+
CLAUDE_CONFIG_DIR: configDir,
|
|
194
201
|
LANG: "C.UTF-8",
|
|
195
202
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
196
203
|
},
|
|
@@ -202,12 +209,15 @@ export function claudeInvocation(request, root, mcpSpecPath = null) {
|
|
|
202
209
|
// this boundary; CLI stdout can contain account identity and is never forwarded.
|
|
203
210
|
export function readClaudeAuthentication({
|
|
204
211
|
root,
|
|
212
|
+
configDir = `${root}/claude`,
|
|
205
213
|
execFileImpl = execFile,
|
|
206
214
|
timeoutMs = 8_000,
|
|
207
215
|
} = {}) {
|
|
208
216
|
const invocation = claudeInvocation(
|
|
209
217
|
{ instructions: "", input: "", model: "claude-opus", effort: "high" },
|
|
210
|
-
root
|
|
218
|
+
root,
|
|
219
|
+
null,
|
|
220
|
+
configDir
|
|
211
221
|
);
|
|
212
222
|
return new Promise((resolve) => {
|
|
213
223
|
execFileImpl(
|
|
@@ -234,6 +244,90 @@ export function readClaudeAuthentication({
|
|
|
234
244
|
});
|
|
235
245
|
}
|
|
236
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
|
+
|
|
237
331
|
export function parseClaudeResult(stdout, request) {
|
|
238
332
|
const result = JSON.parse(stdout);
|
|
239
333
|
const models = Object.keys(result.modelUsage ?? {});
|
|
@@ -272,7 +366,12 @@ export function parseClaudeResult(stdout, request) {
|
|
|
272
366
|
|
|
273
367
|
export function startClaudeRun(
|
|
274
368
|
request,
|
|
275
|
-
{
|
|
369
|
+
{
|
|
370
|
+
root,
|
|
371
|
+
configDir = `${root}/claude`,
|
|
372
|
+
spawnImpl = spawn,
|
|
373
|
+
timeoutMs = 750_000,
|
|
374
|
+
} = {}
|
|
276
375
|
) {
|
|
277
376
|
// The tool server's environment travels in a private per-run file, never
|
|
278
377
|
// in argv, and is removed when the run ends.
|
|
@@ -288,7 +387,7 @@ export function startClaudeRun(
|
|
|
288
387
|
const removeSpec = () => {
|
|
289
388
|
if (mcpSpecPath) rmSync(mcpSpecPath, { force: true });
|
|
290
389
|
};
|
|
291
|
-
const invocation = claudeInvocation(request, root, mcpSpecPath);
|
|
390
|
+
const invocation = claudeInvocation(request, root, mcpSpecPath, configDir);
|
|
292
391
|
let child;
|
|
293
392
|
try {
|
|
294
393
|
child = spawnImpl(invocation.command, invocation.args, invocation.options);
|
|
@@ -334,12 +433,16 @@ export function startClaudeRun(
|
|
|
334
433
|
clearTimeout(timer);
|
|
335
434
|
clearTimeout(killTimer);
|
|
336
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
|
+
);
|
|
337
442
|
if (reason || code !== 0)
|
|
338
443
|
return reject(new Error(reason ?? "claude_runs_cli_failed"));
|
|
339
444
|
try {
|
|
340
|
-
resolve(
|
|
341
|
-
parseClaudeResult(Buffer.concat(chunks).toString("utf8"), request)
|
|
342
|
-
);
|
|
445
|
+
resolve(parseClaudeResult(stdout, request));
|
|
343
446
|
} catch {
|
|
344
447
|
reject(new Error("claude_runs_output_rejected"));
|
|
345
448
|
}
|
|
@@ -353,39 +456,100 @@ export function startClaudeRun(
|
|
|
353
456
|
export function createClaudeRunsApi({
|
|
354
457
|
token,
|
|
355
458
|
connection,
|
|
356
|
-
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) => {
|
|
357
480
|
let activeRun;
|
|
358
481
|
let stopped = false;
|
|
359
482
|
return {
|
|
360
483
|
promise: (async () => {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
);
|
|
369
|
-
|
|
370
|
-
|
|
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");
|
|
371
513
|
})(),
|
|
372
514
|
stop() {
|
|
373
515
|
stopped = true;
|
|
374
516
|
activeRun?.stop();
|
|
375
517
|
},
|
|
376
518
|
};
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
+
};
|
|
389
553
|
// The run's own tools and the profile's MCP server, or null for a tool-free run.
|
|
390
554
|
const resolveTools = (request) => {
|
|
391
555
|
if (!connection.toolEndpointIds || !toolLanes) return null;
|
|
@@ -465,12 +629,11 @@ export function createClaudeRunsApi({
|
|
|
465
629
|
)
|
|
466
630
|
return json(res, 403, { error: "claude_runs_scope_rejected" });
|
|
467
631
|
if (req.method === "GET" && req.url === "/health") {
|
|
468
|
-
const authentication = await
|
|
469
|
-
() => "UNAVAILABLE"
|
|
470
|
-
);
|
|
632
|
+
const { authentication, accounts: accountHealth } = await readHealth();
|
|
471
633
|
return json(res, 200, {
|
|
472
634
|
ok: authentication === "READY",
|
|
473
635
|
authentication,
|
|
636
|
+
accounts: accountHealth,
|
|
474
637
|
provider: "claude-code",
|
|
475
638
|
active: inFlight.plain + inFlight.tools,
|
|
476
639
|
toolActive: inFlight.tools,
|
|
@@ -521,7 +684,7 @@ export function createClaudeRunsApi({
|
|
|
521
684
|
inFlight[pool]++;
|
|
522
685
|
runs.set(run.run_id, run);
|
|
523
686
|
try {
|
|
524
|
-
run.execution =
|
|
687
|
+
run.execution = executeRun(request);
|
|
525
688
|
run.execution.promise
|
|
526
689
|
.then(
|
|
527
690
|
(result) =>
|
|
@@ -542,6 +705,7 @@ export function createClaudeRunsApi({
|
|
|
542
705
|
error: [
|
|
543
706
|
"claude_runs_sign_in_required",
|
|
544
707
|
"claude_runs_auth_check_unavailable",
|
|
708
|
+
"claude_runs_usage_limited",
|
|
545
709
|
].includes(error.message)
|
|
546
710
|
? error.message
|
|
547
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
|