promigence 0.1.0-beta.12
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/NOTICE +14 -0
- package/README.md +88 -0
- package/dist/account.js +850 -0
- package/dist/agent.js +374 -0
- package/dist/argv.js +159 -0
- package/dist/bin.js +332 -0
- package/dist/bundle.js +133 -0
- package/dist/capacity.js +69 -0
- package/dist/commands.d.ts +110 -0
- package/dist/commands.js +2675 -0
- package/dist/connectreq.js +125 -0
- package/dist/credfile.js +188 -0
- package/dist/credits.js +197 -0
- package/dist/devcontainer.d.ts +99 -0
- package/dist/devcontainer.js +744 -0
- package/dist/device.js +129 -0
- package/dist/dirsnap.js +378 -0
- package/dist/errfix.js +262 -0
- package/dist/generated.d.ts +35 -0
- package/dist/generated.js +307 -0
- package/dist/github.js +307 -0
- package/dist/gitlab.js +210 -0
- package/dist/guide.d.ts +19 -0
- package/dist/guide.js +366 -0
- package/dist/handlers.js +511 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +7 -0
- package/dist/init.d.ts +40 -0
- package/dist/init.js +352 -0
- package/dist/install.d.ts +23 -0
- package/dist/install.js +127 -0
- package/dist/io.d.ts +37 -0
- package/dist/io.js +318 -0
- package/dist/live.js +469 -0
- package/dist/login.js +1063 -0
- package/dist/mcp.js +889 -0
- package/dist/money.js +5 -0
- package/dist/orgs.js +475 -0
- package/dist/pricing.d.ts +52 -0
- package/dist/pricing.js +181 -0
- package/dist/report.js +132 -0
- package/dist/routes.js +135 -0
- package/dist/runners-github.js +257 -0
- package/dist/runners-gitlab.js +125 -0
- package/dist/runners-spec.js +137 -0
- package/dist/runners.js +1458 -0
- package/dist/sdk/blake3.d.ts +10 -0
- package/dist/sdk/blake3.js +111 -0
- package/dist/sdk/bundle.d.ts +96 -0
- package/dist/sdk/bundle.js +227 -0
- package/dist/sdk/ca-guard.js +166 -0
- package/dist/sdk/caller.d.ts +73 -0
- package/dist/sdk/caller.js +47 -0
- package/dist/sdk/client.d.ts +3015 -0
- package/dist/sdk/client.js +2583 -0
- package/dist/sdk/command-errors.d.ts +41 -0
- package/dist/sdk/command-errors.js +36 -0
- package/dist/sdk/context.d.ts +119 -0
- package/dist/sdk/context.js +588 -0
- package/dist/sdk/core.d.ts +285 -0
- package/dist/sdk/core.js +266 -0
- package/dist/sdk/credstore.d.ts +90 -0
- package/dist/sdk/credstore.js +206 -0
- package/dist/sdk/dispatcher.js +86 -0
- package/dist/sdk/github.d.ts +158 -0
- package/dist/sdk/github.js +78 -0
- package/dist/sdk/gitlab.d.ts +104 -0
- package/dist/sdk/gitlab.js +20 -0
- package/dist/sdk/handles.d.ts +82 -0
- package/dist/sdk/handles.js +135 -0
- package/dist/sdk/hint.js +7 -0
- package/dist/sdk/http2.js +1921 -0
- package/dist/sdk/index.d.ts +34 -0
- package/dist/sdk/index.js +15 -0
- package/dist/sdk/keyed.gen.js +7 -0
- package/dist/sdk/models.gen.d.ts +6162 -0
- package/dist/sdk/models.gen.js +1 -0
- package/dist/sdk/proxy.js +249 -0
- package/dist/sdk/pty.d.ts +171 -0
- package/dist/sdk/pty.js +496 -0
- package/dist/sdk/sandbox.d.ts +537 -0
- package/dist/sdk/sandbox.js +725 -0
- package/dist/sdk/ssh.d.ts +25 -0
- package/dist/sdk/ssh.js +114 -0
- package/dist/sdk/trace.js +80 -0
- package/dist/sdk/transport-worker.js +466 -0
- package/dist/sdk/warm.js +40 -0
- package/dist/ssh.js +267 -0
- package/dist/surface.d.ts +95 -0
- package/dist/surface.js +1452 -0
- package/dist/templates.js +53 -0
- package/dist/term.js +50 -0
- package/dist/timing.js +71 -0
- package/dist/untar.js +324 -0
- package/dist/uploadguard.js +75 -0
- package/llms.txt +209 -0
- package/package.json +52 -0
package/dist/agent.js
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { DEFAULT_SNAPSHOT, PromigenceError, checkImageRef, checkRepoUrl, imageSnapshot, } from "./sdk/index.js";
|
|
2
|
+
import { snapshotArg } from "./bundle.js";
|
|
3
|
+
import { bool, client, nexts, spendCap, str, waitBound, } from "./commands.js";
|
|
4
|
+
import { emit, errorEnvelope, errorTextLines, okEnvelope, parseDuration, usageError, write, } from "./io.js";
|
|
5
|
+
import { findCommand } from "./surface.js";
|
|
6
|
+
import { LIVE_HANDLERS } from "./live.js";
|
|
7
|
+
import { escapeLine, jsonLine, sh, shellArg } from "./term.js";
|
|
8
|
+
export const AGENT_CLIS = {
|
|
9
|
+
claude: {
|
|
10
|
+
bin: "claude",
|
|
11
|
+
pkg: "@anthropic-ai/claude-code@2.1.278",
|
|
12
|
+
defaultSecret: "ANTHROPIC_API_KEY",
|
|
13
|
+
keyVars: ["ANTHROPIC_API_KEY"],
|
|
14
|
+
headless: 'IS_SANDBOX=1 exec claude -p "$1" --dangerously-skip-permissions < /dev/null',
|
|
15
|
+
},
|
|
16
|
+
codex: {
|
|
17
|
+
bin: "codex",
|
|
18
|
+
pkg: "@openai/codex@0.155.1",
|
|
19
|
+
defaultSecret: "OPENAI_API_KEY",
|
|
20
|
+
keyVars: ["OPENAI_API_KEY", "CODEX_API_KEY"],
|
|
21
|
+
headless: 'exec codex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox "$1" < /dev/null',
|
|
22
|
+
},
|
|
23
|
+
opencode: {
|
|
24
|
+
bin: "opencode",
|
|
25
|
+
pkg: "opencode-ai@1.18.31",
|
|
26
|
+
defaultSecret: "ANTHROPIC_API_KEY",
|
|
27
|
+
keyVars: ["ANTHROPIC_API_KEY"],
|
|
28
|
+
headless: 'exec opencode run "$1" < /dev/null',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const AGENT_NAMES = Object.keys(AGENT_CLIS);
|
|
32
|
+
function withModelFlag(headless) {
|
|
33
|
+
return headless.replace(/(exec (?:claude -p "\$1"|codex exec|opencode run))/, '$1 --model "$2"');
|
|
34
|
+
}
|
|
35
|
+
export const SETUP_SCRIPT = [
|
|
36
|
+
"set -e",
|
|
37
|
+
'export HOME="${HOME:-/root}"',
|
|
38
|
+
'if ! command -v "$1" >/dev/null 2>&1; then',
|
|
39
|
+
' command -v npm >/dev/null 2>&1 || { echo "neither $1 nor npm is in this snapshot: use one with Node.js, or with $1 installed" >&2; exit 127; }',
|
|
40
|
+
' npm install -g --no-audit --no-fund --loglevel=error "$2" >&2',
|
|
41
|
+
"fi",
|
|
42
|
+
'if [ -n "$3" ] && [ ! -d "$4/.git" ]; then',
|
|
43
|
+
' mkdir -p "$(dirname "$4")"',
|
|
44
|
+
' git clone --quiet -- "$3" "$4" >&2',
|
|
45
|
+
' if [ -n "${5:-}" ]; then chown -R -- "$5" "$4"; fi',
|
|
46
|
+
"fi",
|
|
47
|
+
'"$1" --version',
|
|
48
|
+
].join("\n");
|
|
49
|
+
async function snapshotUser(c, snapshot) {
|
|
50
|
+
if (snapshot === DEFAULT_SNAPSHOT)
|
|
51
|
+
return undefined;
|
|
52
|
+
const rec = await c.snapshots.get(snapshot).catch(() => undefined);
|
|
53
|
+
const u = rec?.user;
|
|
54
|
+
return typeof u === "string" && u !== "" && u !== "root" && u !== "0" ? u : undefined;
|
|
55
|
+
}
|
|
56
|
+
export function repoDir(url) {
|
|
57
|
+
const last = url.replace(/[/]+$/, "").split(/[/:]/).pop() ?? "";
|
|
58
|
+
const name = last.replace(/\.git$/, "").replace(/[^A-Za-z0-9._-]/g, "") || "repo";
|
|
59
|
+
return `/work/${name === "." || name === ".." ? "repo" : name}`;
|
|
60
|
+
}
|
|
61
|
+
function checkRepo(url) {
|
|
62
|
+
checkRepoUrl(url, "--repo");
|
|
63
|
+
const ok = /^(https?|git|ssh):\/\/[^\s]+$/.test(url) || /^[\w.-]+@[\w.-]+:[^\s]+$/.test(url);
|
|
64
|
+
if (!ok) {
|
|
65
|
+
throw usageError(`--repo: '${url}' is not a git URL`, "Pass --repo https://github.com/owner/name (a public repository).");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const PROVIDER_OF_VAR = {
|
|
69
|
+
ANTHROPIC_API_KEY: "Anthropic", OPENAI_API_KEY: "OpenAI", CODEX_API_KEY: "OpenAI",
|
|
70
|
+
GEMINI_API_KEY: "Google", GOOGLE_API_KEY: "Google", XAI_API_KEY: "xAI",
|
|
71
|
+
};
|
|
72
|
+
const OPENCODE_PROVIDER_VAR = {
|
|
73
|
+
anthropic: "ANTHROPIC_API_KEY", openai: "OPENAI_API_KEY", xai: "XAI_API_KEY", google: "GEMINI_API_KEY",
|
|
74
|
+
};
|
|
75
|
+
const OWN_PROVIDER = {
|
|
76
|
+
claude: { id: "anthropic", name: "Anthropic" }, codex: { id: "openai", name: "OpenAI" },
|
|
77
|
+
};
|
|
78
|
+
function withModel(which, agent, model) {
|
|
79
|
+
if (model === undefined)
|
|
80
|
+
return { agent };
|
|
81
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,127}$/.test(model)) {
|
|
82
|
+
throw usageError(`--model '${model}' is not a model name`, "Pass the provider's model id, e.g. --model xai/grok-4-0709 (opencode) or --model claude-sonnet-4-5 (claude).");
|
|
83
|
+
}
|
|
84
|
+
const own = OWN_PROVIDER[which];
|
|
85
|
+
if (own) {
|
|
86
|
+
const slash = model.indexOf("/");
|
|
87
|
+
if (slash < 0)
|
|
88
|
+
return { agent, model };
|
|
89
|
+
const provider = model.slice(0, slash).toLowerCase();
|
|
90
|
+
if (provider === own.id)
|
|
91
|
+
return { agent, model: model.slice(slash + 1) };
|
|
92
|
+
throw usageError(`${which} runs ${own.name} models; --model ${model} is another provider's`, `For that model: ${sh `promigence agent start opencode --model ${model}`} with that provider's key (--key-secret NAME=VAR).`);
|
|
93
|
+
}
|
|
94
|
+
const slash = model.indexOf("/");
|
|
95
|
+
if (slash <= 0 || slash === model.length - 1) {
|
|
96
|
+
throw usageError(`opencode takes --model as provider/model, got '${model}'`, "Name the provider first, e.g. --model xai/grok-4-0709 or --model anthropic/claude-sonnet-4-5.");
|
|
97
|
+
}
|
|
98
|
+
const v = OPENCODE_PROVIDER_VAR[model.slice(0, slash).toLowerCase()];
|
|
99
|
+
return { agent: v ? { ...agent, defaultSecret: v, keyVars: [v] } : agent, model };
|
|
100
|
+
}
|
|
101
|
+
const an = (word) => (/^[AEIOU]/.test(word) ? "an" : "a");
|
|
102
|
+
function keyBindings(agent, spec, agentName = agent.bin) {
|
|
103
|
+
let name = agent.defaultSecret;
|
|
104
|
+
let vars = agent.keyVars;
|
|
105
|
+
if (spec !== undefined) {
|
|
106
|
+
const eq = spec.indexOf("=");
|
|
107
|
+
if (eq === 0 || spec === "" || eq === spec.length - 1) {
|
|
108
|
+
throw usageError(`--key-secret '${spec}' is not NAME or NAME=VAR`, `Pass --key-secret ${agent.defaultSecret} or --key-secret MY_KEY=${agent.keyVars[0]}.`);
|
|
109
|
+
}
|
|
110
|
+
const theirs = PROVIDER_OF_VAR[spec];
|
|
111
|
+
const ours = PROVIDER_OF_VAR[agent.defaultSecret];
|
|
112
|
+
if (eq < 0 && theirs !== undefined && ours !== undefined && theirs !== ours) {
|
|
113
|
+
throw usageError(`--key-secret ${spec} is ${an(theirs)} ${theirs} key; ${agentName} here takes ${an(ours)} ${ours} key (${agent.keyVars[0]})`, `Store ${an(ours)} ${ours} key and pass --key-secret ${agent.defaultSecret}` +
|
|
114
|
+
`${theirs === "OpenAI" && ours !== "OpenAI" ? " (promigence agent start codex takes an OpenAI key)" : ""}. ` +
|
|
115
|
+
`To bind a stored secret to another variable on purpose, pass --key-secret NAME=VAR.`);
|
|
116
|
+
}
|
|
117
|
+
if (eq < 0)
|
|
118
|
+
name = spec;
|
|
119
|
+
else {
|
|
120
|
+
name = spec.slice(0, eq);
|
|
121
|
+
vars = [spec.slice(eq + 1), ...agent.keyVars.slice(1)];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const bindings = [...new Set(vars)].map((v) => (v === name ? name : { name, as: v }));
|
|
125
|
+
return { name, vars: [...new Set(vars)], bindings };
|
|
126
|
+
}
|
|
127
|
+
function terminalSecrets(agent, name, vars) {
|
|
128
|
+
const interactive = agent.bin === "codex" ? vars.slice(0, 1) : vars;
|
|
129
|
+
return interactive.map((v) => (v === name ? name : `${name}=${v}`));
|
|
130
|
+
}
|
|
131
|
+
function attachCommand(sid, agent, secrets, cwd, model) {
|
|
132
|
+
return `promigence sandbox shell ${shellArg(sid)}${cwd ? ` --cwd ${shellArg(cwd)}` : ""} ${secrets.map((x) => `--secret ${shellArg(x)}`).join(" ")} -- ${agent.bin}${model ? ` --model ${shellArg(model)}` : ""}`;
|
|
133
|
+
}
|
|
134
|
+
function fail(ctx, err, exit, fields, next) {
|
|
135
|
+
const env = { ...errorEnvelope(ctx.cmd.name, err, next), ...fields };
|
|
136
|
+
return emit(ctx.mode, env, errorTextLines(ctx.cmd.name, err, exit, next), exit, ctx.pointer);
|
|
137
|
+
}
|
|
138
|
+
function checkSource(ctx) {
|
|
139
|
+
const image = str(ctx, "--image");
|
|
140
|
+
if (image === undefined)
|
|
141
|
+
return;
|
|
142
|
+
if (str(ctx, "--snapshot") !== undefined) {
|
|
143
|
+
throw usageError("agent start takes --snapshot or --image, not both", "Pick one; with neither, the agent is installed into promigence/base.");
|
|
144
|
+
}
|
|
145
|
+
checkImageRef(image);
|
|
146
|
+
}
|
|
147
|
+
async function snapshotFor(ctx, c) {
|
|
148
|
+
const image = str(ctx, "--image");
|
|
149
|
+
if (image !== undefined) {
|
|
150
|
+
await write(process.stderr, ctx.mode === "json"
|
|
151
|
+
? jsonLine(process.stderr, { hint: "image snapshot", image })
|
|
152
|
+
: `image ${image}: creating or reusing its snapshot (a first build takes minutes)\n`);
|
|
153
|
+
return imageSnapshot(c, { image });
|
|
154
|
+
}
|
|
155
|
+
return str(ctx, "--snapshot") === undefined ? DEFAULT_SNAPSHOT : snapshotArg(ctx, c);
|
|
156
|
+
}
|
|
157
|
+
const EARLY_EXIT_WAIT_MS = 8_000;
|
|
158
|
+
const PROVIDER_WATCH_MS = 30_000;
|
|
159
|
+
const PROVIDER_POLL_MS = 1_000;
|
|
160
|
+
const PROVIDER_REFUSALS = [
|
|
161
|
+
{ kind: "credit", re: /no credits remaining/i },
|
|
162
|
+
{ kind: "credit", re: /credit balance is too low/i },
|
|
163
|
+
{ kind: "credit", re: /insufficient_quota|exceeded your current quota/i },
|
|
164
|
+
{ kind: "credit", re: /doesn'?t have any credits|purchase credits|spending limit/i },
|
|
165
|
+
{ kind: "credit", re: /you'?ve hit your usage limit/i },
|
|
166
|
+
{ kind: "key", re: /incorrect api key provided|invalid_api_key/i },
|
|
167
|
+
{ kind: "key", re: /invalid (?:x-)?api[ -]key|authentication_error|please run \/login/i },
|
|
168
|
+
{ kind: "key", re: /unexpected status 40[13]\b/i },
|
|
169
|
+
{ kind: "key", re: /API key is missing\. Pass it using/i },
|
|
170
|
+
];
|
|
171
|
+
function providerOf(vars) {
|
|
172
|
+
return vars.map((v) => PROVIDER_OF_VAR[v]).find((p) => p !== undefined) ?? "the model provider";
|
|
173
|
+
}
|
|
174
|
+
export function providerRefusal(text) {
|
|
175
|
+
for (const line of text.split("\n")) {
|
|
176
|
+
const hit = PROVIDER_REFUSALS.find((p) => p.re.test(line));
|
|
177
|
+
if (hit)
|
|
178
|
+
return { kind: hit.kind, line: line.trim().slice(0, 200) };
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
async function watchForRefusal(c, sid, execId, until) {
|
|
183
|
+
const at = { stdout: 0, stderr: 0 };
|
|
184
|
+
let tail = "";
|
|
185
|
+
for (;;) {
|
|
186
|
+
let running = true;
|
|
187
|
+
for (const stream of ["stdout", "stderr"]) {
|
|
188
|
+
const r = await c.sandboxes.execs.read(sid, execId, stream, { offset: at[stream], len: 65_536 }).catch(() => undefined);
|
|
189
|
+
if (!r)
|
|
190
|
+
continue;
|
|
191
|
+
at[stream] = r.offset + r.data.length;
|
|
192
|
+
tail = (tail + new TextDecoder().decode(r.data)).slice(-16_384);
|
|
193
|
+
running = r.record.state === "running";
|
|
194
|
+
}
|
|
195
|
+
const hit = providerRefusal(tail);
|
|
196
|
+
if (hit || !running || Date.now() + PROVIDER_POLL_MS > until)
|
|
197
|
+
return hit;
|
|
198
|
+
await new Promise((res) => setTimeout(res, PROVIDER_POLL_MS));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async function lastLines(c, sid, execId) {
|
|
202
|
+
const out = [];
|
|
203
|
+
for (const stream of ["stderr", "stdout"]) {
|
|
204
|
+
const r = await c.sandboxes.execs.read(sid, execId, stream).catch(() => undefined);
|
|
205
|
+
const text = r ? new TextDecoder().decode(r.data).trim() : "";
|
|
206
|
+
if (text)
|
|
207
|
+
out.push(...text.split("\n").slice(-3));
|
|
208
|
+
}
|
|
209
|
+
return escapeLine(out.join(" | ").slice(-400));
|
|
210
|
+
}
|
|
211
|
+
const agentStart = async (ctx) => {
|
|
212
|
+
const which = ctx.args[0];
|
|
213
|
+
if (which === undefined) {
|
|
214
|
+
throw usageError("agent start needs an agent", `Pass one of ${AGENT_NAMES.join(" | ")}, e.g. promigence agent start claude.`);
|
|
215
|
+
}
|
|
216
|
+
if (ctx.args.length > 1) {
|
|
217
|
+
throw usageError(`agent start takes one agent, got ${ctx.args.length}`, "Quote a task and pass it as --prompt TEXT.");
|
|
218
|
+
}
|
|
219
|
+
const known = AGENT_CLIS[which];
|
|
220
|
+
if (known === undefined) {
|
|
221
|
+
throw usageError(`unknown agent '${which}'`, `Pick one of ${AGENT_NAMES.join(" | ")}.`);
|
|
222
|
+
}
|
|
223
|
+
const { agent, model } = withModel(which, known, str(ctx, "--model"));
|
|
224
|
+
const key = keyBindings(agent, str(ctx, "--key-secret"), which);
|
|
225
|
+
const fromEnv = str(ctx, "--key-from-env");
|
|
226
|
+
let keyValue;
|
|
227
|
+
if (fromEnv !== undefined) {
|
|
228
|
+
keyValue = process.env[fromEnv];
|
|
229
|
+
if (!keyValue) {
|
|
230
|
+
throw usageError(`--key-from-env ${fromEnv}: the variable is empty or unset`, `Export it first (export ${fromEnv}=...), or store the key once: ${sh `promigence secrets set ${key.name} --from-env ${fromEnv}`}.`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
checkSource(ctx);
|
|
234
|
+
const repo = str(ctx, "--repo");
|
|
235
|
+
if (repo !== undefined)
|
|
236
|
+
checkRepo(repo);
|
|
237
|
+
const prompt = str(ctx, "--prompt");
|
|
238
|
+
if (prompt !== undefined && prompt.trim() === "")
|
|
239
|
+
throw usageError("--prompt is empty", "Pass the task: --prompt 'fix the failing test'.");
|
|
240
|
+
const attach = bool(ctx, "--attach");
|
|
241
|
+
if (attach && prompt !== undefined) {
|
|
242
|
+
throw usageError("--attach starts the agent in a terminal; --prompt runs it headless", "Pick one.");
|
|
243
|
+
}
|
|
244
|
+
if (attach && !(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
245
|
+
throw usageError("--attach needs a terminal", "Run it from an interactive shell, or leave --attach off and run the printed attach command.");
|
|
246
|
+
}
|
|
247
|
+
const timeout = str(ctx, "--timeout");
|
|
248
|
+
const timeoutMs = timeout === undefined ? 60 * 60_000 : parseDuration(timeout, "--timeout");
|
|
249
|
+
const cwd = repo === undefined ? undefined : repoDir(repo);
|
|
250
|
+
const c = client(ctx);
|
|
251
|
+
if (keyValue !== undefined)
|
|
252
|
+
await c.secrets.set(key.name, keyValue.replace(/\r?\n$/, ""));
|
|
253
|
+
keyValue = undefined;
|
|
254
|
+
const snapshot = await snapshotFor(ctx, c);
|
|
255
|
+
const started = await c.runs.start({
|
|
256
|
+
snapshot, count: 1, timeoutMs, spendCap: await spendCap(ctx, c), secrets: key.bindings,
|
|
257
|
+
labels: { agent: which },
|
|
258
|
+
});
|
|
259
|
+
const runId = started.run_id;
|
|
260
|
+
const bound = waitBound(ctx);
|
|
261
|
+
const waited = await c.runs.waitReady(runId, { waitTimeoutMs: bound });
|
|
262
|
+
const sb = waited.sandboxes.find((s) => s.state === "ready" || s.state === "running");
|
|
263
|
+
if (waited.timedOut || sb === undefined) {
|
|
264
|
+
const next = nexts([sh `promigence run report ${runId} --json`, "why it did not start"], [sh `promigence run kill ${runId}`, "stop waiting for it"]);
|
|
265
|
+
const err = waited.timedOut
|
|
266
|
+
? new PromigenceError({ code: "timeout", message: `no sandbox ready within ${bound}ms; nothing was killed`, fix: `Wait with ${sh `promigence run status ${runId} --wait`}, or raise --wait-timeout.`, exitCode: 11 })
|
|
267
|
+
: new PromigenceError({ code: "env_failed", message: "the sandbox did not start", fix: `Read ${sh `promigence run report ${runId}`}; it was not billed.`, exitCode: 3 });
|
|
268
|
+
return fail(ctx, err, err.cliExit, { run_id: runId, sandboxes: waited.sandboxes }, next);
|
|
269
|
+
}
|
|
270
|
+
const sid = sb.sandbox_id;
|
|
271
|
+
const endOnThrow = async (p) => {
|
|
272
|
+
try {
|
|
273
|
+
return await p;
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
await c.sandboxes.kill(sid).catch(() => undefined);
|
|
277
|
+
throw e;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const runAs = await snapshotUser(c, snapshot);
|
|
281
|
+
const setup = await endOnThrow(c.sandboxes.exec(sid, {
|
|
282
|
+
argv: ["sh", "-c", SETUP_SCRIPT, "agent-setup", agent.bin, agent.pkg, repo ?? "", cwd ?? "", runAs ?? ""],
|
|
283
|
+
user: "root",
|
|
284
|
+
timeoutMs: 10 * 60_000,
|
|
285
|
+
}));
|
|
286
|
+
if (setup.exit_code !== 0) {
|
|
287
|
+
await c.sandboxes.kill(sid).catch(() => undefined);
|
|
288
|
+
const tail = (setup.stderr ?? "").trim().split("\n").slice(-3).join(" | ").slice(0, 400);
|
|
289
|
+
const err = new PromigenceError({
|
|
290
|
+
code: "nonzero_exit",
|
|
291
|
+
message: `setting up ${agent.bin} failed (exit ${setup.exit_code ?? "none"}): ${tail || "no output"}`,
|
|
292
|
+
fix: `The sandbox was killed. Use a snapshot with ${agent.bin} or Node.js installed (--snapshot), or check --repo.`,
|
|
293
|
+
exitCode: 4,
|
|
294
|
+
});
|
|
295
|
+
return fail(ctx, err, 4, { run_id: runId, sandbox_id: sid, killed: true }, nexts([sh `promigence run report ${runId} --json`, "the sandbox's record"]));
|
|
296
|
+
}
|
|
297
|
+
const version = (setup.stdout ?? "").trim().split("\n").pop() ?? "";
|
|
298
|
+
const shellSecrets = terminalSecrets(agent, key.name, key.vars);
|
|
299
|
+
const attachCmd = attachCommand(sid, agent, shellSecrets, cwd, model);
|
|
300
|
+
const killCmd = sh `promigence sandbox kill ${sid}`;
|
|
301
|
+
const base = {
|
|
302
|
+
agent: which, version, run_id: runId, sandbox_id: sid, snapshot,
|
|
303
|
+
key_secret: key.name, key_vars: key.vars, ...(cwd ? { cwd } : {}),
|
|
304
|
+
};
|
|
305
|
+
if (prompt !== undefined) {
|
|
306
|
+
const rec = await endOnThrow(c.sandboxes.execBackground(sid, {
|
|
307
|
+
argv: model === undefined
|
|
308
|
+
? ["sh", "-c", `export HOME="\${HOME:-/root}"; ${agent.headless}`, "agent", prompt]
|
|
309
|
+
: ["sh", "-c", `export HOME="\${HOME:-/root}"; ${withModelFlag(agent.headless)}`, "agent", prompt, model],
|
|
310
|
+
...(cwd ? { cwd } : {}),
|
|
311
|
+
timeoutMs,
|
|
312
|
+
}));
|
|
313
|
+
const started = Date.now();
|
|
314
|
+
const early = await c.sandboxes.execs.get(sid, rec.exec_id, { waitMs: EARLY_EXIT_WAIT_MS }).catch(() => undefined);
|
|
315
|
+
const ended = early !== undefined && early.state !== "running";
|
|
316
|
+
const exitCode = early?.exit?.exit_code ?? null;
|
|
317
|
+
const signal = early?.exit?.signal ?? null;
|
|
318
|
+
if (ended && (exitCode !== 0 || signal !== null)) {
|
|
319
|
+
const said = await lastLines(c, sid, rec.exec_id);
|
|
320
|
+
await c.sandboxes.kill(sid).catch(() => undefined);
|
|
321
|
+
const err = new PromigenceError({
|
|
322
|
+
code: "nonzero_exit",
|
|
323
|
+
message: `${agent.bin} exited ${signal !== null ? `on signal ${signal}` : exitCode ?? "without an exit code"} within ${EARLY_EXIT_WAIT_MS / 1000} s of starting: ${said || "it wrote nothing"}`,
|
|
324
|
+
fix: `The sandbox was ended. A wrong or expired model key is the usual cause: store the right one (${sh `promigence secrets set ${key.name} --from-env ${key.name}`}) and start again.`,
|
|
325
|
+
exitCode: 4,
|
|
326
|
+
});
|
|
327
|
+
return fail(ctx, err, 4, { ...base, mode: "headless", exec_id: rec.exec_id, exit_code: exitCode, signal, killed: true }, nexts([sh `promigence run report ${runId} --json`, "the sandbox's record"]));
|
|
328
|
+
}
|
|
329
|
+
if (!ended) {
|
|
330
|
+
const window = Number(process.env["PROMIGENCE_AGENT_WATCH_MS"] ?? "") || PROVIDER_WATCH_MS;
|
|
331
|
+
if (ctx.mode === "text") {
|
|
332
|
+
await write(process.stderr, `watching ${agent.bin}'s first ${Math.round(window / 1000)} s for its provider refusing it (no credit, a bad key)...\n`);
|
|
333
|
+
}
|
|
334
|
+
const refused = await watchForRefusal(c, sid, rec.exec_id, started + window);
|
|
335
|
+
if (refused) {
|
|
336
|
+
await c.sandboxes.kill(sid).catch(() => undefined);
|
|
337
|
+
const provider = providerOf(key.vars);
|
|
338
|
+
const err = new PromigenceError({
|
|
339
|
+
code: "nonzero_exit",
|
|
340
|
+
message: `${provider} refused ${agent.bin}: ${refused.kind === "credit" ? "the account is out of credit" : "the key was not accepted"} (it said: ${escapeLine(refused.line)})`,
|
|
341
|
+
fix: refused.kind === "credit"
|
|
342
|
+
? `The sandbox was ended. Add credit to your ${provider} account, then start again.`
|
|
343
|
+
: `The sandbox was ended. Check the key: store the right one (${sh `promigence secrets set ${key.name} --from-env ${key.name}`}) and start again.`,
|
|
344
|
+
exitCode: 4,
|
|
345
|
+
});
|
|
346
|
+
return fail(ctx, err, 4, { ...base, mode: "headless", exec_id: rec.exec_id, refused: refused.kind, killed: true }, nexts([sh `promigence run report ${runId} --json`, "the sandbox's record"]));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const next = nexts([sh `promigence exec output ${rec.exec_id} --sandbox ${sid} --follow`, "follow the agent until it exits"], [sh `promigence exec kill ${rec.exec_id} --sandbox ${sid}`, "stop the agent"], [killCmd, "end the sandbox (billing stops)"]);
|
|
350
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...base, mode: "headless", exec_id: rec.exec_id, stop: [next[1].cmd, killCmd] }, next), [
|
|
351
|
+
`agent ${which} ${version} working in ${sid} (run ${runId})`,
|
|
352
|
+
` follow: ${next[0].cmd}`,
|
|
353
|
+
` stop: ${next[1].cmd} · ${killCmd}`,
|
|
354
|
+
], 0, ctx.pointer);
|
|
355
|
+
}
|
|
356
|
+
const next = nexts([attachCmd, "open a terminal running the agent"], [killCmd, "end the sandbox (billing stops)"]);
|
|
357
|
+
if (attach) {
|
|
358
|
+
await write(process.stderr, `agent ${which} ${escapeLine(version)} ready in ${sid}; exit it, then: ${killCmd}\n`);
|
|
359
|
+
const shell = findCommand(["sandbox", "shell"]);
|
|
360
|
+
const flags = new Map();
|
|
361
|
+
if (cwd)
|
|
362
|
+
flags.set("--cwd", [cwd]);
|
|
363
|
+
flags.set("--secret", shellSecrets);
|
|
364
|
+
return LIVE_HANDLERS["sandbox shell"]({ ...ctx, cmd: shell, args: [sid], flags, cmdTail: [agent.bin], pointer: false });
|
|
365
|
+
}
|
|
366
|
+
return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...base, mode: "interactive", attach: attachCmd, stop: [killCmd] }, next), [
|
|
367
|
+
`agent ${which} ${version} ready in ${sid} (run ${runId})`,
|
|
368
|
+
` attach: ${attachCmd}`,
|
|
369
|
+
` stop: exit the agent, then ${killCmd}`,
|
|
370
|
+
], 0, ctx.pointer);
|
|
371
|
+
};
|
|
372
|
+
export const AGENT_HANDLERS = {
|
|
373
|
+
"agent start": agentStart,
|
|
374
|
+
};
|
package/dist/argv.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { GLOBAL_FLAGS, findCommand, knownFlags } from "./surface.js";
|
|
2
|
+
function empty() {
|
|
3
|
+
return { words: [], flags: new Map(), cmdTail: [], help: false, version: false };
|
|
4
|
+
}
|
|
5
|
+
function add(p, name, value) {
|
|
6
|
+
const cur = p.flags.get(name);
|
|
7
|
+
if (cur)
|
|
8
|
+
cur.push(value);
|
|
9
|
+
else
|
|
10
|
+
p.flags.set(name, [value]);
|
|
11
|
+
}
|
|
12
|
+
function global(p, a) {
|
|
13
|
+
if (a === "--help" || a === "-h") {
|
|
14
|
+
p.help = true;
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (a === "--version" || a === "-V") {
|
|
18
|
+
p.version = true;
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (a === "--json") {
|
|
22
|
+
p.modeFlag = "json";
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (a === "--text") {
|
|
26
|
+
p.modeFlag = "text";
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
function parsePlain(argv) {
|
|
32
|
+
const p = empty();
|
|
33
|
+
for (let i = 0; i < argv.length; i++) {
|
|
34
|
+
const a = argv[i];
|
|
35
|
+
if (a === "--") {
|
|
36
|
+
p.cmdTail = argv.slice(i + 1);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (global(p, a))
|
|
40
|
+
continue;
|
|
41
|
+
if (a.startsWith("--")) {
|
|
42
|
+
const eq = a.indexOf("=");
|
|
43
|
+
if (eq > 0)
|
|
44
|
+
add(p, a.slice(0, eq), a.slice(eq + 1));
|
|
45
|
+
else if (i + 1 < argv.length && !argv[i + 1].startsWith("-"))
|
|
46
|
+
add(p, a, argv[++i]);
|
|
47
|
+
else
|
|
48
|
+
add(p, a, true);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
p.words.push(a);
|
|
52
|
+
}
|
|
53
|
+
return p;
|
|
54
|
+
}
|
|
55
|
+
export function positionalsBeforeTail(c) {
|
|
56
|
+
const words = (c.args ?? "").split(/\s+/).filter(Boolean);
|
|
57
|
+
const at = words.findIndex((w) => w === "--" || w === "[--");
|
|
58
|
+
return at < 0 ? words.length : at;
|
|
59
|
+
}
|
|
60
|
+
export const takesTail = (c) => /(^|\s)\[?--(\s|$)/.test(c.args ?? "");
|
|
61
|
+
const isShortFlag = (a) => /^-[A-Za-z]/.test(a);
|
|
62
|
+
function shapedFlag(p, argv, i, known, withValue) {
|
|
63
|
+
const a = argv[i];
|
|
64
|
+
const eq = a.indexOf("=");
|
|
65
|
+
if (a.startsWith("--") && eq > 0) {
|
|
66
|
+
add(p, a.slice(0, eq), a.slice(eq + 1));
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
const next = argv[i + 1];
|
|
70
|
+
if (withValue.has(a)) {
|
|
71
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
72
|
+
add(p, a, next);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
add(p, a, true);
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
if (known.has(a) || isShortFlag(a)) {
|
|
79
|
+
add(p, a, true);
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
83
|
+
add(p, a, next);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
add(p, a, true);
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
function shapes(cmd) {
|
|
90
|
+
const withValue = new Set();
|
|
91
|
+
for (const f of [...GLOBAL_FLAGS, ...cmd.flags])
|
|
92
|
+
if (f.arg)
|
|
93
|
+
withValue.add(f.name);
|
|
94
|
+
return { known: knownFlags(cmd), withValue };
|
|
95
|
+
}
|
|
96
|
+
function parseShaped(argv, cmd) {
|
|
97
|
+
const p = empty();
|
|
98
|
+
const { known, withValue } = shapes(cmd);
|
|
99
|
+
for (let i = 0; i < argv.length; i++) {
|
|
100
|
+
const a = argv[i];
|
|
101
|
+
if (a === "--") {
|
|
102
|
+
p.cmdTail = argv.slice(i + 1);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
if (global(p, a))
|
|
106
|
+
continue;
|
|
107
|
+
if (a.startsWith("--") || isShortFlag(a)) {
|
|
108
|
+
i += shapedFlag(p, argv, i, known, withValue);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
p.words.push(a);
|
|
112
|
+
}
|
|
113
|
+
return p;
|
|
114
|
+
}
|
|
115
|
+
function parseWithTail(argv, cmd) {
|
|
116
|
+
const p = empty();
|
|
117
|
+
const { known, withValue } = shapes(cmd);
|
|
118
|
+
const room = cmd.name.split(" ").length + positionalsBeforeTail(cmd);
|
|
119
|
+
for (let i = 0; i < argv.length; i++) {
|
|
120
|
+
const a = argv[i];
|
|
121
|
+
if (a === "--" && p.words.length >= cmd.name.split(" ").length) {
|
|
122
|
+
p.cmdTail = argv.slice(i + 1);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
if (global(p, a))
|
|
126
|
+
continue;
|
|
127
|
+
if (a.startsWith("--") || isShortFlag(a)) {
|
|
128
|
+
i += shapedFlag(p, argv, i, known, withValue);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (p.words.length < room) {
|
|
132
|
+
p.words.push(a);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
p.cmdTail = argv.slice(i);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
return p;
|
|
139
|
+
}
|
|
140
|
+
export function parse(argv, platform = process.platform) {
|
|
141
|
+
const plain = parsePlain(argv);
|
|
142
|
+
const lead = [];
|
|
143
|
+
for (const a of argv) {
|
|
144
|
+
if (a.startsWith("-"))
|
|
145
|
+
break;
|
|
146
|
+
lead.push(a);
|
|
147
|
+
}
|
|
148
|
+
const cmd = (lead.length ? findCommand(lead) : undefined) ?? findCommand(plain.words);
|
|
149
|
+
if (!cmd)
|
|
150
|
+
return plain;
|
|
151
|
+
if (platform === "win32" && takesTail(cmd)) {
|
|
152
|
+
const cut = argv.indexOf("--");
|
|
153
|
+
const before = cut < 0 ? argv : argv.slice(0, cut);
|
|
154
|
+
const overflow = parseShaped(before, cmd).words.length > cmd.name.split(" ").length + positionalsBeforeTail(cmd);
|
|
155
|
+
if (cut < 0 || overflow)
|
|
156
|
+
return parseWithTail(argv, cmd);
|
|
157
|
+
}
|
|
158
|
+
return parseShaped(argv, cmd);
|
|
159
|
+
}
|