@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
1,030 lines (1,012 loc) โข 33.2 kB
JavaScript
import { execFile, spawn } from "node:child_process";
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
import {
appendFileSync,
chmodSync,
existsSync,
lstatSync,
mkdirSync,
readdirSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { createServer } from "node:http";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import {
endpointHermesSessionId,
endpointMcpDescriptorPath,
} from "../mcp-context-proxy.mjs";
import { normalizeRunsApiBaseUrl } from "./endpoint-model-selection.mjs";
import { validateOpenerOutputContract } from "./hermes-runs-client.mjs";
import {
parseRunsApiConnections,
RUNS_API_CONNECTIONS_ENV,
} from "./runs-api-connections.mjs";
// Each pool runs as many generations at once as one Hermes endpoint may
// (ENDPOINT_USER_MAX_CONCURRENCY); the runtime still caps the machine at 10.
export const CLAUDE_RUNS_MAX_CONCURRENCY = 8;
const MAX_BODY = 320 * 1024;
const MAX_OUTPUT = 48 * 1024;
const MAX_CLI_OUTPUT = 512 * 1024;
const MCP_SESSION = fileURLToPath(
new URL("./claude-code-mcp-session.mjs", import.meta.url)
);
const hash = (s) => createHash("sha256").update(s).digest("hex");
const record = (v) => v && typeof v === "object" && !Array.isArray(v);
// The public service (models null) has no model list: any Claude model, at
// the efforts the CLI takes.
const publicModel = (body) =>
typeof body.model === "string" &&
/^claude-[a-z0-9-]{1,100}$/.test(body.model) &&
["low", "medium", "high"].includes(body.model_options.reasoning_effort);
// The run's one remote MCP server, in the shape the Runs client sends it.
function remoteTools(tools) {
const entry = Array.isArray(tools) && tools.length === 1 ? tools[0] : null;
const url = normalizeRunsApiBaseUrl(entry?.server_url);
const names = entry?.allowed_tools;
if (
!record(entry) ||
Object.keys(entry).sort().join(",") !==
"allowed_tools,authorization,require_approval,server_label,server_url,type" ||
entry.type !== "mcp" ||
entry.server_label !== "sellable" ||
entry.require_approval !== "never" ||
!url?.startsWith("https://") ||
typeof entry.authorization !== "string" ||
!/^srt_[A-Za-z0-9-]{1,128}_[A-Za-z0-9_-]{43}$/.test(entry.authorization) ||
!Array.isArray(names) ||
names.length < 1 ||
names.length > 32 ||
new Set(names).size !== names.length ||
names.some(
(name) => typeof name !== "string" || !/^[a-z][a-z0-9_]{0,63}$/.test(name)
)
)
throw new Error("claude_runs_tools_rejected");
return {
names: [...names],
remote: { url, authorization: entry.authorization },
};
}
export function validateClaudeRunsRequest(body, models) {
const allowed = [
"input",
"instructions",
"conversation_history",
"session_id",
"provider",
"model",
"model_options",
...(models === null ? ["tools"] : []),
];
if (
!record(body) ||
Object.keys(body).some((k) => !allowed.includes(k)) ||
typeof body.input !== "string" ||
!body.input.length ||
Buffer.byteLength(body.input) > 128 * 1024 ||
typeof body.instructions !== "string" ||
!body.instructions.length ||
Buffer.byteLength(body.instructions) > 128 * 1024 ||
!Array.isArray(body.conversation_history) ||
body.conversation_history.length > 64 ||
Buffer.byteLength(JSON.stringify(body.conversation_history)) > 128 * 1024 ||
body.conversation_history.some(
(item) =>
!record(item) ||
Object.keys(item).sort().join(",") !== "content,role" ||
!["user", "assistant"].includes(item.role) ||
typeof item.content !== "string"
) ||
!/^sellable_endpoint_[a-f0-9]{32}$/.test(body.session_id ?? "") ||
body.provider !== "claude-code" ||
!record(body.model_options) ||
Object.keys(body.model_options).some(
(key) =>
!["reasoning_effort", "sellable_opener_output_contract"].includes(key)
) ||
!(models === null
? publicModel(body)
: models.some(
(model) =>
model.provider === body.provider &&
model.model === body.model &&
model.reasoningEfforts.includes(body.model_options.reasoning_effort)
))
)
throw new Error("claude_runs_request_rejected");
const contract = body.model_options.sellable_opener_output_contract;
const schema =
contract === undefined
? null
: validateOpenerOutputContract(contract).schema;
return {
input: body.conversation_history.length
? JSON.stringify({
conversation_history: body.conversation_history,
input: body.input,
})
: body.input,
instructions: body.instructions,
schema,
model: body.model,
effort: body.model_options.reasoning_effort,
sessionId: body.session_id,
tools: body.tools === undefined ? null : remoteTools(body.tools),
};
}
/**
* A run's own allowed tools, read from the signed turn descriptor the runtime
* installed for its session before submitting. With a tool list, only runs on
* those endpoints get tools (and must have some); without one, every run gets
* exactly the tools its descriptor allows, and a run allowed none is tool-free
* (null). This only narrows: the context proxy verifies the descriptor itself
* before it lists or calls any tool.
*/
export function endpointToolNames({
turnDescriptorPath,
sessionId,
toolEndpointIds = undefined,
}) {
const path = endpointMcpDescriptorPath(turnDescriptorPath, sessionId);
if (!path) return null;
try {
if (!lstatSync(path).isFile())
throw new Error("claude_runs_tool_descriptor_rejected");
} catch (error) {
if (error.code === "ENOENT") return null;
throw error;
}
const body = JSON.parse(readFileSync(path, "utf8"))?.body;
if (toolEndpointIds && !toolEndpointIds.includes(body?.endpointId))
return null;
const names = body?.allowedToolNames;
if (
endpointHermesSessionId(body) !== sessionId ||
!Array.isArray(names) ||
(toolEndpointIds && !names.length) ||
names.some(
(name) => typeof name !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(name)
)
)
throw new Error("claude_runs_tool_descriptor_rejected");
return names.length ? [...names] : null;
}
export function claudeInvocation(
request,
root,
mcpSpecPath = null,
configDir = `${root}/claude`
) {
return {
command: `${root}/cli/node_modules/.bin/claude`,
args: [
"-p",
"--model",
request.model,
"--effort",
request.effort,
"--output-format",
"json",
...(request.schema
? ["--json-schema", JSON.stringify(request.schema)]
: []),
"--tools",
"",
"--permission-mode",
"dontAsk",
"--strict-mcp-config",
"--mcp-config",
// A tool lane's only MCP server is the run's own session wrapper, and a
// public run's is its own remote server, read from its private file.
// Either way its allowed tools are exactly the run's. Every other run
// has none.
...(request.tools
? [
request.tools.remote
? request.mcpConfigPath
: JSON.stringify({
mcpServers: {
sellable: {
command: process.execPath,
args: [MCP_SESSION, mcpSpecPath],
},
},
}),
"--allowedTools",
request.tools.names
.map((name) => `mcp__sellable__${name}`)
.join(","),
]
: ['{"mcpServers":{}}']),
"--setting-sources",
"",
"--settings",
'{"disableAllHooks":true,"autoMemoryEnabled":false}',
"--disable-slash-commands",
"--no-session-persistence",
"--max-turns",
request.tools ? "16" : "3",
"--system-prompt",
request.instructions,
],
// Whitelist instead of inheriting the runtime's provider keys, URLs, hooks,
// plugin paths, NODE_OPTIONS, or workspace access credentials.
options: {
cwd: `${root}/work`,
detached: true,
stdio: ["pipe", "pipe", "pipe"],
env: {
PATH: "/usr/local/bin:/usr/bin:/bin",
HOME: configDir,
CLAUDE_CONFIG_DIR: configDir,
LANG: "C.UTF-8",
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
},
},
};
}
// Uses the same isolated environment as generation. Only a typed status leaves
// this boundary; CLI stdout can contain account identity and is never forwarded.
export function readClaudeAuthentication({
root,
configDir = `${root}/claude`,
execFileImpl = execFile,
timeoutMs = 8_000,
} = {}) {
const invocation = claudeInvocation(
{ instructions: "", input: "", model: "claude-opus", effort: "high" },
root,
null,
configDir
);
return new Promise((resolve) => {
execFileImpl(
invocation.command,
["auth", "status"],
{
cwd: invocation.options.cwd,
env: invocation.options.env,
timeout: timeoutMs,
maxBuffer: 16 * 1024,
encoding: "utf8",
},
(error, stdout) => {
try {
const value = JSON.parse(stdout);
if (value.loggedIn === false) return resolve("SIGN_IN_REQUIRED");
if (!error && value.loggedIn === true) return resolve("READY");
} catch {
/* A missing or incompatible CLI is unavailable, never ready. */
}
resolve("UNAVAILABLE");
}
);
});
}
// Each sign-in is its own Claude config folder: the primary `claude`, then
// `accounts/<name>` in name order. Refresh tokens rotate inside a folder, so a
// login is never copied between folders.
export function claudeAccountDirs(root) {
const dirs = [`${root}/claude`];
let names = [];
try {
names = readdirSync(`${root}/accounts`).sort();
} catch {
/* No extra accounts. */
}
for (const name of names)
if (
/^[a-z0-9][a-z0-9-]{0,62}$/.test(name) &&
existsSync(`${root}/accounts/${name}/.credentials.json`)
)
dirs.push(`${root}/accounts/${name}`);
return dirs;
}
const MONTHS = "janfebmaraprmayjunjulaugsepoctnovdec";
const USAGE_LIMIT_RETRY_MS = 15 * 60_000;
const SIGN_IN_TRUST_MS = 5 * 60_000;
// Claude Code reports a used-up plan as an API 429 result, for example
// "You've hit your weekly limit ยท resets Oct 7, 10am (UTC)". Returns when that
// account can be tried again, or null for any other result.
export function claudeUsageLimit(stdout, now = Date.now()) {
let value;
try {
value = JSON.parse(stdout);
} catch {
return null;
}
if (
!record(value) ||
value.is_error !== true ||
value.api_error_status !== 429
)
return null;
const reset =
/resets\s+(?:([a-z]{3})[a-z]*\s+(\d{1,2}),?\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*\(UTC\)/i.exec(
String(value.result ?? "")
);
let retryAt = null;
if (reset) {
const hour =
(Number(reset[3]) % 12) + (reset[5].toLowerCase() === "pm" ? 12 : 0);
const minute = Number(reset[4] ?? 0);
const today = new Date(now);
if (reset[1]) {
const month = MONTHS.indexOf(reset[1].toLowerCase()) / 3;
if (Number.isInteger(month)) {
retryAt = Date.UTC(
today.getUTCFullYear(),
month,
Number(reset[2]),
hour,
minute
);
if (retryAt < now - 86_400_000)
retryAt = Date.UTC(
today.getUTCFullYear() + 1,
month,
Number(reset[2]),
hour,
minute
);
}
} else {
retryAt = Date.UTC(
today.getUTCFullYear(),
today.getUTCMonth(),
today.getUTCDate(),
hour,
minute
);
if (retryAt <= now) retryAt += 86_400_000;
}
}
if (retryAt === null || retryAt <= now || retryAt > now + 8 * 86_400_000)
retryAt = now + USAGE_LIMIT_RETRY_MS;
return { retryAt };
}
// A failure reason the run may report: a fixed code, never raw CLI text.
const RUN_FAILURE_CODE = /^claude_runs_[a-z_]{1,48}$/;
export function parseClaudeResult(stdout, request) {
const result = JSON.parse(stdout);
const models = Object.keys(result.modelUsage ?? {});
if (
result.type !== "result" ||
result.is_error !== false ||
result.subtype !== "success" ||
(request.schema
? !record(result.structured_output)
: typeof result.result !== "string")
)
throw new Error("claude_runs_output_rejected");
if ((result.permission_denials ?? []).length)
throw new Error("claude_runs_permission_denied");
if (
!models.length ||
models.some(
(model) =>
model !== request.model && !model.startsWith(`${request.model}-`)
)
)
throw new Error("claude_runs_model_mismatch");
const output = request.schema
? JSON.stringify(result.structured_output)
: result.result;
if (!output.trim()) throw new Error("claude_runs_output_empty");
if (Buffer.byteLength(output) > MAX_OUTPUT)
throw new Error("claude_runs_output_too_large");
return {
output,
runtime: { provider: "claude-code", model: models[0] },
usage: {
input_tokens: result.usage?.input_tokens ?? 0,
output_tokens: result.usage?.output_tokens ?? 0,
cache_read_tokens: result.usage?.cache_read_input_tokens ?? 0,
cache_write_tokens: result.usage?.cache_creation_input_tokens ?? 0,
},
};
}
export function startClaudeRun(
request,
{
root,
configDir = `${root}/claude`,
spawnImpl = spawn,
timeoutMs = 750_000,
} = {}
) {
// The tool server's environment travels in a private per-run file, never
// in argv, and is removed when the run ends.
const mcpSpecPath = request.tools?.server
? join(root, "work", `mcp-session-${randomBytes(12).toString("hex")}.json`)
: null;
if (mcpSpecPath)
writeFileSync(
mcpSpecPath,
JSON.stringify({ sessionId: request.sessionId, ...request.tools.server }),
{ mode: 0o600, flag: "wx" }
);
const removeSpec = () => {
if (mcpSpecPath) rmSync(mcpSpecPath, { force: true });
};
const invocation = claudeInvocation(request, root, mcpSpecPath, configDir);
let child;
try {
child = spawnImpl(invocation.command, invocation.args, invocation.options);
} catch (error) {
removeSpec();
throw error;
}
let reason = null;
let killTimer;
const stop = (code = "claude_runs_cancelled") => {
reason ??= code;
try {
process.kill(-child.pid, "SIGTERM");
} catch {
child.kill("SIGTERM");
}
killTimer ??= setTimeout(() => {
try {
process.kill(-child.pid, "SIGKILL");
} catch {
child.kill("SIGKILL");
}
}, 5_000);
killTimer.unref();
};
const promise = new Promise((resolve, reject) => {
const chunks = [];
let bytes = 0;
const timer = setTimeout(() => stop("claude_runs_timeout"), timeoutMs);
child.stdout.on("data", (chunk) => {
bytes += chunk.length;
if (bytes > MAX_CLI_OUTPUT) stop("claude_runs_output_too_large");
else chunks.push(chunk);
});
// Drain diagnostics without logging prompts, credentials, or message bodies.
child.stderr.on("data", () => {});
child.once("error", () => {
clearTimeout(timer);
removeSpec();
reject(new Error("claude_runs_cli_unavailable"));
});
child.once("close", (code) => {
clearTimeout(timer);
clearTimeout(killTimer);
removeSpec();
const stdout = Buffer.concat(chunks).toString("utf8");
const limit = reason ? null : claudeUsageLimit(stdout);
if (limit)
return reject(
Object.assign(new Error("claude_runs_usage_limited"), limit)
);
if (reason || code !== 0)
return reject(new Error(reason ?? "claude_runs_cli_failed"));
try {
resolve(parseClaudeResult(stdout, request));
} catch (error) {
reject(
error instanceof Error && RUN_FAILURE_CODE.test(error.message)
? error
: new Error("claude_runs_output_rejected")
);
}
});
child.stdin.on("error", () => {});
child.stdin.end(request.input);
});
return { promise, stop };
}
// A public run's remote server and grant, in a 0600 file under a 0700
// directory of its own: never in argv or the CLI's environment.
function writeRemoteMcpConfig(dir, remote) {
mkdirSync(dir, { recursive: true, mode: 0o700 });
chmodSync(dir, 0o700);
const stat = lstatSync(dir);
if (stat.isSymbolicLink() || !stat.isDirectory() || (stat.mode & 0o077) !== 0)
throw new Error("claude_runs_run_directory_rejected");
const path = join(dir, "mcp.json");
writeFileSync(
path,
JSON.stringify({
mcpServers: {
sellable: {
type: "http",
url: remote.url,
headers: { Authorization: `Bearer ${remote.authorization}` },
},
},
}),
{ mode: 0o600, flag: "wx" }
);
return path;
}
export function createClaudeRunsApi({
token,
connection,
// "public": the Admin's internet-facing listener. The bearer is its only
// scope, and a run's tools come from its own request, never a local lane.
mode = "loopback",
root = connection?.localRoot,
execute = null,
audit = () => {},
maxConcurrency = CLAUDE_RUNS_MAX_CONCURRENCY,
// Tool lanes (replies, follow-ups) have their own slots so a batch of
// openers can never crowd them out.
maxToolConcurrency = CLAUDE_RUNS_MAX_CONCURRENCY,
toolLanes = null,
checkAuthentication = (configDir) =>
readClaudeAuthentication({ root, configDir }),
accounts = () => claudeAccountDirs(root),
startRun = startClaudeRun,
now = Date.now,
} = {}) {
if (!/^[a-f0-9]{64}$/.test(token ?? ""))
throw new Error("claude_runs_key_rejected");
// An account whose plan is used up rests until its reset; runs go to the
// next signed-in account meanwhile, and back to the first once it resets.
const usageLimitedUntil = new Map();
const resting = (configDir) =>
(usageLimitedUntil.get(configDir) ?? 0) > now();
// `claude auth status` starts a whole CLI: on the 4-CPU Admin machine seven
// at once take longer than its 8s timeout. Runs that arrive together share
// one check, a good answer is trusted for five minutes, and only a definite
// SIGN_IN_REQUIRED keeps a run off an account; an unanswered check runs and
// lets the CLI itself fail the run if the sign-in is really gone.
const signedInAt = new Map();
const signInChecks = new Map();
const signInState = (configDir) => {
if (now() - (signedInAt.get(configDir) ?? -Infinity) < SIGN_IN_TRUST_MS)
return Promise.resolve("READY");
let check = signInChecks.get(configDir);
if (!check) {
check = Promise.resolve()
.then(() => checkAuthentication(configDir))
.catch(() => "UNAVAILABLE")
.then((state) => {
signInChecks.delete(configDir);
if (state === "READY") signedInAt.set(configDir, now());
else signedInAt.delete(configDir);
return state;
});
signInChecks.set(configDir, check);
}
return check;
};
const runOnAccounts = (request) => {
let activeRun;
let stopped = false;
return {
promise: (async () => {
let failure = null;
for (const configDir of accounts()) {
if (resting(configDir)) {
failure = "claude_runs_usage_limited";
continue;
}
const state = await signInState(configDir);
if (stopped) throw new Error("claude_runs_cancelled");
if (state === "SIGN_IN_REQUIRED") {
failure ??= "claude_runs_sign_in_required";
continue;
}
activeRun = startRun(request, { root, configDir });
try {
return await activeRun.promise;
} catch (error) {
if (stopped || error.message !== "claude_runs_usage_limited") {
signedInAt.delete(configDir);
throw error;
}
usageLimitedUntil.set(configDir, error.retryAt);
failure = "claude_runs_usage_limited";
}
}
throw new Error(failure ?? "claude_runs_sign_in_required");
})(),
stop() {
stopped = true;
activeRun?.stop();
},
};
};
const executeRun = execute ?? runOnAccounts;
const readHealth = async () => {
const dirs = accounts();
let ready = 0;
let signedOut = 0;
const retryAts = [];
for (const configDir of dirs) {
if (resting(configDir)) {
retryAts.push(usageLimitedUntil.get(configDir));
continue;
}
const state = await signInState(configDir);
if (state === "READY") ready++;
if (state === "SIGN_IN_REQUIRED") signedOut++;
}
return {
authentication: ready
? "READY"
: signedOut === dirs.length
? "SIGN_IN_REQUIRED"
: "UNAVAILABLE",
accounts: {
total: dirs.length,
ready,
usageLimited: retryAts.length,
retryAt: retryAts.length
? new Date(Math.min(...retryAts)).toISOString()
: null,
},
};
};
// The run's own tools and the profile's MCP server, or null for a tool-free run.
const resolveTools = (request) => {
if (!toolLanes) return null;
const names = endpointToolNames({
turnDescriptorPath: toolLanes.turnDescriptorPath,
sessionId: request.sessionId,
toolEndpointIds: connection.toolEndpointIds,
});
if (!names) return null;
const server = toolLanes.server();
if (!server) throw new Error("claude_runs_tool_server_unavailable");
return {
names,
server: {
command: server.command,
args: [...server.args],
env: { PATH: "/usr/local/bin:/usr/bin:/bin", ...server.env },
},
};
};
const recordAudit = (entry) => {
try {
audit(entry);
} catch {
/* Logging cannot crash the Admin runtime. */
}
};
const runs = new Map();
const inFlight = { plain: 0, tools: 0 };
const limits = { plain: maxConcurrency, tools: maxToolConcurrency };
const full = (pool) => inFlight[pool] >= limits[pool] || runs.size >= 200;
const json = (res, status, body) => {
res.writeHead(status, {
"content-type": "application/json",
"cache-control": "no-store",
"retry-after": "2",
});
res.end(JSON.stringify(body));
};
const terminal = (run, event, fields) => {
Object.assign(run, fields);
run.event = {
event,
run_id: run.run_id,
timestamp: new Date().toISOString(),
...(run.status === "completed"
? { output: run.output, runtime: run.runtime, usage: run.usage }
: run.status === "failed"
? { error: run.error }
: {}),
};
for (const res of run.listeners)
res.end(`data: ${JSON.stringify(run.event)}\n\n`);
run.listeners.clear();
recordAudit({
runId: run.run_id,
status: run.status,
toolLane: run.pool === "tools",
servedRuntime: run.runtime ?? null,
usage: run.usage ?? null,
durationMs: Date.now() - run.startedAt,
outputDigest: run.output ? hash(run.output) : null,
});
setTimeout(() => runs.delete(run.run_id), 3_600_000).unref();
};
const server = createServer(async (req, res) => {
const provided = Buffer.from(req.headers.authorization ?? "");
const expected = Buffer.from(`Bearer ${token}`);
if (
provided.length !== expected.length ||
!timingSafeEqual(provided, expected)
)
return json(res, 401, { error: "claude_runs_auth_required" });
if (
mode !== "public" &&
(req.headers["x-sellable-workspace-id"] !== connection.workspaceId ||
req.headers["x-sellable-agent-id"] !== connection.agentId)
)
return json(res, 403, { error: "claude_runs_scope_rejected" });
if (req.method === "GET" && req.url === "/health") {
const { authentication, accounts: accountHealth } = await readHealth();
// Public health names no accounts, load, or scope.
if (mode === "public")
return json(res, 200, {
ok: authentication === "READY",
authentication,
provider: "claude-code",
tools: ["mcp"],
});
return json(res, 200, {
ok: authentication === "READY",
authentication,
accounts: accountHealth,
provider: "claude-code",
active: inFlight.plain + inFlight.tools,
toolActive: inFlight.tools,
scope: {
workspaceId: connection.workspaceId,
agentId: connection.agentId,
},
});
}
if (req.method === "POST" && req.url === "/v1/runs") {
if (full("plain") && ((mode !== "public" && !toolLanes) || full("tools")))
return json(res, 429, { error: "claude_runs_busy" });
let request;
try {
let size = 0;
const chunks = [];
for await (const chunk of req) {
size += chunk.length;
if (size > MAX_BODY) {
json(res, 413, { error: "claude_runs_input_too_large" });
return;
}
chunks.push(chunk);
}
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
request = validateClaudeRunsRequest(
body,
mode === "public" ? null : connection.models
);
// A public run's tools are its own entry's, never a local lane's.
if (mode !== "public") request.tools = resolveTools(request);
} catch (error) {
recordAudit({
status: "request-rejected",
reason: /^claude_runs_[a-z_]+$/.test(error.message ?? "")
? error.message
: "claude_runs_invalid_request",
});
return json(res, 400, { error: "claude_runs_request_rejected" });
}
// Check again after reading: concurrent uploads cannot over-admit.
const pool = request.tools ? "tools" : "plain";
if (full(pool)) return json(res, 429, { error: "claude_runs_busy" });
const run = {
run_id: `run_${randomBytes(16).toString("hex")}`,
status: "running",
startedAt: Date.now(),
listeners: new Set(),
consumed: false,
pool,
};
inFlight[pool]++;
runs.set(run.run_id, run);
const runDir = request.tools?.remote
? join(root, "work", "runs", run.run_id)
: null;
const removeRunDir = () => {
if (runDir) rmSync(runDir, { recursive: true, force: true });
};
try {
if (runDir)
request.mcpConfigPath = writeRemoteMcpConfig(
runDir,
request.tools.remote
);
run.execution = executeRun(request);
run.execution.promise
.then(
(result) =>
terminal(run, "run.completed", {
status: "completed",
...result,
}),
(error) =>
terminal(
run,
run.stopping && error.message === "claude_runs_cancelled"
? "run.cancelled"
: "run.failed",
run.stopping && error.message === "claude_runs_cancelled"
? { status: "cancelled" }
: {
status: "failed",
// The reason, so a failure can be told apart later;
// anything else the CLI threw stays generic.
error: RUN_FAILURE_CODE.test(error?.message ?? "")
? error.message
: "claude_runs_generation_failed",
}
)
)
.finally(() => {
inFlight[pool]--;
removeRunDir();
});
} catch {
inFlight[pool]--;
removeRunDir();
terminal(run, "run.failed", {
status: "failed",
error: "claude_runs_cli_unavailable",
});
}
return json(res, 202, { run_id: run.run_id, status: "started" });
}
const match =
/^\/v1\/runs\/(run_[a-f0-9]{32})(?:\/(events|stop|approval))?$/.exec(
req.url ?? ""
);
const run = match && runs.get(match[1]);
if (!run) return json(res, 404, { error: "claude_runs_run_missing" });
if (req.method === "GET" && !match[2])
return json(res, 200, {
run_id: run.run_id,
status: run.status,
...(run.output
? { output: run.output, runtime: run.runtime, usage: run.usage }
: {}),
...(run.error ? { error: run.error } : {}),
});
if (req.method === "GET" && match[2] === "events") {
if (run.consumed)
return json(res, 404, { error: "claude_runs_events_consumed" });
run.consumed = true;
res.writeHead(200, {
"content-type": "text/event-stream",
"cache-control": "no-store",
});
res.flushHeaders();
if (run.event) return res.end(`data: ${JSON.stringify(run.event)}\n\n`);
run.listeners.add(res);
const heartbeat = setInterval(
() => res.write(": keep-alive\n\n"),
15_000
);
res.once("close", () => {
clearInterval(heartbeat);
run.listeners.delete(res);
});
return;
}
if (req.method === "POST" && match[2] === "stop") {
if (run.event)
return json(res, 200, {
run_id: run.run_id,
status: run.status,
...(run.output
? { output: run.output, runtime: run.runtime, usage: run.usage }
: {}),
});
run.stopping = true;
run.execution.stop();
return json(res, 200, { run_id: run.run_id, status: "stopping" });
}
return json(res, 405, { error: "claude_runs_method_rejected" });
});
server.stopActiveRuns = async () => {
const activeRuns = [...runs.values()].filter(
(run) => !run.event && run.execution
);
for (const run of activeRuns) {
run.stopping = true;
run.execution.stop();
}
await Promise.allSettled(activeRuns.map((run) => run.execution.promise));
};
return server;
}
const readKey = (path) => readFileSync(path, "utf8").trim();
// Every listener audits to its own root and stops its runs before closing.
const auditTo = (root) => (entry) =>
appendFileSync(
`${root}/audit.jsonl`,
`${JSON.stringify({ at: new Date().toISOString(), ...entry })}\n`,
{ mode: 0o600 }
);
const listen = (server, port, host) =>
new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(port, host, resolve);
});
async function stopServer(server) {
await server.stopActiveRuns();
await new Promise((resolve) => {
server.close(resolve);
server.closeAllConnections();
});
}
// The Admin runtime owns these listeners for its lifetime. Image-owned startup
// plus the persistent CLI/auth directory makes normal restarts repeatable.
export async function startConfiguredClaudeRunsApis({
env = process.env,
diagnostic = () => {},
createApi = createClaudeRunsApi,
readToken = readKey,
toolLanes = null,
} = {}) {
const services = [];
for (const connection of parseRunsApiConnections(
env[RUNS_API_CONNECTIONS_ENV]
)) {
if (
!connection.localRoot ||
connection.runtimeId !== env.SELLABLE_RUNTIME_ID
)
continue;
try {
const token = readToken(connection.apiKeyFile);
const server = createApi({
token,
connection,
toolLanes,
audit: auditTo(connection.localRoot),
});
await listen(
server,
Number(new URL(connection.baseUrl).port),
"127.0.0.1"
);
services.push(server);
diagnostic({
ok: true,
code: "claude_runs_api_ready",
baseUrl: connection.baseUrl,
});
} catch {
diagnostic({ ok: false, code: "claude_runs_api_unavailable" });
}
}
return {
async stop() {
for (const server of services) await stopServer(server);
},
};
}
// The Admin's internet-facing listener; Fly terminates TLS at the edge. It
// starts only on a declared port with the machine's private connection key.
export async function startPublicClaudeRunsApi({
env = process.env,
diagnostic = () => {},
root = "/opt/data/private-opener",
createApi = createClaudeRunsApi,
readToken = readKey,
} = {}) {
const value = env.SELLABLE_AGENT_PUBLIC_RUNS_SERVICE_PORT ?? "";
const port = Number(value);
const keyFile = `${root}/api-key`;
let key = null;
try {
key = lstatSync(keyFile);
} catch {
/* No key, no public listener. */
}
if (
!/^[0-9]{4,5}$/.test(value) ||
port < 1024 ||
port > 65535 ||
// lstat: a symlink is not a file.
!key?.isFile() ||
(key.mode & 0o077) !== 0
)
return null;
const server = createApi({
mode: "public",
token: readToken(keyFile),
root,
audit: auditTo(root),
});
await listen(server, port, "0.0.0.0");
diagnostic({ ok: true, code: "claude_runs_public_api_ready", port });
return { stop: () => stopServer(server) };
}